Docker 介绍

Docker的特性:

  1. 文件系统隔离:每个进程容器运行在一个完全独立的根文件系统里。
  2. 资源隔离:系统资源,像CPU和内存等可以分配到不同的容器中,使用cgroup。
  3. 网络隔离:每个进程容器运行在自己的网络空间,虚拟接口和IP地址。
  4. 日志记录:Docker将会收集和记录每个进程容器的标准流(stdout/stderr/stdin),用于实时检索或批量检索。
  5. 变更管理:容器文件系统的变更可以提交到新的映像中,并可重复使用以创建更多的容器。无需使用模板或手动配置。
  6. 交互式shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上

为啥要用Docker / Docker的优势在哪

Docker提供了一种打包(镜像)标准,一个打好的镜像,可以在任意机器上的任意环境运行,降低了“在我这儿好使啊”这种纷争。
比如我们一个普通的java应用部署, 需要告诉运维机器要安装jdk, 版本要是7+, 要安装tomcat, 版本是7+, 然后才是部署我们的war包。
如果有什么特殊的启动参数, 最后启动的时候还要告诉运维启动命令要加哪些参数。 这样部署起来非常复杂, 以后服务要迁移也很麻烦。

那么Docker怎么解决这个问题?

答案很简单, Docker把所有依赖的环境都封装成一个包, 连启动命令都封装好了!一个镜像的构成如下所示:

镜像结构示意图

docker的镜像跟virtualbox或者vmware的镜像不一样。在虚拟机中,镜像是一个系统的完整体,包括了系统、用户在上面做的操作等等。而在docker中,镜像是一组文件的分层叠加。

Docker安装

版本要求

以Ubuntu系统为例子(其他系统的安装方法见官方文档), 推荐的Ubuntu版本如下:

  1. Yakkety 16.10
  2. Xenial 16.04 (LTS)
  3. Trusty 14.04 (LTS)

当然不是这些版本应该也可以, 根据以往经验, 内核版本在3.10以上的应该都可以(本次未测试)。

安装方式

  1. 使用docker资源库安装, 这是比较简单也是推荐的方式。
  2. 下载DEB包, 手动安装

使用Docker资源库安装

  1. 安装支持资源库的https包(一般默认已经安装了):

     sudo apt-get install apt-transport-https  ca-certificates
    
  2. 安装Docker的GPG key:

     curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add -
    
  3. 添加Docker的stable资源库:

     sudo add-apt-repository \
            "deb https://apt.dockerproject.org/repo/ \
            ubuntu-$(lsb_release -cs) \
            main"
    
  4. 更新资源库:

     sudo apt-get update -y
    
  5. 安装Docker Engine:

     sudo apt-get install -y docker-engine
    
  6. 安装完成后, 验证一下安装:

     sudo docker ps
    
     adam@adam-host:~$ sudo  docker ps
     [sudo] password for adam:
     CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    

至此, Docker就已经安装成功了.

Docker 简单使用

常见的应用都已经有了docker镜像, 所有的开放镜像都存在于Docker镜像仓库, 可以使用docker快速启动而无需安装, 比如要运行一个redis,
只需要一条命令:

docker run --name redis -d -p 6379:6379 redis

就可以启动redis实例,用telnet就可以连接上此redis.

搜索镜像

上面说到了Docker的一个镜像仓库, 那么应该怎么使用这个仓库呢? 比如我要看一下Elasticsearch镜像有没有, 怎么搜索?

搜索镜像有两种办法:

  1. 第一种直接登录Docker镜像仓库网站搜索, 还可以看到镜像的具体使用方法。

  2. 第二种办法, 使用docker命令搜索:

     docker search elasticsearch
    

    输出类似于:

     NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     elasticsearch                     Elasticsearch is a powerful open source se...   1974      [OK]
     itzg/elasticsearch                Provides an easily configurable Elasticsea...   42                   [OK]
     nshou/elasticsearch-kibana        Elasticsearch-5.1.1 Kibana-5.1.1                19                   [OK]
    

    NAME 镜像名, DESCRIPTION 描述信息, STARS 收藏数, OFFICIAL 是否官方 等信息 .

这里比较推荐第一中方法, 因为单单拿到了镜像可能还不知道怎么使用, 启动镜像需要哪些参数等信息。

Dockerfile

从上面redis的例子可以看出,使用docker非常方便, 无需安装redis只要拉取镜像即可使用。 那么我们如何把自己的程序打成一个镜像呢?

答案就是Dockerfile

Dockerfile是一个描述镜像如何构建的文本文件, 这个文件里包含了顺序执行的一系列docker命令。Docker可以根据里面的命令自动的去构建一个镜像。
这里先用一个例子简单介绍一下, 可以有个直观的感受:

FROM java:8-jdk
MAINTAINER pengfw.wu@qunar.com

RUN rm /etc/localtime \
        && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
        && echo "Asia/Shanghai" > /etc/timezone \
        && dpkg-reconfigure -f noninteractive tzdata \
        && mkdir /boot-demo \
        && export LANG=en_US.UTF-8 \
        && export LANGUAGE=en_US:en

WORKDIR /boot-demo

ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en PROFILE=release \
    JAVA_OPTS="-Xmx2048m -Xms1024m -XX:+UseConcMarkSweepGC"

EXPOSE 9090

VOLUME /boot-demo/log

COPY ./boot-demo-1.7-SNAPSHOT-boot.jar boot-demo-1.7-SNAPSHOT-boot.jar

ENTRYPOINT java -server -jar -Dspring.profiles.active=$PROFILE $JAVA_OPTS boot-demo-1.7-SNAPSHOT-boot.jar

FROM关键字用来表示使用哪个父镜像。 前面我们说了镜像中的文件其实是分层组织的。一般应用的镜像都有一个父镜像,是建立在父镜像之上的。 而没有父镜像的被称为基础镜像。
关于基础镜像如何构建这里就不多说了, 感兴趣的可以参考 这篇官方文档

MAINTAINER用来指明维护人的信息

RUN关键字用来执行一些shell命令, 此关键字会在新的一层上执行后面的命令, 然后提交结果。 RUN有两种形式 : RUN <command> shell形式 以及
RUN ["executable", "param1", "param2"]。 这里解释一下为什么要把多条命令写在一个RUN关键字里面, 因为每个RUN命令都会生成一层新的文件, 会增加最终镜像的大小,
所以这么做一是为了减少镜像大小,二是为了可读性可维护性。

WORKDIR为后面的命令比如RUN, ENTRYPOINT, COPY 等命令设置工作目录。 如果此目录不存在, 则会自动创建。

ENV用于设置环境变量, 后面的命令可以使用$引用环境变量。 在启动镜像的时候, 可以在命令行设置环境变量的值。

COPY用于将文件或者目录复制到镜像的文件系统中。

ENTRYPOINT用于设置镜像的入口, 就是在执行docker run命令的时候, 实际启动的镜像内部程序的命令.

有了Dockerfile, 我们只需要执行docker build -t <imagename> -f <dockerfile-path> 即可构建镜像.

关于Dockerfile的命令以及详细使用方法参考Dockerfile reference
以及 dockerfile_best-practices

Container

Container就是所谓的容器, 它本质上是镜像的运行状态的一个实例。 容器有以下几种常用操作:

启动

使用docker run 命令可以启动一个容器。 类似上面启动的redis

adam@adam-host:~$ docker run --name redis -d -p 6379:6379 redis
dff7830910eb086cb2d29b695bf485189d81e4598d15e13d557f7c6404e5432a

以交互式方式运行容器

adam@adam-host:~$ docker run -it --rm ubuntu /bin/bash
root@7d5eb8f0eb24:/#
root@7d5eb8f0eb24:/# echo hello world
hello world
root@7d5eb8f0eb24:/# exit

查看所有容器

使用docker ps 可以看到正在运行的容器, docker ps -a可以看到所有的容器.

adam@adam-host:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
dff7830910eb        redis               "docker-entrypoint..."   36 seconds ago      Up 35 seconds       0.0.0.0:6379->6379/tcp   redis

停止

使用docker stop <container-name/container-id> 可以停止一个运行的容器

docker stop redis

重启

使用docker restart <container-name/container-id> 可以重启一个容器

docker restart redis

删除

使用docker rm <container-name/container-id> 可以删除一个已经停止的容器

docker rm redis

进入容器内部

使用docker exec命令可以进入容器的交互式窗口

adam@adam-host:~$ docker exec -it redis /bin/bash
root@dff7830910eb:/data# cat /etc/issue
Debian GNU/Linux 8 \n \l

root@dff7830910eb:/data#

Docker图形化管理工具

shipyardportainer(原dockerui)

以portainer为例:

docker run -d -p 9000:9000 --name dokcerui -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

访问 localhost:9000 即可。

Swarm && 集群

Swarm是docker的一个集群管理工具, 最开始swarm是单独一个镜像, 现在已经嵌入到了docker-engine里面。 我们简单看一下如何创建docker集群。

要创建一个swarm集群, 我们需要三台机器, 一台做manager管理节点, 另外的做从节点。 当然前提是这三个节点是互通的。这里我使用三个虚拟机adam-host, adam-host1, adam-host2

使用swarm创建集群

  1. 使用docker swarm init可以初始化一个集群。 首先, 登录一台想作为管理节点的机器,这里选择adam-host, 在上面执行:

     adam@adam-host:~$ docker swarm init
     Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (10.0.2.15 on enp0s3 and 192.168.1.4 on enp0s8) - specify one with --advertise-addr
    

    这里报了一个错, 说我们机器上有多个ip, 要手动指定一个作为其他节点连接的ip。

    看一下:

     adam@adam-host:~$ ip addr
     1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
         inet 127.0.0.1/8 scope host lo
            valid_lft forever preferred_lft forever
         inet6 ::1/128 scope host
            valid_lft forever preferred_lft forever
     2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
         link/ether 08:00:27:9c:86:8a brd ff:ff:ff:ff:ff:ff
         inet 10.0.2.15/8 brd 10.255.255.255 scope global enp0s3
            valid_lft forever preferred_lft forever
         inet6 fe80::a00:27ff:fe9c:868a/64 scope link
            valid_lft forever preferred_lft forever
     3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
         link/ether 08:00:27:34:9e:50 brd ff:ff:ff:ff:ff:ff
         inet 192.168.1.4/24 brd 192.168.1.255 scope global enp0s8
            valid_lft forever preferred_lft forever
         inet6 fe80::a00:27ff:fe34:9e50/64 scope link
            valid_lft forever preferred_lft forever
     4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
         link/ether 02:42:0b:af:23:21 brd ff:ff:ff:ff:ff:ff
         inet 172.17.0.1/16 scope global docker0
            valid_lft forever preferred_lft forever
         inet6 fe80::42:bff:feaf:2321/64 scope link
            valid_lft forever preferred_lft forever
     9: docker_gwbridge: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
         link/ether 02:42:c9:7d:07:66 brd ff:ff:ff:ff:ff:ff
         inet 172.18.0.1/16 scope global docker_gwbridge
            valid_lft forever preferred_lft forever
         inet6 fe80::42:c9ff:fe7d:766/64 scope link
            valid_lft forever preferred_lft forever
    

    这里确实配置了两个IP, 其中192.168.1.4是内网IP, 我们把这个指定为swarm管理节点的IP:

     adam@adam-host:~$ docker swarm init --advertise-addr 192.168.1.4
     Swarm initialized: current node (qvsktm9hcc59tg66t369wb63r) is now a manager.
    
     To add a worker to this swarm, run the following command:
    
         docker swarm join \
         --token SWMTKN-1-4fmyk4dm7qbnt87bp8gfh3scp1szfyrmm4x3zmb390a3ooh1tp-12mixtt7bpul8l2vjzcbka7u5 \
         192.168.1.4:2377
    
     To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    
     adam@adam-host:~$
    

    我们可以看到输出: docker swarm join \ --token SWMTKN-1-4fmyk4dm7qbnt87bp8gfh3scp1szfyrmm4x3zmb390a3ooh1tp-12mixtt7bpul8l2vjzcbka7u5 \ 192.168.1.4:2377
    在剩余的两个节点上面执行这个命令即可加入集群。 在adam-host2上面:

    adam@adam-host2:~$ docker swarm join \
    >             --token SWMTKN-1-4fmyk4dm7qbnt87bp8gfh3scp1szfyrmm4x3zmb390a3ooh1tp-12mixtt7bpul8l2vjzcbka7u5 \
    >             192.168.1.4:2377
    This node joined a swarm as a worker.
    

    最后一台机器adam-host3

     adam@adam-host3:~$ docker swarm join \
     >             --token SWMTKN-1-4fmyk4dm7qbnt87bp8gfh3scp1szfyrmm4x3zmb390a3ooh1tp-12mixtt7bpul8l2vjzcbka7u5 \
     >             192.168.1.4:2377
     This node joined a swarm as a worker.
     adam@adam-host3:~$
    

    剩余两个几点成功加入了集群。 现在返回到管理节点adam-host上面, 看一下集群中的节点:

     adam@adam-host:~$ docker node ls
     ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
     n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Active
     qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
     zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active
     adam@adam-host:~$
    

    可以看到有三个节点, 其中一个节点正是自己, 是管理节点的角色。

管理集群中的节点:

  1. 查看集群中的节点, 上面已经说了是docker node ls命令:

     adam@adam-host:~$ docker node ls
     ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
     n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Active
     qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
     zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active
     adam@adam-host:~$
    

    上面的结果中, AVAILABILITY 共有一下几种取值:

    1. Active 表示此节点可以接受分配新任务
    2. Pause 表示此节点再接受新任务, 但是已经分配给它的任务会继续执行
    3. Drain 表示此节点不再接受新任务, 同时已经分配的任务也不再执行,而是由调度器分发到其他可用的节点上。

    MANAGER STATUS 这一列共有以下几种取值:

    1. 空值 表示此节点不参与swarm的管理
    2. Leader 表示此节点是目前的管理节点
    3. Reachable 表示此节点备用管理节点, 当Leader的管理节点失效之后, 此节点可以参与选举, 并可能被选为Leader节点
    4. Unavailable 表示此节点无法与其他管理节点通讯, 这种情况下, 当Leader节点不可用之后, 需要新加入一个管理节点或者把工作节点提升为管理节点。
  2. 查看节点详细信息: docker node inspect

     adam@adam-host:~$ docker node inspect adam-host2
     [
         {
             "ID": "n55kjb6mn2u8g2lkcrt03gu0t",
             "Version": {
                 "Index": 15
             },
             "CreatedAt": "2017-02-04T05:53:51.114898465Z",
             "UpdatedAt": "2017-02-04T05:53:51.237011222Z",
             "Spec": {
                 "Role": "worker",
                 "Availability": "active"
             },
             "Description": {
                 "Hostname": "adam-host2",
                 "Platform": {
                     "Architecture": "x86_64",
                     "OS": "linux"
                 },
                 "Resources": {
                     "NanoCPUs": 1000000000,
                     "MemoryBytes": 2097512448
                 },
                 "Engine": {
                     "EngineVersion": "1.13.0",
                     "Plugins": [
                         {
                             "Type": "Network",
                             "Name": "bridge"
                         },
                         {
                             "Type": "Network",
                             "Name": "host"
                         },
                         {
                             "Type": "Network",
                             "Name": "macvlan"
                         },
                         {
                             "Type": "Network",
                             "Name": "null"
                         },
                         {
                             "Type": "Network",
                             "Name": "overlay"
                         },
                         {
                             "Type": "Volume",
                             "Name": "local"
                         }
                     ]
                 }
             },
             "Status": {
                 "State": "ready",
                 "Addr": "192.168.1.5"
             }
         }
     ]
    
  3. 更新节点

    我们可以更新节点的可用状态、 增加或删除节点的标签数据、 改变节点的角色等操作。

    1. 修改节点可用状态:
      上面我们说了每个节点都有Active,Pause,Drain 这三个状态, 我们可以手动修改他们的状态:

       adam@adam-host:~$ docker node update --availability drain adam-host2
       adam-host2
       adam@adam-host:~$ docker node ls
       ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
       n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Drain
       qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
       zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active
      

      可以看到adam-host2这个节点已经是Drain状态了,这样其实就是把此节点下线了, 可以做一些维护操作, 接着把它改回Active状态:

       adam@adam-host:~$ docker node update --availability Active adam-host2
       adam-host2
       adam@adam-host:~$ docker node ls
       ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
       n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Active
       qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
       zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active
      
    2. 增加或删除标签元信息:
      每个node都可以设置一些标签, 标签可以用来管理节点或者限制服务调度。

      使用docker node update --label-add 命令可以添加标签, --label-add 支持 key 或者 key=value 这样的格式。

       adam@adam-host:~$ docker node update --label-add worker --label-add order=1 adam-host2
       adam-host2
      

      我们为adam-host2这个node添加了一个worker标签和order标签, 现在看一下有没有生效:

       adam@adam-host:~$ docker node inspect adam-host2
       [
           {
               "ID": "n55kjb6mn2u8g2lkcrt03gu0t",
               "Version": {
                   "Index": 23
               },
               "CreatedAt": "2017-02-04T05:53:51.114898465Z",
               "UpdatedAt": "2017-02-04T06:24:42.049105301Z",
               "Spec": {
                   "Labels": {
                       "order": "1",
                       "worker": ""
                   },
                   "Role": "worker",
                   "Availability": "active"
               }
               ...
      

      可以看到label已经添加上了。

    3. 将工作节点提升为管理节点:
      使用docker node promote 可以提升节点角色, 此命令可以一次性提升多个节点

       adam@adam-host:~$ docker node promote adam-host2 adam-host3
       Node adam-host2 promoted to a manager in the swarm.
       Node adam-host3 promoted to a manager in the swarm.
      
       adam@adam-host:~$ docker node ls
       ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
       n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Active        Reachable
       qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
       zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active        Reachable
      

      可以看到, 目前三个都是管理节点了, 只不过adam-host还是主, 其他的是备用。

    4. 将管理节点降低为工作节点:
      类似的, 使用docker node demote 可以降node的角色:

       adam@adam-host:~$ docker node demote adam-host2 adam-host3
       Manager adam-host2 demoted in the swarm.
       Manager adam-host3 demoted in the swarm.
       adam@adam-host:~$
       adam@adam-host:~$
       adam@adam-host:~$ docker node ls
       ID                           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS
       n55kjb6mn2u8g2lkcrt03gu0t    adam-host2  Ready   Active
       qvsktm9hcc59tg66t369wb63r *  adam-host   Ready   Active        Leader
       zdd201qxmgwnqtrzq60ft6prj    adam-host3  Ready   Active
      
    5. 离开集群:
      如果node要离开一个swarm集群, 首先要在node节点执行:

       adam@adam-host2:~$ docker swarm leave
       Node left the swarm.
      

      然后在manager节点执行 :

       adam@adam-host2:~$ docker swarm leave
       Node left the swarm.
      

      adam-host2节点就已经移除了。

在集群中部署服务

  1. 在集群中部署一个服务, 这里以redis为例:

     adam@adam-host:~$ docker service create --name redis -p 6379:6379 --replicas 2 redis
     kdxwcqo77pnsil9fscliofo6g
    

    上面--name指定服务名, --replicas 指定冗余数量, -p 指定端口映射. 更多docker service create
    的参数参考docker service create

    我们在部署一个mysql服务:

     adam@adam-host:~$ docker service create --name mysql -p 3306:3306 --env 'MYSQL_ROOT_PASSWORD=123456' --replicas 2 mysql
     6oz914dv7ok6oplblqmnuqjpc
    
  2. 查看集群中服务:

     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     6oz914dv7ok6  mysql  replicated  2/2       mysql:latest
     kdxwcqo77pns  redis  replicated  2/2       redis:latest
    

    可以看到,当前集群中有mysql redis 两个服务, 各有两个冗余, 那么这两个服务具体是怎么在节点分布的呢?

     adam@adam-host:~$ docker service ps redis
     ID            NAME     IMAGE         NODE        DESIRED STATE  CURRENT STATE           ERROR  PORTS
     t50gm1xp4gui  redis.1  redis:latest  adam-host2  Running        Running 11 minutes ago
     9no6uos3yrpu  redis.2  redis:latest  adam-host   Running        Running 11 minutes ago
     adam@adam-host:~$
     adam@adam-host:~$
     adam@adam-host:~$
     adam@adam-host:~$ docker service ps mysql
     ID            NAME     IMAGE         NODE        DESIRED STATE  CURRENT STATE           ERROR  PORTS
     e2a4s3ys0gtc  mysql.1  mysql:latest  adam-host3  Running        Running 56 seconds ago
     f2majlplcc61  mysql.2  mysql:latest  adam-host2  Running        Running 56 seconds ago
    

    现在我们停掉其中一个redis,比如是adam-host2上面的, 看swarm会不会再次启动一个:

     adam@adam-host2:~$ docker ps
     CONTAINER ID        IMAGE                                                                           COMMAND                  CREATED             STATUS              PORTS                    NAMES
     aa4b56cafbdc        mysql@sha256:5e2ec5964847dd78c83410f228325a462a3bfd796b6133b2bdd590b71721fea6   "docker-entrypoint..."   3 minutes ago       Up 3 minutes        3306/tcp                 mysql.2.f2majlplcc61ux83kc14l2hwm
     f8ddec77282a        redis@sha256:afa4b429ef3ee08c8b198e50d684c5da0ffa43ae58631f61b08829bd6df3c500   "docker-entrypoint..."   13 minutes ago      Up 13 minutes       6379/tcp                 redis.1.t50gm1xp4guiu90hfarcx404e
     b983c25a66a1        org.singledog.boot-demo                                                         "/bin/sh -c 'java ..."   3 hours ago         Up 3 hours          0.0.0.0:9091->9090/tcp   test
    
     adam@adam-host2:~$ docker stop f8ddec77282a
     f8ddec77282a
    

    再回到管理节点看一看:

     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     kdxwcqo77pns  redis  replicated  2/2       redis:latest
     u7ckw4btv8p9  mysql  replicated  2/2       mysql:latest
    
     adam@adam-host:~$ docker service ps redis
     ID            NAME         IMAGE         NODE        DESIRED STATE  CURRENT STATE            ERROR  PORTS
     oit9ge9u67by  redis.1      redis:latest  adam-host2  Running        Running 29 seconds ago
     t50gm1xp4gui   \_ redis.1  redis:latest  adam-host2  Shutdown       Complete 34 seconds ago
     9no6uos3yrpu  redis.2      redis:latest  adam-host   Running        Running 15 minutes ago
    

    可以看到, swarm知道其中一个redis停了, 它再次给启动了一个实例以满足指定的2个冗余。

  3. 服务伸缩

    上面我们说到, 可以在创建服务的时候指定服务的冗余数量, 这个数量是可以后期修改的, 就是动态扩容. 比如我们把redis扩展为3个实例:

     adam@adam-host:~$ docker service scale redis=3
     redis scaled to 3
    
     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     kdxwcqo77pns  redis  replicated  3/3       redis:latest
     u7ckw4btv8p9  mysql  replicated  2/2       mysql:latest
    
     adam@adam-host:~$ docker service ps redis
     ID            NAME         IMAGE         NODE        DESIRED STATE  CURRENT STATE           ERROR  PORTS
     oit9ge9u67by  redis.1      redis:latest  adam-host2  Running        Running 4 minutes ago
     t50gm1xp4gui   \_ redis.1  redis:latest  adam-host2  Shutdown       Complete 4 minutes ago
     9no6uos3yrpu  redis.2      redis:latest  adam-host   Running        Running 18 minutes ago
     f0nbj00l3crw  redis.3      redis:latest  adam-host3  Running        Running 38 seconds ago
    

    可以看到redis已经扩展成了3个。 现在把它降为以1个:

     adam@adam-host:~$ docker service scale redis=1
     redis scaled to 1
    
     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     kdxwcqo77pns  redis  replicated  1/1       redis:latest
     u7ckw4btv8p9  mysql  replicated  2/2       mysql:latest
    
     adam@adam-host:~$ docker service ps redis
     ID            NAME     IMAGE         NODE       DESIRED STATE  CURRENT STATE           ERROR  PORTS
     9no6uos3yrpu  redis.2  redis:latest  adam-host  Running        Running 20 minutes ago
    

    已经降为了一个实例。

  4. 删除停止服务

    使用docker service rm 即可删除服务:

     adam@adam-host:~$ docker service rm redis
     redis
    
     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     u7ckw4btv8p9  mysql  replicated  2/2       mysql:latest
    
     adam@adam-host:~$ docker service ps redis
     Error: No such service: redis
    

不同的容器间通讯 && docker network

docker在多个机器之间通讯一直是一个难题, 在docker network出现之前, 比较普遍的方案是采用docker weave 这个中间件。
不过后来docker自己也意识到这个问题, 所以在docker中集成了network模块用于解决这个问题。

关于network的东西可以另写一篇文章了, 这里先不展开讲, 用一个例子来看一下如何使用overlay网络在不同容器间的通讯。

  1. 创建overlay 网络

     adam@adam-host:~$ docker network create --driver overlay my-net
     txsfwempwmwc0jzzqgddcwy8i
    
     adam@adam-host:~$ docker network ls
     NETWORK ID          NAME                DRIVER              SCOPE
     2df627c1986e        bridge              bridge              local
     8edb29069734        docker_gwbridge     bridge              local
     5af457014d24        host                host                local
     ay97hon0djy5        ingress             overlay             swarm
     txsfwempwmwc        my-net              overlay             swarm
     3e2f26ff5cf7        none                null                local
    
  2. 在swarm启动服务, 指定网络为刚刚创建的my-net:

     adam@adam-host:~$ docker service create --name redis -p 6379:6379 --replicas 2 --network my-net redis
     66hfjyyda36a1sjwu7ry71r4v
    
     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     66hfjyyda36a  redis  replicated  2/2       redis:latest
    
     adam@adam-host:~$ docker service create --name mysql -p 3306:3306 --env 'MYSQL_ROOT_PASSWORD=123456' --replicas 2 --network my-net mysql
     3whbbl9p0hzs3bsthfn4e3pcg
    
     adam@adam-host:~$ docker service ls
     ID            NAME   MODE        REPLICAS  IMAGE
     3whbbl9p0hzs  mysql  replicated  2/2       mysql:latest
     66hfjyyda36a  redis  replicated  2/2       redis:latest
    

    启动参数都是一样的, 除了多了--network my-net.

  3. 进入到任意一个redis容器内, 执行 ping mysql 已经可以ping通了。

     adam@adam-host:~$ docker exec -it b129a168cb33 /bin/bash
     root@b129a168cb33:/data#
     root@b129a168cb33:/data# ping mysql
     PING mysql (10.0.0.5): 56 data bytes
     64 bytes from 10.0.0.5: icmp_seq=0 ttl=64 time=0.101 ms
     64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.139 ms
     ^C--- mysql ping statistics ---
     2 packets transmitted, 2 packets received, 0% packet loss
     round-trip min/avg/max/stddev = 0.101/0.120/0.139/0.000 ms
     root@b129a168cb33:/data#
    

更多关于docker network的文章, 参考官方文档

原创文章,转载请注明出处。 如发现文章有误, 请联系作者

Q.E.D.