云原生概念风生云起,最近算是非常火热,而其最基本的依仗就在于能将任何程序运行在轻量级Cell上的容器Docker。Docker如果仅只是使用的话,是一点都不难的,但作为一个所有应用程序运行其上的基石,对其的细节了解是非常有必要的。
本文会记录从最基本的Docker基础,到日常的工作操作,到比较深入的原理细节,都会有所涉及。
版本信息如下:
$ docker version
Client: Docker Engine - Community
Version: 18.09.2
API version: 1.39
Go version: go1.10.8
Git commit: 6247962
Built: Sun Feb 10 04:12:39 2019
OS/Arch: darwin/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
此外,docker info比较长,就不贴这里了,放在下面的资料部分。简单看了下应该是不带敏感信息的,这部分应该是做了剔除的。
这里顺道加一个非常重要的文档:Docker Tutorials and Labs。这个文档库里存放的是docker官方的tutorial以及一些架构设计等的资料。对于想要深入的读者来说是非常重要的资料。
MAC下的安装比较简单,直接下载Docker官方的桌面版本即可:Docker Desktop for Mac。文档在:Get started with Docker Desktop for Mac。
此外,为了能使用(下载)一些官方(软件公司)发布镜像文件,你需要注册一个docker hub账号(其实也就是docker官方账号):docker hub。
Docker的核心功能点之一就是能将部署这个事情代码化。本来运维的工作中充斥着各种不确定性,版本不兼容、软件包不同、Linux内核不同导致的安装问题,等等。而Docker可以使用Dockerfile将一个镜像的制作(以代码形式)固定下来,保证只要是一份Dockerfile,制作出来的镜像是完全一致的。
此外,只要保证Docker的版本一致,docker镜像的运行状态及结果是可预期的。这就保证了一个软件的研发流程中,只要镜像被制作完成了,后续就可以使用这个镜像文件进行分发了,不再需要每个部署环境都从Dockerfile从头开始制作一份本地的镜像。
而分发这个过程就需要镜像仓库的介入:本地制作镜像 => 上传镜像到仓库 => 部署服务器拉取镜像 => 部署服务器本地运行镜像。
Docker官方有一个开放的镜像仓库,一般知名的第三方软件提供者都会将自己软件的镜像文件发布到这个开放的仓库中,方便第三方使用者下载。当然,私有仓库对于大部分软件公司来说都是必须的。
制作一个私有的镜像仓库非常简单,直接使用docker命令即可,官方文档在:Docker Registry。
运行:
docker run -d -p 5000:5000 --name registry registry:2
这样就在localhost:5000
运行了一个镜像仓库。
从镜像仓库获得镜像以及上传镜像都需要有对应的身份认证。使用:
$ docker login --help
Usage: docker login [OPTIONS] [SERVER]
Log in to a Docker registry
Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
即便是从官方镜像仓库获取镜像也是需要认证的,所以一开始需要注册一个账号。如果使用的是Docker Desktop的话,可以从UI界面上进行登录。
如果登录的是私有仓库,[SERVER]
这块需要输入私有仓库地址。
如果是从私有仓库上进行对应的拉取和推送镜像,需要在镜像名字之前补完仓库地址:
docker push localhost:5000/imagename:major.minor.patch
docker pull localhost:5000/imagename:major.minor.patch
docker的命令行工具没有提供查询一个镜像所有tags的命令(ridiculous),可以使用以下方法来查询:
# require tool jq
brew update --verbose
brew install jq --verbose
curl -s https://registry.hub.docker.com/v2/repositories/${repo_name}/tags/?page_size=1024 | jq .
# sample repo_name: elastic/filebeat
显示所有镜像(本地)
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.10 b977ae81df17 2 weeks ago 73.9MB
删除镜像,可以同时删除多个,给的名字可以是镜像名也可以是镜像id
$ docker rmi imagename imageid ...
可以显示所有的容器(本地),包括不在运行状态的。-a
一般来说是必须的,否则无法将停止状态的镜像也列出来。
$ docker ps -a
删除容器,可以同时删除多个,给的名字可以是容器名也可以是容器id
$ docker rm containername containerid ...
从一个Dockerfile构建一个镜像,一般都会进入到Dockerfile同一层目录进行构建:docker build [OPTIONS] .
,这里的.
就是Dockerfile的路径。全选项参数可以通过:docker build --help
来获得,这里就不贴了。
细节相当多,有需要的可以看下官方文档:docker build。
此外,docker build命令与Dockerfile息息相关,部分优化相关内容描述在Dockerfile内,请去此处查看:5. Dockerfile。
通过-t
在构建的时候对镜像进行命名,并打上标签。一般来说这是必须的,方便后续进行镜像查找和仓库内巨量镜像的维护。
$ docker build -t imagename:major.minor.patch .
通过-f
指定镜像文件路径。
$ docker build -f yourrepo/Dockerfile .
在构建镜像的时候为了对内部的应用程序提供一些配置,常用环境变量的方式进行注入,而在镜像构建的时候,可以使用--build-args XXX=...
这样的方式进行操作。
$ docker build --build-arg HTTP_PROXY=http://10.20.30.2:1234 --build-arg FTP_PROXY=http://40.50.60.5:4567 .
如果是裸用Docker的话,这样的操作还算是比较容易理解的,但一般来说大型分布系统肯定还使用了K8S这样的容器管理系统或者还甚至用了类似于Istio这样的服务编排系统,就不需要在制作镜像的时候这么弄了。
通过--ulimit
来指定该镜像文件在运行时候的ulimit。
通过--cgroup-parent
来指定该镜像文件在运行时候的资源限制群组。关于cgroup这个话题,后面会专门起一个章节来深入。详见:8.3 资源限制。
通过--add-host=domain.com:10.180.0.1
来向/etc/hosts里添加匹配。
通过--target
来指定构建的目标环境:
FROM debian AS build-env
...
FROM alpine AS production-env
...
$ docker build -t mybuildimage --target build-env .
--no-cache
:在构建镜像的时候不使用cache,在频繁更新某个镜像时很有用,防止cache污染--ssh
:SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|[=|[,]])--cpu-period
:Limit the CPU CFS (Completely Fair Scheduler) period--cpu-quota
:Limit the CPU CFS (Completely Fair Scheduler) quota--cpu-shares
:CPU shares (relative weight)--cpuset-cpus
:CPUs in which to allow execution (0-3, 0,1)--cpuset-mems
:MEMs in which to allow execution (0-3, 0,1)--memory
:Memory limit--memory-swap
:Swap limit equal to memory plus swap: ‘-1’ to enable unlimited swap详见:8.3 资源限制。
将镜像运行成容器的命令,虽然是一个很简单的命令,但细节相当多,用起来其实还蛮麻烦的。全选项参数可以通过:docker run --help
来获得,这里就不贴了。中文的帖子也有,可以看了参考:Docker run 命令参数及使用
细节相当多,有需要的可以看下官方文档:docker run。
命令的基本使用格式:
$ docker run [OPTIONS] IMAGE[:TAG\|@DIGEST] [COMMAND] [ARG...]
通过-d
来让docker run命令运行起来的容器转为后台常驻进程。
$ docker run -d -p 80:80 my_image service nginx start
通过--name
来让docker run命名运行起来的容器,后续可以使用这个名字来访问这个启动的容器,不再需要docker ps -a
查找这个容器的UUID。在使用同一个镜像启动多个容器的时候特别有用。
$ docker run --name myconname -d ubuntu:18.10
网络这块相关的配置内容也非常多,细节的内容建议直接阅读官方的文档:docker run > Network settings。
--network=...
用来控制网络模式:
none
:No networking in the container.bridge (default)
:Connect the container to the bridge via veth interfaces.host
:Use the host’s network stack inside the container.container:<name\|id>
:Use the network stack of another container, specified via its name or id.NETWORK
:Connects the container to a user created network (using docker network create command)参数同docker build命令,但很奇怪的是docker官方给出的例子这里倒是没有=
:
$ docker run -it --add-host domain.com:10.180.0.1 ubuntu cat /etc/hosts
通过--restart
可以让docker run启动的容器在退出之后按策略重启。官方文档在:docker run > Restart policies (—restart)。
no
:Do not automatically restart the container when it exits. This is the default.on-failure[:max-retries]
:Restart only if the container exits with a non-zero exit status. Optionally, limit the number of restart retries the Docker daemon attempts.always
:Always restart the container regardless of the exit status. When you specify always, the Docker daemon will try to restart the container indefinitely. The container will also always start on daemon startup, regardless of the current state of the container.unless-stopped
:Always restart the container regardless of the exit status, including on daemon startup, except if the container was put into a stopped state before the Docker daemon was stopped.该选项与--rm
是冲突的。
接上面的重试策略,on-failure
会检查退出代码。官方文档在:docker run > Exit Status。
通过--rm
可以让docker run启动的容器在退出之后删除所有的留存信息。如果没有加这个参数的话,在容器退出、停止之后使用docker ps -a
可以找到刚才启动的容器。这对于测试来说是很麻烦的,每次停止之后还要使用docker rm
命令删除。这时候就可以通过--rm
来命令容器退出后自动清理:
# not cleanup
$ docker run -i -t ubuntu:18.10 ps afx
$ docker ps -a
# cleanup
$ docker run -i -t --rm ubuntu:18.10 ps afx
$ docker ps -a
参见官方文档:docker run > Security configuration。
参见官方文档:docker run > Runtime privilege and Linux capabilities。后续详见:8.3 资源限制。
通过--log-driver
容器可以指定和docker daemon不同的日志输出设备。参见官方文档:Logging drivers (—log-driver)。
主要是以下几项:
细节参见官方文档:Overriding Dockerfile image defaults。
使用如下命令可以启动容器之后执行命令进行测试,并在退出之后自动清理刚才生成的容器。
$ docker run --rm -i -t $image_name:$version $command
容器之间拥有自己的PID命名空间,并相互隔离。通过--pid=...
可以让容器之间共享进程。具体的例子可以直接阅读官方的文档:PID settings (—pid)。
简单的例子可以尝试(看下输出的内容有什么不同):
# without pid
$ docker run -i -t --rm ubuntu:18.10 ps afx
...
# with pid
$ docker run -i -t --rm --pid=host ubuntu:18.10 ps afx
...
--uts
和--ipc
也是类似的,可以参考官方文档里的:UTS settings (—uts)和IPC settings (—ipc)。中文资料可以看:Docker run参考(5) – UTS(–uts)和IPC (–ipc)设置。
参见官方文档:docker run > Runtime constraints on resources。后续详见:8.3 资源限制。
将本地的输入输出及错误流附到一个运行中的容器上。简单来说可以理解为:进入到容器的命令行中。
如果需要在一个容器启动之后连上去的话,需要容器在启动(docker run
)的时候指定:-i -t
参数:
$ docker run -i -t -d --rm ubuntu:18.10
这样就可以在之后使用docker attach
命令附到该容器上并打开命令行了:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
80172e3d5574 ubuntu:18.10 "/bin/bash" 38 seconds ago Up 37 seconds
$ docker attach 80172e3d5574
root@80172e3d5574:/#
新手常犯的一个错误就是使用exit
命令退出附上的shell,结果就是非但退出了shell还把容器本身给stop了。正确的做法是在shell里ctrl+p
然后ctrl+q
,这里切记是先按p的组合键,然后按q的组合键,而不是同时ctrl+p+q
。
$ docker attach f269e680ae14
root@f269e680ae14:/#
# ctrl+p & ctrl+q
root@f269e680ae14:/# read escape sequence
$
资料:
在一个已经处于运行中
状态的容器里执行一条命令。
一般来说,如果是有连续工作的话,还是会使用docker attach
附上去之后再操作。但如果仅只是一两句命令,或自动化的脚本,就需要使用到docker exec
了。这里需要注意,exec只能用在running的容器上,如果没有容器且需要执行命令测试的话,也可以使用docker run
命令,直接附带上需要执行的命令即可。
也有有趣的用法:
$ docker exec -it e8c541f9fb33 /bin/bash
这条语句的效果和docker attach
是一致的。
在某些不能以/bin/bash
作为命令启动的镜像(默认入口已经在Dockerfile里指定了,这种情况还蛮多的,稍微复杂点的镜像一般都会把入口设置掉),比如说Elasticsearch、nginx,等等。在这种情况下,如果需要attach到这个运行中的镜像,就必须使用exec了:
$ docker exec -it e8c541f9fb33 /bin/sh
此外,以Alpine镜像为基础的镜像,默认是没有/bin/bash
的,这种时候就需要换成/bin/sh
。
--unsafe-perm
,具体可以看:grpc/grpc-node#604Dockerfile是用来进行镜像构建的文本文件,也是docker能将构建整个过程转化为文本固定下来的关键,可以说是docker能如此风靡的核心功能点也不为过。
官方的文档:
下面行文不会过于注重Dockerfile如何编写的语法,因为这东西你看看手册和几个例子也就会了。主要还是关注在几个比较麻烦的概念上。
几点零碎的:
COPY
、EXPOSE
之类的,都被称为指令(instruction),这个单词可以了解下官方文档:RUN。
运行指令,有两种语法模式:
这里需要注意exec模式
并不是在shell里运行的:
Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, RUN [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: RUN [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.
建议是使用exec模式
,细节可以看下面一个小节。
官方文档:
这两个东西可以放在一起说,因为他们做的事情基本上是类似的。两者都是在容器启动之后执行一个命令,CMD提供的是在镜像启动后会默认执行的一个命令,而ENTRYPOINT则是在镜像启动后提供一个程序入口。
区别在于:
docker run $imagename $command
后面接命令进行覆盖,这样操作的话写在Dockerfile CMD里的命令就不会执行了,被替代了;CMD在一个Dockerfile里只能有一个生效,如果编写了多个则只有最后的那个会被执行功能入口
,不会被docker run $imagename $command
中的命令替换掉,除非指定--entrypoint=...
进行替换;当然ENTRYPOINT也只允许有一个一般来说通常的做法是:ENTRYPOINT提供容器运行之后程序入口,而CMD则提供供给给程序入口的参数,方便后续在docker run的时候进行替换。这两者以这样的方式进行协同工作。
官方文档里也有这部分内容:Understand how CMD and ENTRYPOINT interact。
CMD
or ENTRYPOINT
commands.ENTRYPOINT
should be defined when using the container as an executable.CMD
should be used as a way of defining default arguments for an ENTRYPOINT
command or for executing an ad-hoc command in a container.CMD
will be overridden when running the container with alternative arguments.这两个指令和5.2 RUN一样,都有多种模式可以选择,一般来说最常使用的是直接写命令的shell模式
以及使用JSON格式编写的exec模式
。最佳实践是:在所有的情况下都使用exec模式
。原因如下:
/bin/sh
,后续的信号不能很好传递到真正执行的命令上/bin/sh
,但某些微型镜像不一定有Best practices for writing Dockerfiles > ENTRYPOINT:
Configure app as PID 1
This script uses the exec Bash command so that the final running application becomes the container’s PID 1. This allows the application to receive any Unix signals sent to the container. For more, see the ENTRYPOINT reference.
CMD指令还多一种模式:CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
,这也是刚才提到的参数提供者角色的做法。
e.g
FROM ubuntu:trusty
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]
$ docker run ping
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.025 ms
...
$ docker run ping docker.io
PING docker.io (162.242.195.84) 56(84) bytes of data.
64 bytes from 162.242.195.84: icmp_seq=1 ttl=61 time=76.7 ms
...
资料:
有时间的话,官方的ENTRYPOINT文档可以通读一下:Dockerfile > ENTRYPOINT,里面信息量不小。
官方文档:VOLUME。
卷加载相关在docker里是一个比较麻烦的概念,这里不作展开,只列出文档里提到的注意点:
Changing the volume from within the Dockerfile
: If any build steps change the data within the volume after it has been declared, those changes will be discarded.The host directory is declared at container run-time
: The host directory (the mountpoint) is, by its nature, host-dependent. This is to preserve image portability, since a given host directory can’t be guaranteed to be available on all hosts. For this reason, you can’t mount a host directory from within the Dockerfile. The VOLUME
instruction does not support specifying a host-dir
parameter. You must specify the mountpoint when you create or run the container.更多的深入理解可以查看:9.2 存储。
官方文档:ARG。
除了用户定义的变量之外,docker还有一部分预定义的变量,可以通过docker build --build-arg name=value
来加入:Predefined ARGs。
官方文档:HEALTHCHECK。
类似于客户端服务器保持连接的心跳检查的概念,这个指令是用来检查当前的容器其提供的服务是否正常的。
用法:HEALTHCHECK [OPTIONS] CMD command
。CMD之前的选项有:
范例,每5分钟检查一次WEB服务器是否正常工作(3秒内能响应请求):
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
退出代码:
同样细节比较多,可以仔细阅读下官方文档:Dockerfile > HEALTHCHECK。
资料:
有相当多的指令在本文中并没有展开,可以查看其官方文档:
官方文档:Understand build context。
简单理解就是保证运行docker build
命令的根工作文件夹里的内容尽量少,最好只保有构建镜像必须的文件,能显著加速镜像构建的速度(减少Context传送需要花费的时间)。
举个例子:
FROM alpine:3.9.2
ENTRYPOINT ["/bin/sh"]
$ mkdir ~/Downloads/docker && cd ~/Downloads/docker
$ docker build -f Dockerfile -t empty_img:0.0.1 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:3.9.2
---> 5cb3aa00f899
Step 2/2 : ENTRYPOINT ["/bin/sh"]
---> Running in 6190582d6894
Removing intermediate container 6190582d6894
---> bd0e931e4706
Successfully built bd0e931e4706
Successfully tagged empty_img:0.0.1
然后拷贝点垃圾文件到~/Downloads/docker
,再执行一次:
$ docker build -f Dockerfile --no-cache -t dummy_img:0.0.1 .
Sending build context to Docker daemon 630.1MB
Step 1/2 : FROM alpine:3.9.2
---> 5cb3aa00f899
Step 2/2 : ENTRYPOINT ["/bin/sh"]
---> Running in 0b6ebe8953ca
Removing intermediate container 0b6ebe8953ca
---> 681e135d49b6
Successfully built 681e135d49b6
Successfully tagged dummy_img:0.0.1
Sending build context to Docker daemon 630.1MB
这一步花了很长时间(因为我拷贝过去的是630MB很零碎的小文件)。如果Context再大点,到百千GB级别的话,那影响更大。
对于构建出来的镜像大小是没有任何影响的:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dummy_img 0.0.1 681e135d49b6 40 seconds ago 5.53MB
empty_img 0.0.1 bd0e931e4706 8 minutes ago 5.53MB
alpine 3.9.2 5cb3aa00f899 3 weeks ago 5.53MB
官方文档:Use multi-stage builds。
一般来说总是希望镜像的体积越小越好,有利于传输也有利于减小容器的资源占用。而在镜像构建的时候,总会有很多中间产物。以go语言来举例,go语言最后编译产生的可执行文件是自包含的,对于外部的类库等都是不作要求的,而在go源码构建成可执行文件的过程中,则会有很多SDK等的环境需求。这就对构建
和生产
两个环境做了不同的定义及隔离要求。而多阶段构建
这个功能,就是为了这种需求而服务的。
简单来说就是在同一个Dockerfile里有多个``FROM指令
,每个FROM指令可以使用as ...
这样的语法进行命名,这样的一行语句就定义了一个阶段
。后面的阶段可以随意利用之前阶段里产生任何资源。通常的用法就是定义两个FROM即两个阶段,构建阶段和生产阶段。将所有的环境设置等都定义在构建阶段,然后将构建阶段编译产生的二进制文件拷贝到生产环境,这样生产环境就能够做到最小化了。
详细的例子可以直接查看官方文档,里面有一份非常详细的范例,正好就是按go制作的范例。
官方文档:Leverage build cache。
这部分建议通读后理解,因为在docker build
的时候,只要不添加--no-cache
选项的话,构建就会检查并使用缓存。除非在工作中每次都放弃使用cache(在某些情况下会大大增加构建的时间消耗),否则对于缓存的命中还是有理解的必要。
Only the instructions RUN, COPY, ADD create layers. Other instructions create temporary intermediate images, and do not increase the size of the build.
因此对于RUN
、COPY
以及ADD
指令的使用需要非常小心,特别是RUN指令,尽量使用&&
将其串起来。
镜像文件的很多细节在之前的4.5 docker build以及5. Dockerfile都有提到了,所以讲到这里其实已经没什么很有价值的内容可以讲了。关于Image,有一篇非常不错的文章,只要过了一遍基本上Image本身就没什么神秘的了,可以看下:What’s in a Docker image?。
一些额外的资料:
官方文档:Docker Machine Overview。
Docker machine是用来辅助Ops对大批量机器进行docker部署时使用的工具。现如今规模化使用容器一般都会使用类似K8S这样的工具,所以这东西了解下就好。
资料:
上面的文章基本上把docker的命令以及一些日常使用的细节都过了一遍,也包含了类似镜像体积等优化内容。后面就会进入一些docker比较底层的东西了,类似于docker的网络模式、docker的磁盘模式等。
docker的网络子系统是可插拔式设计,其中可选的有:
bridge
:默认的网络驱动。如果不指定一个驱动的话,默认就是bridge模式。bridge模式通常应用在独立运行的容器相互之间需要通讯的应用场景host
:移除独立容器和Docker主机之间的网络隔离,并直接使用主机的网络。host模式仅针对高于Docker 17.06版本的swarm services可用overlay
:Overlay模式将多个Docker daemon连接起来,并启用swarm service来互通。你也可以使用overlay模式促进swarm service和独立容器之间的沟通,或是两个分别归属于不同Docker daemon的容器。这个策略简化了容器之间的互通,不再依赖于OS级别的路由macvlan
:Macvlan模式允许将一个MAC地址交付给一个容器,使得这个容器在你的网络中以一个硬件设备的身份出现。Docker daemon通过MAC地址将通讯路由到容器。macvlan模式通常用来处理需要直接连接到物理网络的遗留应用,而不需要路经Docker主机的网络栈none
:将某个容器禁用网络。通常与自定义的网络驱动结合使用。none驱动对swarm service不可用Third-party network plugins
:第三方网络插件,可自由安装使用。可以通过Docker Hub来安装,或通过第三方vendor安装选择建议:
User-defined bridge networks
:当相同Docker主机内的多个容器需要相互通讯的时候,这是最优解Host networks
:当容器的网络不应该与Docker主机隔离,且容器的其他方面应该与Docker主机隔离的情况下,这是最优解Overlay networks
:当需要运行在不同的Docker主机上的容器相互之间进行通讯,或当多个容器需要使用swarm service进行协同工作的时候,这是最优解Macvlan networks
:当从VM环境迁移到Docker环境或需要让容器看起来像物理主机的时候(每个容器都拥有一个唯一的MAC地址),这是最优解Third-party network plugins
:允许你将Docker整合到特殊的网络栈中通篇过一下之后会发现,基本上常用的场景,只要有bridge驱动就够了。后面会主要看下bridge驱动,其他的可以查看官方文档。
此外,有一篇比较老的文章,用中文举了点例子进行网络模式的说明,可以一读:Docker网络模式。
如果在创建容器的时候不指定网络设置的话,容器会使用默认的bridge驱动。而如果用户进行自定义bridge设置的话,使用的网络驱动就是稍微有点不同:User-defined bridge networks
。这里还是有不少区别的:Differences between user-defined bridges and the default bridge:
自定义bridge驱动使用:
$ docker network create my-net
$ docker network rm my-net
$ docker create --name my-nginx \
--network my-net \
--publish 8080:80 \
nginx:latest
$ docker network connect my-net my-nginx
$ docker network disconnect my-net my-nginx
官方文档里写清楚了,默认的bridge驱动已经属于遗留功能,不建议在生产环境上使用。
The default bridge network is considered a legacy detail of Docker and is not recommended for production use.
见文档:Use the default bridge network。
后面几种总的来说应用面都不大,在结合K8S使用的情况下(应该是大部分应用场景),只需要K8S即可。这里都可以略过。
如果只是照范例抄启动命令的话,一般来说总会有几个知识点理解不是很透彻,这个章节就仔细看下这些知识点。
每个容器在启动之后,都有其在设定的--network=xxx
下的固定IP地址(e.g 172.17.0.35)。因此不要在让容器上运行的应用程序监听Listener=127.0.0.1:xxx
这样的地址,否则其他服务将无法找到该应用。这和在本地运行应用程序是不一样的。
在本地开发和运行应用的时候,实际上所有的应用程序是部署在同一台物理机上的,因此回环地址可以正确找到应用。但使用容器的时候一般一个容器内只会有一个应用程序,会启动不同的几个容器让他们相互之间通讯,这时候回环地址就不会起效果了。
这是一个理解上的盲点,需要注意。
上面举的例子中,如果多个容器之间的应用程序需要相互访问,最好的监听地址配置方法是使用容器名
作为监听的host
。e.g Listener=node1:xxx
。容器只要启动在同一个network下,相互之间是可以通过容器名查找到的。
两者之间的区别很简单:
如果你的应用全部运行在同一个network下,那么就用expose就够了,如果有多个docker network,相互之间的访问需要publish port。
参见:
官方文档:Manage data in Docker。
任何在容器内创建的文件都会存储在容器的可写层内。这意味着:
data volumn
相比,后者直接向主机的文件系统写入数据Docker提供了选项来让容器在物理主机上存储文件,这样才能保证容器在停止之后文件仍旧存在:
Volumes
:会将数据写入到主机磁盘上的Docker指定
地点(/var/lib/docker/volumes/ on Linux),且这些文件是由Docker进程进行管理的,其他任何进程都不应该直接修改这些文件。Volumes是在Docker内存储数据的最佳选择Bind mounts
:可将数据写入到主机磁盘上的任何
地点,甚至可以是重要的系统文件,或文件夹。非Docker进程及Docker容器都可以在任何时候修改这些文件tmpfs mounts
:仅将数据保存在内存中,并永远不会存储到磁盘上更进一步的各种mount类型细节可以查阅官方文档:More details about mount types。这里做一下整理:
Volumns
docker volume prune
Bind mounts
官方还给了最佳使用范例:
几点tips:
在我看来:
其他进程有直接访问文件需求
的场景。volumes是docker进程自组织的,对外就是一个block文件,无法看到细节,其实不太友好,在docker之外的进程也需要对文件进行访问的时候就很不方便了,这种场景就需要bind mounts$ docker volume create my-vol
$ docker volume ls
$ docker volume inspect my-vol
$ docker volume rm my-vol # delete volume
$ docker volume prune # delete all
官方文档:Use volumes。
优势:
可以使用--volume
或--mount
来指定容器运行时需要mount的卷,使用上有点些微的不同:
$ docker run --volume \
$volume_name:$path_mounted_in_container:$option1,$option2,...
$ docker run --mount \
type=volume,source|src=$volume_name,destination|dst|target=$path_mounted_in_container,readonly,volume-opt=$key1=$val1,volume-opt=$key2=$val2,...
几点不常用但可能用得到的点:
官方文档:Use bind mounts。
可以使用--volume
或--mount
来指定容器运行时需要mount的卷,使用上有点些微的不同:
$ docker run --volume \
$path_on_host_tobe_bound:$path_mounted_in_container:$option1,$option2,...
$ docker run --mount \
type=bind,source|src=$path_on_host_tobe_bound,destination|dst|target=$path_mounted_in_container,readonly,bind-propagation=rprivate|private|rshared|shared|rslave|slave
对bind mounts来说--volume
和--mount
在使用上是有区别的:
--volume
:指定的主机位置不存在的话,会主动创建出来(总是创建成文件夹)--mount
:指定的主机位置不存在的话,不会主动创建,而是报错举个简单的例子:
$ cd /Users/XXX && mkdir ./docker && cd ./docker && touch ./dummy.txt && echo dummy > ./dummy.txt
$ docker run -it -d --rm -v /Users/XXX/Downloads/docker/:/dirinc --name bind_mount_test ubuntu:18.10
$ docker attach 8d11c4aad023
root@8d11c4aad023:/# ll /dirinc
...
-rw-r--r-- 1 root root 8 Apr 3 02:23 dummy.txt
root@8d11c4aad023:/# echo changed > /dirinc/dummy.txt
$ cat /Users/XXX/docker/dummy.txt
changed
几点不常用但可能用得到的点:
官方文档:Docker storage drivers。
这部分里会讲到的storage drivers
和之前的volumes是不同的东西,所谓的storage drivers
是指docker从镜像生成容器之后,在容器的最上面创建出来的那一层可写入层里,使用的文件处理策略(驱动)。
这部分不会太过展开,主要是因为:
因此实际上这块的实用性并不高。后面主要以认识storage drivers
为主。
Docker支持的storage drivers:
overlay2
:首选的驱动,对现行的所有Linux发行版本可用,且无需额外的配置aufs
:对 Docker 18.06 及更老旧的版本来说是首选驱动,或 Ubuntu 14.04 kernel 3.13 不支持 overlay2 的环境devicemapper
:受到支持,但在生产环境需要direct-lvm
,是因为loopback-lvm
虽然无需配置,但性能不是很好btrfs
and zfs
storage drivers are used if they are the backing filesystem (the filesystem of the host on which Docker is installed). These filesystems allow for advanced options, such as creating “snapshots”, but require more maintenance and setup. Each of these relies on the backing filesystem being configured correctly.vfs
storage driver is intended for testing purposes, and for situations where no copy-on-write filesystem can be used. Performance of this storage driver is poor, and is not generally recommended for production use.要查看各Linux发行版本支持的驱动,可以查看:Supported storage drivers per Linux distribution。
要查看各文件系统支持的驱动,可以查看:Supported backing filesystems。
后面还有很多各种驱动的细节,这里就不展开了,给出一些资料:
这块的话题有点大,本质上来说,Docker的资源限制利用的还是Linux本身的机制。即便撇开Docker,这些知识点也是值得一看的,但鉴于主题,这里就不多展开了。后续这个章节的主要目标是将Docker的一些限制手段以及后面的Linux原理相关的思路整理出来,并附上资料,不会过于深入。
实际上在真实场景使用的时候一般也会使用类似K8S这样的系统来进行集群管理,不太会直接在Docker这一层工具上对资源限制这块过多设置。
Linux中的namespace、cgroup、capabilities等核心概念,可以看下面几篇:
Docker官方对于资源限制也有不小的篇幅进行解说,在使用上有需要的时候可以查看:
其他资料:
Docker官方的监控,这块和上面的内容类似也比较鸡肋,一般来说不会直接裸用Docker,如果有K8S之类的,那监控也不会从Docker来了。所以这里就给点资料即可,有需要的可以深入下官方文档:
docker stats
是类似top的工具,可以对整体运行的容器有一个overall的把握在以容器为主的系统架构中,除了主机监控之外还需要监控容器,因为应用程序都是运行在容器中的。而且也有可能某些主机运行了多个容器,那么这些容器(应用程序)占用了多少资源就需要进行监控了。Google出品的cAdvisor基本上是最优解:
注意cAdvisor不仅仅只服务于Docker,也可以用在其他容器运行时上。
官方文档:Docker security。
老话重提,里面的知识点仍旧是上面提到过的Linux cgroup、capabilities等几个知识点。
此外,用户管理也是一个需要关注的点,Docker默认的用户是root,在某些情况下使用者有必要进行更换。参见:Docker creates files as root in mounted volume [duplicate]。
vm.max_map_count
需要改大,MAC下操作方法:Update max_map_count for ElasticSearch docker container Mac host
技术过硬的话,Alpine应该说是制作生产环境产品镜像的首选了,毕竟体积摆在那边,只有几兆的基本镜像可真的没几个好选的。
一些资料:
DNS问题算是Alpine被诟病得比较多的一点,可以看一个issue:DNS Issue #255。官方在github的文档上也给了说明,可以看下:caveats.md >> DNS,当然具体是否有这个情况以及如何解决,就要看实践了。
$ docker images
$ docker ps -a
$ docker system df -v
$ docker system prune
几个概念:
docker system prune 自动清理说明:
该指令默认会清除所有如下资源:
参见:
# 删除所有悬空镜像,但不会删除未使用镜像:
$ docker rmi $(docker images -f "dangling=true" -q)
# 删除所有未使用镜像和悬空镜像。
# 【说明】:轮询到还在被使用的镜像时,会有类似"image is being used by xxx container"的告警信息,所以相关镜像不会被删除,忽略即可。
$ docker rmi $(docker images -q)
如果通过 docker system df 分析,是卷占用了过高空间。则可以根据业务情况,评估相关卷的使用情况。对于未被任何容器调用的卷(-v 结果信息中,“LINKS” 显示为 0),可以使用如下指令手工清理:
# 删除所有未被任何容器关联引用的卷:
$ docker volume rm $(docker volume ls -f "dangling=true" -q)
# 也可以直接使用如下指令,删除所有未被任何容器关联引用的卷(但建议使用上面的方式)
# 【说明】轮询到还在使用的卷时,会有类似"volume is in use"的告警信息,所以相关卷不会被删除,忽略即可。
$ docker volume rm $(docker volume ls -q)
见过好几个日志文件膨胀到把主机磁盘吃光的情况,所以这方面还是要小心处理。
官方文档:
在docker run命令中修改日志输出driver,以及限定最大日志空间占用:
$ docker run \
--log-driver json-file --log-opt max-size=10m \
alpine echo hello world
更多的可以查看官方文档。
注意下面的例子中双花括号的转译符需要自行去除:
# 只获取名字
$ docker network inspect -f '\{\{ range $key, $value := .Containers \}\}\{\{ printf "%s\n" $value.Name \}\}\{\{ end \}\}' ${网络名}
# 观察打印出来的结构中的 Containers 部分,可以获得更详细的信息
$ docker network inspect ${网络名}
注意下面的例子中双花括号的转译符需要自行去除:
# 只获取名字
$ docker inspect -f '\{\{ range $key, $value := .NetworkSettings.Networks \}\}\{\{ printf "%s\n" $key \}\}\{\{ end \}\}' ${容器名}
# 观察打印出来的结构中的 Networks 部分,可以获得更详细的信息
$ docker inspect ${容器名}
注意下面的例子中双花括号的转译符需要自行去除:
$ docker stats # 默认一直刷新,类似top命令
$ docker stats --no-stream # 不刷新,只获取一次
$ docker stats --no-stream ${容器名} # 获取指定的容器状态
$ docker stats --format "table \{\{.Name\}\}\t\{\{.CPUPerc\}\}\t\{\{.MemUsage\}\}" # 格式化输出
# JSON格式输出
$ docker stats --no-stream --format \
"{\"container\":\"\{\{ .Container \}\}\",\"memory\":{\"raw\":\"\{\{ .MemUsage \}\}\",\"percent\":\"\{\{ .MemPerc \}\}\"},\"cpu\":\"\{\{ .CPUPerc \}\}\"}"
参见:
如今在集群解决方案方面执牛耳的无疑是Kubernetes以及Mesh解决方案Istio,但docker自身也具备集群管理和部署的能力。在Docker中这被称为Swarm,具体使用上来说:
相关官方文档:
Swarm集群使用的网络不再是简单的bridge类型的driver,而是overlay类型的driver。如果仅只是在单一一台物理机上部署docker container,并能够让他们相互感知并进行通讯的话,只需要使用bridge类型的driver network,而这样的网络类型是不能让两台相互物理隔离的服务器上的container相互通讯的。要做到在swarm集群内各容器相互之间能够进行通讯,则必须使用overlay类型的driver的network。
有作者做了一个系列的博文,虽然细节上来说并不算特别好,但看看还行了:Docker Swarm 系列教程。最主要是三篇:
我这里也做了点实践进行试验:dist-system-practice/experiment/swarm/。
下面一步步进行解释。
虚拟机
docker-machine可以使用远程物理host进行docker环境部署和操控,也可以在本地进行虚拟机的制作,并进行控制和部署。虚拟机需要单独下载并安装virtualbox
。
MAC下安装:
$ brew cask install virtualbox
此外需要下载virtualbox的镜像,并放到对应的文件夹下,下载releases:boot2docker/boot2docker。下载完成后放到:~/.docker/machine/cache
。
虚拟机的创建,driver类型为virtualbox
:
$ docker-machine create -d virtualbox --help # 帮助选项查看
$ docker-machine create -d virtualbox \
--virtualbox-boot2docker-url ~/.docker/machine/cache/boot2docker.iso \
--virtualbox-hostonly-cidr "192.168.99.1/24" \
--engine-opt dns=192.168.99.1 \
host1
Running pre-create checks...
(host1) Boot2Docker URL was explicitly set to "/Users/xxx/.docker/machine/cache/boot2docker.iso" at create time, so Docker Machine cannot upgrade this machine to the latest version.
Creating machine...
(host1) Boot2Docker URL was explicitly set to "/Users/xxx/.docker/machine/cache/boot2docker.iso" at create time, so Docker Machine cannot upgrade this machine to the latest version.
(host1) Downloading /Users/xxx/.docker/machine/cache/boot2docker.iso from /Users/xxx/.docker/machine/cache/boot2docker.iso...
(host1) Creating VirtualBox VM...
(host1) Creating SSH key...
(host1) Starting the VM...
(host1) Check network to re-create if needed...
(host1) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env host1
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
host1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.2
host2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.2
远程host
远程物理host创建,driver类型为generic
(文档:Generic)。如果要创建某些受官方支持的云服务的远程环境,可以参考:Machine drivers。如果创建的只是普通的远程host实例,则选择driver为generic
即可。
$ docker-machine create -d generic --help # 帮助选项查看
$ docker-machine create -d generic \
--generic-ip-address=x.x.x.x \
--generic-ssh-port 22 \
--generic-ssh-key ~/.ssh/id_rsa \
--generic-ssh-user root \
vultr
Running pre-create checks...
Creating machine...
(vultr) No SSH key specified. Assuming an existing key at the default location.
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Error creating machine: Error checking the host: Error checking and/or regenerating the certs: There was an error validating certificates for host "x.x.x.x:2376": tls: DialWithDialer timed out
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which might stop running containers.
$ docker-machine regenerate-certs vultr
Regenerate TLS machine certs? Warning: this is irreversible. (y/n): y
Regenerating TLS certificates
Waiting for SSH to be available...
Detecting the provisioner...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
vultr - generic Running tcp://x.x.x.x:2376 v18.09.6
其实主要是在本地控制记录中将对象删除:
$ docker-machine rm vultr
About to remove vultr
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed vultr
当远程实例创建完成之后,在管理者机器上形成记录,并能够远程进行命令的执行。远程命令执行有两种方法:
ssh
使用docker-machine ssh $name "$command"
的方式可以执行远程命令,如果使用docker-machine ssh $name
则可以直接登录到远程机器环境。
env
ssh方法可以做很多事情,但打命令会比较繁琐,毕竟每条命令都需要带上docker-machine前缀。另一种方法则是使用eval "$(docker-machine env $name)"
命令,来将当前会话窗口的执行环境切换到$name
机器,即当上述命令执行完毕后,当前会话内的所有命令都是在$name
机器上执行的,而不是管理者机器。
如果要退出远程机器的会话,则需要执行:eval "$(docker-machine env -u)"
。
Swarm集群由一个Manager节点,多个备用Manager节点,以及大量Worker节点组成。官方文档:docker swarm。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
3fff9l59dvn5jvot2s27gf9n2 * ManagerX Ready Active Leader
4byjmtcm1ag8qffxjwnhwsf4l WorkerA Ready Active
sks1qb0zqlaetmpsqfj5tfx56 WorkerB Ready Active
Manager状态正常为Leader
,备选为Reachable
。Worker节点正常状态为Active
。
创建Swarm集群
记住,所有的Swarm集群操作都必须要到远程机器上执行,而不是在管理者机器上执行。也就是说必须使用docker-machine ssh ...
或eval "$(docker-machine env ...)"
来操作。
如果要创建一个Swarm集群,需要到被选为Manager的节点上:
$ docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (lvfnka9mz92699ke3j189dznd) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-4zc49exg8u9vh9nsasua10xzu2a5qaxahmgblxkejltw6v6mt0-aadqrk196sgi97z4nkn6eagjl 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
后面的IP地址为被选为Manager的机器的自机IP。
添加Worker节点
要向一个只有Manager的Swarm集群添加Worker节点:
$ docker swarm join --token SWMTKN-1-4zc49exg8u9vh9nsasua10xzu2a5qaxahmgblxkejltw6v6mt0-aadqrk196sgi97z4nkn6eagjl 192.168.99.100:2377
This node joined a swarm as a worker.
这句命令在刚才Manager创建Swarm集群的时候已经被打印出来了,照做即可。不要忘记该命令必须到被要求加入Swarm集群的Worker节点上执行。
离开集群
如果需要将某个节点剔除出集群,则需要到该节点上执行:
$ docker swarm leave --force
Node left the swarm.
Manager节点必须要带上--force
option,Worker节点则不需要。
在Swarm集群中,一个容器被称为服务 Service
,实际上还是个容器,只不过叫法不一样而已。官方文档:docker service。
创建服务
$ docker service create \
--name memcached \
--network dist_net \
--replicas 1 \
-p 11211:11211 \
--constraint node.hostname==host1 \
memcached:1.5.14-alpine \
-l 0.0.0.0 \
-p 11211 \
-m 64
uezlg3xa9ku412l4la8i8tue7
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
大部分的option和使用docker run的时候没有差别,这里需要注意的是:
constraint
结合使用,指定部署的节点范围node.hostname
指定的是被命令的单个节点,该服务只允许部署到这个节点上停止服务
$ docker service rm memcached
memcached
其他命令
其他服务相关命令可以查看:
$ docker service --help
类似于docker service就是docker run的Swarm版本,stack可以理解为Swarm版本的compose。一样需要指定一个compose.yaml配置文件,然后开始stack的部署。这里需要注意,一般compose的部分选项在Swarm的stack中是不可用的,当然大部分都是通用的。
启动stack
$ docker stack deploy ./cluster.yaml dist
Creating network dist_net
Creating service dist_memcache_admin
Creating service dist_memcached
停止stack
$ docker stack rm dist
Removing service dist_memcache_admin
Removing service dist_memcached
Removing network dist_net
查看所有远程主机
该命令需要在管理者机器上执行,不是远程主机,注意。
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
host1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.2
host2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.2
查看Swarm集群所有节点
需要到Swarm集群Manager节点上执行命令。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
z70e57nrkfpbcs33j5pyzzaqi * host1 Ready Active Leader 18.09.2
x2f6tereq1btochykl18lrrtv host2 Ready Active 18.09.2
查看Swarm单个节点细节
需要到对应节点上执行命令。
$ docker node inspect host1 --pretty
ID: un1mkdvkh814oehap89wna4zw
Hostname: host1
Joined at: 2019-06-24 08:04:35.836543206 +0000 utc
Status:
State: Ready
Availability: Active
Address: 192.168.99.116
Manager Status:
Address: 192.168.99.116:2377
Raft Status: Reachable
Leader: Yes
Platform:
Operating System: linux
Architecture: x86_64
Resources:
CPUs: 1
Memory: 989.4MiB
Plugins:
Log: awslogs, fluentd, gcplogs, gelf, journald, json-file, local, logentries, splunk, syslog
Network: bridge, host, macvlan, null, overlay
Volume: local
Engine Version: 18.09.2
查看Swarm集群所有服务
需要到Swarm集群Manager节点上执行命令。
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
b3ozvdgrgudv memcache_admin replicated 1/1 plopix/docker-memcacheadmin:latest *:9083->9083/tcp
yujhl39733lg memcached replicated 1/1 memcached:1.5.14-alpine *:11211->11211/tcp
查看Swarm集群单个服务
需要到Swarm集群Manager节点上执行命令。
$ docker service ps memcached
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
oe41a6ioom9r memcached.1 memcached:1.5.14-alpine host1 Running Running about a minute ago
$ docker service ps memcache_admin
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
w9y6zhukra0w memcache_admin.1 plopix/docker-memcacheadmin:latest host2 Running
查看Swarm集群Stack细节
需要到Swarm集群Manager节点上执行命令。
$ docker stack ps dist
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kscj4niatj9n dist_memcached.1 memcached:1.5.14-alpine host1 Running Running 1 second ago
4hcwv6ni3elv dist_memcache_admin.1 plopix/docker-memcacheadmin:latest host2 Running Running 31 seconds ago
查看Swarm单个服务的日志
需要到Swarm集群Manager节点上执行命令。服务日志其实就是容器日志。
$ docker service logs $name
从compose.yaml进行docker stack deploy出来的服务总是会遇到:getaddrinfo(): name does not resolve
,启动的服务无法解析域名,一直都没有查到问题。
一开始以为是dns问题:
然后根据stackoverflow的讨论进行machine create的options设定,发现--engine-opt dns=8.8.8.8
会被docker-machine create忽略:
cat ~/.docker/machine/machines/cache/config.json
"HostOptions": {
"Driver": "",
"Memory": 0,
"Disk": 0,
"EngineOptions": {
"ArbitraryFlags": [
"dns=192.168.99.1"
],
"Dns": null,
...
Dns选项为null,而要求的内容则出现在了EngineOptions > ArbitraryFlags里。
最后抛开DNS不管,直接不使用compose配置文件,也不使用docker stack deploy命令,直接使用docker service create命令,和compose配置文件中一模一样的option却发现启动成功。估计是stack命令内部实现有点问题。
Swarm因为与K8S竞争失败,最近已经很久听不到消息了,官方的资料以及社区的讨论都是非常老的资料,有问题什么都查不到。此外,很简单的一个容器服务,在Swarm集群中以Service启动非常非常慢,好几十秒才把很简单的一个memcached启动起来。实际使用如果也是这种性能的话是完全不能接受的。
总之,Swarm的现状来看,完全不适合使用在生产环境。应用风险非常之高。
在很多docker的文档里都提到了docker host
这个概念。至于什么是docker host,可以参见链接:Clarify what is the host。
I think you pretty much found out the answer by yourself. In that diagram you mention a ‘docker host’ is any machine that is running the Docker daemon. This means that you’re either running Docker natively in your operating system, or a virtual machine that has Docker installed.
Docker machine is basically the second option. When you do a docker-machine create it creates a new virtual machine with Docker already installed. This means that to access any containers running on this machine you’ll have to use the IP address of the virtual machine. Also, to give Docker access to a file you’ll either have to copy it to the VM that’s running Docker, or share a directory between your OS and the VM that’s running Docker.
在MAC上使用docker,如果放一些有真实负载的container,一般都会遇到这个问题(甚至是没什么负载,只是搭建环境都会)。
官方论坛有一个issue:High CPU Utilization of Hyperkit in Mac #1759,到现在还是open状态
,且看起来解决遥遥无期。
issue中似乎有用的只有改大max open file选项这条意见,具体操作可以参见:Maximum limits (macOS etc.)。
不确定是否一定能起效果,应该是对部分情况有效的。但对我来说貌似是没用,我的ulimit已经是:
$ launchctl limit maxfiles
# maxfiles 65536 65536
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 18.09.2
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 9754871865f7fe2f4e74d43e2fc7ccd237edcbce
runc version: 09c8266bf2fcf9519a651b04ae54c967b9ab86ec
init version: fec3683
Security Options:
seccomp
Profile: default
Kernel Version: 4.9.125-linuxkit
Operating System: Docker for Mac
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 1.952GiB
Name: linuxkit-025000000001
ID: CNVU:5KZS:A2M7:WY5W:NUEW:KPW3:WXOA:IH2Q:EBAN:LP7C:3EQR:36U4
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): true
File Descriptors: 24
Goroutines: 50
System Time: 2019-03-27T06:19:11.1044801Z
EventsListeners: 2
HTTP Proxy: gateway.docker.internal:3128
HTTPS Proxy: gateway.docker.internal:3129
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Product License: Community Engine
EOF