docker in all
docker vs hyper-v,vmware,xen,kvm
docker host, docker container, docker engineen, docker image
images = stopped container
container = running images
docker操作示意圖
workflow
开始使用docker(以windows下为例)
PS G:\dockerdata> docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
PS G:\dockerdata> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest fce289e99eb9 2 months ago 1.84kB
docker4w/nsenter-dockerd latest 2f1c802f322f 4 months ago 187kB
以上docker run hello-world命令本质上的执行过程:
1. docker client向docker daemon(engine)联络,告诉docker engine,请帮我运行一个hello-wold container
2. docker daemon(engine)收到该命令后先在本地查找是否有hello-world这个image,如果没有则从regisry查找并且pull下来
3. docker daemon以该image实例化一个container,并且运行该image定义的executable,而这个executable将产生output;
4. docker daemon streamed that output to the docker client,这样我们就看到了hello world的消息
docker image到底包含了什么?
强烈建议: https://www.csdn.net/article/2015-08-21/2825511
我们知道linux系统由内核+发行版组成,同样的内核比如3.8之上,我们可以有debian, ubuntu, centos等不同的发行版本。类似地,Docker镜像就是类似于“ubuntu操作系统发行版”,可 以在任何满足要求的Linux内核之上运行。简单一点有“Debian操作系统发行版”Docker镜像、“Ubuntu操作系统发行版”Docker镜 像;如果在Debian镜像中安装MySQL 5.6,那我们可以将其命名为Mysql:5.6镜像;如果在Debian镜像中安装有Golang 1.3,那我们可以将其命名为golang:1.3镜像;以此类推,大家可以根据自己安装的软件,得到任何自己想要的镜像。
修改默认pull image存放位置
在windows下本质上docker engine是工作在hyper-v虚拟机中,所有的docker客户端敲的命令在该虚拟机中运行,pull的image也放在该虚拟机中,因此我们要修改image保存的位置实际上只要修改hyper-v的MobyLinuxVM对应的vhdx文件的位置即可。
http://www.cnblogs.com/show668/p/5341283.html
docker ps/docker images
PS G:\dockerdata> docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 2 months ago 1.84kB docker4w/nsenter-dockerd latest 2f1c802f322f 4 months ago 187kB PS G:\dockerdata> docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES PS G:\dockerdata> docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 135da1372a06 hello-world "/hello" 24 minutes ago Exited (0) 24 minutes ago modest_spence
pull特定版本的image
docker pull ubuntu:14.04
Repository:是对一个docker image的存储定义
将docker hub mirror配置为阿里云加速器
删除本地的image
PS G:\dockerdata> docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 47b19964fb50 3 weeks ago 88.1MB alpine latest caf27325b298 4 weeks ago 5.53MB hello-world latest fce289e99eb9 2 months ago 1.84kB docker4w/nsenter-dockerd latest 2f1c802f322f 4 months ago 187kB PS G:\dockerdata> docker rmi ubuntu Untagged: ubuntu:latest Untagged: ubuntu@sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210 Deleted: sha256:47b19964fb500f3158ae57f20d16d8784cc4af37c52c49d3b4f5bc5eede49541 Deleted: sha256:d4c69838355b876cd3eb0d92b4ef27b1839f5b094a4eb1ad2a1d747dd5d6088f Deleted: sha256:1c29a32189d8f2738d0d99378dc0912c9f9d289b52fb698bdd6c1c8cd7a33727 Deleted: sha256:d801a12f6af7beff367268f99607376584d8b2da656dcd8656973b7ad9779ab4 Deleted: sha256:bebe7ce6215aee349bee5d67222abeb5c5a834bbeaa2f2f5d05363d9fd68db41
docker run detached mode启动一个web服务
PS G:\dockerdata> docker run -d --name web -p 9090:8080 nigelpoulton/pluralsight-docker-ci Unable to find image 'nigelpoulton/pluralsight-docker-ci:latest' locally latest: Pulling from nigelpoulton/pluralsight-docker-ci a3ed95caeb02: Pull complete 3b231ed5aa2f: Pull complete 7e4f9cd54d46: Pull complete 929432235e51: Pull complete 6899ef41c594: Pull complete 0b38fccd0dab: Pull complete Digest: sha256:7a6b0125fe7893e70dc63b2c42ad779e5866c6d2779ceb9b12a28e2c38bd8d3d Status: Downloaded newer image for nigelpoulton/pluralsight-docker-ci:latest 27b4bc07a3e299e738ea8fc05bb6de9fa160c192a5ab71886b84e432d5422aea #这就是docker host主机上面的container id
PS G:\dockerdata> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
27b4bc07a3e2 nigelpoulton/pluralsight-docker-ci "/bin/sh -c 'cd /src…" 4 minutes ago Up 4 minutes 0.0.0.0:9090->8080/tcp web
上面的命令执行后将在docker host主机上启动一个web服务器,使用http://localhost:9090就可以直接访问到该container的服务了!!
启动一个container并且在该container中执行bash
PS G:\dockerdata> docker run -it --name temp ubuntu:latest /bin/bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 6cf436f81810: Pull complete 987088a85b96: Pull complete b4624b3efe06: Pull complete d42beb8ded59: Pull complete Digest: sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210 Status: Downloaded newer image for ubuntu:latest root@9b4970dcb02a:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
简单批量维护命令:
PS G:\dockerdata> docker ps -aq 9b4970dcb02a 27b4bc07a3e2 135da1372a06 PS G:\dockerdata> docker stop $(docker ps -aq) 9b4970dcb02a 27b4bc07a3e2 135da1372a06
swarm:
一群docker engines加入一个cluster分组就被称为swarm, a cluster = a swarm
swarm里面的engine工作于swarm mode
manager nodes维护swarm,
worker nodes 执行manager nodes分发过来的tasks
services: declarative/scalable
tasks: assigned to worker nodes ,means ~ containers currently
docker swarm init --advertise-addr xxx:2377 --listen-addr xxx:2377 # engine port 2375, secure engine port: 2376, swarm port: 2377
docker service create --name web-fe --replicas 5 ...
Container
container is isolated area of an OS with resource usage limits applied.
它由name space和control group(限定cpu,ram,networking吞吐量,io吞吐量)约束形成的独立运行环境。
engine
engine通过外部api接受命令负责屏蔽OS的namespace及cgroup,并且创建对应的container运行于host环境中
不同module协同工作实现的container运行过程
一旦container被启动运行后,containerd和它之间就可以没有了关系,以后可以通过发现过程来取得新的联系
image
image包含app运行所需的
1.OS Files library, objects;
2. app files
3. manifest-->定义这些文件是如何组织在一起工作的
image是层叠结构的文件系统.
docker image pull redis的工作分两步:第一步从registry这里获取到manifest文件;第二步pull layers
docker history redis # 罗列出所有能够创建redis这个image的命令列表
$ docker image inspect redis [ { "Id": "sha256:0f55cf3661e92cc44014f9d93e6f7cbd2a59b7220a26edcdb0828289cf6a361f", "RepoTags": [ "redis:latest" ], "RepoDigests": [ "redis@sha256:dd5b84ce536dffdcab79024f4df5485d010affa09e6c399b215e199a0dca38c4" ], "Parent": "", "Comment": "", "Created": "2019-02-06T09:02:43.375297494Z", "Container": "1abd8103d4a4423fa8339aabdb3442026bf6b8e9dca21c4ed44973e73ffd90cf", "ContainerConfig": { "Hostname": "1abd8103d4a4", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "6379/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "GOSU_VERSION=1.10", "REDIS_VERSION=5.0.3", "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.3.tar.gz", "REDIS_DOWNLOAD_SHA=e290b4ddf817b26254a74d5d564095b11f9cd20d8f165459efa53eb63cd93e02" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"redis-server\"]" ], "ArgsEscaped": true, "Image": "sha256:68d73e8c5e2090bf28a588569b92595ab2d60e38eb92ba968be552b496eb6ed3", "Volumes": { "/data": {} }, "WorkingDir": "/data", "Entrypoint": [ "docker-entrypoint.sh" ], "OnBuild": null, "Labels": {} }, "DockerVersion": "18.06.1-ce", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "6379/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "GOSU_VERSION=1.10", "REDIS_VERSION=5.0.3", "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.3.tar.gz", "REDIS_DOWNLOAD_SHA=e290b4ddf817b26254a74d5d564095b11f9cd20d8f165459efa53eb63cd93e02" ], "Cmd": [ "redis-server" ], "ArgsEscaped": true, "Image": "sha256:68d73e8c5e2090bf28a588569b92595ab2d60e38eb92ba968be552b496eb6ed3", "Volumes": { "/data": {} }, "WorkingDir": "/data", "Entrypoint": [ "docker-entrypoint.sh" ], "OnBuild": null, "Labels": null }, "Architecture": "amd64", "Os": "linux", "Size": 94993858, "VirtualSize": 94993858, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/1aeb385f6b9def8e0c2048213c6a68446b233f4d44c9230657859257505dace5/diff:/var/lib/docker/overlay2/5e8dc35e2ed45cee79a8b5108cc74bfe7000311e75db45bd83d254f21e1892e7/diff:/var/lib/docker/overlay2/bfb61b0335946076ea36f25716da9e43d133dd6e8cf0211e7abadb6a23c001f3/diff:/var/lib/docker/overlay2/591b4074f127d18d3b7d84078891e464eb9c808439bd70f78f653ece9fa1101e/diff:/var/lib/docker/overlay2/30c283b2c4910e51dc162b23d6344575697e9fb478aeccf330edcef05c90aeae/diff", "MergedDir": "/var/lib/docker/overlay2/358068125c47e5995e7b1308b71a7ba11dd1509a9a69b36c1495e5c23a5c71f0/merged", "UpperDir": "/var/lib/docker/overlay2/358068125c47e5995e7b1308b71a7ba11dd1509a9a69b36c1495e5c23a5c71f0/diff", "WorkDir": "/var/lib/docker/overlay2/358068125c47e5995e7b1308b71a7ba11dd1509a9a69b36c1495e5c23a5c71f0/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:0a07e81f5da36e4cd6c89d9bc3af643345e56bb2ed74cc8772e42ec0d393aee3", "sha256:943fb767d8100f2c44a54abbdde4bf2c0f6340da71125f4ef73ad2db7007841d", "sha256:16d37f04beb4896e44557df69c060fc93e1486391c4c3dedf3c6ebd773098d90", "sha256:5e1afad325f9c970c66dcc5db47d19f034691f29492bf2fe83b7fec680a9d122", "sha256:d98df0140af1ee738e8987862268e80074503ab33212f6ebe253195b0f461a43", "sha256:b437bb5668d3cd5424015d7b7aefc99332c4af3530b17367e6d9d067ce9bb6d5" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ]
docker支持的网络模式
bridge模式: -net = bridge
这是默认网络,docker engine一旦启动后就会在宿主host上创建一个docker0的网桥(可以理解为switch),默认创建的容器都是添加到该网桥(switch)的网段中,可以想象这些容器就是连接在一个交换机的不同网口上,他们的网关就是docker0的ip(172.17.0.1)
host模式: -net = host
容器不会获得独立的network namespace,而是与宿主host主机共用一个,这也意味着container不会拥有自己的网卡信息,而是使用宿主机的。host模式的容器之间除了网络,其他都是隔离的。
none模式: -net = none
容器将获取独立的network namespace,但是不会为容器进行任何网络配置,需要我们自己去手工配置
container模式: -net = container:Name/ID
这种模式创建的容器将与指定的容器使用同一个network namespace,具有同样的网络配置信息,这种容器之间除了网络,其他都是隔离的。
自定义网络模式:
与默认的bridge原理一样,但自定义网络内部具备dns发现的能力,可以通过容器名或者主机名容器之间网络通信
docker logs通过查看容器log来定位调试问题
默认情况下docker logs和ldocker service logs命令显示命令执行的输出,就像是你在命令行直接执行该程序时的情形一样。unix和linux程序往往会打开三个I/O Streams,分别称为STDIN,STDOUT,STDERR。其中stdin是命令的input stream, 可以包含从键盘获得的input或者从其他命令的输出作为input; stdout是应用程序的normal output.而stderr则被用于错误信息输出。默认情况下,docker logs将显示命令的stdout和stderr输出。基于以上信息,在多重场景下docker logs无法提供有效的log:
1. 如果你使用了一个logging driver(logging driver是docker提供的从运行的container或者service中获取有用信息的机制)将log发往一个文件,或者一个外部的主机,一个数据库或者其他的logging back-end,那么docker logs将不会显示任何有用的信息;
https://docs.docker.com/config/containers/logging/configure/
docker daemon有一个默认的logging driver,每个启动的容器都将使用它除非你配置了使用一个不同的logging driver.
比如,我们可以配置docker daemon使用syslog来做log的收集,他就会通过syslog将运行容器的stdout,stderr信息实时打印到远程服务器。在这种情况下,我们实际上就不可能使用docker logs来查看运行时的状态,而只能通过syslog服务器来获取信息;
2. 如果我们的image运行在non-interactive 进程中,比如web server或者database的进程,这种进程会将其输出信息直接送往log文件,而不是stdout或者stderr.
在这种情况下,我们一方面可以进入容器来查看类似nginx和myql的log文件获取运行时信息;另外一方面官方的nginx,httpd都提供了workaround方式,比如nginx image的构建中通过创建一个符号连接将 /var/log/nginx/access.log指向到/dev/stdout; 将/var/log/nginx/error.log指向到/dev/stderr的方式来解决。 httpd image则默认输出到/proc/self/fd/1 (stdout), error则将写往 /proc/self/fd/2(stderr)
这样我们依然可以通过docker logs -tail 8 -f来实时查看log
docker networking
https://success.docker.com/article/networking
建议使用自定义网络,docker默认的docker0 bridge支持--link参数,但是--link参数将来也会废弃。
$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242bd712cd8 no br-9694b511a9af 8000.0242e7c72a3d no br-81195db0babc 8000.0242d6feb257 no veth375600f vethbc86c59 br-c301fa0c30d5 8000.024241d93a8e no veth73040a3 veth72eebce vethd5af9cd veth12d8ab4 veth6d89a9d
咱们来看一下使用laradock docker-compose up -d nginx mysql之后的网络拓补图分析过程:
$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242bd712cd8 no br-9694b511a9af 8000.0242e7c72a3d no br-81195db0babc 8000.0242d6feb257 no veth375600f vethbc86c59 br-c301fa0c30d5 8000.024241d93a8e no veth73040a3 veth72eebce vethd5af9cd veth12d8ab4 veth6d89a9d [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 25dd9253f860 laradock_nginx "/bin/bash /opt/star…" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp laradock_nginx_1 a2070a01035c laradock_php-fpm "docker-php-entrypoi…" 2 hours ago Up 2 hours 9000/tcp laradock_php-fpm_1 d1f9327cb61c laradock_workspace "/sbin/my_init" 2 hours ago Up 2 hours 0.0.0.0:2222->22/tcp laradock_workspace_1 a70f2b180a0d laradock_mysql "docker-entrypoint.s…" 2 hours ago Up 2 hours 0.0.0.0:3306->3306/tcp, 33060/tcp laradock_mysql_1 01f438a6efa9 docker:dind "dockerd-entrypoint.…" 2 hours ago Up 2 hours 2375/tcp laradock_docker-in-docker_1 [node1] (local) root@192.168.0.13 ~/apiato/laradock $ [node1] (local) root@192.168.0.13 ~/apiato/laradock $ [node1] (local) root@192.168.0.13 ~/apiato/laradock $ [node1] (local) root@192.168.0.13 ~/apiato/laradock $ [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network ls NETWORK ID NAME DRIVER SCOPE 60e8d0d3dd8c bridge bridge local 5130e0e1e134 host host local c301fa0c30d5 laradock_backend bridge local 9694b511a9af laradock_default bridge local 81195db0babc laradock_frontend bridge local cb098f68c7be none null local [node1] (local) root@192.168.0.13 ~/apiato/laradock $ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242bd712cd8 no br-9694b511a9af 8000.0242e7c72a3d no br-81195db0babc 8000.0242d6feb257 no veth375600f vethbc86c59 br-c301fa0c30d5 8000.024241d93a8e no veth73040a3 veth72eebce vethd5af9cd veth12d8ab4 veth6d89a9d [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network inspect c301 [ { "Name": "laradock_backend", "Id": "c301fa0c30d5f44e8daab0ffecf8166012f63edee764ce2abeaf3e884ce54446", "Created": "2019-03-13T12:25:42.645372888Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.21.0.0/16", "Gateway": "172.21.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "01f438a6efa996b4e5c8df8f36b742ae468bf09762a1e6eabdefd66f5c920e11": { "Name": "laradock_docker-in-docker_1", "EndpointID": "d01c244fc579cd288bf8b1e79a6e936486b348f3167db3e7034044e08beae44c", "MacAddress": "02:42:ac:15:00:02", "IPv4Address": "172.21.0.2/16", "IPv6Address": "" }, "25dd9253f860588321b1ff05ae4b43226ae6c22f83044973b86c0c57871ed924": { "Name": "laradock_nginx_1", "EndpointID": "24b527973345960c10bf2f97a11612c33562a5146732e9c4049625fc99cadca8", "MacAddress": "02:42:ac:15:00:06", "IPv4Address": "172.21.0.6/16", "IPv6Address": "" }, "a2070a01035cbd8c15005c074e9e19ea18f795cdf6a2bc48863d86cc638b35b5": { "Name": "laradock_php-fpm_1", "EndpointID": "b3071a2d3d019a6e10b0b778ce0b4f99efbaff28898d295d3829d41e840aa15c", "MacAddress": "02:42:ac:15:00:05", "IPv4Address": "172.21.0.5/16", "IPv6Address": "" }, "a70f2b180a0dfcc18c26e4991897946b9389b678ce4ea2cd6527859c301bb78e": { "Name": "laradock_mysql_1", "EndpointID": "815e801431b16f4a245b0a243e08cc9642482b3933b09480928ae40fadd56b14", "MacAddress": "02:42:ac:15:00:03", "IPv4Address": "172.21.0.3/16", "IPv6Address": "" }, "d1f9327cb61cbd26f43c55911cbffa1cd3f53b912f783725bbf73e0c6edad5ef": { "Name": "laradock_workspace_1", "EndpointID": "5bbe5ceae7d15ff3eb65236ab0243619591d69474f3a0a13df07e507d2e25a22", "MacAddress": "02:42:ac:15:00:04", "IPv4Address": "172.21.0.4/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "backend", "com.docker.compose.project": "laradock", "com.docker.compose.version": "1.23.2" } } ] [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network inspect 8119 [ { "Name": "laradock_frontend", "Id": "81195db0babc4aff1b4ae09b2ad078038b74643c798b396409a46f2948ff89c8", "Created": "2019-03-13T12:25:42.057604176Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.20.0.0/16", "Gateway": "172.20.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "25dd9253f860588321b1ff05ae4b43226ae6c22f83044973b86c0c57871ed924": { "Name": "laradock_nginx_1", "EndpointID": "e1ad08b19608cc3884a9da04e509a71566ca4847245db12310d77463bcb80814", "MacAddress": "02:42:ac:14:00:03", "IPv4Address": "172.20.0.3/16", "IPv6Address": "" }, "d1f9327cb61cbd26f43c55911cbffa1cd3f53b912f783725bbf73e0c6edad5ef": { "Name": "laradock_workspace_1", "EndpointID": "64d65215f6e0d6135bb7dbf5f341bd858972bc8e869cd8a177991d27d5652491", "MacAddress": "02:42:ac:14:00:02", "IPv4Address": "172.20.0.2/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "frontend", "com.docker.compose.project": "laradock", "com.docker.compose.version": "1.23.2" } } ] [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network inspect 9694 [ { "Name": "laradock_default", "Id": "9694b511a9afac9a43d3b45ae4296976bf193633148465141f5e0cd787b12082", "Created": "2019-03-13T12:25:41.924774946Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "laradock", "com.docker.compose.version": "1.23.2" } } ] [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network inspect 5130 [ { "Name": "host", "Id": "5130e0e1e1340fb58d5704528257cfb0f7dc98e9f718055c3e32f96705355597", "Created": "2019-03-13T12:23:30.472608001Z", "Scope": "local", "Driver": "host", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} } ] [node1] (local) root@192.168.0.13 ~/apiato/laradock $ docker network inspect 60e8 [ { "Name": "bridge", "Id": "60e8d0d3dd8c376a31a802f9965227301dc06a74910852895f9b010d07fd4417", "Created": "2019-03-13T12:23:30.540268336Z", "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": {}, "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": {} } ]
关于环境变量env
https://vsupalov.com/docker-arg-env-variable-guide/
关于volumes
https://docs.docker.com/storage/volumes/
如果我们不需要永久持久化,但是又需要在运行时保存一些状态信息,可以考虑使用tmpfs mount直接mount到内存中,加快速度。
容器中进程启动的两种模式:shell模式和exec模式
docker容器内启动的所有进程全部都是宿主机上的独立进程;另外,该进程是不是docker容器进程本身(即:1号进程)取决于dockerfile的写法。
在ENTRYPOINT和CMD命令中,有两种不同的进程执行方式:shell和exec.
1.在shell方式中,CMD/ENTRYPOINT指令如下方式定义
CMD executable param1 param2
此时PID=1的进程为/bin/sh -c "executable param1 param2",真正的executable工作进程是其子进程
2.在exec方式中,CMD/ENTRYPOINT指令则如下方式定义:
CMD ["executable", "param1","param2"]
此时PID=1的进程直接是工作进程executable param1 param2
这两种启动模式还带来进程退出机制的区别,如果使用不当会造成僵尸进程。
docker提供了docker stop和docker kill两个命令向1号进程发送信号。当执行docker stop时,docker会先想PID1的进程发送一个SIGTERM信号,如果容器收到该信号后没有结束进程,则docker daemon会在等待10秒后发送SIGKILL信号,将容器进程杀死(PID1)并变为退出状态。
PID1的进程必须能够正确处理SIGTERM信号并通知所有子进程退出。如果用shell脚本启动容器,其1号进程为shell进程,而shell进程中并没有对SIGTERM信号的处理逻辑,因此会忽略接收到的SIGTERM信号,这样就无法实现优雅的退出(比如持久化数据),因此docker官方建议的模式是:令每个容器中只包含一个进程,同时采用exec模式启动进程。或者使用定制化shell脚本启动,需要能够接受SIGTERM信号并且分发该信号到所有的子进程,或者工作进程以exec方式启动,同时该工作进程能够处理SIGTERM并负责分发给子进程。
docker daemon只监控1号进程。
Docker容器的运行时模型
linux中的父进程用fork命令创建子进程,然后调用exec执行子进程函数,每个进程都有一个PID。另外,除了常见的一般应用进程,操作系统中还有以下特殊的进程。
1. PID=0是调度进程,该进程是内核的一部分,不会执行磁盘上的任何程序;
2. PID=1为init进程,通常读取与系统有关的初始化文件/etc/rc*文件,/etc/inittab,/etc/init.d/中的文件
3. PID=2为页守护进程,负责支持虚拟存储系统的分页操作。
Docker启动时,利用fork命令从Docker-containerd进程中fork出一个子进程,然后以exec方式启动自己的程序。容器进程被fork之后便创建了namespace,下面就要执行一系列的初始化操作,该操作分为三个阶段,dockerinit负责初始化网络栈;ENTRYPOINT负责完成用户态配置;CMD负责启动入口。启动后的docker容器和docker daemon就通过sock文件描述符实现IPC通信。
docker volumes vs binding mount
docker数据持久化建议有两种或者说3种模式:
1. bind mounts;
2. named volumes
3. volumes in dockerfile
bind mounts的作用是将host的本地目录mount到container中,
docker run -v /hostdir:/containerdir IMAGE_NAME docker run --mount type=bind,source=/hostdir,target=/containerdir IMAGE_NAME
named volumes是通过docker volume create volume_name的方式手工创建的volumes,他们都存储在/var/lib/docker/volumes目录下,可以仅仅使用volume name来引用。比如,如果我们创建了mysql_data这个volume,则可以在docker run -v mysql_data:/containerdata IMAGE_NAME来引用它。
而在dockerfile中定义的volumes,是使用VOLUME指令来创建的,他们也存储于/var/lib/docker/volumes中,但是他们没有一个自定义的名字,一般使用hash作为其名称,并且dockerfile中定义的volumes后续参数实际上是指定了在container中的路径,如果在image中已经populate了数据,则container执行后会自动将该目录数据copy到host自动创建的目录中(如果指定了host路径则不会覆盖host的数据!)
https://stackoverflow.com/questions/41935435/understanding-volume-instruction-in-dockerfile
docker from development to production
一般来说,我们在开发时希望通过一个volume来绑定host主机的source代码以方便即改即调的快捷流程,但是在production阶段
我们往往直接将代码复制到image中从而实现容器就是代码,独立于主机可以在任何地点运行的便捷。
一个比较好的策略是
docker-compose.yml中这样定义:
version: '2' services: app: build: . image: app:1.0.0-test volumes: - ./host_src:/user/share/nginx/html ports: - "8080:80"
其中nginx app build时需要使用的Dockerfile可以简单定义如下:
FROM nginx
COPY host_src /usr/share/nginx/html
在nginx app中首先COPY host_src到container对应的目录中,随后在dev的compose yml中为方便实时修改代码和测试则mount了一个volume将host_src也映射到nginx app中相同目录下;
随后,在nginx app变为production时,我们可以这样创建一个docker-compose-production.yml
version: '2' services: app: build: . image: app:1.0.0-production ports: - "80:80"
和dev的yml文件相比,我们仅仅剔除了volume的绑定,而是直接使用COPY到image中的代码去运行
是否可以修改从parent image中继承的volume data?
比如,A image的dockerfile如下:
FROM bash RUN mkdir "/data" && echo "FOO" > "/data/test" VOLUME "/data"
我们再定义一个B image,它继承于A,我们在dockerfile中希望修改A image中的“默认”数据:
FROM A RUN echo "BAR" > "/data/test"
以上测试中B image中的/data/test实际上其值为FOO,并不是BAR
这实际上是Docker本身的特性使然,如何workaround?
1. 直接修改parent docker file,我们从google搜索以下信息
docker <image-name:version> source
我们就能够找到对应的父亲image的dockerfile,通过删除其volume来实现。
VOLUMES本身并不是IMAGE的一部分,因此我们需要通过seed data来实现上面的需求。当docker image被放到另外地方运行时,它将在启动后是一个空的volume,因此,如果你希望将数据和image一起打包,就不要使用volume,而应该使用copy.
如果你确实需要重新build新的image的话,你应该先将这个volume删除掉。
https://stackoverflow.com/questions/46227454/modifying-volume-data-inherited-from-parent-image
docker volume create$docker run -v /host_path:container_path$VOLUME in Dockerfile
使用volume是docker推荐的持久化数据的方式,但是volume的用法有很多种,他们之间到底有什么区别?
要回答这个问题先得明白"volume是一个持久化数据的目录,存在于/var/lib/docker/volumes/..."
这个事实。你可以:
1. 在Dockerfile中声明一个volume,这意味着每次从image中运行一个container时,该volume就将被created,但是确是(empty)空的,即便你并未使用一个-v参数在docker run -v命令中
2.你可以在运行时指定mount的volume:
docker run -v [host-dir:]container-dir
docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
# -v和--mount有相同的效果,如果还不存在myvol2则创建一个volume到/var/lib/docker/volumes目录,随后mount到container中
docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
这种模式就结合了VOLUME in dockerfile和docker run -v两者的优点,他会将host folder mount到由container持久化并存储于/var/lib/docker/volumes/...的卷
3.docker volume create将创建一个命名式的volume,可以快速被其他容器来mount
https://stackoverflow.com/questions/34809646/what-is-the-purpose-of-volume-in-dockerfile
docker run -d \ -it \ --name devtest \ --mount type=bind,source="$(pwd)"/target,target=/app \ nginx:latest # 等价于以下命令,bind mount主机的目录到target机器上 docker run -d \ -it \ --name devtest \ -v "$(pwd)"/target:/app \ nginx:latest
dockerfile执行顺序及变更
1 FROM ubuntu:16.04 2 RUN apt-get update 3 RUN apt-get install nginx 4 RUN apt-get install php5
如果上面的dockerfile我们做过build,随后我们想把nginx换成apache,并重新build,则这时候第1和第2行不会再运行,因为都保存在cache中,但是第3和第4行都会重新执行,因为第3行做了变更,而第4行又依赖于第3行,因此第3和第4行都将重新执行最终构建出image
Docker AUFS原理
使用docker数据容器的备份策略
我们知道在网站日常运维中会有很多数据产生,包括数据库本身,很多配置文件,包括dockerfile, docker-compose等数据,如何备份这个数据是一个挑战。以前直接使用云主机提供商提供的数据卷镜像备份虽然可以work,但是往往备份了很多不必要的数据,额外占用的空间将产生额外的费用。而目前很多容器服务提供商能够免费提供私有数据容器存储,这又可以为我们节省一笔开支。
我的建议思路是:使用busybox基础镜像,COPY指令将需要备份的数据copy到镜像中,并且tag后push到私有仓库来存储。
FROM busybox:1.30 COPY ./datainhost /dataincontainer
需要注意的是./datainhost目录是相对于Dockerfile-databackup这个文件的相对路径。
如果需要copy不在build context中的目录到image中,可以这么做:
- go to you build path
- mkdir -p some_name
- sudo mount --bind src_dir ./some_name
然后在dockerfile的copy指令中直接用some_name来引用外部文件夹并且实施copy即可。
随后在host上(包含dockerfile的那个目录上)执行以下shell命令:
docker build -f Dockerfile-databackup -t registry-internal.cn-shanghai.aliyuncs.com/namespace/reponame:$(date +"%F") .
该命令将会生成registry-internal.../reponame:2019-03-20类似的tag到构建好的image上去。
随后直接push一下就好了。
注意上述registry对于阿里云主机使用内网ip不占用带宽,非常快速好用