四:网络
1:命名容器
在各种docker命令中,可以通过名字中找到对应的容器。之前创建的容器都是由docker自动命名的,可以在docker run中,通过--name参数指定容器的名字。比如:
$ docker run -d -P --name web training/webapp python app.py $ docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web
容器的名字必须是唯一的。因此只能有一个容器叫做web,如果想再次使用这个名字,必须删除之前的容器。
2:在默认网络上运行容器
docker默认提供两种网络驱动,分别是bridge和overlay。docker允许自己写网路驱动插件,不过这属于高级话题了,暂不讨论。
安装docker引擎后自动包含了三种默认的网络:
[hh_93_197 ~]# docker network ls NETWORK ID NAME DRIVER 57f0a920665a none null e8fad866218e host host 4948e842e58f bridge bridge
除非在docker run中特意指定,否则docker总是将容器运行在bridge网络上,比如:
[hh_93_197 ~]# docker run -itd --name=networktest ubuntu df38005e240bcd0a2502f1c47b8b2d1b31059f1b7c75f0522006cd0b986e3916
使用docker network inspect命令看一下默认的bridge网络:
[hh_93_197 ~]# docker network inspect bridge [ { "Name": "bridge", "Id": "4948e842e58f3fedd25afe97bf292d53d7023ddeb90fefb7825103b56c5251b8", "Scope": "local", "Driver": "bridge", "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16" } ] }, "Containers": { "df38005e240bcd0a2502f1c47b8b2d1b31059f1b7c75f0522006cd0b986e3916": { "Name": "networktest", "EndpointID": "ee6b49e5c03ca2bd61f2d2b46c45c9067ae5ccf098bb4c14beaeeb64fde8bb5f", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } }, "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" } } ]
可见该网络使用的网段是172.17.0.0/16,容器networktest是attach到该网络上的唯一容器,其IP地址为172.17.0.2。主机上的docker0就是该网络上的网桥。
下面,进入容器networktest,用ifconfig看一下它的IP地址,确实是172.17.0.2。
[hh_93_197 ~]# docker attach networktest root@df38005e240b:/# ifconfig eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02 inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
可以将容器从一个网络中移除(断开),比如在另一个终端上执行下面的命令:
[hh_93_197 ~]# docker network disconnect bridge networktest
此时,在容器中查看网络配置:
root@df38005e240b:/# ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
网络是隔离容器最合适的方法。可以创建自己的网络:
3:创建自己的网络
bridge网络将容器限制在运行dokcer引擎的主机上,而overlay网络则可以跨越多个主机(高级话题)。
可以创建自己的bridge网络:
[hh_93_197 ~]# docker network create -d bridge my-bridge-network c6198f9bb78a4275088d7f06116ef839871ca7aa1dbbd7b120f1187df4224cb4 [hh_93_197 ~]# docker network ls NETWORK ID NAME DRIVER 57f0a920665a none null e8fad866218e host host 4948e842e58f bridge bridge c6198f9bb78a my-bridge-network bridge
使用-d选项,表明新创建的网络使用bridge驱动。分别使用docker network inspect 和ifconfig看一下:
[hh_93_197 ~]# docker network inspect my-bridge-network [ { "Name": "my-bridge-network", "Id": "c6198f9bb78a4275088d7f06116ef839871ca7aa1dbbd7b120f1187df4224cb4", "Scope": "local", "Driver": "bridge", "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1/16" } ] }, ... [hh_93_197 ~]# ifconfig ... br-c6198f9bb78a: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.18.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 ether 02:42:81:54:91:a3 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 ether 02:42:c6:77:db:9e txqueuelen 0 (Ethernet) RX packets 107 bytes 7532 (7.3 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 135 bytes 11825 (11.5 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 ...
可见br-c6198f9bb78a就是新创建的my-bridge-network中的网桥。
4:网络隔离
下面分别将两个容器加入到两个独立的网络中查看一下,首先将一个基于ubuntu镜像的容器加入到新创建的网络中:
[hh_93_197 ~]# docker run -itd --net=my-bridge-network --name testnetwork ubuntu 559048438ae5972c8c4e578f63195d14d4c7a155e9306630f3eff6d9d18fcb2c [hh_93_197 ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' testnetwork 172.18.0.2
可见该容器的IP地址为172.18.0.2。接下来再创建一个web容器,不指定任何网络参数,因此该容器会attach到默认的bridge网络上:
[hh_93_197 ~]# docker run -d --name web training/webapp python app.py 226dc5d3a98a9d2b009e870462c7f072180c98ba5a1f6144b5cff4e15b0a1638 [hh_93_197 ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web 172.17.0.2
可见该容器的IP地址为172.17.0.2。两个容器附加到不同的网络上,相当于已经隔离了。进入testnetwork容器,在该容器中ping一下172.17.0.2,ping不通:
[hh_93_197 ~]# docker exec -it testnetwork bash root@559048438ae5:/# ping 172.17.0.2 PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data. ^C --- 172.17.0.2 ping statistics --- 21 packets transmitted, 0 received, 100% packet loss, time 19999ms
docker允许将一个容器attach到任意多的网络上。下面,在另一个终端上,将web容器attach到my-bridge-network上:
[hh_93_197 ~]# docker network connect my-bridge-network web
此时,在testnetwork容器中ping一下web容器:
root@559048438ae5:/# ping web PING web (172.18.0.3) 56(84) bytes of data. 64 bytes from web.my-bridge-network (172.18.0.3): icmp_seq=1 ttl=64 time=0.105 ms 64 bytes from web.my-bridge-network (172.18.0.3): icmp_seq=2 ttl=64 time=0.046 ms ...
说明web已经和testnetwork处于同一个网络上了。其实,web容器目前是分别attach到my-bridge-network和bridge两个网络上,具有两个IP地址,分别是172.17.0.2和172.18.0.3。
五:存储
1:数据卷(Data Volumes)
数据卷用于持久化数据以及共享数据,它具有以下功能:
a:当容器创建时会初始化卷。如果容器的基础镜像在特定挂载点上包含数据,则这些数据会在卷初始化时复制到新卷中(当挂载宿主机目录时不会这么做)。
b:数据卷可以在多个容器间共享和重用;
c:数据卷上的改变,是立即可见的;
d:当升级镜像时,不会包含数据卷中修改的内容;
e:容器被删除时,数据卷依然存在。
数据卷用于持久化数据,并且独立于容器的生命周期。因此即使删除容器时,Docker也不会自动的删除卷,也不会在没有容器引用一个卷时,将其清理。
2:增加一个数据卷
在docker create和docker run中使用-v选项可以在创建容器时增加数据卷,也可以使用多个"-v"选项挂载多个数据卷。
例子如下:
[hh_93_197 ~]# docker run -it --name testvolume2 -v /dev ubuntu /bin/bash root@aa644c48392b:/# ls /dev agpgart core kmem loop6 midi03 mixer3 ram ram14 ram6 rmidi2 smpte3 tty1 tty8 audio dsp loop0 loop7 midi1 mpu401data ram0 ram15 ram7 rmidi3 sndstat tty2 tty9 audio1 dsp1 loop1 mem midi2 mpu401stat ram1 ram16 ram8 sequencer stderr tty3 urandom audio2 dsp2 loop2 midi0 midi3 null ram10 ram2 ram9 shm stdin tty4 zero audio3 dsp3 loop3 midi00 mixer port ram11 ram3 random smpte0 stdout tty5 audioctl fd loop4 midi01 mixer1 ptmx ram12 ram4 rmidi0 smpte1 tty tty6 console full loop5 midi02 mixer2 pts ram13 ram5 rmidi1 smpte2 tty0 tty7
可见,直接在容器中创建了/dev这个数据卷,而且/dev是ubuntu镜像原有的挂载点,其中的内容也已经复制到这个数据卷了。
在容器中,进入/dev目录下,新建readme.txt文件,内容是:”thisis data volume in container”。
在宿主机上,使用docker inspect命令查看该容器,得到信息如下:
… "Mounts": [ { "Name": "35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429", "Source": "/var/lib/docker/volumes/35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429/_data", "Destination": "/dev", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ], …
因此,容器中的数据卷,对应着宿主机上的目录是/var/lib/docker/volumes/35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429/_data,查看该目录的内容:
[hh_93_197 /var/lib/docker/volumes/35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429/_data]# ls agpgart core kmem loop6 midi03 mixer3 ram ram14 ram6 rmidi1 smpte2 tty0 tty7 audio dsp loop0 loop7 midi1 mpu401data ram0 ram15 ram7 rmidi2 smpte3 tty1 tty8 audio1 dsp1 loop1 mem midi2 mpu401stat ram1 ram16 ram8 rmidi3 sndstat tty2 tty9 audio2 dsp2 loop2 midi0 midi3 null ram10 ram2 ram9 sequencer stderr tty3 urandom audio3 dsp3 loop3 midi00 mixer port ram11 ram3 random shm stdin tty4 zero audioctl fd loop4 midi01 mixer1 ptmx ram12 ram4 readme.txt smpte0 stdout tty5 console full loop5 midi02 mixer2 pts ram13 ram5 rmidi0 smpte1 tty tty6 [hh_93_197 /var/lib/docker/volumes/35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429/_data]# cat readme.txt this is data volume in container
可见,readme.txt文件已经在宿主机上可见了。反之也成立。在宿主机上的该目录下创建文件readme2.txt,在容器中也是立即可见的。
使用该容器制作新镜像,不会包含数据卷中的新增内容:
[hh_93_197 ~]# docker commit -m "test data volume" -a "hh" testvolume2 hh/ubuntu:volume2 sha256:1c4275b3ac33ee0c4e64ea533d836ee407369de7a1c604e9933b997e22027ff1 [hh_93_197 ~]# docker run -ti hh/ubuntu:volume2 /bin/bash root@f6987d519b9a:/# ls /dev agpgart core kmem loop6 midi03 mixer3 ram ram14 ram6 rmidi2 smpte3 tty1 tty8 audio dsp loop0 loop7 midi1 mpu401data ram0 ram15 ram7 rmidi3 sndstat tty2 tty9 audio1 dsp1 loop1 mem midi2 mpu401stat ram1 ram16 ram8 sequencer stderr tty3 urandom audio2 dsp2 loop2 midi0 midi3 null ram10 ram2 ram9 shm stdin tty4 zero audio3 dsp3 loop3 midi00 mixer port ram11 ram3 random smpte0 stdout tty5 audioctl fd loop4 midi01 mixer1 ptmx ram12 ram4 rmidi0 smpte1 tty tty6 console full loop5 midi02 mixer2 pts ram13 ram5 rmidi1 smpte2 tty0 tty7
当删除上面的容器后,宿主机上的数据卷目录依然存在:
[hh_93_197 /var/lib/docker/volumes/35aa73994cc70828d861620dca03fd1e91699ff46d1b560b6e3ded6128b1e429/_data]# ls agpgart core kmem loop6 midi03 mixer3 ram ram14 ram6 rmidi0 smpte1 tty tty6 audio dsp loop0 loop7 midi1 mpu401data ram0 ram15 ram7 rmidi1 smpte2 tty0 tty7 audio1 dsp1 loop1 mem midi2 mpu401stat ram1 ram16 ram8 rmidi2 smpte3 tty1 tty8 audio2 dsp2 loop2 midi0 midi3 null ram10 ram2 ram9 rmidi3 sndstat tty2 tty9 audio3 dsp3 loop3 midi00 mixer port ram11 ram3 random sequencer stderr tty3 urandom audioctl fd loop4 midi01 mixer1 ptmx ram12 ram4 readme2.txt shm stdin tty4 zero console full loop5 midi02 mixer2 pts ram13 ram5 readme.txt smpte0 stdout tty5
3:挂载宿主机目录到容器中
使用-v选项,还可以将宿主机上的目录,挂载到容器中:
[hh_93_197 ~]# docker run -it -v /host/foo:/opt/foo ubuntu /bin/bash root@0235be2f412d:/# ls /opt foo
以上的命令,就将宿主机上的/host/foo目录,挂载到容器中的/opt/foo中了。
在宿主机中/host/foo创建文件,容器中/opt/foo会立即可见;容器中/opt/foo创建的文件,宿主机中/host/foo也是立即可见的。
如果宿主机目录/host/foo不存在,则docker会自动创建。如果容器中的目录/opt/foo中原先已存在且包含文件,则挂载宿主机目录后,/opt/foo中的内容不会被删除,只是不可见了,一旦解除mount,则/opt/foo中的内容会恢复(这事mount的特点)。
在-v选项中,容器目录必须是绝对路径的形式,宿主机目录可以是一个绝对路径,也可以是一个名字,使用名字时,docker会使用改名字创建一个命名卷。
默认情况下,docker使用的是读写挂载,可以指定只读挂载,这种情况下,容器中就不能修改相应目录的内容了:
[hh_93_197 ~]# docker run -it -v /host/foo:/opt/foo:ro ubuntu /bin/bash root@b1ba14479947:/# cd /opt/foo/ root@b1ba14479947:/opt/foo# touch readme2.txt touch: cannot touch 'readme2.txt': Read-only file system
注意,因为挂载主机目录是依赖于特定主机的,因此,不允许在Dockerfile中挂载主机目录,因为创建的镜像必须是可移植的,包含主机目录挂载的镜像不能应用到其他主机上。
除了可以挂载目录之外,也可以将主机的文件挂载到容器中:
[hh_93_197 ~]# docker run --rm -it -v ~/proxy_on:/root/proxy_on ubuntu /bin/bash root@2305c0b5e1b4:/# cat /root/proxy_on export https_proxy=http://19.28.20.22:1888 export http_proxy=http://12.28.20.22:8000
4:创建并挂载一个数据卷容器(数据卷共享)
可以将数据卷在多个容器中共享。比如下面先创建第一个容器sd1,其中挂载了数据卷/sharedata:
[hh_93_197 ~]# docker run -it -v /sharedata --name sd1 ubuntu
然后在docker run中,使用--volumes-from参数,创建新容器,并把sd1中的/sharedata挂载到新容器中:
[hh_93_197 ~]# docker run -it --volumes-from sd1 --name sd2 ubuntu [hh_93_197 ~]# docker run -it --volumes-from sd2 --name sd3 ubuntu
此时,sd1、sd2和sd3这三个容器都具有数据卷/sharedata,而且是共享的。在其中任意一个容器中的/sharedata中修改内容,都可以在其他容器中立即可见。
删除容器sd1、sd2或者sd3时,该数据卷并不会被删除。可以在docker rm命令中使用-v参数,当最后一个关联数据卷的容器删除时,相应的数据卷也会被删除。
[hh_93_197 ~]# docker rm -v sd1 sd1 [hh_93_197 ~]# docker rm -v sd2 sd2 [hh_93_197 ~]# docker rm -v sd3 sd3
这样删除sd3之后,数据卷也就相应的被删除了。
如果不指定-v选项,则当一个数据卷的所有关联容器都被删除之后,该数据卷就成了” dangling”卷。可以使用命令docker volume ls -f dangling=true列出所有” dangling”卷,并使用命令dockervolume rm <volume name>删除即可。
5:数据备份、迁移和恢复
使用--volumes-from参数,可以实现数据备份、迁移和恢复的功能。例子如下:
首先创建一个挂载数据卷/sharedata的容器,然后在/sharedata中创建新文件,当做需要备份的数据,然后退出:
[hh_93_197 ~]# docker run -it -v /sharedata --name sd1 ubuntu root@c62d94cb3c91:/# cd /sharedata/ root@c62d94cb3c91:/sharedata# echo "hello world" >> readme.txt root@c62d94cb3c91:/sharedata# cat readme.txt hello world root@c62d94cb3c91:/sharedata# exit exit
然后使用--rm选项,创建一个临时容器。--rm选项可以在容器停止运行后立即将其删除。
该临时容器通过--volumes-from参数,与sd1共享/sharedata目录,并且挂载宿主机目录,运行tar命令压缩备份/sharedata内容,将备份数据存放到宿主机目录中:
[hh_93_197 ~]# docker run --rm --volumes-from sd1 -v /root/testbackup:/backup ubuntu tar cvf /backup/backup.tar /sharedata tar: Removing leading `/' from member names /sharedata/ /sharedata/readme.txt
接下来,创建需要恢复数据的容器sd2:
[hh_93_197 ~]# docker run -ti -v /sharedata --name sd2 ubuntu /bin/bash root@0d44aee764fa:/# ls /sharedata/
目前其/sharedata目录是空的,然后再创建一个临时容器,将数据恢复到sd2中:
[hh_93_197 /host/foo]# docker run --rm --volumes-from sd2 -v /root/testbackup:/backup ubuntu bash -c "cd /sharedata && tar xvf /backup/backup.tar --strip 1" sharedata/readme.txt
此时,在sd2中查看/sharedata,数据已经恢复了:
root@0d44aee764fa:/# cat /sharedata/readme.txt hello world
注意,使用共享数据卷时,当多个容器同时修改共享卷中的同一个文件时,有可能会造成数据损坏。
而且,数据卷在宿主机上也是可见的,如果宿主机和容器同时修改同一个文件,也可能会造成数据损坏。
六:Docker镜像库
DockerHub(hub.docker.com)是由Docker公司维护的镜像库。可以通过docker search,pull,login和push命令与Docker Hub交互。
dockerlogin用于向Docker Hub注册用户;
dockersearch用于在Docker Hub搜索镜像;
dockerpull用于下载镜像;
dockerpush用于上传镜像;
除了Docker Hub之外,还可以从其他仓库下载镜像,从其它仓库下载时需要指定完整的仓库注册服务器地址。比如:
[hh_93_197 ~]# docker pull registry.aliyuncs.com/ddbmh/redis Using default tag: latest latest: Pulling from ddbmh/redis 80ab95908a2b: Pull complete a3ed95caeb02: Pull complete 47a0d79f89b9: Pull complete 7190081b1686: Pull complete fe09c22d81ac: Pull complete a5eae2bcc645: Pull complete 662723161f77: Pull complete b568670a8ccd: Pull complete a1a961e320bc: Pull complete Digest: sha256:68a1aa9675f25e77d97fe9436dececd29be5ba6eae4ce04169288da5bfe9096b Status: Downloaded newer image for registry.aliyuncs.com/ddbmh/redis:latest [hh_93_197 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ... registry.aliyuncs.com/ddbmh/redis latest a70b69767606 7 months ago 109.3 MB
https://docs.docker.com/engine/userguide/containers/networkingcontainers/
https://docs.docker.com/engine/userguide/containers/dockervolumes/
https://docs.docker.com/engine/userguide/containers/dockerrepos/