Docker基本使用与服务编排

859 阅读16分钟

安装Docker

安装Docker的环境要求

  1. Linux的内核版本要求3.10以上
    使用如下命令查看内核版本
uname -r

执行下面的命令开始安装Docker

1. 卸载旧版本(如果安装过旧版本的话)

yum remove -y docker*

2. 安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的

yum install -y yum-utils

3. 设置yum源,并更新 yum 的包索引

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast

4. 查看所有仓库中所有docker版本,并选择特定版本安装

yum list docker-ce --showduplicates | sort -r

5. 我这里使用docker-ce-18.09.9版本,因为这个版本我个人用的比较多

yum install docker-ce-18.09.9 docker-ce-cli-18.09.9 containerd.io -y

到了这一步,如果前面的步骤没出错那么Docker应该已经安装成功了。Docker内部写死了一个远程的镜像库,就类似于Maven的中央仓库一样,我们拉取的镜像默认都是从这个远程镜像库拉取的,很慢。可以到 /etc/docker/daemon.json 配置阿里云的镜像加速器:

{
  "registry-mirrors": ["https://4ulabqzs.mirror.aliyuncs.com"]
}

启动docker并设置开机启动:

systemctl start docker
systemctl enable docker

Docker使用

使用docker --help 命令查看docker都有哪些命令

我们通过一个Nginx的例子,把Docker镜像,容器相关的常用命令都跑一遍,边使用边解释:
拉取Nginx的镜像,Nginx有很多版本,第一次用肯定不知道该拉取那些版本,可以到远程镜像仓库看一下:registry.hub.docker.com/ ;我这里就使用nginx:1.15.2这个版本,如果不指定版本的话,默认拉取的是latest版本

docker pull nginx:1.15.2

镜像拉取完成后,已经存储在宿主机的硬盘上面了,使用docker images | grep nginx 可以看到刚才拉取的镜像,开始运行

docker run -d --name mynginx -p 80:80 nginx:1.15.2

启动起来之后我们使用docker ps查看nginx已经在运行了并且容器名称是 mynginx,映射到宿主机的端口是80,此时,在浏览器就可以访问到Nginx了(注意:要访问宿主机暴露出来的端口),下面我们可以进入容器看看Nginx安装在什么地方

docker exec -it mynginx /bin/bash
whereis nginx

配置文件在/etc/nginx/conf.d目录下,静态页面在/etc/share/nginx/html目录下,因为容器是会随时关闭的,一旦关闭,容器在运行过程中产生的数据都会丢失,并且维护容器内部的配置文件相当麻烦,所以此时我们需要对容器内的文件进行挂载,让他存储在宿主机上面,基于这个Nginx镜像重新生成一个容器,进行挂载

docker run -d --name nginx -p 81:80 -v /docker/nginx/config:/etc/nginx/conf.d -v /docker/nginx/html:/etc/share/nginx/html nginx:1.15.2

此时再次访问浏览器已经是404了,因为我们自己去挂载了html目录,所以容器内的html目录被覆盖了,里面没有html文件了。进入容器里面找到/etc/share/nginx/html发现里面是空的,不要慌,因为我们已经挂载了目录,此时只需要把html文件放到宿主机的/docker/nginx/conf//docker/nginx/html目录下即可,找到之前启动的mynginx容器,把他的配置文件复制到宿主机

docker cp mynginx:/etc/nginx/conf.d /docker/nginx/config
docker cp mynginx:/etc/share/nginx/html /docker/nginx/html

文件复制完之后重启容器:docker restart nginx,浏览器再次访问即可访问到;

Docker的网络模式

上面的例子跑的很顺利,但是我们忽略了一个细节:为什么通过宿主机映射出来的端口可以访问到容器呢,这就涉及到了Docker的网络模式:

  1. 网桥模式(默认):此模式会为每一个容器分配Network Namespace、设置IP等,并将并将一个主机上的Docker容器连接到一个虚拟网桥上,在宿主机执行指令ifconfig可以看到,多了一个docker0虚拟网卡,默认网关便是docker的IP地址,在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中
  2. 容器模式:指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。
  3. 主机模式:一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
  4. NONE模式:Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。

服务容器化

服务容器化需要依赖一个Dockerfile的打包文件,文件的编写格式如下:

  1. FROM: 第一条指令必须为FROM指令,用于指定基础镜像。
  2. MAINTAINER: 指定维护者信息。
  3. RUN: 会在shell终端运行命令。
  4. EXPOSE: 声明容器需要暴露的端口号。
  5. ENV: 指定一个环境变量,可以被后续的RUN引用,并且在容器中记录该环境变量。
  6. ADD: 该命令将复制指定的到容器中的。 其中可以是Dockerfile所在目录的一个相对路径;也可以是tar文件(自动解压)。
  7. VOLUME: 格式为 VOLUME [path]。 创建一个可以从本地主机或其他容器挂载点,一般用来存放需要保持的数据。
  8. USER: 指定运行容器时的用户名,后续的RUN也会指定该用户。
  9. WORKDIR: 指定工作空间,后续命令都在此目录下执行。
  10. CMD: docker启动后要执行的命令,可以在启动的时候覆盖。
  11. ENTRYPOINT: docker启动后要执行的命令,无法被覆盖,只能追加。

1. 手动构建

先来编写一个最简单的Dockerfile,在服务器上创建一个eureka文件夹,把jar包放进去,并编写Dockerfile文件

# 依赖的基础镜像
FROM java:8
# 把打包好的服务放到容器的根目录下并改名叫app.jar
ADD service-eureka-0.0.1-SNAPSHOT.jar app.jar
# 容器启动后要执行的命令,与CMD不同的是,ENTRYPOINT的命令无法被覆盖
ENTRYPOINT ["java", "-jar", "/app.jar"]
# 声明容器对外暴露的端口(可以不写)
EXPOSE 8761
[root@localhost eureka]# ll
总用量 49248
-rw-r--r--. 1 root root      118 5月  23 16:39 Dockerfile
-rw-r--r--. 1 root root 50424847 5月  23 16:33 service-eureka-0.0.1-SNAPSHOT.jar

执行命令 docker build -t eureka:release . 命令解释:

  • build 基于Dockerfile构建镜像;
  • -t 给生成的镜像打标签(tag);
  • . Dockerfile所在的目录(这里是当前目录); 最终会生成一个eureka镜像,然后进行docker run 运行容器就好了;

Dockerfile是用来构建镜像的一个模板,可以使用他来构建我们自己的应用程序,这种方式可以很方便的进行服务容器化部署,这种方式是手动在服务器上进行构建;

2. Maven的Docker插件构建

手动构建的方式大量的工作量在运维同学,而且运维同学还需要知道你的服务依赖的基础镜像和容器内的日志目录,这部分的工作开发比较熟,使用maven集成的docker打包插件可以把容器化的过程放在开发这边,运维只需要跑一整条命令即可

  • 在项目中引入docker插件
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.4.13</version>
        <configuration>
            <!-- 构建的镜像的名称 -->
            <imageName>dockermvc:peter</imageName>
            <dockerDirectory>${project.basedir}</dockerDirectory>
            <skipDockerBuild>false</skipDockerBuild>
            <resources>
                <resource>
                    <directory>${project.build.directory}</directory>
                    <include>${project.build.finalName}.jar</include>
                </resource>
            </resources>
        </configuration>
    </plugin>
</plugins>

通过Git,将代码推送到服务器上,执行命令

mvn clean package docker:build docker:run

Docker的镜像仓库

基本搭建

在自己的公司内部搭建镜像仓库,存放公司自己的镜像(类似于maven的私服),需要单独弄一台物理机进行存放,从镜像库里面拉取registry镜像docker pull registry,启动在5000端口:docker run ....;然后需要在之前的主机的配置一个镜像地址vim /etc/docker/daemon.json,添加insecure-registries,指向这台镜像仓库的地址:

{
  "registry-mirrors": ["https://4ulabqzs.mirror.aliyuncs.com"],
  "insecure-registries":["192.168.254.100:5000"]
}

重启docker

systemctl restart docker

现在我把本地的eureka镜像传到仓库里面:

# 后面的ip是仓库的地址
docker tag eureka 192.168.254.100:5000/eureka:tml
# 推送到仓库
docker push 192.168.254.100:5000/eureka

执行完上面的步骤之后,访问镜像仓库看看里面有没有:192.168.254.100:5000/v2/_catalog

Docker构建微服务与动态扩容

1. Docker Compose

1. 1. 什么是Docker Compose

在微服务架构中,我们需要打包的服务很多,规模一般的情况下得有30多个微服务,在线上可能还要根据实际情况动态扩缩容,不可能一个一个的进行build,这时候我们需要对微服务项目进行统一管理。

1. 2. Docker Compose管理容器的结构

Docker Compose将所管理的容器分为三层,分别是工程( project),服务(service)以及容器( container)。 Docker Compose运行目录下的所有文件( docker-compose.yml、 extends文件或环境变量文件等)组成一个工程(默认为 docker-compose.yml所在目录的目录名称)。一个工程可包含多个服务,每个服务中定义了容器运行的镜像、参数和依赖,一个服务可包括多个容器实例。 同一个docker compose内部的容器之间可以用服务名相互访问,服务名就相当于hostname,可以直接 ping 服务名,得到的就是服务对应容器的ip,如果服务做了扩容,一个服务对应了多个容器,则 ping 服务名 会轮询访问服务对应的每台容器ip ,docker底层用了LVS等技术帮我们实现这个负载均衡。

1.3. 使用

  • 安装
sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose 
sudo chmod +x /usr/local/bin/docker-compose
  • 弄几个微服务项目,一个微服务对应一个文件夹,我这里有3个:
[root@localhost app]# ll
总用量 3
drwxr-xr-x. 2 root root  65 523 16:39 eureka
drwxr-xr-x. 2 root root  64 523 17:22 order
drwxr-xr-x. 2 root root  63 523 17:23 user

每个服务里面都对应有一个Dockerfile文件

[root@localhost app]# ll eureka/; ll order/; ll user/
总用量 49248
-rw-r--r--. 1 root root      118 5月  23 16:39 Dockerfile
-rw-r--r--. 1 root root 50424847 5月  23 16:33 service-eureka-0.0.1-SNAPSHOT.jar
总用量 43024
-rw-r--r--. 1 root root      117 5月  23 16:38 Dockerfile
-rw-r--r--. 1 root root 44050593 5月  23 17:22 service-order-0.0.1-SNAPSHOT.jar
总用量 43024
-rw-r--r--. 1 root root      116 5月  23 16:36 Dockerfile
-rw-r--r--. 1 root root 44050819 5月  23 17:22 service-user-0.0.1-SNAPSHOT.jar
  • 上面说过,compose的结构最外层是工程,所以我们在工程下创建一个docker-compose.yaml文件,并进行容器编排:
version: '3'
services:
  eureka:
    build: eureka
    container_name: eureka
    restart: always
    ports:
      - "8761:8761"
  user:
    build: user
    container_name: user
    restart: always
    depends_on:
      - eureka
    ports:
      - "9001:9001"
  order:
    build: order
    container_name: order
    restart: always
    depends_on:
      - user
    ports:
      - "9002:9002"

具体的语法可以参考官网的例子,写的很详细docs.docker.com/compose/com… 执行docker-compose up -d 就可以对容器进行批量管理,当然也可以进行扩缩容,给指定的服务扩容,使用docker scale
docker compose功能不够强大,基本也就在开发环境用用,肯定不会用它去上生产环境的,它存在一些问题:

  • 容器的管理都是基于一台宿主机的,只是伪分布式;
  • 动态扩缩容也是基于一台宿主机的,也是伪集群;
  • 无法滚动更新

2. Docker Swarm

1. Swarm结构搭建

准备2台物理机:

  • 192.168.18.236 ->> swarm1
  • 192.168.18.165 ->> swarm2 swarm1:
docker swarm init --advertise-addr 192.168.18.236 --listen-addr 192.168.18.236

返回信息: docker swarm join --token SWMTKN-1-1zm6dj1d4du21w937isecmmgsmzzyfa9rzgad55iuk2yhu4bmg-daeiy9n6k5yaa11a7eg3j4ltu 192.168.18.236:2377 把这个命令复制到swarm2执行:

[root@swarm2 ~]# docker swarm join --token SWMTKN-1-1zm6dj1d4du21w937isecmmgsmzzyfa9rzgad55iuk2yhu4bmg-daeiy9n6k5yaa11a7eg3j4ltu 192.168.18.236:2377
This node joined a swarm as a worker.

swarm1中查看集群信息:

[root@swarm1 system]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
ry4anpndmeboenys44yw0pu2t *   swarm1              Ready               Active              Leader              18.09.9
zqyzsadxc28hcytik2rh6sda9     swarm2              Ready               Active                                  18.09.9

现在集群已经建立:swarm1是主节点,swarm2是从节点

2. 集群节点中部署应用

docker service create -p 80:80 --name nginx --replicas 2 nginx:1.15.2

查看集群中服务列表

[root@swarm1 system]# docker service ls
ID                  NAME             MODE                REPLICAS            IMAGE               PORTS
fx00qoitocdj        nginx          replicated              2/2             nginx:1.15.2        *:80->80/tcp

已经启动了2个nginx副本,对外暴露的端口都是80,在swarm2中也可以看到启动的容器:

[root@swarm2 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
794b5084af47        nginx:1.15.2        "nginx -g 'daemon of…"   4 minutes ago       Up 4 minutes        80/tcp              nginx

查看这两台nginx启动的节点

[root@swarm1 system]# docker service ps nginx
ID                  NAME           IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
tlewkpujja47        nginx.1        nginx:1.15.2        swarm2              Running             Running 7 minutes ago                       
uq5pdur0cmfe        nginx.2        nginx:1.15.2        swarm1              Running             Running 7 minutes ago  

Nginx集群分别在swarm1和swarm2,通过浏览器访问各个节点的nginx:192.168.18.236:80192.168.18.165:80

3. 集群节点动态扩容

上面我们说docker compose动态扩容是基于一台物理机的,那么swarm的方式能解决这个问题吗。下面对nginx进行扩容,副本由原来的2变为4:

[root@swarm1 system]# docker service scale nginx=4
keen_booth scaled to 4
overall progress: 4 out of 4 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
4/4: running   [==================================================>] 
verify: Service converged

看一下扩容之后的节点分布

[root@swarm1 system]# docker service ps nginx
ID                  NAME           IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
tlewkpujja47        nginx.1        nginx:1.15.2        swarm2              Running             Running 31 minutes ago                       
uq5pdur0cmfe        nginx.2        nginx:1.15.2        swarm1              Running             Running 31 minutes ago                       
8k507dtt3xrb        nginx.3        nginx:1.15.2        swarm2              Running             Running 2 minutes ago                        
sjiysgk5fma4        nginx.4        nginx:1.15.2        swarm1              Running             Running 2 minutes ago

swarm1和swarm2各运行了两个nginx容器,说明swarm方式是可以解决单台机器扩容问题的,在Swarm的内部也实现了高可用机制,如果某台机器的某个服务挂掉了,他可以内部帮我们维护好4个副本数,保证我们的副本数量不受环境影响。

4. 服务的滚动更新

Docker Swarm可以实现服务平滑升级,即服务不停机更新,用户无感知。
我们现在先把集群副本缩容为2

[root@swarm1 system]# docker service scale nginx=2
nginx scaled to 2
overall progress: 2 out of 2 tasks 
1/2: running   
2/2: running   
verify: Service converged

现在swarm1和swarm2上面各运行了一个nginx容器

[root@swarm1 system]# docker service ps nginx
ID                  NAME           IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
tlewkpujja47        nginx.1        nginx:1.15.2        swarm2              Running             Running 45 minutes ago                       
uq5pdur0cmfe        nginx.2        nginx:1.15.2        swarm1              Running             Running 45 minutes ago

现在的Nginx版本是1.15.2, 现在一个一个的把他升级成最新版本的,也就是latest版本:

[root@swarm1 system]# docker service update --image nginx nginx
nginx
overall progress: 2 out of 2 tasks 
1/2: running   
2/2: running   
verify: Service converged

在更新的过程中是先更新第1个节点再更新第2个节点,在更新第1个节点的时候如果用户请求会被转发到第2个节点,以此来实现平滑升级,用户无感知。我们再来看一下升级之后运行的镜像版本:

[root@swarm2 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
09b986422d9c        nginx:latest        "/docker-entrypoint.…"   11 seconds ago      Up 6 seconds        80/tcp              nginx

现在运行的已经是latest版本的nginx了。

5. 集群数据持久化

之前使用docker run 的方式运行一个容器的时候使用-v参数指定的数据挂在是宿主机和容器之间的数据挂载,那么我们现在做了集群之后,需要容器和容器之间进行数据挂载与共享。

  1. 单独搞一台机器做NFS 192.168.18.171
  2. 在每台机器上面都安装nfs-utils yum -y install nfs-utils
  3. 在nfs机器启动nfs服务
systemctl start nfs
systemctl enable nfs
  1. 在nfs上创建一个文件共享的目录 mkdir /nfs
  2. 编辑 /etc/exports文件,添加内容/nfs *(insecurerw,sync,no_root_squash)
  3. 重启nfs systemctl restart nfs
  4. /nfs目录下随便创建一个文件
  5. 在其他两个swarm机器上执行systemctl start rpcbind
  6. 在其他两个swarm机器上创建文件夹,存放容器内的挂载数据mkdir /mynfs
  7. 将swarm机器的/mynfs目录挂载到nfs服务器:mount -t nfs 192.168.18.171:/nfs /mynfs

核心思想

  1. swarm1创建一个数据卷将容器挂载到宿主机
  2. 将swarm1宿主机的数据卷挂载到nfs进行数据共享

总结

本文主要介绍了Docker在工作中的基本使用,尤其着重讲解了Docker Swarm部分,DockerSwarm算是比较重要的,虽然这部分功能被k8s代替,几乎没有人去用,但是k8s对于容器的编排思想和DockerSwarm还是很像的,所以这部分也介绍的比较多,把这部分搞明白了后面再去学k8s的时候也好上手一点。另外,本人的职业是Java开发,运维这部分接触的比较少,对于本篇文章理解的可能不够深入,希望大家理解一下,主要是把平时工作中接触到的和自己学到的做了个总结,如果文章中有错误之处还请各位大佬提出来,我立马改正。谢谢!

九十九次的理论不如一次的行动来得实际。