管理swarm(1) – swarm模式概述

要Docker Engine在swarm模式下使用,需要从https://github.com/docker/docker/releases安装Docker Engine v1.12.0或更高的版本。或者安装最新版本的Docker for Mac或Docker for Windows Beta。
Docker Engine 1.12引入了本地管理Docker Engines集群的swarm模式,称为swarm。使用Docker CLI创建一个swarm,部署应用服务到swarm和管理swarm行为。

主要功能

  • 集群管理与Docker Engine集成:使用Docker Engine CLI来创建一个你能部署应用服务到Docker Engine的swarm。你不需要其他编排软件来创建或管理swarm。
  • 分散式设计:Docker Engine不是在部署时处理节点角色之间的差异,而是在运行时扮演自己角色。你可以使用Docker Engine部署两种类型的节点,管理器和worker。这意味着你可以从单个磁盘映像构建整个swarm。
  • 声明性服务模型: Docker Engine使用声明性方法来定义应用程序堆栈中各种服务的所需状态。例如,你可以描述由消息队列服务和数据库后端的Web前端服务组成的应用程序。
  • 伸缩性:对于每个服务,你可以声明要运行的任务数。当你向上或向下缩放时,swarm管理器通过添加或删除任务来自动适应,以保持所需状态。
  • 期望的状态协调:swarm管理器节点持续监控群集状态,并调整你描述的期望状态与实际状态之间的任何差异。 例如,如果设置运行一个10个副本容器的服务,这时worker机器托管其中的两个副本崩溃,管理器则将创建两个新副本以替换已崩溃的副本。 swarm管理器将新副本分配给正在运行和可用的worker。
  • 多主机网络:你可以为服务指定覆盖网络(overlay network)。 当swarm管理器初始化或更新应用程序时,它会自动为容器在覆盖网络(overlay network)上分配地址。
  • 服务发现:Swarm管理器节点为swarm中的每个服务分配唯一的DNS名称,并负载平衡运行中的容器。 你可以通过嵌入在swarm中的DNS服务器查询在swarm中运行中的每个容器。
  • 负载平衡:你可以将服务的端口暴露给外部的负载均衡器。 在内部,swarm允许你指定如何在节点之间分发服务容器。
  • 安全通信:swarm中的每个节点强制执行TLS相互验证和加密,以保护其自身与所有其他节点之间的通信。 你可以选择使用自签名根证书或来自自定义根CA的证书。
  • 滚动更新:在上线新功能期间,你可以增量地应用服务更新到节点。 swarm管理器允许你控制将服务部署到不同节点集之间的延迟。 如果出现任何问题,你可以将任务回滚到服务的先前版本。
  • Docker实践(28) – 直接运行容器内的命令

    在docker早期,许多用户添加SSH server到它们的镜像里,以便他们能通过外部的shell来访问容器。这样做的话相当于把容器当作虚拟机用了,添加了一个进程无疑增加了系统的开销。于是Docker引入了exec命令,提供了不需要安装ssh server直接在容器内执行命令的方法。下面我们介绍这个命令。

    问题

    你想在一个运行的容器内执行命令。

    解决方法

    使用docker exec命令。

    讨论

    为了演示exec命令,我们先在后台启动一个容器,名称为sleeper,仅执行一个sleep命令。

    1. docker run -d –name sleeper debian sleep infinity

    现在我们启动了一个容器,你可以在上面使用docker exec命令完成各种试验。exec命令有三种模式,如下:

  • 基本模式 – 在命令行中同步运行容器中的命令
  • 后台模式 – 后台执行容器中的命令
  • 交互模式 – 执行一个命令,允许用户与之交互
  • 首先我们学习基本模式。下面的命令在容器sleeper内执行一个echo命令:

    1. $ docker exec sleeper echo "hello host from container"
    2. hello host from container

    有没有注意到这个命令的结构与docker run命令类似,只是我们给的ID是正式运行容器的ID而不是镜像ID。运行的echo命令是容器内的命令,不是主机上的。
    后台模式是在后台运行命令,在你的终端不会看到输出。这个模式通常用于执行花费比较长的任务,并且不关心它的输出,如清除日志文件:

    1. $ docker exec -d sleeper find / -ctime 7 -name ‘*log’ -exec rm {} ;

    最后是交互模式。通常用来执行一个shell,然后与之交互:

    1. $ docker exec -i -t sleeper /bin/bash
    2. root@d46dc042480f:/#

    Docker实践(27) – 使用UI For Docker Web界面管理Docker

    一般情况下我们使用docker自带的cli来管理docker,如镜像及容器等。除了通过cli来管理docker,docker来提供了完整的API来管理各种组件,这就方便开发者来开发各种方便的工具来管理docker,比如通过浏览器的web界面。目前docker web界面开发得较好的是https://github.com/kevana/ui-for-docker。

    问题

    你想通过其它更简便的方式来管理docker,而不是用cli。

    解决方法

    使用ui-for-docker。

    讨论

    ui-for-docker项目托管在github上,地址为https://github.com/kevana/ui-for-docker。安装非常简单,就一个命令:

    1. docker run -d -p 9000:9000 –privileged -v /var/run/docker.sock:/var/run/docker.sock uifd/ui-for-docker

    完成后你就可以打开浏览器输入http://[你的主机IP]:9000来打开docker web管理界面了,如图:
    虚拟化技术

    虚拟化技术

    虚拟化技术
    虚拟化技术
    通过ui-for-docker,你可以对容器,容器网络,镜像,主机网络,Volumes进行管理。

    Docker实践(26) – 设置从Dockerfile指定点缓存失效

    使用–no-cache构建镜像大多情况下足够解决由于缓存引起的问题。不过有时候你想要一个更细粒度的解决方案。例如你构建的镜像需要时间比较长,你仍然想一些步骤使用缓存,然后从指定的点开始不使用缓存重新运行命令构建镜像。

    问题

    你想在构建镜像时设置从Dockerfile的指定位置开始使缓存失效。

    解决方法

    在命令的后面添加注释来使缓存失效。

    讨论

    例如我们在以下的Dockerfile中的CMD命令后添加注释以让缓存从这里失效:

    1. FROM node
    2. MAINTAINER [email protected]
    3. RUN git clone https://github.com/docker-in-practice/todo.git
    4. WORKDIR todo
    5. RUN npm install
    6. RUN chmod -R 777 /todo
    7. EXPOSE 8000
    8. CMD ["npm","start"] #bust the cache

    输出为:

    1. $ docker build .
    2. Sending build context to Docker daemon  2.56 kB
    3. Sending build context to Docker daemon
    4. Step 0 : FROM node
    5.  —> 91cbcf796c2c
    6. Step 1 : MAINTAINER [email protected]
    7.  —> Using cache
    8. A “normal” docker build
    9.    —> 8f5a8a3d9240
    10. Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
    11.  —> Using cache
    12.  —> 48db97331aa2
    13. Step 3 : WORKDIR todo
    14.  —> Using cache
    15.  —> c5c85db751d6
    16. Step 4 : RUN npm install
    17.  —> Using cache
    18.  —> be943c45c55b
    19. Step 5 : EXPOSE 8000
    20.  —> Using cache
    21.  —> 805b18d28a65
    22. Step 6 : CMD ["npm","start"] #bust the cache
    23.  —> Running in fc6c4cd487ce
    24.  —> d66d9572115e
    25. Removing intermediate container fc6c4cd487ce
    26. Successfully built d66d9572115e

    从输出你会看到在第6步已经不使用缓存了,而之前的步骤仍使用缓存,这既缩短了构建镜像的时间,又能解决缓存可能引起的问题。
    这个技巧工作的原因是我们在命令后添加了非空字符,所以Docker认为这是一个新的命令,也就不使用缓存了。
    你会好奇如果我们更改的是第4步命令RUN npm install,那后面的第5步第6步还会使用缓存吗?答案是从更改的第4步开始下面的就不能使用缓存了。所以由于这个原因,建议尽可能的把一些不常需要变更的命令往上移。

    Docker实践(25) – 不使用缓存重建镜像

    使用Dockerfile构建镜像可以利用它的缓存功能:只有在命令已更改的情况下,才会重建已构建的步骤。下面是重新构建之前涉及到的to-do app的示例:

    1. $ docker build .
    2. Sending build context to Docker daemon  2.56 kB
    3. Sending build context to Docker daemon
    4. Step 0 : FROM node
    5.   —> 91cbcf796c2c
    6. Step 1 : MAINTAINER [email protected]
    7.  —> Using cache
    8. Indicates you’re using the cache
    9. Specifies the cached image/layer ID
    10.    —> 8f5a8a3d9240
    11. Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
    12.  —> Using cache
    13.  —> 48db97331aa2
    14. Step 3 : WORKDIR todo
    15.  —> Using cache
    16.  —> c5c85db751d6
    17. Step 4 : RUN npm install > /dev/null
    18.  —> Using cache
    19.  —> be943c45c55b
    20. Step 5 : EXPOSE 8000
    21.  —> Using cache
    22.  —> 805b18d28a65
    23. Step 6 : CMD npm start
    24.  —> Using cache
    25.  —> 19525d4ec794
    26. Successfully built 19525d4ec794

    缓存非常有用并且省时间,不过有时候docker缓存的行为不都能达到你的期望。
    用以上Dockerfile作为示例,假设你更改了代码并push到Git仓库。新代码不会check out下来,因为git clone命令没有更改。在Docker看来git clone的步骤一样,所以使用了缓存。
    在这种情况下,你可能不想开启docker的缓存了。

    问题

    你想不用缓存重建Dockerfile。

    解决方法

    构建镜像时使用–no-cache参数。

    讨论

    为了强制docker构建镜像时不用缓存,执行带–no-cache参数的docker build命令。下面的示例是使用了–no-cache构建镜像。

    1. $ docker build –no-cache .
    2. Sending build context to Docker daemon  2.56 kB
    3. Sending build context to Docker daemon
    4. Step 0 : FROM node
    5.  —> 91cbcf796c2c
    6. Step 1 : MAINTAINER [email protected]
    7.  —> Running in ca243b77f6a1
    8.  —> 602f1294d7f1
    9. Removing intermediate container ca243b77f6a1
    10. Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
    11.  —> Running in f2c0ac021247
    12.  —> 04ee24faaf18
    13. Removing intermediate container f2c0ac021247
    14. Step 3 : WORKDIR todo
    15.  —> Running in c2d9cd32c182
    16.  —> 4e0029de9074
    17. Removing intermediate container c2d9cd32c182
    18. Step 4 : RUN npm install > /dev/null
    19.  —> Running in 79122dbf9e52
    20. npm WARN package.json [email protected] No repository field.
    21.  —> 9b6531f2036a
    22. Removing intermediate container 79122dbf9e52
    23. Step 5 : EXPOSE 8000
    24.  —> Running in d1d58e1c4b15
    25.  —> f7c1b9151108
    26. Removing intermediate container d1d58e1c4b15
    27. Step 6 : CMD npm start
    28.  —> Running in 697713ebb185
    29.  —> 74f9ad384859
    30. Removing intermediate container 697713ebb185
    31. Successfully built 74f9ad384859

    以上的构建镜像步骤没有使用到缓存,每一层的镜像ID都与之间的不同。

    Docker实践(24) – 使用ADD命令添加文件到镜像

    虽然在Dockerfile内能使用RUN命令或者shell命令来添加文件到镜像,不过这可能很快变得难以管理。Dockerfile命令之一的ADD命令设计用来满足将大量文件放入镜像的需求。

    问题

    你想以一个简单的方法下载和解压一个tarball文件到你的镜像。

    解决方法

    tar打包和压缩你的文件,并在你的Dockerfile使用ADD指令。

    讨论

    使用mkdir add_example && cd add_example为Docker构建镜像准备一个新的环境。然后获取tarball文件:

    1. $ curl https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz >  my.tar.gz

    创建Dockerfile:

    1. FROM debian
    2. RUN mkdir -p /opt/libeatmydata
    3. ADD my.tar.gz /opt/libeatmydata/
    4. RUN ls -lRt /opt/libeatmydata

    使用docker build –no-cache构建Dockerfile,输出类似如下:

    1. $ docker build –no-cache .
    2. Sending build context to Docker daemon 422.9 kB
    3. Sending build context to Docker daemon
    4. Step 0 : FROM debian
    5.  —> c90d655b99b2
    6. Step 1 : RUN mkdir -p /opt/libeatmydata
    7.  —> Running in fe04bac7df74
    8.  —> c0ab8c88bb46
    9. Removing intermediate container fe04bac7df74
    10. Step 2 : ADD my.tar.gz /opt/libeatmydata/
    11.  —> 06dcd7a88eb7
    12. Removing intermediate container 3f093a1f9e33
    13. Step 3 : RUN ls -lRt /opt/libeatmydata
    14.  —> Running in e3283848ad65
    15. /opt/libeatmydata:
    16. total 4
    17. drwxr-xr-x 7 1000 1000 4096 Oct 29 23:02 libeatmydata-105
    18. /opt/libeatmydata/libeatmydata-105:
    19. total 880
    20. drwxr-xr-x 2 1000 1000  4096 Oct  29 23:02 config
    21. drwxr-xr-x 3 1000 1000  4096 Oct  29 23:02 debian
    22. drwxr-xr-x 2 1000 1000  4096 Oct  29 23:02 docs
    23. drwxr-xr-x 3 1000 1000  4096 Oct  29 23:02 libeatmydata
    24. drwxr-xr-x 2 1000 1000  4096 Oct  29 23:02 m4
    25. -rw-r–r– 1 1000 1000  4096 Oct  29 23:01 config.h.in
    26. […edited…]
    27. -rw-r–r– 1 1000 1000   1824 Jun 18  2012 pandora_have_better_malloc.m4
    28. -rw-r–r– 1 1000 1000    742 Jun 18  2012 pandora_header_assert.m4
    29. -rw-r–r– 1 1000 1000    431 Jun 18  2012 pandora_version.m4
    30.  —> 2ee9b4c8059f
    31. Removing intermediate container e3283848ad65
    32. Successfully built 2ee9b4c8059f

    你从输出看到tarball文件已经被Docker daemon解压到了目标目录。Docker支持大多数类型的压缩文件,如.gz,.bz2,.xz,.tar。
    值得注意的是如果你在Dockerfile直接从url下载tarball文件,那么它们是不会自动解压的,Docker daemon只解压使用ADD命令添加本地的压缩文件。
    如果你使用如下的Dockerfile再次构建镜像,你会发现文件只下载没有解压:

    1. FROM debian The file is retrieved from
    2. RUN mkdir -p /opt/libeatmydata the internet using a URL.
    3. ADD https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz /opt/libeatmydata/
    4. RUN ls -lRt /opt/libeatmydat

    下面是输出:

    1. Sending build context to Docker daemon 422.9 kB
    2. Sending build context to Docker daemon
    3. Step 0 : FROM debian
    4.  —> c90d655b99b2
    5. Step 1 : RUN mkdir -p /opt/libeatmydata
    6.  —> Running in 6ac454c52962
    7. —> bdd948e413c1
    8. Removing intermediate container 6ac454c52962
    9. Step 2 : ADD  https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz  /opt/libeatmydata/
    10. Downloading [==================================================>]
    11. 419.4 kB/419.4 kB
    12.  —> 9d8758e90b64
    13. Removing intermediate container 02545663f13f
    14. Step 3 : RUN ls -lRt /opt/libeatmydata
    15.  —> Running in a947eaa04b8e
    16. /opt/libeatmydata:
    17. total 412
    18. -rw——- 1 root root 419427 Jan  1  1970
    19. libeatmydata-105.tar.gz
    20.  —> f18886c2418a
    21. Removing intermediate container a947eaa04b8e
    22. Successfully built f18886c2418a

    如果你想添加一个压缩文件到镜像而不想解压,你可以使用COPY命令,这个命令与ADD命令类似,区别就是COPY命令不解压文件。

    Docker实践(23) – 找出容器IP

    虽然Docker命令让你能够访问有关镜像和容器的信息,但有时你想要了解这些Docker对象的内部元数据,如IP地址。

    问题

    你想找出容器IP地址。

    解决方法

    使用docker inspect命令获取和过滤容器元数据。

    讨论

    通过docker inspect命令能得到JSON格式的docker内部元数据。这会得到很多数据,所以下面只列出一部分。
    镜像的原始数据:

    1. $ docker inspect ubuntu | head
    2. [{
    3.     "Architecture": "amd64",
    4.     "Author": "",
    5.     "Comment": "",
    6.     "Config": {
    7.         "AttachStderr": false,
    8.         "AttachStdin": false,
    9.         "AttachStdout": false,
    10.         "Cmd": [
    11.             "/bin/bash"
    12. $

    你可以通过名称或ID来查看镜像和容器的信息。当然它们的元数据会有所不同 – 比如容器会有“state”字段,而镜像没有。
    你可以通过docker inspect命令,带一个format参数来找出容器的IP:

    1. docker inspect –format ‘{{.NetworkSettings.IPAddress}}’

    这个命令对于自动化可能会比较有用,而且这个命令获取的IP比通过其它docker命令要可靠得多。下面的命令是获取所有运行的容器的IP并尝试ping它们。

    1. $ docker ps -q | xargs docker inspect –format='{{.NetworkSettings.IPAddress}}’ | xargs -l1 ping -c1
    2. PING 172.17.0.5 (172.17.0.5) 56(84) bytes of data.
    3. 64 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.095 ms
    4. — 172.17.0.5 ping statistics —
    5. 1 packets transmitted, 1 received, 0% packet loss, time 0ms
    6. rtt min/avg/max/mdev = 0.095/0.095/0.095/0.000 ms

    Docker实践(22) – 通过NFS共享数据

    在部分公司已经在使用NFS共享文件了。如果应用部署在docker容器里,如果能访问这些共享的文件呢?
    docker不支持NFS开箱即用,而在每个容器中安装NFS客户端以便能挂载远程目录不是一个好方法。建议的方法是使用一个docker容器充当其它容器访问NFS共享数据的代理。

    问题

    你想无缝访问NFS的远程文件系统

    解决方法

    使用数据容器代理访问

    讨论

    数据容器的相对介绍请参考http://devops.webres.wang/2016/11/docker-data-container/。下图显示这种方法如何工作。
    虚拟化技术
    NFS服务器暴露内部目录为/export,此目录与/opt/test/db绑定挂载。然后docker主机使用NFS协议挂载到目录/mnt。然后数据容器挂载主机的/mnt来代理其它容器对共享目录的访问。
    虽然NFS的介绍超过了本文的范畴,但NFS与本文的主题密切相关,还是有必要说明下。
    假设你想共享系统为Ubuntu14.04的/opt/test/db目录给其它主机的容器。
    切换为root权限,安装NFS服务器并创建一个export目录:

    1. # apt-get install nfs-kernel-server
    2. # mkdir /export
    3. # chmod 777 /export

    现在绑定挂载db目录到/export目录:

    1. # mount –bind /opt/test/db /export

    你现在应该能在/export目录访问/opt/test/db的内容。

    如果你想让重启后绑定还生效,添加/opt/test/db /export none bind 0 0到/etc/fstab文件。

    现在添加如下行到/etc/exports文件:

    1. /export 127.0.0.1(ro,fsid=0,insecure,no_subtree_check,async)

    因为我们只用了一台机器做试验,即nfs服务器与容器都在一台,所以这里埴写了127.0.0.1,如果是在其它机器,记得把127.0.0.1替换为nfs客户端IP。
    考虑到安全,我们在这里只读挂载,不过你也可以把ro替换为rw设置读写挂载,如果这样做,记得在async选项后添加no_root_squash。
    然后通过NFS挂载/export目录到/mnt目录:

    1. # mount -t nfs 127.0.0.1:/export /mnt
    2. # exportfs -a
    3. # service nfs-kernel-server restart

    现在运行一个数据容器:

    1. # docker run -ti –name nfs_client –privileged -v /mnt:/mnt busybox /bin/true

    然后其它容器关联此数据容器的volumes:

    1. # docker run -ti –volumes-from nfs_client debian /bin/bash
    2. root@079d70f79d84:/# ls /mnt
    3. myb
    4. root@079d70f79d84:/# cd /mnt
    5. root@079d70f79d84:/mnt# touch asd
    6. touch: cannot touch `asd’: Read-only file system

    Docker实践(21) – 使用sshfs挂载远程volume

    我们已经讨论过如何挂载本地文件,不过很快我们就会遇到如何挂载远程文件系统的问题了。例如可能你想共享远程服务器上的数据库并把它当作本地数据库使用。
    虽然理论上可以设置一个NFS服务器并通过在你服务器挂载目录访问远程文件,不过有一种对于大多数用户快速简单的方法,此方法不需要在服务器端安装任何软件(只需要SSH)。

    设置sshfs需要root权限,并且需要安装FUSE(Linux的“用户空间文件系统”内核模块)。你可以通过执行ls /dev/fuse来判断系统是否已经安装有。

    问题

    你想找到一个不需要配置服务器端的方法来挂载远程的文件系统。

    解决方法

    使用sshfs来挂载远程文件系统

    讨论

    sshfs通过使用FUSE内核模块提供的文件系统标准接口和SSH访问远程文件系统,所有的通信使用SSH进行。SSHFS还提供了各种幕后功能(例如远程文件预读),以让我们觉得是在操作本地文件。下图说明它是如何工作的。
    虚拟化技术
    我们现在来做下试验。首先在你主机运行一个带–privileged参数的容器:

    1. $ docker run -t -i –privileged debian /bin/bash

    然后当它启动后,在容器内部运行apt-get update && apt-get install sshfs安装SSHFS。
    当SSHFS安装完成后,按如何登录远程主机:

    1. $ LOCALPATH=/path/to/local/directory
    2. $ mkdir $LOCALPATH
    3. $ sshfs user@host:/path/to/remote/directory $LOCALPATH

    现在你应该能通过本地的$LOCALPATH目录看到远程主机的目录内容了。
    不需要时可以通过fusermount命令来取消挂载:

    1. fusermount -u /path/to/local/directory

    Docker实践(20) – 数据容器

    如果你在主机上使用很多volumes,管理容器的启动可能会变得棘手。你可能希望数据只由容器管理,不管在主机上能直接访问数据。一种简单的方法是使用data-only容器。

    问题

    你想在容器内部访问外部volume,不过你希望这些volume只能由docker访问。

    解决方法

    设置一个数据容器并在其它容器运行时使用–volumes-from参数连接此数据容器。

    讨论

    下图展示了数据容器模式的结构,并解释了它的工作原理。
    虚拟化技术
    最主要是注意第二个主机,容器不需要知道数据在硬盘的哪个位置。它们所要知道的是数据容器的名称。这使得容器的操作更便捷。
    这种方法比直接映射主机目录的另一个好处是,只能由docker访问这些文件,其它进程无法访问。

    数据容器不需要运行! 一个常见的困惑是是否需要运行数据容器。不需要!仅仅需要数据容器在主机运行过并不被删除,之后就不需要运行了,但要保存它一直存在。

    我们看一个示例看看数据容器怎么使用。
    首先运行一个数据容器:

    1. $ docker run -v /shared-data –name dc busybox touch /shared-data/somefile

    -v参数不映射主机上的目录,它只在容器责任范围内创建一个目录。此目录使用touch创建了一个文件,这样数据容器马上生效了 – 数据容器不需要运行使用。我们使用busybox镜像来减小容器大小。
    然后运行另一个容器来连接数据容器:

    1. docker run -t -i –volumes-from dc busybox /bin/sh
    2. / # ls /shared-data
    3. somefile

    –volumes-from参数允许你关联数据容器的volume到当前容器的目录 – 你只需要指定数据容器的ID。

    volumes永久存在!当最后一个容器取消关联数据容器时,数据容器的数据不会被删除。 这是为了防止不必要的数据丢失。

    使用数据容器可能比直接挂载目录管理起来要难,因为数据只能由docker访问,而不能像直接挂载的能在主机上直接访问。不过你如果希望把volumes给一个容器来管理,希望以后变动时不那么麻烦,那么数据容器可能适合你。

    如果你的多个容器的应用程序正在往数据容器里写日志,那么确保它们在数据容器里日志的路径不一样很重要。因为如果日志路径一样的话,那么不同的容器可能会覆盖或截断文件,导致数据丢失。