要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
Docker实践(28) – 直接运行容器内的命令
在docker早期,许多用户添加SSH server到它们的镜像里,以便他们能通过外部的shell来访问容器。这样做的话相当于把容器当作虚拟机用了,添加了一个进程无疑增加了系统的开销。于是Docker引入了exec命令,提供了不需要安装ssh server直接在容器内执行命令的方法。下面我们介绍这个命令。
问题
你想在一个运行的容器内执行命令。
解决方法
使用docker exec命令。
讨论
为了演示exec命令,我们先在后台启动一个容器,名称为sleeper,仅执行一个sleep命令。
- docker run -d –name sleeper debian sleep infinity
现在我们启动了一个容器,你可以在上面使用docker exec命令完成各种试验。exec命令有三种模式,如下:
首先我们学习基本模式。下面的命令在容器sleeper内执行一个echo命令:
- $ docker exec sleeper echo "hello host from container"
- hello host from container
有没有注意到这个命令的结构与docker run命令类似,只是我们给的ID是正式运行容器的ID而不是镜像ID。运行的echo命令是容器内的命令,不是主机上的。
后台模式是在后台运行命令,在你的终端不会看到输出。这个模式通常用于执行花费比较长的任务,并且不关心它的输出,如清除日志文件:
- $ docker exec -d sleeper find / -ctime 7 -name ‘*log’ -exec rm {} ;
最后是交互模式。通常用来执行一个shell,然后与之交互:
- $ docker exec -i -t sleeper /bin/bash
- 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。安装非常简单,就一个命令:
- docker run -d -p 9000:9000 –privileged -v /var/run/docker.sock:/var/run/docker.sock uifd/ui-for-docker
Docker实践(26) – 设置从Dockerfile指定点缓存失效
使用–no-cache构建镜像大多情况下足够解决由于缓存引起的问题。不过有时候你想要一个更细粒度的解决方案。例如你构建的镜像需要时间比较长,你仍然想一些步骤使用缓存,然后从指定的点开始不使用缓存重新运行命令构建镜像。
问题
你想在构建镜像时设置从Dockerfile的指定位置开始使缓存失效。
解决方法
在命令的后面添加注释来使缓存失效。
讨论
例如我们在以下的Dockerfile中的CMD命令后添加注释以让缓存从这里失效:
- FROM node
- MAINTAINER [email protected]
- RUN git clone https://github.com/docker-in-practice/todo.git
- WORKDIR todo
- RUN npm install
- RUN chmod -R 777 /todo
- EXPOSE 8000
- CMD ["npm","start"] #bust the cache
输出为:
- $ docker build .
- Sending build context to Docker daemon 2.56 kB
- Sending build context to Docker daemon
- Step 0 : FROM node
- —> 91cbcf796c2c
- Step 1 : MAINTAINER [email protected]
- —> Using cache
- A “normal” docker build
- —> 8f5a8a3d9240
- Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
- —> Using cache
- —> 48db97331aa2
- Step 3 : WORKDIR todo
- —> Using cache
- —> c5c85db751d6
- Step 4 : RUN npm install
- —> Using cache
- —> be943c45c55b
- Step 5 : EXPOSE 8000
- —> Using cache
- —> 805b18d28a65
- Step 6 : CMD ["npm","start"] #bust the cache
- —> Running in fc6c4cd487ce
- —> d66d9572115e
- Removing intermediate container fc6c4cd487ce
- Successfully built d66d9572115e
从输出你会看到在第6步已经不使用缓存了,而之前的步骤仍使用缓存,这既缩短了构建镜像的时间,又能解决缓存可能引起的问题。
这个技巧工作的原因是我们在命令后添加了非空字符,所以Docker认为这是一个新的命令,也就不使用缓存了。
你会好奇如果我们更改的是第4步命令RUN npm install,那后面的第5步第6步还会使用缓存吗?答案是从更改的第4步开始下面的就不能使用缓存了。所以由于这个原因,建议尽可能的把一些不常需要变更的命令往上移。
Docker实践(25) – 不使用缓存重建镜像
使用Dockerfile构建镜像可以利用它的缓存功能:只有在命令已更改的情况下,才会重建已构建的步骤。下面是重新构建之前涉及到的to-do app的示例:
- $ docker build .
- Sending build context to Docker daemon 2.56 kB
- Sending build context to Docker daemon
- Step 0 : FROM node
- —> 91cbcf796c2c
- Step 1 : MAINTAINER [email protected]
- —> Using cache
- Indicates you’re using the cache
- Specifies the cached image/layer ID
- —> 8f5a8a3d9240
- Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
- —> Using cache
- —> 48db97331aa2
- Step 3 : WORKDIR todo
- —> Using cache
- —> c5c85db751d6
- Step 4 : RUN npm install > /dev/null
- —> Using cache
- —> be943c45c55b
- Step 5 : EXPOSE 8000
- —> Using cache
- —> 805b18d28a65
- Step 6 : CMD npm start
- —> Using cache
- —> 19525d4ec794
- 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构建镜像。
- $ docker build –no-cache .
- Sending build context to Docker daemon 2.56 kB
- Sending build context to Docker daemon
- Step 0 : FROM node
- —> 91cbcf796c2c
- Step 1 : MAINTAINER [email protected]
- —> Running in ca243b77f6a1
- —> 602f1294d7f1
- Removing intermediate container ca243b77f6a1
- Step 2 : RUN git clone -q https://github.com/docker-in-practice/todo.git
- —> Running in f2c0ac021247
- —> 04ee24faaf18
- Removing intermediate container f2c0ac021247
- Step 3 : WORKDIR todo
- —> Running in c2d9cd32c182
- —> 4e0029de9074
- Removing intermediate container c2d9cd32c182
- Step 4 : RUN npm install > /dev/null
- —> Running in 79122dbf9e52
- npm WARN package.json [email protected] No repository field.
- —> 9b6531f2036a
- Removing intermediate container 79122dbf9e52
- Step 5 : EXPOSE 8000
- —> Running in d1d58e1c4b15
- —> f7c1b9151108
- Removing intermediate container d1d58e1c4b15
- Step 6 : CMD npm start
- —> Running in 697713ebb185
- —> 74f9ad384859
- Removing intermediate container 697713ebb185
- Successfully built 74f9ad384859
以上的构建镜像步骤没有使用到缓存,每一层的镜像ID都与之间的不同。
Docker实践(24) – 使用ADD命令添加文件到镜像
虽然在Dockerfile内能使用RUN命令或者shell命令来添加文件到镜像,不过这可能很快变得难以管理。Dockerfile命令之一的ADD命令设计用来满足将大量文件放入镜像的需求。
问题
你想以一个简单的方法下载和解压一个tarball文件到你的镜像。
解决方法
tar打包和压缩你的文件,并在你的Dockerfile使用ADD指令。
讨论
使用mkdir add_example && cd add_example为Docker构建镜像准备一个新的环境。然后获取tarball文件:
- $ curl https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz > my.tar.gz
创建Dockerfile:
- FROM debian
- RUN mkdir -p /opt/libeatmydata
- ADD my.tar.gz /opt/libeatmydata/
- RUN ls -lRt /opt/libeatmydata
使用docker build –no-cache构建Dockerfile,输出类似如下:
- $ docker build –no-cache .
- Sending build context to Docker daemon 422.9 kB
- Sending build context to Docker daemon
- Step 0 : FROM debian
- —> c90d655b99b2
- Step 1 : RUN mkdir -p /opt/libeatmydata
- —> Running in fe04bac7df74
- —> c0ab8c88bb46
- Removing intermediate container fe04bac7df74
- Step 2 : ADD my.tar.gz /opt/libeatmydata/
- —> 06dcd7a88eb7
- Removing intermediate container 3f093a1f9e33
- Step 3 : RUN ls -lRt /opt/libeatmydata
- —> Running in e3283848ad65
- /opt/libeatmydata:
- total 4
- drwxr-xr-x 7 1000 1000 4096 Oct 29 23:02 libeatmydata-105
- /opt/libeatmydata/libeatmydata-105:
- total 880
- drwxr-xr-x 2 1000 1000 4096 Oct 29 23:02 config
- drwxr-xr-x 3 1000 1000 4096 Oct 29 23:02 debian
- drwxr-xr-x 2 1000 1000 4096 Oct 29 23:02 docs
- drwxr-xr-x 3 1000 1000 4096 Oct 29 23:02 libeatmydata
- drwxr-xr-x 2 1000 1000 4096 Oct 29 23:02 m4
- -rw-r–r– 1 1000 1000 4096 Oct 29 23:01 config.h.in
- […edited…]
- -rw-r–r– 1 1000 1000 1824 Jun 18 2012 pandora_have_better_malloc.m4
- -rw-r–r– 1 1000 1000 742 Jun 18 2012 pandora_header_assert.m4
- -rw-r–r– 1 1000 1000 431 Jun 18 2012 pandora_version.m4
- —> 2ee9b4c8059f
- Removing intermediate container e3283848ad65
- Successfully built 2ee9b4c8059f
你从输出看到tarball文件已经被Docker daemon解压到了目标目录。Docker支持大多数类型的压缩文件,如.gz,.bz2,.xz,.tar。
值得注意的是如果你在Dockerfile直接从url下载tarball文件,那么它们是不会自动解压的,Docker daemon只解压使用ADD命令添加本地的压缩文件。
如果你使用如下的Dockerfile再次构建镜像,你会发现文件只下载没有解压:
- FROM debian The file is retrieved from
- RUN mkdir -p /opt/libeatmydata the internet using a URL.
- ADD https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz /opt/libeatmydata/
- RUN ls -lRt /opt/libeatmydat
下面是输出:
- Sending build context to Docker daemon 422.9 kB
- Sending build context to Docker daemon
- Step 0 : FROM debian
- —> c90d655b99b2
- Step 1 : RUN mkdir -p /opt/libeatmydata
- —> Running in 6ac454c52962
- —> bdd948e413c1
- Removing intermediate container 6ac454c52962
- Step 2 : ADD https://www.flamingspork.com/projects/libeatmydata/libeatmydata-105.tar.gz /opt/libeatmydata/
- Downloading [==================================================>]
- 419.4 kB/419.4 kB
- —> 9d8758e90b64
- Removing intermediate container 02545663f13f
- Step 3 : RUN ls -lRt /opt/libeatmydata
- —> Running in a947eaa04b8e
- /opt/libeatmydata:
- total 412
- -rw——- 1 root root 419427 Jan 1 1970
- libeatmydata-105.tar.gz
- —> f18886c2418a
- Removing intermediate container a947eaa04b8e
- Successfully built f18886c2418a
如果你想添加一个压缩文件到镜像而不想解压,你可以使用COPY命令,这个命令与ADD命令类似,区别就是COPY命令不解压文件。
Docker实践(23) – 找出容器IP
虽然Docker命令让你能够访问有关镜像和容器的信息,但有时你想要了解这些Docker对象的内部元数据,如IP地址。
问题
你想找出容器IP地址。
解决方法
使用docker inspect命令获取和过滤容器元数据。
讨论
通过docker inspect命令能得到JSON格式的docker内部元数据。这会得到很多数据,所以下面只列出一部分。
镜像的原始数据:
- $ docker inspect ubuntu | head
- [{
- "Architecture": "amd64",
- "Author": "",
- "Comment": "",
- "Config": {
- "AttachStderr": false,
- "AttachStdin": false,
- "AttachStdout": false,
- "Cmd": [
- "/bin/bash"
- $
你可以通过名称或ID来查看镜像和容器的信息。当然它们的元数据会有所不同 – 比如容器会有“state”字段,而镜像没有。
你可以通过docker inspect命令,带一个format参数来找出容器的IP:
- docker inspect –format ‘{{.NetworkSettings.IPAddress}}’
这个命令对于自动化可能会比较有用,而且这个命令获取的IP比通过其它docker命令要可靠得多。下面的命令是获取所有运行的容器的IP并尝试ping它们。
- $ docker ps -q | xargs docker inspect –format='{{.NetworkSettings.IPAddress}}’ | xargs -l1 ping -c1
- PING 172.17.0.5 (172.17.0.5) 56(84) bytes of data.
- 64 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.095 ms
- — 172.17.0.5 ping statistics —
- 1 packets transmitted, 1 received, 0% packet loss, time 0ms
- 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目录:
- # apt-get install nfs-kernel-server
- # mkdir /export
- # chmod 777 /export
现在绑定挂载db目录到/export目录:
- # mount –bind /opt/test/db /export
你现在应该能在/export目录访问/opt/test/db的内容。
如果你想让重启后绑定还生效,添加/opt/test/db /export none bind 0 0到/etc/fstab文件。
现在添加如下行到/etc/exports文件:
- /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目录:
- # mount -t nfs 127.0.0.1:/export /mnt
- # exportfs -a
- # service nfs-kernel-server restart
现在运行一个数据容器:
- # docker run -ti –name nfs_client –privileged -v /mnt:/mnt busybox /bin/true
然后其它容器关联此数据容器的volumes:
- # docker run -ti –volumes-from nfs_client debian /bin/bash
- root@079d70f79d84:/# ls /mnt
- myb
- root@079d70f79d84:/# cd /mnt
- root@079d70f79d84:/mnt# touch asd
- 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参数的容器:
- $ docker run -t -i –privileged debian /bin/bash
然后当它启动后,在容器内部运行apt-get update && apt-get install sshfs安装SSHFS。
当SSHFS安装完成后,按如何登录远程主机:
- $ LOCALPATH=/path/to/local/directory
- $ mkdir $LOCALPATH
- $ sshfs user@host:/path/to/remote/directory $LOCALPATH
现在你应该能通过本地的$LOCALPATH目录看到远程主机的目录内容了。
不需要时可以通过fusermount命令来取消挂载:
- fusermount -u /path/to/local/directory
Docker实践(20) – 数据容器
如果你在主机上使用很多volumes,管理容器的启动可能会变得棘手。你可能希望数据只由容器管理,不管在主机上能直接访问数据。一种简单的方法是使用data-only容器。
问题
你想在容器内部访问外部volume,不过你希望这些volume只能由docker访问。
解决方法
设置一个数据容器并在其它容器运行时使用–volumes-from参数连接此数据容器。
讨论
下图展示了数据容器模式的结构,并解释了它的工作原理。
最主要是注意第二个主机,容器不需要知道数据在硬盘的哪个位置。它们所要知道的是数据容器的名称。这使得容器的操作更便捷。
这种方法比直接映射主机目录的另一个好处是,只能由docker访问这些文件,其它进程无法访问。
数据容器不需要运行! 一个常见的困惑是是否需要运行数据容器。不需要!仅仅需要数据容器在主机运行过并不被删除,之后就不需要运行了,但要保存它一直存在。
我们看一个示例看看数据容器怎么使用。
首先运行一个数据容器:
- $ docker run -v /shared-data –name dc busybox touch /shared-data/somefile
-v参数不映射主机上的目录,它只在容器责任范围内创建一个目录。此目录使用touch创建了一个文件,这样数据容器马上生效了 – 数据容器不需要运行使用。我们使用busybox镜像来减小容器大小。
然后运行另一个容器来连接数据容器:
- docker run -t -i –volumes-from dc busybox /bin/sh
- / # ls /shared-data
- somefile
–volumes-from参数允许你关联数据容器的volume到当前容器的目录 – 你只需要指定数据容器的ID。
volumes永久存在!当最后一个容器取消关联数据容器时,数据容器的数据不会被删除。 这是为了防止不必要的数据丢失。
使用数据容器可能比直接挂载目录管理起来要难,因为数据只能由docker访问,而不能像直接挂载的能在主机上直接访问。不过你如果希望把volumes给一个容器来管理,希望以后变动时不那么麻烦,那么数据容器可能适合你。
如果你的多个容器的应用程序正在往数据容器里写日志,那么确保它们在数据容器里日志的路径不一样很重要。因为如果日志路径一样的话,那么不同的容器可能会覆盖或截断文件,导致数据丢失。