Docker及DockerSwarm讲解

Docker

Docker的特点

  1. 更快的交付和部署
  2. 更高效的虚拟化
  3. 更轻松的迁移和扩展
  4. 更简单的管理

Docker和虚拟机的区别

  • 虚拟机是运行在每个应用层级的客户端操作系统上的,对操作系统依赖性强
  • Docker容器是基于进程的隔离,多个容器可以共享单个内核,并且创建Docker容器的镜像所需要的配置不依赖于宿主机系统,真是因为这种隔离性,使Docker的应用可以运行在任何地方

Docker的安装

配置阿里云yum源

配置阿里云Docker Yum源
# Set up repository
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# Use Aliyun Docker
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

查看Docker版本:

yum list docker-ce --showduplicates

安装yumutils库

$ yum install -y yum-utils

安装docker

$ yum install docker-ce-19.03.12 docker-ce-cli-19.03.12 containerd.io
#启动docker
$ systemctl start docker

Docker compose安装

#下载docker-compose并写入到/usr/local/bin/docker-compose
$ curl -L "https://github.com/docker/compose/releases/download/1.27.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
#这里可能下载会很慢,可直接到ftp下载:ftp://[email protected]/home/share/开发环境/软件/docker-compose
#赋予可执行权限
$ chmod +x /usr/local/bin/docker-compose

Docker运行机制

​ docker engine是docker的核心部分,使用的是C/S架构模式,包含三大核心组件:

  • docker cli

    docker命令行接口,可以在命令行中使用docker相关命令与docker守护进程交互,从而管理如images,container, network, data volumes等实体

  • rest api

    应用程序api接口,开发者可以使用该api接口与docker守护进程交互,从而指示后台进行相关操作

  • docker daemon

    Docker的服务端组件,他是docker架构中运行在后台的一个守护进程,可以接收并处理来自命令行接口及api接口的命令,然后进行相应的后台操作

Docker架构

​ docker架构主要包括Client,Docker Host, Registry三个部分

  • Client

    即Docker客户端,也就是上面提到的docker cli,开发者使用这个客户端相关指令与docker守护进程进行交互,从而进行docker镜像的创建,拉去和运行

  • Docker Host (docker主机)

    即docker内部引擎运行的主机,主要值docker daemon,可以通过docker守护进程与客户端还有docker的镜像仓库Registry进行交互,从而管理images,container等

  • Registry(仓库)

    docker的镜像仓库,默认使用docker hub官方仓库,也可以使用开发者本地的私有仓库

  • Images(镜像)

    docker镜像是一个只读的模版,包含了创建docker容器的操作指令,通常情况下一个docker镜像是基于另一个基础镜像构建的,并且新创建的镜像会额外包含一些功能配置

    例如:开发者可以依赖一个centos的基础镜像创建一个新镜像,并可以在新镜像中安装python,java等软件

  • Container(容器)

    Docker容器属于镜像的一个可运行实例,类似java中的类和对象的关系,可以使用cli 命令行进行容器的创建,运行,停止,移动,删除等,也可以将一个容器连接到一个或者多个网络中,将数据存储与容器进行关联

Dockerfile

​ 创建一个docker镜像一般从Dockerfile开始。Dockerfile包含了我们创建镜像的指令,如一个基本的dockerfile文件:

#基础镜像
FROM python:3
# 设置工作目录
WORKDIR /app
# 复制当前目录下的所有内容到容器内的/app目录下
ADD . /app
# 安装python依赖库
RUN pip install -r requirements.txt
# 设置容器暴漏端口80
EXPOSE 8080
#定义环境变量
ENV NAME World
# 容器启动后运行脚本 使用exec方式执行
CMD ["python","app.py"]

#使用exec方式执行,推荐方式
#CMD ["executable", "param1", "param2"]
# 在/bin/sh中执行
#CMD command param1 param2
# 提供给ENTRYPOINT的参数
#CMD ["param1","param2"]

Dockerfile基本结构

dockerfile是一个普通的文本文件,一般情况下dockerfile可分为四个部分:基础镜像信息,维护者信息,镜像操作指令和容器启动时的执行指令

常用指令

  1. FROM

    FROM指令用于初始化一个新的镜像构建阶段,同时为之后的指令设置一个基础镜像,设置的基础镜像可以从DockerHub仓库中或者本地镜像列表获取,如果本地镜像列表中已经存在则直接使用本地镜像,否则从镜像仓库中拉取

    语法格式

    FROM 
    FROM :
    

    在使用FROM指令时,需要注意以下几点:

    • 一个有效的Dockerfile必须以FROM指令开头
    • 为了创建多重镜像或者相互依赖的镜像,在同一个Dockerfile中可能出现多个FROM指令
    • 参数是可选的,主要用来区分基础镜像的版本,默认是latest
  2. MAINTAINER

    指定当前镜像的维护这信息,没有格式要求,一般使用用户名和邮箱进行标识,如:

    MAINTAINER 'zhangsan"
    
  3. RUN

    RUN指令用于执行指定的脚本命令,有两种格式,语法如下:

    # 这种将在shell终端中运行命令,即/bin/sh -c ""
    RUN 
    #这种使用exec执行,指定使用其他终端可以使用这种方式,例如:
    # RUN ["/bin/bash","-c","echo hello"]
    RUN ["executable", "param1", "param2"]
    

    其中每条RUN指令都将在当前镜像基础上执行指定命令并提交为新的镜像,如果要执行多条RUN指令,通常会将多条RUN指令合成一条,并使用斜杠” \ “来换行,这样可以减少构建镜像的体积

  4. CMD

    CMD指令用于指定启动容器时执行的命令,有三种格式:

    # 使用exec执行,推荐方式
    CMD ["executable", "param1", "param2"]
    # 在/bin/sh中执行,提供给需要交互的应用
    CMD command param1 param2
    # 提供给ENTRYPOINT的默认参数
    CMD ["param1", "param2"]
    

    每个Dockerfile只能有一条CMD指令,如果有多条只有最后一条生效,如果启动容器时指定了运行指令,则会覆盖掉CMD指定的命令

  5. EXPOSE

    用于声明容器内部暴露的端口号,供容器访问连接使用,如法格式如下:

    EXPOSE  [...]
    
  6. ENV

    ENV指令用于为上下文设定一个环境变量,该指令在后续指令或内联文件中都可以使用,语法如下:

    ENV  
    ENV = =
    

    第一种格式为属性设置唯一的属性值,属性第一个空格之后的所有字符串(包括空格,引号)都将被视为该属性的值;第二种格式允许同时为多个属性赋值,而这种方式里面的引号,反斜杠等都将被解析掉

  7. ARG

    ARG指令和ENV指令的效果一样,都是设置环境变量,不同的是,ARG设置的是构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的,格式如下:

    ARG <参数名>[=<默认值>]
    

    dockerfile中的ARG指令是定义参数名以及定义其默认值,该默认值可以通过在构建命令docker build中使用–build-arg <参数名>=<值>来覆盖。

  8. ADD

    ADD指令用于赋值src资源文件到容器的dest目录下,复制的资源可以是文件,目录以及远程URLs资源,格式如下:

    ADD ... 
    

    在使用ADD指令时,复制的src资源必须是当前上下文目录或者子目录,复制的内容实际上是该目录下的所有内容,其中包括文件系统的元数据,而目录本身不会被复制,当dest目录不存在时,会在复制文件时自动创建,需要注意的是,当使用ADD指令复制一个压缩包时,ADD指令会在复制后自动解压该文件

  9. COPY

    作用和ADD指令类似,区别在于不能赋值Urls路径文件,也不能解压文件,语法如下:

    COPY ... 
    
  10. ENTRYPOINT

ENTRYPOINT指令是配置容器启动后执行的命令,每一个dockerfile只能有一个entrypoint,有两种语法格式,如下:

# exec格式,推荐
ENTRYPOINT ["executable","param1","param2"]
# shell格式
ENTRYPOINT command param1 param
  1. WORKDIR

    用于为后续的指令(如:RUN, CMD, ENTRYPOINT, COPY, ADD)指定工作目录, 在同一个dockerfile中可以多次使用WORKDIR指令,语法格式如下:

    WORKDIR /path/to/workdir
    

.dockerignore

​ docker在读取应用上下文中的Dockerfile文件之前,会先查看应用上下文是否包含一个.dockerignore文件,如果该文件存在,则Docker会先将.dockerignore文件中盛声明的文件或目录排除,然后在构建镜像,使用.dockerignore有助于进行文件复制过程中避免加入过大或者敏感的无用的文件和目录,如:

*/temp*
*/*/temp*
temp?

Docker客户端常用指令

  1. 列出镜像

    查看本地镜像列表已有的镜像,格式如下:

    $ docker images
    

    结果列:

    • REPOSITORY:镜像名称
    • TAG:镜像标签,版本号,默认latest
    • IMAGE ID: 镜像ID,显示12位,实际是64位,这里显示缩写形式
    • CREATED:距今创建镜像的时间
    • SIZE:镜像大小
  2. 搜索镜像

    搜索docker hub上的镜像,格式如下:

    $ docker search ubuntu
    

    结果列:

    • NAME:镜像的名称,这里有两种格式,其中不带"/"的表示官方镜像,带”/“表示其他用户公开的镜像,公开镜像”/“前面是用户在dockerhub上的用户名(唯一),后面是对应的镜像名
    • DESCRIPTION:镜像的描述
    • STARS:该镜像的收藏数
    • OFFICAL:表示是否为官方镜像
    • AUTOMETED:表示是否自动构建镜像,例如,用户可以将docker hub绑定到git hub,当代码提交更新后,可以自动构建镜像
  3. 拉取镜像

    docker pull 可以拉取镜像到本地,格式如下:

    $ docker pull ubuntu
    
  4. 构建镜像

    除了使用docker pull拉取镜像外,还可以使用docker build构建镜像,通常情况下都是通过Dockerfile文件来构建镜像,格式如下:

    $ docker build -t hellodocker .
    

    上面命令通过 ”.“ 使用当前应用的上下文的Dockerfile文件来构建镜像,也可以指定Dockerfile目录

  5. 删除镜像

    再删除镜像时,需要指定镜像名或者镜像ID,格式如下:

    $ docker rmi -f hellodocker
    

    需要注意,再删除镜像之前,必须先删除和停止由该镜像创建的容器,另外通过镜像名操作镜像时,如果操作的不是默认latest镜像,需要在镜像名称后面指定镜像tag标签参数来确保镜像的唯一性

  6. 创建并启动容器

    可以使用docker run来创建并运行容器,格式如下:

    $ docker run -d -p 5000:8080 --name test hellodocker
    
    • docker run : 表示创建并启动一个容器,而后面的hellodocker表示要运行的镜像名称
    • -d : 表示容器启动时在后台运行
    • -p 5000:8080:表示将容器暴露的8080端口映射到宿主机的5000端口,也可以使用-P(大写)来映射主机的随机端口
    • –name :表示为创建后的容器指定名称为test,如果没有该参数,则生成一个随机的容器名
  7. 列出容器

    生成容器后可以通过docker ps指令查看当前运行的容器,格式如下:

    $ docker ps 
    

    结果列:

    • CONTAINER ID:表示容器的ID
    • IMAGE:表示生成该容器的镜像名称
    • COMMAND:表示容器启动时运行的命令,Docker要求在容器启动时必须运行一个命令
    • CREATED:表示容器创建的时间
    • STATUS:容器运行状态,Up表示运行中,Exited表示已停止
    • PORTS:表示内部暴漏的端口映射到主机的端口
    • NAMES:表示生成容器的名称,自动生成,可以使用–name指定
  8. 执行命令

    生成容器后,客户端可用通过docker exec指令与运行的容器进行通信,在通信时需要指定容器ID或名称,格式如下:

    $ docker exec e82f0de93902 ls -l
    

    上述命令会将容器中的文件列举出来

  9. 停止容器

    当不需要容器运行时,可以使用docker stop指令停止指定的容器,在停止容器时需要指定容器ID或名称,格式如下:

    $ docker stop e82f0de93902
    

    使用该指令停止容器会有略微的延迟,停止成功后会返回该容器的ID,可以使用docker kill e82f0de93902立即杀死运行的容器进程

  10. 启动容器

    容器停止后,如果需要重新访问该容器中的程序,则需要重启该容器,格式如下:

    docker start e82f0de93902
    

    除了docker start指令可以启动已经停止的容器之外,还可以使用docker restart指令重启容器,docker restart既可以重新启动已经停止的容器,也可以重启当前正在运行的容器 docker restart e82f0de93902

Docker Hub远程镜像管理

​ Docker除了提供常用的镜像,容器的操作指令外,还提供了一些管理指令

管理指令 说明
docker container 用于管理容器
docker image 用于管理镜像
docker network 用于管理Docker网络
docker node 用于管理Swarm集群节点
docker plugin 用于管理插件
docker secret 管理Docker密钥
docker service 管理docker的一些服务
docker stack 管理docker堆栈
docker swarm 管理swarm
docker system 管理docker
docker volume 管理数据卷

需要说明的是,管理指令中的docker container和docker image与上一节中讲的常用操作指令是相同的,只是上一节的常用指令省略了container和image关键字,因为docker默认是对镜像和容器进行操作的。

Docker镜像管理

上面我们通过docker 引擎提供的客户端工具实现了创建镜像,生成并启动容器,存储镜像和搜索镜像等,对于Docker镜像管理Docker提供了一些工具,如Docker Hub和Docker Registry

Docker Hub远程镜像管理

Docker hub是一个基于云的注册服务,来提供镜像的管理。

下面演示如果通过DockerHub来管理我们的镜像

  1. 首先你需要先在docker hub官网注册一个账号,注册成功以后即可以登录到docker hub中心,可以在上面创建一个仓库用于存放我们的镜像

  2. 当有了镜像仓库之后,就可以将本地的镜像推送到docker hub仓库进行保存了,例如我们的hellodocker镜像,我们要将hellodocker镜像推送到远程仓库,该镜像名就必须符合DockerID/repository的形式规范,其中DockerID为docker hub上的账户名,repository为镜像名,我们需要先通过docker tag指令修改我们的镜像名,具体指令如下(这里假设我们的docker hub账号是zhangsan):

    $ docker tag hellodocker:latest zhangsan/hellodocker:latest
    

    使用上述的命令会复制一份名为zhangsan/hellodocker的镜像,而原名称hellodocker的镜像不变

  3. 登录认证,要想把客户端镜像推送到远程仓库需要先登录认证,指令如下:

    $ docker login
    

    运行上面的命令,会要求输入用户名密码进行认证登录,认证成功之后就会返回Login Successed的消息

  4. 推送镜像

    我们在客户端使用docker push指令向远程仓库推

    送镜像,格式如下:

    $ docker push zhangsan/hellodocker:latest
    

    完成后就可以在docker hub上进行查看了,docker hub会定时对上传的公开镜像仓库进行索引,一段时间之后,任何人都可以搜索到公开镜像仓库

Docker Registry私有仓库

相比Docker hub而言,docker registry功能就不够全面了,如果开发者想要严格控制镜像的存储位置,完全拥有自己的镜像分配渠道,或者想要将镜像存储和分步紧密嵌入到自己的开发中,则使用Docker Registry更加合适,下面针对Docker Registry本地私有仓库管理进行讲解

  1. 启动Docker Registry

    使用Docker官方提供的Registry镜像就可以搭建本地私有镜像仓库,具体指令如下:

    $ docker run -d -p 5000:5000 --restart=always --name registry \
      -v /mnt/registry:/var/lib/registry \
      registry:2
    

    参数说明:

    • -d :表示后台运行
    • -p 5000:5000:表示将私有镜像仓库内部暴露的端口5000映射到宿主机的5000端口
    • –restart=always:表示本地私有镜像仓库宕机后始终会自动重启
    • –name=registry:容器的名称
    • -v /mnt/registry:/var/lib/registry:表示将容器内的默认存储位置/var/lib/registry中的数据挂在到宿主机/mnt/registry目录下,这样当容器销毁后,在容器中/var/lib/registry目录下的数据会自动备份到宿主机的指定目录
  2. 重命名镜像

    之前推送镜像时,都是默认推送到远程镜像仓库,而本次是将镜像推送到本地私有镜像仓库,由于推送本地私有镜像仓库的镜像名必须符合”仓库IP:端口号/repositry“的形式,因此需要修改镜像的名称,具体指令如下:

    $ docker tag hellodocker:latest localhost:5000/hellodocker
    
  3. 配置docker忽略TLS

    其他docker机器如果想要与配置有自签名证书和账号认证的私有仓库进行通信,就必须在各自Docker主机上配置该私有仓库地址,进行使用登记, 在Docker机器终端使用vim /etc/docker/daemon.json 命令编辑daemon.json文件,添加以下内容:

    "insecure-registries":["10.9.225.198:5000"]
    

    上述内容就是私有仓库的地址,配置完后记得重启docker

    注意:即使是没有配置自签名和账号认证,在跨主机访问Docker Registry也要在daemon.json配置文件中配置该私有仓库地址。

  4. 推送镜像

    本地私有镜像仓库搭建完成,且要推送的镜像已经准备就绪后,就可以将指定镜像推送到私有仓库了,具体指令如下:

    $ docker push localhost:5000/hellodocker
    

    执行上述指令后,就可以完成镜像的推送,为了验证推送结果,我们可以在宿主机浏览器上输入地址:http://localhost:5000/v2/hellodocker/tags/list进行查看,也可以到本地私有镜像仓库指定的镜像目录下查看是否有我们的镜像(/mnt/registry/docker/registry/v2/repositories)

    经过上面的配置,我们的私有仓库基本上已经完成,但是我们还需要保护我们的仓库,不是谁都可以访问的,而是需要用户名密码的,下面我们会配置私有仓库的密钥和用户名密码。

    保护Docker Registry

    1. 生成签名证书

      下面的命令会生成一个csr证书申请。

      # 在/home目录下创建对应的文件夹,用于存放证书文件
      $ mkdir registry && cd registry && mkdir certs && cd certs
      # 生成一个私钥
      $ openssl genrsa -out private.key 1024
      # 生成证书申请,按照实际情况填写信息
      $ openssl req -new -key private.key -out my.csr
      

      CA颁发证书(需要先配置CA证书,具体可以参考openssl):

      openssl ca -in my.csr -out my.crt -days 365
      

      执行上面的命令就会生成一个my.crt文件,这个就是我们的证书文件,把证书文件放到上面创建的目录下(/home/registry/certs/)

    2. 生成用户名和密码

      生成自签名证书之后,为了确保Docker机器与Docker Registry本地镜像仓库的交互,还需要生成一个连接认证的用户名和密码,使其他docker用户只有通过用户名和密码登录后才允许连接到Docker Registry私有镜像仓库,生成连接认证的具体指令如下:

      $ cd /home/registry && mkdir auth
      # 使用htpasswd生成密码 -B bcrypt算法,-b 直接在命令行上输入密码 -n display results on stdout.
      $ htpasswd -Bbn neteye neteye > auth/htpasswd
      

      执行上述命令后,首先会在前面的registry目录下再创建一个auth目录,然后使用htpasswd命令生成用于访问docker registry仓库服务的用户名和密码(用户名是neteye,密码是neteye)

    3. 启动Docker Registry镜像服务

      完成上面的准备工作后,就可以正式部署带有安全认证的私有镜像仓库了(需要将前面小节中运行的Docker Registry删除),具体指令如下:

      $ docker run -d \
      	-p 5000:5000 \
      	--restart=always \
      	--name registry \
      	-v /mnt/registry:/var/lib/registry \
      	-v "$(pwd)/auth:/auth" \
      	-e "REGISTRY_AUTH=htpasswd" \
      	-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
          -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
          -v "$(pwd)/certs:/certs" \
          -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/my.crt" \
          -e "REGISTRY_HTTP_TLS_KEY=/certs/private.key" \
          registry:2
      
    4. 登录Docker Registry私有仓库

      在Docker主机上通过docker login指令先登录docker registry私有仓库,指令如下

      $ docker login 10.9.225.198:5000
      

      使用上述指令就可以进行镜像仓库的登录了,登录成功就会返回“Login Successed”提示信息

    5. 再次推送并验证是否推送成功

      登录成功之后就可以再次使用docker push指令推送镜像到私有仓库,推送成功之后就会有提示信息,当然最严谨和直观的方法就是在Docker Registry服务挂载的镜像目录上进行验证,在私有仓库磁盘的/mnt/registry/docker/registry/v2/repositories目录进行查看,效果如下:

      $ ls /mnt/registry/docker/registry/v2/repositories/
      busybox
      

      可以看到busybox镜像已经成功的推送到了私有仓库,或者也可以直接在浏览器访问接口https://10.9.225.198:5000/v2/busybox/tags/list 查看私有仓库busybox镜像的tag列表。

Docker Network

Docker默认使用bridge(单主机互联)和overlay(跨主机互联)两种网络驱动来进行容器的网络管理,如果需要还可以自定义网络驱动进行网络管理。

Docker默认网络管理

Docker在安装之后默认安装了三种网络,可以使用docker network ls查看,如下:

$ docker network ls
NETWORK ID          NAME                   DRIVER              SCOPE
7432201b99cc        bridge                 bridge              local
a3bbe8e1266a        host                   host                local
b88805eac801        none                   null                local

可以看出来默认的三种网络分别是bridge,host和none,其中bridge的网络就是默认的bridge驱动网络,也是容器创建时默认的网络管理方式,配置后可以与宿主机通信从而实现与互联网通信功能,而host和none属于无网络,容器添加到这两个网络时不能与外界网络通信。

下面演示默认的bridge网络管理方式

  1. 创建并启动容器,执行如下指令:

    $ docker run -itd --name networktest hellodocker
    
  2. 使用网络查看指令查看网络详情,执行如下指令

    $ docker network inspect bridge
    

    上述指令用于核查名称为bridge的网络详情,需要指定网络名称或者是网络ID,执行之后效果如下:

    $ docker network inspect bridge
    [
        {
            "Name": "bridge",
            "Id": "7432201b99ccc137cb4a923174a6931a5ad8876274b8b650c00743fbe60d0fa8",
            "Created": "2020-09-15T14:52:28.874222377+09:00",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": null,
                "Config": [
                    {
                        "Subnet": "172.17.0.0/16"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {
                "d7deaeeede00b5ce9a7571ea96bbbd8d7a20c0ddb786c75153c6027b7f5cddab": {
                    "Name": "networktest",
                    "EndpointID": "aa90388cc2436791ec7fa16edc7af9026ae2fded96760e67f603471751eb41f3",
                    "MacAddress": "02:42:ac:11:00:02",
                    "IPv4Address": "172.17.0.2/16",
                    "IPv6Address": ""
                }
            },
            "Options": {
                "com.docker.network.bridge.default_bridge": "true",
                "com.docker.network.bridge.enable_icc": "true",
                "com.docker.network.bridge.enable_ip_masquerade": "true",
                "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
                "com.docker.network.bridge.name": "docker0",
                "com.docker.network.driver.mtu": "1500"
            },
            "Labels": {}
        }
    ]
    
    

    从图中可以看出,其中就包括了使用默认的bridge网络管理方式启动的名称为networktest的容器,

​ 需要注意的是,这里介绍的三种网络bridge,host,none都是在非集群环境下Docker提供的默认网络,而在Docker Swarm集群环境下,Docker还提供了docker_gwbridge和ingress两种默认网络

自定义网络

虽然Docker提供的默认网络使用比较简单,但是为了保证容器中应用的安全性,在实际开发中更推荐使用自定义网络进行容器管理。

在Docker中可以自定义bridge网络,overlay网络,也可以创建network plugin(网络插件)或者远程网络以实现容器网络的完全定制和控制,下面分别对几种自定义网络进行讲解

  1. Bridge networks(桥接网络)

    为了保证容器的安全性,我们可以使用基于bridge的驱动创建新的bridge网络,这种基于bridge驱动的自定义网络可以较好的实现容器的隔离,

    需要说明的是,这种自定义的基于bridge驱动的网络对于单主机的小型网络环境管理是一个不错的选择,但是对于大型的网络环境管理(如集群)就需要考虑使用自定义overlay集群网络

  2. Overlay network in swarm mode(Swarm集群中的覆盖网络)

    在docker swarm集群环境下可以创建基于overlay驱动的自定义网络,为了保证安全性,swarm集群使自定义的overlay网络只适用于需要服务的集群中的节点,而不会对外部其他服务后者docker主机开放。

  3. Custom network plugins(定制网络插件)

    自定义网络插件会在Docker进程所在的主机上作为另一个运行的进程,由于自定义网络插件使用较少,这里不做详解

自定义Bridge网络

下面对自定义Bridge网络进行讲解

  1. 创建自定义网络

    在docker主机上可以使用docker network create指令来创建网络,指令如下:

    $ docker network create --driver bridge mybridgenw
    

    执行上面的命令可以创建一个基于bridge驱动名为mybridgenw的网络,其中–driver(可简写为-d)用于指定网络驱动类型,mybridgenw就是新创建的网络的名称,需要说明的是–driver可以省略,默认使用bridge驱动。

    创建完成后可以使用docker network ls查看网络是否创建成功,指令如下:

    
    $ docker network ls
    NETWORK ID          NAME                   DRIVER              SCOPE
    7432201b99cc        bridge                 bridge              local
    a3bbe8e1266a        host                   host                local
    b7304d4b790d        mybridgenw             bridge              local
    b88805eac801        none                   null                local
    
    

    可以看到新创建的名为mybridgenw的网络已经在网络列表中,还可以你用docker network inspect mybridgenw查看新建网络的详细信息

  2. 使用自定义网络启动容器

    自定义网络创建成功后就可以使用该网络启动一个容器了,具体指令如下:

    $ docker run --network=mybridgenw -itd --name=nwtest hellodocker
    

    执行上述指令后,会创建一个名为nwtest的容器,指令中的–network参数指定了该容器的网络连接为自定义的mybridgenw网络。

    通过docker inspect nwtest指令可以查看启动后的容器的网络详情,来查看其网络管理方式,效果如下:

Docker及DockerSwarm讲解_第1张图片

从图中可以看出名称为nwtest的容器使用的就是自定义的mybridgenw网络进行容器网络管理的。

  1. 为容器添加网络管理

    名为nwtest的容器使用的只有自定义的mybridgenw一种网络管理方式,我们还可以继续为该容器添加其他的网络管理方式,指令如下:

    $ docker network connect bridge nwtest
    

    执行上述指令后,会为容器nwtest另添加一种默认的bridge网络管理方式,再次使用docker inspect nwtest指令查看该容器网络情况,效果如下:

Docker及DockerSwarm讲解_第2张图片

可以看到执行完容器添加网络管理的指令后,容器nwtest就拥有了两种网络管理方式,分别为默认的bridge网络和自定义的mybridgenw网络。

  1. 断开容器网络连接

    容器即可以连接网络,也可以断开网络。这里以断开nwtest容器的自定义网络mybridgenw为例进行演示,断开网络连接的指令如下:

    $ docker network disconnect mybridgenw nwtest
    

    断开网络连接的指令与连接网络的指令类似,在使用时需要指定网络名称和容器名称。

  2. 移除自定义网络

    当不再需要某个网络时,可以将该网络移除,但在网络移除之前一定要先将所有与该网络连接的容器断开,移除自定义网络的指令如下:

    $ docker network rm mybridgenw
    

    执行上面的指令就可以移除名为mybridgenw的自定义网络

容器之间的网络通信

Docker中不同容器之间需要通过网络来进行通信,那么各个容器之间具体是如何实现通信的呢?接下来我们以非集群环境下的容器通信为例,对docker容器之间的通信进行讲解。具体步骤如下:

  1. 创建容器

    (1)创建两个使用默认的bridge网络的容器,指令如下

    $ docker run -itd --name=container1 busybox
    $ docker run -itd --name=container2 busybox
    

    执行上面的指令,会创建两个名为container1和container2的容器,同时他们都使用默认的bridge进行网络管理。

    (2)创建一个使用自定义的mybridgenw网络的容器,具体指令如下:

    $ docker run --network=mybridgenw -itd --name=container3 busybox
    

    执行上述指令,会创建一个名为container3的容器,使用–network参数指定了该容器的网络管理为自定义的mybridgenw。

    (3)为container2容器新增一个自定以的mybridgenw网络连接,指令如下:

    $ docker network connect mybridgenw container2
    

    执行上述指令后,container2容器就同时拥有了bridge和mybridgenw两种网络管理方式。

    执行完前面3个步骤后,container1使用的是默认的bridge网络,container3使用的是自定义的mybridgenw网络,而container2使用的是默认的bridge网络和自定义的mybridgenw网络,这三个容器的具体网络管理如下图所示:

    Docker及DockerSwarm讲解_第3张图片

    从图中可以看出,container1和container2在同一个默认的bridge网络下,这俩个容器可以相互通信,而container2又和container3在同一个自定义的mybridgenw网络下,这两个容器也可以相互通信,但是container1和container3属于不同的网络,所以这两个容器无法通信。

  2. 容器地址查看

    为了演示不同容器之间的网络通信情况,这里需要先查看各个容器的网络地址。

    首先进入container2容器,具体指令如下:

    $ docker attach container2
    

    然后使用ifconfig指令查看当前容器被动态分配的IP地址,如下所示:

    / # ifconfig
    eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:04
              inet addr:172.17.0.4  Bcast:172.17.255.255  Mask:255.255.0.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:8 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)
    
    eth1      Link encap:Ethernet  HWaddr 02:42:AC:14:00:04
              inet addr:172.20.0.4  Bcast:172.20.255.255  Mask:255.255.0.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:8 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)
    
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    

    从运行结果可以看出,在container2容器内部有两个网卡eth0和eth1(这就是使用了两种网络管理方式自动产生的),并分别对应的IP地址为:172.17.0.4和172.20.0.4。

    接下来分别进入container1和container3,并通过ifconfig查看对应容器的IP地址,效果如下:

    $ docker attach container1
    / # ifconfig
    eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
              inet addr:172.17.0.3  Bcast:172.17.255.255  Mask:255.255.0.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:8 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)
    
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    $ docker attach container3
    / # ifconfig
    eth0      Link encap:Ethernet  HWaddr 02:42:AC:14:00:03
              inet addr:172.20.0.3  Bcast:172.20.255.255  Mask:255.255.0.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:8 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)
    
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    
    

    可以看出container1的ip地址为:172.17.0.3,container3的IP地址为:172.20.0.3

    说明:当使用docker attach指令进入容器后,可以使用exit命令或者快捷键CTRL+p+q组合键退出当前容器,只不过exit退出容器后,该容器就会停止运行,而CTRL+p+q组合键退出,容器会继续运行

  3. 容器通信测试

    首先使用docker attach container1指令进入container1容器内部,使用ping命令连接container3查看是否能够通信,如下所示:

    $ docker attach container1
    / # ping -w 4 172.20.0.3
    PING 172.20.0.3 (172.20.0.3): 56 data bytes
    
    --- 172.20.0.3 ping statistics ---
    5 packets transmitted, 0 packets received, 100% packet loss
    / # ping -w 4 container3
    ping: bad address 'container3'
    

    从上面结果可以看出,无论我们ping ip地址还是ping 容器名称都无法连通container3,这也就验证了两个容器不在同一个网络环境下,无法通信的判断。

    接着使用docker attach container2指令进入container2容器,使用容器IP分别连接container1和container3进行通信测试,效果如下:

    $ docker attach container2
    / # ping -w 4 172.17.0.3
    PING 172.17.0.3 (172.17.0.3): 56 data bytes
    64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.153 ms
    64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.078 ms
    64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.089 ms
    64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.077 ms
    
    --- 172.17.0.3 ping statistics ---
    5 packets transmitted, 4 packets received, 20% packet loss
    round-trip min/avg/max = 0.077/0.099/0.153 ms
    
    / # ping -w 4 172.20.0.3
    PING 172.20.0.3 (172.20.0.3): 56 data bytes
    64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.221 ms
    64 bytes from 172.20.0.3: seq=1 ttl=64 time=0.098 ms
    64 bytes from 172.20.0.3: seq=2 ttl=64 time=0.092 ms
    64 bytes from 172.20.0.3: seq=3 ttl=64 time=0.085 ms
    
    --- 172.20.0.3 ping statistics ---
    5 packets transmitted, 4 packets received, 20% packet loss
    round-trip min/avg/max = 0.085/0.124/0.221 ms
    

    从上面运行结果可以看到,在container2内部使用“ping IP”指令可以同时连通container1和container3,这也与前面的分析结果相同。

    最后,再在container2容器内部使用容器名称分别连接contaienr1和container3进行通信测试,如下所示:

    / # p ping -w 4 container1
    ping: bad address 'container1'
    / # ping -w 4 container3
    PING container3 (172.20.0.3): 56 data bytes
    64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.155 ms
    64 bytes from 172.20.0.3: seq=1 ttl=64 time=0.119 ms
    64 bytes from 172.20.0.3: seq=2 ttl=64 time=0.097 ms
    64 bytes from 172.20.0.3: seq=3 ttl=64 time=0.105 ms
    
    --- container3 ping statistics ---
    5 packets transmitted, 4 packets received, 20% packet loss
    round-trip min/avg/max = 0.097/0.119/0.155 ms
    

    从上面运行结果可以看出,在container2内部使用”ping 容器名称“的指令可以连通container3,而连接container1却显示“bad address ‘container1’”错误。

    通过前面的测试,我们可以得出一个结论:不同容器之间想要相互通信必须在同一网络环境下;使用默认的bridge网络管理的容器可以使用容器ip通信,但无法使用容器名称通信;而使用自定义网络管理的容器则同时可以使用容器IP和容器名称进行通信。

    补充: 默认bridge网络下可以使用–link参数通过容器名称进行通信

    在默认网络下,可以使用–link参数的具体指令如下:

    $ docker run -itd --name=container4 --link container1:c1 busybox
    

    执行上述的指令后,会创建一个名为container4的容器,-itd参数用于指定后台交互式运行,–name指定容器名称,而–link container1:c1则将新建的container4容器连接到了container1容器且为container1容器定义了别名c1.

    这种使用了–link参数创建的默认网络下的容器就可以使用容器名号称或别名与指定连接的容器进行通信了,所以这里contaienr4可以使用容器名container1或者别名c1与container1进行通信,但容器container1仍不可以使用容器名称与container4进行通信。

    不过根据docker官方声明,–link属于低版本的遗留功能,这里只有容器需要在默认网络下使用容器名进行通信的情况下才会使用–link功能,官方更推荐使用用户自定义网络进行容器管理。

Docker Swarm集群

上一节我们对Docker中的网络管理进行了讲解,同时也对非集群环境下基于bridge驱动的自定义网络的使用进行了讲解,然而Docker中还涉及到另一种网络overlay必须在Docker Swarm集群环境下才能使用,接下来我们针对Docker Swarm集群进行详细讲解

Docker Swarm概述

Docker Swarm是一个用于创建和管理Docker集群的工具,Docker1.12以及以后的版本集成了swarmkit工具,该工具主要用于Docker集群管理和容器编排,因此开发者可以不用安装额外的软件包,只需要简单的命令就可以创建并管理DockerSwarm集群。

DockerSwarm集群的特点如下:

  1. 方便创建和管理集群

    DockerSwarm是docker原生的集群管理工具,可以直接使用docker客户端来创建并管理一个Docker Swarm集群,然后在其中部署应用程序服务,而不需要额外的编配软件。

  2. 可扩展

    对于集群中的每个服务,都可以声明要运行的副本任务的数量,当向下或向上进行扩展时,集群管理器将通过添加或删除副本任务来自动适应所需的状态

  3. 可实现期望的状态调节

    集群管理器节点不断监视集群状态,并协调实际状态和所期望状态之间的任何差异,例如,如果启动一个服务的10个副本任务,当一个Docker节点承载其中两个副本崩溃时,那么管理器将创建两个新的副本来替换崩溃的副本。

  4. 集群中多主机网络自动扩展管理

    Docker Swarm为集群服务提供了一个覆盖网络,当它初始化或更新应用程序时,集群管理器会自动将在工作节点闯将或更新网络来管理服务。

  5. 提供服务发现功能

    集权管理器节点为集群中的每个服务分配一个唯一的dns名称,通过docker swarm集群提供的负载均衡功能,可以通过嵌入在集群中的dns服务器来查询集群中运行的每个容器

  6. 可实现负载均衡

    可以将容器中服务的端口暴露给外部负载均衡器,而在内部,集群允许指定如何在节点之间分配服务容器。

  7. 安全性强

    集群中的每个节点强制使用TLS相互认证和加密,以确保自身和其他节点之间的通信安全。除此之外,集群还支持使用自定义的自签名证书来保证安全。

  8. 支持延迟更新和服务会滚

    在进行服务更新时,可以将服务更新逐步延伸到每个节点上,集群管理器允许服务部署到不同节点组之间时出现延迟,如果某个节点出现问题,还可以将服务回滚到以前的版本。

Docker Swarm使用

Docker Swarm是Docker原生的,同时也是最简单,最易学,最节省资源的,下面使用一个具体的示例演示Docker Swarm集群的基本使用

使用Docker Swarm集群的具体步骤如下。

  1. 环境搭建

    • 准备三台主机(即用于搭建集群的3个Docker机器),每台机器都需要安装Docker并且可以链接网络。

    • 集群管理节点Docker机器的IP地址必须固定,集群中所有节点都能够访问该管理节点

    • 集群节点之间必须使用相应的协议并保证其以下端口号可用:

      (1) 用于集群管理通信的TCP端口2377;

      (2) TCP和UDP端口7946,用于节点间的通信;

      (3) UDP端口4789,用于覆盖网络流量;

      为了进行本实例的演示,此处准备了三台主机分别为localhost1(作为管理节点),localhost2(作为工作节点),localhost3(作为工作节点),其ip地址分别如下:

      localhost1 192.168.56.101
      localhost2 192.168.56.102
      localhost3 192.168.56.103
      
  2. 创建docker swarm集群

    (1)在名为manager1的docker机器上创建docker swarm集群,指令如下:

    $ docker swarm init --advertise-addr 192.168.56.101
    

    执行上述指令后,docker就会自动在ip为192.168.56.101的机器上(manager1)创建一个Swarm集群,并将该ip地址的机器设置为集群管理节点。说明:如果只是测试单节点的集群,只需要执行docker swarm init指令即可

    执行之后输出以下信息表示创建成功:

    Swarm initialized: current node (2f7osqpdu8x35basubj1wvern) is now a manager.
    
    To add a worker to this swarm, run the following command:
    
        docker swarm join --token SWMTKN-1-5mhurlyodmb45yldg1qt21m84svgmvjylug1s7l7tbwnswxmfl-7psx06fpll5yantnv99e0n02r 192.168.56.101:2377
    
    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    

    上面的运行结果,显示了两条指令,这两条指令分别是添加工作节点和管理节点时使用的。

    (2)在管理节点上,使用docker node ls指令查看集群节点的信息,如下:

    $ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    2f7osqpdu8x35basubj1wvern *   localhost1          Ready               Active              Leader              19.03.12
    

从上面运行结果可以看出,此时只是创建了一个集群节点(默认为管理节点),而没有其他工作节点,因此只显示一条节点信息。

  1. 向Docker Swarm集群添加工作节点

    (1)启动另外两台Docker机器worker1和worker2,分别打开终端窗口,执行向集群中加入工作节点的指令,指令如下:

    $ docker swarm join --token SWMTKN-1-5mhurlyodmb45yldg1qt21m84svgmvjylug1s7l7tbwnswxmfl-7psx06fpll5yantnv99e0n02r 192.168.56.101:2377
    

    需要注意的是,上面–token参数表示向指定集群中加入工作节点的认证信息,一定要使用你对应集群的token,如果忘记该token可以在集群管理节点上执行“docker swarm join-token worker"指令进行查看。

    (2)再次在集群管理节点上使用docker node ls指令查看集群节点信息,如下:

    $ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    2f7osqpdu8x35basubj1wvern *   localhost1          Ready               Active              Leader              19.03.12
    5h484ur5b1xoo9ukoy8on55h6     localhost2          Ready               Active                                  19.03.12
    ms1v49hz127zksxzrn4sumyp6     localhost3          Ready               Active                                  19.03.12
    

    上面执行结果显示了1个管理节点(Manager Status为Leader),和2个工作节点,这说明Swarm集群搭建成功。

  2. 向Docker Swarm集群部署服务

    在Docker Swarm集群中部署服务时,既可以使用Docker hub上自带的镜像来启动服务,也可以使用我们自己通过Dockerfile构建的镜像来启动服务,如果使用自己通过Dockerfile构建的镜像来启动服务必须先将镜像推送到docker hub中心仓库。

    下面我们直接使用docker hub上自带的alpine镜像为例来部署集群服务,指令如下:

    $ docker service create --replicas 1 --name helloworld alpine ping docker.com
    

    上述部署服务指令中参数说明如下:

    • docker service create:用于在swarm集群中创建一个基于alpine镜像的服务

    • –replicas:指定了该服务只有一个副本实例

    • –name:指定服务的名称

    • ping docker.com:表示服务启动后执行的命令

      Docker Swarm集群中的服务管理与容器操作基本类似,只不过服务管理指令是以“docker service”开头,而容器管理指令是以“docker container”开头,个别指令除外,如–replicas。

  3. 查看Swarm集群中的服务

    (1)当服务部署完成后,在管理节点上可以通过docker service ls指令查看当前集群中的服务列表信息,指令如下:

    $ docker service ls
    

    (2)可以使用docker service inspect指令,查看部署的服务的具体详情信息,指令如下:

    $ docker service inspect helloworld
    

    (3)可以使用docker service ps指令查看指定服务在集群节点上的分配和运行情况,指令如下:

    $ docker service ps helloworld
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR                         PORTS
    jdnht71ll8k7        helloworld.1        alpine:latest       localhost1          Running             Running 3 minutes ago
    
  4. 更改Swarm集群服务副本数量

    在集群中部署的服务,如果只运行一个副本,就无法体现出集群的优势,并且一旦该机器或者副本崩溃,该服务将无法访问你,所以通常一个服务会启动多个服务副本。

    在管理节点manager1上,更改服务副本数量,指令如下:

    $ docker service scale helloworld=5
    

    更改完成后,就可以使用docker service ps指令查看5个服务副本在3个节点上的具体分布和运行情况,如下:

     $ docker service ps helloworld
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR                         PORTS
    jdnht71ll8k7        helloworld.1        alpine:latest       localhost1          Running             Running 3 minutes ago                           
    x50pmqm7nhku        helloworld.2        alpine:latest       localhost3          Running             Running 2 minutes ago                           
    wlprn1wd290g        helloworld.3        alpine:latest       localhost3          Running             Running 2 minutes ago                           
    asswpoe1nqm4        helloworld.4        alpine:latest       localhost2          Running             Running 3 minutes ago                           
    uwrpc5ln1pp2        helloworld.5        alpine:latest       localhost1          Running             Running 3 minutes ago                                 
    

    从上面可以看出,helloworld服务的5个副本实例被随机的分配到了localhost1,localhost2,localhost3这三个节点上运行,并且状态都是Running,表示服务正常运行。

    在管理节点上执行“docker service ps helloworld”指令查看服务的运行情况后,我们还可以在有服务副本分配的节点上使用docker ps指令查看任务运行的情况。

  5. 删除服务

    对于不需要的服务,我们还可以进行删除,具体操作指令如下:

    $ docker service rm helloworld
    

    在集群管理节点上(localhost1)执行上述删除服务指令后,该服务就会从集群中彻底删除,需要说明是,执行上述删除删除服务指令后,在集群中有该副本的节点上,这些服务需要一定的时间清除,此时我们可以使用docker ps查看具体清除情况。

  6. 访问服务

    前面部署的服务都没有直接向外界暴露服务端口,外界也无法正常访问服务,接下来我们就通过自定义overlay驱动网络为例来讲解集群下网络管理与服务访问,具体实现如下:

    (1)在集群管理节点localhost1上,执行docker network ls指令查看网络列表,效果如下:

    $ docker network ls
    NETWORK ID          NAME                      DRIVER              SCOPE
    8460e3c9d01b        bridge                    bridge              local
    8c02e38be99b        docker_gwbridge           bridge              local
    b089ff71a13d        host                      host                local
    x1u2rhvxuprj        ingress                   overlay             swarm
    dcbe6766a388        none                      null                local
    
    

    从结果可以看出,与非集群环境下的Docker网络对比,Swarm集群网络列表中分别增加了一个以bridge和overlay为驱动的网络,在集群中发布服务时,如果没有指定网络,那么默认都是使用名为ingress网络连接的,而在实际开发中,则会使用自定义的overlay驱动网络进行管理。

    (2) 在集群管理节点localhost1上,创建以overlay为驱动的自定义网络,指令如下:

    $ docker network create --driver overlay my-multi-host-nw
    

    上述指令以overlay为驱动创建了一个名为my-multi-host-nw的网络。

    (3)在集群管理节点localhost1上,再次部署服务,指令如下:

    $ docker service create \
    	--network my-multi-host-nw \
    	--name my-web \
    	--publish 8080:80 \
    	--replicas 2 \
    	nginx
    

    上述部署服务的指令中,–network参数用于指定服务使用自定义的overlay驱动网络my-multi-host-nw连接;–name参数用于指定服务的名称;–publish(也可以使用-p)参数用于映射对外服务的端口;–replicas参数用于指定该服务的副本数量;nginx表示是基于nginx镜像构建的服务。

    提示:前面几步虽然只是在集群管理节点上创建了自定义的overlay驱动网络,但是当管理节点的任务分配到某个集群中的工作节点时,该工作节点会自动创建对应的自定以网络;而当该工作节点上的任务被移除后,该自定义网络也会随之移除。

    (4)在集群管理节点localhost1上,使用docker service ps my-web指令查看服务的两个副本的运行情况,结果如下:

    $ docker service ps my-web
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
    us4zof9paylv        my-web.1            nginx:latest        localhost2          Running             Running about a minute ago                      
    nhn1wy3562q4        my-web.2            nginx:latest        localhost3          Running             Running about a minute ago 
    

    从结果可以看到,该服务的两个副本任务被随机分配到了localhost2和localhost3两台节点上,并已正常运行。

    (5)外界访问服务

    打开浏览器,使用任意一台节点机器的"ip+8080"端口进行服务访问,都可以正常显示,效果如下:

Docker及DockerSwarm讲解_第4张图片

从图中可以看出,当在任意节点上访问服务时,都可以正常访问部署的服务,这时由于集群负载均衡将请求路由到一个活动容器,从而实现容器内部服务的正常访问,这也体现了docker swarm集群的负载均衡这一特点。

Docker 数据管理

当我们对容器进行相关操作时,产生的一系列数据都会存储到容器中 ,而docker内部又是如何管理这些数据的呢?下面针对docker的数据管理的一些知识进行讲解

Docker数据存储机制

docker镜像是通过读取Dockerfile文件中的指令构建的,Dockerfile中每条指令都会创建一个镜像层,并且每层都是只读的,这一系列的镜像层就构成了Docker镜像, 通过镜像构建容器时,会在镜像层上增加一个容器层(即可写层), 所有对容器的更改都会写入容器层,这也是docker默认的数据存储方式。

下面通过一个效果图进行说明:

Docker及DockerSwarm讲解_第5张图片

从图可以看出,Docker容器和镜像之间的主要区别是顶部的容器层,而所有对容器中数据的添加,修改等操作都会被存储在容器层中,当容器被删除时,容器层也会被删除,其中存储的数据会被一同删除,而下面的镜像层却保持不变。

由于所有的容器都是通过镜像构建的,所以每个容器都有各自的容器层,对于容器数据的更改就会保存在各自的容器层中,也就是说,由同一个镜像构建的多个容器,他们会拥有相同的底部镜像层,而拥有不同的容器层,多个容器可以访问相同的镜像层,并且有自己独立的数据状态,也就是说多个容器要共享相同的数据,就需要将这些数据存储到容器之外的地方,而这种方式就是下面要提到的Docker volume数据外部挂载机制。

Docker数据存储方式

默认情况下,Docker中的数据存放在容器层,但是这样存储数据有一定的缺陷,比如:

  • 容器中数据无法永久保存,如果另一个容器需要这些数据,那么将很难从容器中获取数据
  • 容器层与正在运行的主机紧密耦合,不能轻易地移动数据
  • 容器层需要一个存储驱动程序来管理文件系统,存储驱动程序提供了一个使用linux内核的联合文件系统,这种额外的抽象化降低了性能

基于上述种种原因,多数情况下Docker数据管理都不会直接将数据写入容器层,而是使用另一种叫做docker volume数据外部挂载的机制进行数据管理。

针对Docker volume数据外部挂载机制,docker提供了三种不同的方式将数据从容器映射到Docker宿主机,分别为:volumes(数据卷), bind mounts(绑定挂载)和tmpfs mounts(tmpfs挂载),这三种方式要根据实际情况选择,其中volumes最常用也是官方推荐的数据管理方式。

Docker提供的3种数据管理方式略有不同,具体分析如下:

  • volumes:存储在主机文件系统中(在linux系统下是存在/var/lib/Docker/volumes/目录),并有Docker管理,非Docker进程无法修改该文件系统的部分
  • bind mounts:可以存储在主机的任何位置,甚至可能是重要的系统文件和目录,在Docker主机或容器上的非Docker进程可以对他们进行任意的修改
  • tmpfs mounts:只存储在主机系统的内存中,并没有写入主机的文件系统中。

volumes数据卷管理方式

volumes方式完全由Docker管理,是官方想对推荐的数据管理方式,volumes有以下几点优势。

  • 数据卷比绑定挂载更容易备份和迁移
  • 可以使用docker cli指令或docker api来管理数据卷
  • 在linux和windows容器上都可以使用数据卷
  • 在多个容器间可以更安全的共享数据卷
  • 数据卷驱动器允许在远程主机或云提供商上存储数据卷,并且加密数据卷的内容或添加其他功能
  • 一个新的数据卷的内容可以由一个容器预填充

此外在容器的可写层中,数据卷通常是持久化数据更好的选择,因为使用数据卷并不会增加使用容器的大小,而且数据卷的内容存在于给定容器的声明周期之外,如果我们的容器生成了非持久性状态的数据,那么可以考虑使用tmpfs mounts,因为他可以避免永久存储数据,以及写入容器的可写层时增加容器的负担。

volumes数据卷使用

接下来以数据卷为例,演示Docker如何进行数据管理

  1. 创建并管理数据卷

    (1)创建数据卷

    在docker主机终端上,通过docker volume create 指令创建一个名为my-vol的数据卷,指令如下:

    $ docker volume create my-vol
    

    (2)查看数据卷

    使用docker volume ls指令查看本地数据卷列表,指令如下:

    $ docker volume ls
    

    运行上述指令后,会列举出本地Docker机器上的所有数据卷,效果如下:

    $ docker volume ls
    DRIVER              VOLUME NAME
    local               my-vol
    

    (3)核查数据卷

    使用docker volume inspect指令查看指定数据卷详情,效果如下:

    $ docker volume inspect my-vol
    [
        {
            "CreatedAt": "2020-12-08T15:19:31+08:00",
            "Driver": "local",
            "Labels": {},
            "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
            "Name": "my-vol",
            "Options": {},
            "Scope": "local"
        }
    ]
    

    (4)删除数据卷

    $ docker volume rm my-vol
    
  2. 启动容器并加载数据卷

    下面演示如果在容器创建时配置并管理docker volume数据卷。

    (1)查看本机容器和数据卷

    首先我们进入/var/lib/docker目录,即docker默认在本机上的文件系统目录,效果如下:

    $ cd /var/lib/docker/
    $ ll
    总用量 24
    drwx------.  2 root root   24 824 17:38 builder
    drwx--x--x.  4 root root   92 824 17:38 buildkit
    drwx------.  2 root root    6 128 14:21 containers
    drwx------.  3 root root   22 824 17:38 image
    drwxr-x---.  3 root root   19 824 17:38 network
    drwx------. 47 root root 8192 128 14:21 overlay2
    drwx------.  4 root root   32 824 17:38 plugins
    drwx------.  2 root root    6 128 14:10 runtimes
    drwx------.  5 root root   95 128 14:11 swarm
    drwx------.  2 root root    6 128 14:35 tmp
    drwx------.  2 root root    6 824 17:38 trust
    drwx------. 53 root root 8192 128 15:19 volumes
    
    

    本机docker机器的文件系统中包含了两个重要的文件目录,分别为containers和volumes,这两个目录就是用于存放用户创建的容器和数据卷的。

    (2)启动容器并挂载数据卷

    使用docker run指令创建并启动一个容器,同时挂载一个数据卷,指令如下:

    $ docker run -itd \
    	--name devtest \
    	--mount source=myvol,target=/app \
    	busybox:latest
    

    也可以使用-v参数挂载数据卷,指令如下:

    $ docker run -itd \
    	--name devtest2 \
    	-v myvol:/app \
    	busybox:latest
    

    在上述指令中,分别通过–mount和-v两种参数来实现数据卷的挂载,这两个容器共享了同一个数据卷myvol,并将该数据卷挂载到了各自容器中的app目录下。

    上述指令中出现的两个参数–mount和-v,需要进一步说明,具体如下所示:

    –mount参数

    由多个key=value形式的键值对组成,键值对之间用英文逗号分隔,–mount参数语法比-v参数语法更为详细,键的顺序可随意,标记的值也更容易理解,关于–mount的键值说明如下:

    • type(挂载的类型):可以是bind,volume或tmpfs,当前使用的是数据卷,因此类型是volume
    • source(挂载源):对于命名的数据卷来说,这就是数据卷的名称,而对于匿名卷,该字段被省略,该字段可以用source或src表示
    • destination(挂载点):就是将文件或目录挂载到容器中的具体路径,该字段可以用destination,dst或target表示
    • readonly参数:如果出现了该参数,则挂载到容器中的数据就表示是只读的

    -v(–volumn的缩写)参数

    有三个字段组成,分别有冒号(:)分隔,字段必须以正确的顺序排列,而且每个字段的含义都特别明确,关于-v参数属性值的说明如下:

    • 在命名券的情况下,第一个字段是数据卷的名称,在给定的主机上是唯一的,对于匿名卷,将会省略第一个字段
    • 第二个字段是在容器中挂载数据的文件或目录的路径
    • 第三个字段是可选的,是一个逗号分隔的列表,如ro(readonly,即只读)

    需要注意的是,在本小节讲解的是使用Volumes进行数据管理,不管是–mount source=myvol 还是 -v myvol,前面第一个参数都是设置的数据卷名称,执行完指令后,会自动在docker文件系统的数据卷目录/var/lib/docker/volumes下创建一个myvol子目录来保存数据,如果是使用Bind mounts进行数据管理,那么第一个参数就是宿主机保存数据的具体地址(如 -v /src/myvolume/data:/app)。

    (3) 查看本机容器和数据卷列表

    在docker主机终端使用docker ps 和docker volume ls指令查看本地docker机器上的容器和数据卷,效果如下:

    docker ps -a
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                       PORTS               NAMES
    7469b8526be3        busybox:latest      "sh"                     29 minutes ago      Exited (255) 2 minutes ago                       devtest2
    be050467da4e        busybox:latest      "sh"                     29 minutes ago      Exited (255) 2 minutes ago                       devtest
    [root@localhost1 ~]# docker volume ls
    DRIVER              VOLUME NAME
    local               myvol
    

    值得一提的是,如果容器在创建时数据卷不存在,则docker会自动创建。

    (4)检查容器详情

    使用docker inspect指令查看容器详情(主要查看挂载信息部分),指令如下:

    $ docker inspect devtest
    

    效果如下(仅挂载信息部分):

    "Mounts": [
        {
        "Type": "volume",
        "Name": "myvol",
        "Source": "/var/lib/docker/volumes/myvol/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
        }
    ],
    

    可以看出,其中挂载类型为volume,数据卷名称为myvol,数据在本地Docker机器上对应的存储地址,数据在容器中对应地址app目录以及容器中数据是可读写(RW: true)等.

    (6)再次确认本机docker文件系统中容器和数据卷

    再次进入/var/lib/docker/volumes目录查看内容,结果如下:

    $ ll volumes/
    -rw-------. 1 root root 65536 128 15:58 metadata.db
    drwxr-xr-x. 3 root root    19 128 15:31 myvol
    

    可以看到新建的数据卷已经自动生成在本地docker文件目录中,此时如果我们将两个容器都删除,则本地docker文件目录中的容器也会自动消失, 但数据卷却可以保留,除非数据卷也被删除。

           COMMAND                  CREATED             STATUS                       PORTS               NAMES
    

    7469b8526be3 busybox:latest “sh” 29 minutes ago Exited (255) 2 minutes ago devtest2
    be050467da4e busybox:latest “sh” 29 minutes ago Exited (255) 2 minutes ago devtest
    [root@localhost1 ~]# docker volume ls
    DRIVER VOLUME NAME
    local myvol

    
    值得一提的是,如果容器在创建时数据卷不存在,则docker会自动创建。
    
    (4)检查容器详情
    
    使用docker inspect指令查看容器详情(主要查看挂载信息部分),指令如下:
    
    ```bash
    $ docker inspect devtest
    

    效果如下(仅挂载信息部分):

    "Mounts": [
        {
        "Type": "volume",
        "Name": "myvol",
        "Source": "/var/lib/docker/volumes/myvol/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
        }
    ],
    

    可以看出,其中挂载类型为volume,数据卷名称为myvol,数据在本地Docker机器上对应的存储地址,数据在容器中对应地址app目录以及容器中数据是可读写(RW: true)等.

    (6)再次确认本机docker文件系统中容器和数据卷

    再次进入/var/lib/docker/volumes目录查看内容,结果如下:

    $ ll volumes/
    -rw-------. 1 root root 65536 128 15:58 metadata.db
    drwxr-xr-x. 3 root root    19 128 15:31 myvol
    

    可以看到新建的数据卷已经自动生成在本地docker文件目录中,此时如果我们将两个容器都删除,则本地docker文件目录中的容器也会自动消失, 但数据卷却可以保留,除非数据卷也被删除。

你可能感兴趣的:(docker,docker)