[TOC]
0x01 Docker 数据管理
Data Management,在使用Docker时候必然会在容器内产生数据,或者需要将容器内的数据进行备份,甚至多个容器之间进行数据共享,这时数据管理变得尤为重要; WeiyiGeek.数据管理
容器中管理数据的主要有两种方式:
- 数据卷:Data Volumns
- 数据卷容器:Data Volume Dontainers
数据管理共享的方式:
- 使用数据卷容器在容器和主机
- 容器和容器之间共享数据
1.数据卷
描述:是一个可供容器使用的数据目录,并且让文件系统提供很多有用的特性,数据卷的使用类似于Linux对目录或者文件进行mount操作;
数据卷特性:
- 数据库可以在容器之间共享和重用
- 数据卷修改后会立马生效
- 对数据卷的更新不会影响镜像
- 卷会一直存在,即使容器被删除
Q:如何在容器内创建一个数据卷?1
2
3
4
5
6
7
8
9
10#### 创建一个web容器并创建一个数据卷挂载到容器的/webapp目录下(默认将宿主机/根映射到容器中webapp目录中)
$ sudo docker run -d -P 5000 --name web(新建容器) -v /webapp(容器目录) training/webapp(镜像) python app.py
# --name 指定容器的名称
# -v:将镜像的存放位置放在本地指定的路径上.
# -P:是允许外部访问容器需要暴露的port
# -d:是容器的后台运行守护
#cp 把容器文件copy到宿主机,或者把宿主机的文件copy到容器
$docker cp 容器id或者name:/home/wwwroot/1.php /home/Lcy/ #把容器的1.php拷贝到宿主机家目录
$docker cp config.php 容器id或者name:/home/wwwroot/ #把宿主机的config.php拷贝到容器
Q:如何挂载主机目录/文件作为数据卷?1
2
3
4
5
6
7#挂载一个主机目录作为数据卷 /src/webapp[主机目录]:/opt/webapp[容器目录]:rw (权限)
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:rw training/webapp python app.py
# Docker挂载数据卷的默认权限 rw,ro[只读] //加入ro后数据卷的数据就无法修改了
#挂载一个本地文件作为数据卷(注意再挂载文件得时候尽量设置ro自读,防止inode不一致报错)
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
# --rm :当它退出自动移除容器 即docker ps -aq 不能查询到
2.数据卷容器
描述:IF用户需要在容器之间共享一些持续的数据,最简单的方式就是使用数据卷容器(实际上就是一个普通容器);
使用数据卷容器可以让用户在容器之间自由地升级和移动数据卷;
Q: 如何创建一个数据卷容器?
首先创建一个数据卷容器dbdata并在其中创建一个数据卷挂载到/dbdata上;1
2
3
4
5
6
7
8
9
10$ sudo docker run -it -v /dbdate --name dbdate ubuntu
# 然后在容器中进行使用 创建两个容器db1 / db2
$ sudo docker run -it --volumes-from dbdate --name db1 ubuntu
$ sudo docker run -it --volumes-from dbdate --name db2 ubuntu
# 使用--volumes-from参数所挂载数据卷的容器自身并不需要保持运行状态
# 修改目录中其中任何一个文件,其他容器的该目录都会改变
$ sudo docker run -d --name db2 --volumes-from db1 tarining/postgresql #可以从已有挂载了容器卷的容器来挂载数据卷
# 删除挂载的容器(dbdata 、 db1 、db2)数据卷饼不会被自动的删除,必须在删除最后一个挂载着它容器时显示使用docker rm -v 命令来指定同时删除关联的容器;
Q: 如何删除容器及其数据卷?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 挂载
$docker run --name web --rm -dit -v /opt:/opt/ centos /bin/bash
5b3d35fe3305fb458e8b33f39d5fedfbd7d9cb1f6742bcf725cfbf2ecd0245fc
# 进入容器
$docker start -i 5b3
# 建立一个文件
[root@5b3d35fe3305 opt]$ echo 'docker' > containerDokcer.txt
# 宿主机器目录查看
[root@zabbix opt]$ cat containerDokcer.txt
docker
# 挂载单个文件
$docker run --rm -it -v ~/.bash_history:/root/.bash_history centos /bin/bash
$docker rm -vf web #删除容器和数据卷
$docker ps -a #由于使用--rm参数则会在容器退出时候删除容器
Q: 数据卷容器如何迁移数据?
描述: 可以利用数据卷容器对其中的数据卷进行备份、恢复以实现数据的迁移;1
2
3
4
5
6
7# 备份: 创建一个worker容器 ,将本地当前目录挂载到容器中backup目录,进行选择数据目录备份压缩
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdate
# 恢复: 首先创建一个带有数据卷的容器dbdata2
$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
# 解压备份文件到挂载的数据卷中
$ sudo docker run --volumes-from dbdata2 $(pwd):/backup busybox tar xvf /backup/backup.tar
实践案例:
- 示例1.创建一个只有读取权限的数据卷到容器内部,此处利用–mount参数进行设置,实现启动一个挂载数据卷的容器,进行容器之间的资源共享;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37$docker volume create myvol # 等同于 -> docker volume create --driver local --name myvol
$docker volume ls
local myvol
#下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。
$docker run -d -P \
--name web \
# -v my-vol:/wepapp \
# 挂载一个数据卷到容器之中(可读可写)
--mount source=my-vol,target=/webapp \
# 挂载一个本地主机文件作为数据卷 (只读)
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history,readonly \
training/webapp \
sh
#进入webapp修改目录进行修改东文件,在其他容器中验证是否可以进行数据共享交互
$docker run -it --name web2 -v myvol:/webapp centos sh #-v 与 --mount产生的效果差不多,只不过后者功能参数更加强大,可以直接挂nfs网络共享文件系统。
$sh-4.2# cat test.txt #实现了资源共享
asdasdasdasda:
#查看数据卷的具体信息
"Mounts": [
{
"Type": "volume",
"Name": "myvol",
"Source": "/var/lib/docker/volumes/myvol/_data",
"Destination": "/webapp",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
#删除数据卷(注意正在使用的不能删除)
docker volume rm `docker volume ls -q`
- 示例2.利用数据卷容器来备份、恢复、迁移数据卷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#备份
#首先使用 --volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录
#容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu -C "tar cvf /backup/backup.tar /dbdata"
/ # ls
backup dbdata
#恢复
#首先创建一个带有空数据卷的容器 dbdata2
sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
#然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。
$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
-rw-r--r-- 1 root root 5120 5月 24 15:02 dbdata.tar
#为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看
$ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata
补充说明: -v 参数指定数据持久化目录后添加的:Z
与:z
的区别1
2
3
4
5#配置selinux标签如果使用selinux,可以添加z或z选项来修改挂载到容器中的主机文件或目录的selinux标签
#:z选项 表示绑定挂载内容在多个容器之间共享。
#:Z选项 表示绑定挂载内容是私有和非共享的。
#重要:当使用绑定与服务挂载时,selinux标签(:z和:Z)以及:ro将被忽略,设置了z选项以指定多个容器可以共享绑定挂载的内容,此时不能使用——mount标记修改selinux标签
docker run -d --restart=always --name app -v /disk/webapp/war/:/usr/local/tomcat/webapps:z -p 4081:8080 -e JAVA_OPTS=-Dsome.property=value -e Xmx=1536m tomcat-base:6.0.85-jre8
总结说明:
- 推荐直接挂载文件目录到容器中,如果直接挂载一个文件到容器中在使用文本编辑工具时候可能会报错;
- 可以多次使用–volumes-from参数从来多个容器挂载多个数据卷;锁挂载的容器自身并不需要保持在运行状态
- 推荐使用数据卷和数据容器之外的物理备份存储系统,如RAID或者分布式系统如Ceph,GPFS,HDFS等
0x02 Docker 网络管理
描述: 大量互联网服务包括多个服务组件往往需要多个容器之间进行网络通信相互配合,Docker目前提供了映射容器端口与宿主主机和容器互联机制来为容器提网络服务;并且采用Linux系统知道的网络系统来实现对网络服务的支持使之能提供稳定支持以及快速的高性能转发;
1.容器网络原理
描述: 当 Docker 启动时会自动在主机上创建一个 docker0 虚拟网桥,实际上它是 Linux 的一个 bridge (网桥),可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发。
同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口
- 比如典型的 172.17.42.1,掩码为 255.255.0.0
Docker 就创建了在主机和所有容器之间一个虚拟共享网络
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包), 这对接口
- 一端在容器内即 eth0;
- 另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)
通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。 WeiyiGeek.Docker网络
例如, 当在一台未经过特殊网络配置的centos 或 ubuntu机器上安装完docker之后, 在宿主机上通过ifconfig命令可以看到多了一块名为docker0的网卡;不难想到docker0就不只是一个简单的网卡设备了而是一个网桥。 WeiyiGeek.bridge模式图1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28$ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
$route -n
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
#下图即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以eth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
#网桥(等同于交换机)
#在Linux中,可以使用brctl命令查看和管理网桥(需要安装bridge-utils软件包),比如查看本机上的Linux网桥以及其上的端口:
$yum install bridge-utils
$brctl show
bridge name bridge id STP enabled interfaces
br-63791a62ad5a 8000.024258a66d6e no veth4ce6e0e
vethe5abf0f
br-6cde78afe495 8000.02420a2496c6 no veth93e8dc0
vethfda7b14
docker0 8000.024216e63d3c no
#docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.18.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。
#除了使用docker0网桥外,还可以使用自己创建的网桥,比如创建一个名为br0的网桥,配置IP:
brctl addbr br0
ifconfig br0 18.18.0.1
#在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡转发到宿主机ens160网卡)
#需要内核将ip-forward功能打开即将ip_forward系统参数设1
echo 1 > /proc/sys/net/ipv4/ip_forward
在Docker在1.9版本中network子命令和跨主机网络支持,为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork中使用了CNM(Container Network Model)定义了构建容器虚拟化网络的模型。 WeiyiGeek.
libnetwork和Docker daemon及各个网络驱动的关系图:
Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能,libnetwrok中则使用了CNM来完成网络功能的提供;
CNM中主要有沙盒(sandbox)、端点(endpoint)、网络(network)3种组件:
- (1)Sandbox:包含了一个容器网络栈的信息,实现对容器的接口、路由和DNS设置等进行管理;
- (2)Endpoint:它可以加入一个沙盒和一个网络,且一个端点只可以属于一个网络并且只属于一个沙盒;
- (3)Network:可以直接互相联通的端点(包含多个端点),网络的实现可以是Linux bridge、VLAN等
libnetwork共有5种内置驱动:bridge驱动、host驱动、overlay驱动、remote驱动、null驱动。
- (1)bridge驱动: Docker默认设置驱动,它可以将创建出来的Docker容器连接到Docker网桥常规方法能满足容器基础需求,然后在复杂场景下使用又诸多限制(使用NAT转发)
- (2)host驱动:它将不为Docker容器创建网络协议栈(不会创建独立的network namespace),即容器于宿主机共用一个network namespace并使用宿主机的网卡/IP端口等等信息,该驱动适用于对于容器集群规模不大的场景;
- (3)overlay驱动: 采用IETE标准的VXLAN方式,并且是VXLAN中被普遍认为最适合大规模的云计算虚拟化环境的SDN controller模式,使用时候需要额外的配置存储服务(例如Consul、etcd和zookeeper)
- (4)remote驱动: 该驱动并未做真正的网络服务实现而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化,更好地满足了用户的多种需求
- (5)null驱动: 容器拥有自己的网络命名空间,但并不为Docker容器进行任何网络配置;容器除了network namespace自带的loopback网卡名,没有其他任何网卡、IP、路由等信息(需要用户对齐配置)
2.容器网络实践
(1)端口映射实现访问容器
通过-P或者-p来指定端口,
- 使用-P时候:会进行选择 49000 ~ 49900 端口随机分配映射;
- 使用-p时候:会让你您设置固定与容器映射的端口;
支持的格式:
- hostPort:containerPort < 映射到本地指定端口以及容器端口
- ip:hostPort:containerPort < 映射到本地指定地址以及本地指定端口和容器端口
- ip::containerPort < 映射本地指定IP的任意端口和容器端口 (注意此处是::)
1 | #随机开启端口 |
(2)实现容器间通信
容器的链接(Linking)系统是除了端口映射外的另一种可以与容器中应用进行交换的方式;它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息;
Docker两种方式为容器公开连接信息:
- 1.环境变量
- 2.更新/etc/hosts
1 | #首先创建一个新的数据库容器,启动db容器的时候并没有使用-p与-P标记,避免了暴露数据库端口到外部网络上 |
实例: WeiyiGeek.访问容器映射出网站1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37$docker pull nginx
$docker run -d -p 80:80 --name web nginx
$docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
16207d8f2291 nginx "nginx -g 'daemon of…" 19 seconds ago Up 18 seconds 0.0.0.0:80->80/tcp web
#容器有自己的内部网络和IP地址,使用docker inspect + 容器ID 可以获取所有的变量值;
$docker inspect web
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"MacAddress": "02:42:ac:12:00:02",
#然后打开80端口进行访问采用logs打印访问
$docker logs -f web
218.x.x.xx - - [08/May/2019:15:00:11 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0" "-"
#进行link关联容器使之能进行正常通信
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web1 --link web:nginx_web alpine cat /etc/hosts
ebe1df8c1fb00462b127d36201d558a9f62507c81faea1ce6c4bf4b5ea6075e3
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web2 --link web:nginx_web alpine env
baa9dfe5f64519eb5ccbd122fc191e0f40118a4ee28385a818f7ffe6e2e03639
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web1
172.18.0.2 nginx_web 16207d8f2291 web #成功添加web
172.18.0.3 ebe1df8c1fb0
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=baa9dfe5f645
NGINX_WEB_PORT=tcp://172.18.0.2:80 #web2也成功添加web
NGINX_WEB_PORT_80_TCP=tcp://172.18.0.2:80
NGINX_WEB_PORT_80_TCP_ADDR=172.18.0.2
NGINX_WEB_PORT_80_TCP_PORT=80
NGINX_WEB_PORT_80_TCP_PROTO=tcp
NGINX_WEB_NAME=/web2/nginx_web
NGINX_WEB_ENV_NGINX_VERSION=1.15.12-1~stretch
NGINX_WEB_ENV_NJS_VERSION=1.15.12.0.3.1-1~stretch
(3)libnetwork库官方示例
libnetwork官方示例图: WeiyiGeek.libnetwork
执行流程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58#docker内置的默认默认网卡是是无法使用docker network rm进行删除;
$docker network ls
NETWORK ID NAME DRIVER SCOPE
f26c3ed5f7b0 bridge bridge local
fd3572aceb38 host host local
01da6464c812 none null local
#Step 1.创建名为backend、frontend两个网络
$docker network create backend
6cde78afe495a310bb27c5e1a50074b20e204bfa72e71bcaf6a4c37feb300b93
$docker network create frontend
63791a62ad5a5fe4aeb5926616b2ea1d65b490bb9fb718824b7cb1c408ae50c1
$docker network create -d bridge test-net
#-d:参数指定 Docker 网络类型,有 bridge、overlay/none。
#Step 2. 将c1与c2的容器加入到backend网络中,将c3容器加入到frontend网络中
$docker run -itd --name c1 --net backend alpine
729f2abef71ceaf831999d66264d05f78674d9cd2c235f84481a14b366698adb
$docker run -itd --name c2 --net backend alpine
26d47af2d39a1b00f767c60a68cd5f61f1cf5f48652cdcbcb0216968a3185f5e
$docker run -itd --name c3 --net frontend alpine
9cb94f7c66955ba5a95c90d08ce314da0e477f6eddbcea0329309ec36ca5a711
#Step 3. 分别进入c1和c3容器使用ping命令测试其与c2的连通性,因为c1和c2都在backend网络中,所以两者可以连通。但是因为c3和c2不在一个网络中,所以两个容器之间不能连通:
# 使用docker inspect c3 查看IP信息:
C1: 172.19.0.2
C2: 172.19.0.3
C3: "IPAddress": "172.20.0.2",
# 进入c1容器ping c2通、ping c3不通。其它两个容器就不进入演示了,大家自己可以试一下:
# docker exec -it c1 sh
# ip addr
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
# ping c2
PING c2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.065 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.050 ms
/ # ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2): 56 data bytes
# Step 4. 将c2连接加入到front网络中,使用exec进入c2中查看网卡信息,测试c2与c3的连通性后,可以发现两者已经连通
$docker network connect frontend c2
$docker exec -it c2 sh
/ $ ip addr
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0
24: eth1@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.20.0.3/16 brd 172.20.255.255 scope global eth1
/ $ ping c3
PING c3 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.100 ms
(4) 跨主机实现互通
描述: 此外我们还可以通过通过添加路由route的方式进行跨主机实现互通,但是通常情况下我们不会如此操作,只在特殊环境中使用测试。
比如:1
Docker1: 172.18.0.1/24 --Gateways-- 192.168.1.99
总结说明:
- 用户可以链接多个子容器到父容器中比如连接多个web到db容器上;
- 学习额外的机制比如SDN(软件定义网络)或者NFV(网络功能虚拟化)的相关技术
3.高级网络配置
描述: 容器互联 随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 –link 参数
如何自定义配置容器的主机名和 DNS 呢?
答:Docker 会默认用主机上的 /etc/resolv.conf 来配置容器, 所以可以直接在宿主机上的resolv.conf文件中进行更改,也可以在/etc/docker/daemon.json 文件中增加DNS键值对是一个数组;
1 | #新建网络 |
容器访问控制
容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现.
容器访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。1
2
3
4
5$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
#如果在启动 Docker 服务的时候设定 --ip-forward=true, Docker 就会自动设定系统的 ip_forward 参数为 1。
$sysctl -w net.ipv4.ip_forward=1
容器之间访问需要两方面的支持:
- 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。
- 本地系统的防火墙软件 – iptables 是否允许通过。
动容器(docker run)时使用 –link=CONTAINER_NAME:ALIAS 选项,就会在宿主机上添加了 iptables 规则。1
2
3
4
5
6$ sudo iptables -nL
Chain FORWARD (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80
ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80
DROP all -- 0.0.0.0/0 0.0.0.0/0
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
- 容器访问外部实现:容器所有到外部网络的连接,源地址都会被 NAT 成本地系统的 IP 地址
- 外部访问容器实现:可以在 docker run 时候通过 -p 或 -P 参数来启用。
不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则1
2
3
4
5
6
7
8
9
10
11
12$sudo iptables -t nat -nL
#其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。
#MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
#使用端口映射的时候
chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
docker 网桥
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值,值都可以在服务启动的时候进行配置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#由于目前 Docker 网桥是 Linux 网桥,用户可以使用 brctl show 来查看网桥和端口连接信息。
$brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242f0960e9f no veth4fe5b74
#每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。
$ sudo docker run -i -t --rm base /bin/bash
$ip addr show eth0
$ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
#自定义网桥:
#用户也可以指定网桥来连接各个容器,在启动 Docker 服务的时候,使用 -b BRIDGE或--bridge=BRIDGE 来指定使用的网桥。
1. 如果服务已经运行,那需要先停止服务,并删除旧的网桥。
$ sudo systemctl stop docker
$ sudo ip link set dev docker0 down #将网卡处于非工作状态
$ sudo brctl delbr docker0 #删除默认网桥
$ sudo brctl addbr bridge0 #然后创建一个网桥 bridge0。
$ sudo ip addr add 192.168.5.1/24 dev bridge0
$ sudo ip link set dev bridge0 up #查看确认网桥创建并启动。
注:brctl 命令在 Debian、Ubuntu /Centsos采用相同的包中可以使用 sudo apt-get install bridge-utils 来安装。
实践案例: WeiyiGeek.点到点通信
创建一个点到点连接,点到点链路不需要子网和子网掩码。
默认情况下,Docker 会将所有容器连接到由 docker0 提供的虚拟子网中,用户有时候需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。
解决办法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31$docker run -it --rm --net=none --name demo1 alpine sh
39ca23ce4417
$docker run -it --rm --net=none --name demo2 alpine sh
d4b1311349c1
#找到进程号,然后创建网络命名空间的跟踪文件。
$docker inspect -f '{{.State.Pid}}' d4b
6742
$docker inspect -f '{{.State.Pid}}' 39c
6807
sudo mkdir -p /var/run/netns
sudo ln -s /proc/6742/ns/net /var/run/netns/6742
sudo ln -s /proc/6807/ns/net /var/run/netns/6807
#创建一对A B peer 接口,然后配置路由
$ sudo ip link add A type veth peer name B
$ sudo ip link set A netns 6742
$ sudo ip netns exec 6742 ip addr add 10.1.1.1/32 dev A
$ sudo ip netns exec 6742 ip link set A up
$ sudo ip netns exec 6742 ip route add 10.1.1.2/32 dev A
$ sudo ip link set B netns 6807
$ sudo ip netns exec 6807 ip addr add 10.1.1.2/32 dev B
$ sudo ip netns exec 6807 ip link set B up
$ sudo ip netns exec 6807 ip route add 10.1.1.1/32 dev B
#现在这 2 个容器就可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。
容器$ ip addr
0x03 Registry 私有镜像仓库搭建实践
1.前言简述
描述: 镜像仓库主要是用来存储和分发镜像的,并对外提供一套 HTTP API V2。镜像仓库中的所有镜像,都是以数据块 (Blob) 的方式存储在文件系统中。 支持多种文件系统,主要包括filesystem,S3,Swift,OSS等。
docker 的 registry 项目参考地址:https://docs.docker.com/registry/
产生原因:
Docker hub是公开的其他人也是可以下载并不安全因此还可以使用docker registry官方提供的私有仓库;但是我们默认在机器上搭建的registry私有仓库是不需要验证的,因此我们需要进行安全配置仓库并加入认证;
Docker Registry由三个部分组成:
- index: 负责登录、负责认证、负责存储镜像信息和负责对外显示的外部实现.
- registry: 负责存储镜像的内部实现.
- registry client: 则是docker 客户端.
2.搭建使用
描述: registry 私有镜像仓库搭建流程如下所示。 WeiyiGeek.docker仓库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77#1.下载registry仓库并设置数据存放的目录(并生成认证账号密码)
docker pull registry:2
mkdir -vp /opt/data/auth #宿主机认证目录
mkdir -vp /opt/data/registry #宿主机仓库目录
# 采用--entrypoint进行执行
docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > auth/htpasswd
#2.运行下载的仓库镜像(我们常常指定一个本地数据卷给容器)
docker run -d -p 5000:5000 --name registry -v /opt/data/registry:/var/lib/registry registry:2 #未认证
## 加入认证 (已失效)
docker run -d -p 5000:5000 --restart=always --name docker-hub \
-v /opt/data/registry:/var/lib/registry \
-v /opt/data/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
registry
#Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制
# 3.修改docker的配置文件,让他支持http方式,上传私有镜像 (本地)
tee /etc/docker/daemon.json <<EOF
# 写入如下内容
{
"registry-mirror": [
"https://registry.docker-cn.com"
],
"insecure-registries": [
"10.10.107.221:5000"
]
}
EOF
# 4.修改docker的服务配置文件(Ubuntu此步骤可以跳过)
vim /lib/systemd/system/docker.service
# 找到[service]这一代码区域块,写入如下参数
[Service]
EnvironmentFile=-/etc/docker/daemon.json
# 5.重新加载docker服务
systemctl daemon-reload
# 6.重启docker服务
systemctl restart docker
# 注意:重启docker服务,所有的容器都会挂掉
docker run -d -p 5000:5000 --name docker-registry -v /opt/data/registry:/var/lib/registry registry
# 7.修改本地镜像的tag标记,往自己的私有仓库推送
#docker tag weiyigeek/hello-world-docker 10.10.107.221:5000/weiyigeek #对于修改名称的
$docker commit -m "alpine-sh" -a "weiyigeek" a63 10.10.107.221:5000/alpine-sh
sha256:b75ef26a9e1d92924914edcb841de8b7bdc0b336cea2c6cfbaaf6175e24472c6
$docker login 10.10.107.221:5000
Username: testuser
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
#上传我们的镜像文件
$docker push 10.10.107.221:5000/alpine-sh
The push refers to repository [10.10.107.221:5000/alpine-sh]
2a499f806f16: Pushed
f1b5933fe4b5: Pushed
latest: digest: sha256:6d13420cb9ce74095e2a71f565fc8c15ba17b40933e14534fc65775025eedd18 size: 735
# 8.下载私有仓库的镜像(查看)
docker pull 10.10.107.221:5000/weiyige
# http://10.10.107.221:5000/v2/_catalog #查看仓库 需要进行认证
# http://10.10.107.221:5000/v2/[image_name]/tags/list #查看镜像版本列表
curl -XGET http://10.10.107.221:5000/v2/_catalog -u testuser:testpassword
{"repositories":["alpine-sh"]}
可以从下图看到设置账号密码认证后直接访问Registry API将受到限制 WeiyiGeek.regsitryLoginAuth
3.补充说明
(1) 对Registry镜像仓库与Harbor仓库特征说明
描述: 通过 Registry API
获得的两个镜像仓库中相同镜像的 manifest 信息的存储路径和内容完全相同
, 并且两个镜像仓库中相同镜像的 blob 信息的存储路径和内容完全相同
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# (1) Registry API
/var/lib/registry
# (2) Harbor 本地目录中
/data/harbor/registry/docker/registry/v2
# 查看镜像 Manifest 对应的blob id
$ cat repositories/release/cyclone-server/_manifests/tags/v0.5.0-beta.1/current/link
sha256:6d47a9873783f7bf23773f0cf60c67cef295d451f56b8b79fe3a1ea217a4bf98
# 查看镜像 Manifest
$ cat blobs/sha256/6d/6d47a9873783f7bf23773f0cf60c67cef295d451f56b8b79fe3a1ea217a4bf98/data
# Blob 数据的存储
# 查看镜像 blob 的位置和大小
$ du -s blobs/sha256/71/7118e0a5b59500ceeb4c9686c952cae5e8bfe3e64e5afaf956c7098854a2390e/data
7560 blobs/sha256/71/7118e0a5b59500ceeb4c9686c952cae5e8bfe3e64e5afaf956c7098854a2390e/data
(2) 为Registry私有镜像仓库配置auth认证与tls证书
1 | # 创建参数 |
(3) 登陆远程仓库时报证书签名错误及其解决办法
1 | ~$ docker login hub.weiyigeek.top |
0x03 Docker-Compose 容器编排
描述:Docker Compose 是使用 Python 开发的一款基于 Docker 容器进行编排的工具,定义和运行多容器的应用可以一条命令启动多个容器, 从提到的Docker-Machine种可以将用户在其他平台快速安装Docker,而Swarm可以让Docker容器在集群种高效运转,而Compose则可以让用户在集群中部署分布式应用。
从功能上看跟 OpenStack 中的 Heat 十分类似,Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。
简单的说: Docker Compose 属于一个应用层的服务,用户可以定义哪一个容器组运行那些应用,它支持动态改变应用并在需要时扩展;
产生原因:由于单纯的使用Dockerfile只能设置一个Docker镜像且采用多阶段构建的时候entrypoint指令只能存在一个,为了解决这个问题引入了Docker Compose机制;它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project),免去我们需要多次执行docker run命令的烦恼;
其代码目前在:https://github.com/docker/compose
Docker Compose 优点:
- 安装与使用非常简单的
- 能够帮我们处理容器的依赖关系,在每个容器中会将容器的 IP 和服务的名称使用 hosts 的方式绑定,这样我们就能在容器中直接使用服务名称来接入对应的容器了
- 使用 docker-compose.yaml 配置的形式将你需要启动的容器管理起来。
使用Compose 基本上分为三步:
- Dockerfile 定义应用的运行环境
- docker-compose.yml 定义组成应用的各服务
- docker-compose up 启动整个应用
Compose 中有两个重要的概念:
- 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
- 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
应用场景:最常见的项目是 web 网站,该项目应该包含 web 应用和缓存。
一个简单的docker-compose.yml配置文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37version: "2"
services:
nginx:
depends_on:
- "php"
image: "nginx:latest"
volumes:
- "$PWD/src/docker/conf:/etc/nginx/conf.d"
- "$PWD:/home/q/system/m_look_360_cn"
ports:
- "8082:80"
container_name: "m.look.360.cn-nginx"
php:
image: "lizheming/php-fpm-yaf"
volumes:
- "$PWD:/home/q/system/m_look_360_cn"
container_name: "m.look.360.cn-php"
#例如下面这个 Nginx 配置中的php:9000就是利用了这个原理。
server {
listen 80;
server_name dev.m.look.360.cn;
charset utf-8;
root /home/q/system/m_look_360_cn/public;
index index.html index.htm index.php;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ .php$ {
##在每个容器中会将容器的 IP 和服务的名称使用 hosts 的方式绑定
fastcgi_pass php:9000;
#fastcgi_pass unix:/tmp/fcgi.sock;
fastcgi_index index.php;
}
}
1.安装配置
Compose 支持 Linux、macOS、Windows 10 三大平台。
安装方法与流程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#pip方式安装
$sudo pip install -U docker-compose
#采用yum方式安装
$yum install -y docker-compose
#二进制包 在 Linux 上的也安装十分简单,从 官方 GitHub Release 处直接下载编译好的二进制文件即可。
$sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$sudo chmod +x /usr/local/bin/docker-compose
#卸载
sudo rm /usr/local/bin/docker-compose #如果是二进制包方式安装的,删除二进制文件即可。
sudo pip uninstall docker-compose #如果是通过 pip 安装的,则执行如下命令即可删除。
#安装成功测试
$docker-compose --version
docker-compose version 1.18.0, build 8dd22a9
容器中执行:Compose 既然是一个 Python 应用,自然也可以直接用容器来执行它。1
2
3
4
5
6
7
8
9
10
11
12
13
14$ curl -L https://github.com/docker/compose/releases/download/1.8.0/run.sh > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
#其他安装方式
[root@localhost ~]# pip3 -V
pip 9.0.3 from /usr/lib/python3.6/site-packages (python 3.6)
[root@localhost ~]# pip3 install docker-compose
[root@localhost ~]# docker-compose version
docker-compose version 1.25.3, build unknown
docker-py version: 4.1.0
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.1.1c FIPS 28 May 2019
2.命令一览
docker-compose 命令来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。
1 | #docker命令的基本的使用格式是 |
命令选项:
- -f, –file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml可以多次指定。
- -p, –project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。
- –x-networking 使用 Docker 的可拔插网络后端特性
- –x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge
- –verbose 输出更多调试信息。
- -v, –version 打印版本并退出。
命令使用说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22build #构建(重新构建)项目中的服务容器,可以随时在项目目录下运行 docker-compose build 来重新构建服务。
config #验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。
up / down #此命令将会启动/停止 up命令所启动的容器(up是非常重要的),并创建/移除网络
exec #进入指定的容器。
help
images #列出 Compose 文件中包含的镜像。
kill #通过发送 SIGKILL 信号来强制停止服务容器,支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。
logs #查看服务容器的输出以不同颜色来区分,可以通过 --no-color 来关闭颜色。
port #打印某个容器端口所映射的公共端口。
ps #列出项目中目前的所有容器。
pull #拉取服务依赖的镜像。
push #推送服务依赖的镜像到 Docker 镜像仓库。
run #在指定服务上执行一个命令(重点)
scale #设置指定服务运行的容器个数。
start #启动已经存在的服务容器。
pause #暂停一个服务容器。
unpause #恢复处于暂停状态中的服务。
restart #重启项目中的服务。
stop #停止已经处于运行状态的容器,但不删除它。(可通过start启动)
rm #删除所有(停止状态的)服务容器,推荐先执行stop
top #查看各个服务容器内运行的进程。
version
常用命令详解:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47#build格式 docker-compose build [options] [SERVICE...]
选项包括:
--force-rm 删除构建过程中的临时容器。
--no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。
--pull 始终尝试通过 pull 来获取更新版本的镜像。
#run格式为 docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]。
选项:
-d 后台运行容器。
--name NAME 为容器指定一个名字。
--entrypoint CMD 覆盖默认的容器启动指令。
-e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。
-u, --user="" 指定运行容器的用户名或者 uid。
--no-deps 不自动启动关联的服务容器。
--rm 运行命令后自动删除容器,d 模式下将忽略该选项。
-p, --publish=[] 映射容器端口到本地主机。
--service-ports 配置服务端口并映射到本地主机。
-T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。
例如:
$docker-compose run ubuntu ping docker.com #将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。
#up格式 docker-compose up [options] [SERVICE...]
#该up十分强大:它将尝试自动完成包括构建镜像build的工作,(重新)创建服务,启动服务run的工作,并关联服务相关容器的一系列操作。
docker-compose up #前台启动,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。
docker-compose up -d #后台启动,查看信息需要使用logs命令,般推荐生产环境下使用该选项。
选项:
-d 在后台运行服务容器。
--no-color 不使用颜色来区分不同的服务的控制台输出。
--no-deps 不启动服务所链接的容器。
--force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。
--no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。
--no-build 不自动构建缺失的服务镜像。
-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。
#rm格式为 docker-compose rm [options] [SERVICE...]。
选项:
-f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项(使用前建议先stop)
-v 删除容器所挂载的数据卷。
#scale格式为 docker-compose scale [options] [SERVICE=NUM...]
#通过 service=num 的参数来设置数量。例如:
$docker-compose scale web=3 db=2 #将启动 3 个容器运行 web 服务,2 个容器运行 db 服务。
注意:
- 默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。
- 如果用户不希望容器被停止并重新创建,可以使用 docker-compose up –no-recreate。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。
- 如果用户只想重新部署某个服务,可以使用 docker-compose up –no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。
- 使用 docker-compose scale一般的当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之将停止容器。
实际案例:
建议在空目录中建立Dockerfile与docker-compose.yml,并运行docker-compose运行的时候建议在Dockerfile与docker-compose.yml 当前目录下运行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68$docker-compose config #验证Dockerfile脚本-正确返回配置
services:
redis:
image: redis:alpine
web:
build:
context: /opt/app
ports:
- 80:8080/tcp
version: '3.0'
$docker-compose -p FlaskApp build #构建容器
Building web
Step 1/5 : FROM python:3.6-alpine
$docker-compose up -d #后台启动容灾
Creating network "app_default" with the default driver
Building web
Step 1/5 : FROM python:3.6-alpine
---> 35bb01a3d284
Step 2/5 : ADD . /code
$docker-compose images #显示构建运行后的容器
Container Repository Tag Image Id Size
----------------------------------------------------------
app_redis_1 redis alpine 72e76053ebb7 48.5 MB
app_web_1 app_web latest e5024b7bdce1 86.1 MB
$docker-compose ps #项目中目前的容器状态
Name Command State Ports
---------------------------------------------------------------------------
app_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
app_web_1 python app.py Up 0.0.0.0:80->8080/tcp
$docker-compose logs #后台运行的容器信息
Attaching to app_redis_1, app_web_1
web_1 | * Debug mode: on
web_1 | * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
web_1 | * Restarting with stat
web_1 | * Debugger is active!
web_1 | * Debugger PIN: 324-716-279
$docker-compose exec web /bin/sh #进入web服务的容器中(前提是必须运行,退出不影响后台运行的容器)
/code # ls
Dockerfile app.py docker-compose.yml
/code # whoami
root
/code # hostname
2e10bc9c7618
/code #
$docker-compose stop #停止容器
Stopping app_redis_1 ... done
Stopping app_web_1 ... done
$docker-compose rm -v #删除构建的容器
Going to remove app_redis_1, app_web_1
Are you sure? [yN] y
Removing app_redis_1 ... done
Removing app_web_1 ... done
$docker-compose kill -s SIGINT #通过发送 SIGKILL 信号来强制停止服务容器。
补充示例:1
2
3# 更新容器镜像
docker-compose down jenkins_server
docker-compose up -d --build
3.模板文件
标准配置的docker-compose.yaml文件应该包含 version、services、networks 三大部分其中最关键的就是 services 和 networks 两个部分还有就是配置文件的格式
模板文件是使用 Compose 的核心,涉及到的指令关键字也比较多,以下面的案例为例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245#docker-composer版本注意冒号后有个空格
version: '3'
services:
#用户自定义服务名称
web:
#指定服务的镜像名称或镜像ID(先从本地拉取,如果没有找到则从从Docker Hub获取镜像)
image: ubuntu/ubuntu:18.04
#服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径
build:/path/to/build/dir #方式1:存放dockerfile的可以是绝对路径也可以是相对路径
#方式2:设定上下文根目录,然后以该目录为准指定 Dockerfile。
build:
context: ../
dockerfile: path/of/Dockerfile
#在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中再次设置。
args: #使用 arg 指令指定构建镜像时的变量。
buildno: 1
cache_from: #使用 cache_from 指定构建镜像的缓存
- alpine:latest
- corp/web_app:3.14
#自定义容器命名格式是:<项目名称><服务名称><序号>
container_name: app
#lables指令 向容器添加元数据,和Dockerfile的LABEL指令一个意思,格式如下:
labels:
- "com.example.author=weiyigeek"
- "com.example.description=Accounting webapp"
- "com.example.department=Finance"
- "com.example.lrelease=rc3 for v1.0"
#一般项目容器启动的顺序是有要求的, depends_on标签解决了容器的依赖、启动先后的问题。、
#例如下面容器会先启动 redis 和 db 两个服务,最后才启动 web 服务:
depends_on: #定义了依赖关系
- db
- redis
redis:
image: redis
db:
image: postgres
#注意:web 服务不会等待 redis db 「完全启动」之后才启动。
#配置日志选项。
logging:
driver: syslog #目前支持三种日志驱动类型,"none" / "json-file"
options: #配置日志驱动的相关参数。
syslog-address: "tcp://192.168.0.42:123"
max-size: "200k"
max-file: "10"
#ports标签指定映射端口,使用HOST:CONTAINER格式或者只是指定容器的端口,宿主机会随机映射端口。
ports:
- 8080 #注意格式
- "49100:22"
- "127.0.0.1:8001:8001"
#管理全部服务的标签,比如设置全部服务的user标签值为USER,默认属性(用户、角色、类型、级别等)
security_opt:
- label:user:USER
- label:role:ROLE
#设置另一个信号来停止容器,默认情况下是使用SIGTERM停止容器
stop_signal: SIGUSR1
#配置容器连接的网络
networks:
- front-tier
- back-tier
#设置网络模式使用和 docker run 的 --network 参数一样的值(在集群模式下无法使用)。
network_mode: "bridge" # "host" "none" "service:[service name]" "container:[container name/id]"
#和 --dns 参数一样用途,格式如下:
dns:
- 8.8.8.8
- 9.9.9.9
#此外 dns_search 的配置也类似
dns_search:
- dc1.example.com
- dc2.example.com
redis:
image: redis
#tmpfs标签 挂载临时目录到容器内部(挂载一个 tmpfs 文件系统到容器),与 run 的参数一样效果:
tmpfs: /run #两种形式 单个和多个
tmpfs:
- /run
- /tmp
#env_file 标签指定.env 文件来存放设置的 Compose 的变量
#如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中路径会使用配置文件路径。
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
#environment标签设置镜像变量,而且它可以保存变量到镜像/容器里面(类似 docker run -e 的效果)
#如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。
environment:
- RACK_ENV=development
- SHOW=true
- SESSION_SECRET
- MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
#存储敏感数据,例如 mysql 服务密码,需要后面的配置
secrets:
- db_root_password
- my_other_secret
#与Dockerfile中的EXPOSE指令一样,用于指定暴露的端口(只是作为参考实际上常用ports指令)
expose:
- "6379"
#links标签解决的是容器连接问题,与Docker client的--link一样效果,会连接到其它服务中的容器。
#使用的别名将会自动在服务容器中的/etc/hosts里创建
links:
- web
#使用 command 可以覆盖容器启动后默认执行的命令。
command: bundle exec thin -p 3000
#command: [bundle, exec, thin, -p, 3000] #写成类似 Dockerfile 中的格式
#使用entrypoint标签 可以定义接入点
entrypoint: /code/entrypoint.sh
entrypoint:
- php
- -d
- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory_limit=-1
- vendor/bin/phpunit
lb:
image: dockercloud/haproxy
ports:
- 80:80
#extra_hosts 添加主机名的标签,就是往/etc/hosts文件中添加下面的一些记录,与Docker client的--add-host类似:
extra_hosts:
- "somehost:162.242.195.82"
- "otherhost:50.31.209.229"
#让Compose项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。
#链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
links:
- web
#指定父 cgroup 组,意味着将继承该组的资源限制。
cgroup_parent: cgroups_1 #指定父 cgroup 组,意味着将继承该组的资源限制
network_mode: "host" # 使用主机网络模式
#加入指定网络,相同的服务可以在不同的网络有不同的别名。
networks:
- front-tier
- back-tier
#挂载一个目录或者一个已存在的数据卷容器格式[HOSTPATH:CONTAINERPATH:ro] #定路径可以是相对路径,使用 . 或者 .. 来指定相对目录。
#可以宿主机的 $PWD 常量
volumes:
- /var/run/docker.sock:/var/run/docker.sock #使用绝对路径挂载数据卷
- ~/configs:/etc/configs/:ro # 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
- datavolume:/var/lib/mysql # 已经存在的命名的数据卷。
- './nginx/log:/var/log/nginx:rw' # 目录映射权限设置,例如此处的 :ro 是只读不能写
#从其它容器或者服务挂载数据卷可选的参数是 :ro或者 :rw(默认)
volumes_from:
- service_name
- service_name:ro
- container:container_name
- container:container_name:rw
#添加或删除容器的内核功能
cap_add: #指定容器的内核能力(capacity)分配。
- ALL
cap_drop: #去掉 NET_ADMIN 能力可以指定为:
- NET_ADMIN
- SYS_ADMIN
#设备映射列表
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
#通过命令检查容器是否健康运行
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
#跟主机系统共享进程命名空间。
#打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
pid: "host"
#配置容器内核参数
sysctls:
- net.core.somaxconn=32768
- net.ipv4.tcp_syncookies=0
- net.ipv4.ip_forward=1
#指定容器的 ulimits 限制值
#例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
# 在 volumes 中绑定本地目录等同于`docker volume create --driver local --name esdata1`
volumes:
esdata1:
driver: local
datavolume:
driver: local
driver_opts:
type: none
device: $PWD/logs
o: bind
networks:
front-tier:
driver: bridge
back-tier:
driver: bridge
aliases: # 子标签aliases,这是一个用来设置服务别名的标签
- alias1
# 加入已存在的网络(docker network ls)
networks:
mongocluster_default:
external: true
#放在最后
secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true
Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。
环境变量文件中每一行必须符合格式,支持 # 开头的注释行。1
2
3
4
5
6
7
8
9
10# common.env: Set development environment
PROG_ENV=development
MONGO_VERSION=3.6
#例如,下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION} 的值,并写入执行的指令中。
version: "3"
services:
db:
image: "mongo:${MONGO_VERSION}"
4.使用案例
使用案例1:docker-compose 配合Dockerfile构建容器,采用python的flask框架与redis WeiyiGeek. WeiyiGeek.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43#项目结构
├── app.py
├── docker-compose.yml
└── Dockerfile
###############app.py###############
#!/usr/bin/env python
#每次刷新页面,计数就会加 1。
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0",port=8080,debug=True)
########## Docker-compose.yml ##########
version: '3'
services:
web:
#存放了dockerfile目录
build: .
ports:
- "80:8080"
#会进行自连接与网络通信关联-docker-compose的一大特色
redis:
image: "redis:alpine"
########## Dockerfile ##########
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
RUN chown -R $USER:$USER .
CMD ["python", "app.py"]
访问我们构建的服务显示:
使用案例2:采用docker-compose构建博客镜像1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#宿主机直接git clone我的blog项目
git clone https://github.com/WeiyiGeek/blog.git
#建立入口执行shell文件
$nano init.sh
npm config set unsafe-perm true
npm config set registry https://registry.npmmirror.com
npm install -g hexo-cli --save
npm install --save
hexo server
#建立docker-compose.yml文件、
version: "3.0"
services:
blog:
image: "mhart/alpine-node:latest"
container_name: blog
labels:
- "com.example.author=weiyigeek"
- "com.example.description=my blog"
- "com.example.department=IT"
- "com.example.lrelease=v1.0"
ports:
- "80:4000"
volumes:
- "$PWD:/opt/blog/"
working_dir: /opt/blog/
entrypoint: /opt/blog/init.sh #设置的环境变量也由此初始化到脚本之中
注意事项:
如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。
1
2
3
4
5
6
7
8
9
10
11build: ./dir
image: webapp:tag
#Dockerfile 中的 ARG 指令它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:
build:
context: .
#与 ENV 不同的是,ARG 是允许空值的。例如:
args:
- buildno=1
- password=secret
- versionYAML 的布尔值(true, false, yes, no, on, off)必须要使用引号引起来(单引号、双引号均可),否则会当成字符串解析。
如果env_file指定.env文件有变量名称 与 environment 指令冲突则以后者为准。
注意env_file标签的使用,是这里所说的环境变量是对宿主机的 Compose 而言的:
- 如果在配置文件中有 build 操作这些变量并不会进入构建过程中,
- 如果要在构建中使用变量还是首选前面刚讲的 arg 标签。
补充标签与docker run参数一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37configs #仅用于 Swarm mode
deploy #仅用于 Swarm mode
cpu_shares: 73
cpu_quota: 50000
cpuset: 0,1
#指定容器中运行应用的用户名
user: postgresql
#指定容器中工作目录
working_dir: /code
#指定容器中搜索域名、主机名、mac 地址等。
domainname: foo.com
hostname: foo
ipc: host
mac_address: 02:42:ac:11:65:43
#允许容器中运行一些特权命令。
privileged: true
#指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。
restart: always
#以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
read_only: true
shm_size: 64M
#打开标准输入,可以接受外部输入。
stdin_open: true
#模拟一个伪终端。
tty: true
mem_limit: 1000000000
memswap_limit: 2000000000
使用案例3:采用docker-compose构建 MySQL / Redis / Adminer
;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68version: '3.1'
services:
db8:
image: mysql
container_name: mysql8.x
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: www.weiyigeek.top
MYSQL_DATABASE: test
MYSQL_USER: test8
MYSQL_PASSWORD: weiyigeek.top
volumes:
- "/app/mysql8:/var/lib/mysql"
ports:
- 3306:3306
networks:
- database-net
db5:
image: mysql:5.7.29
container_name: mysql5.x
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: www.weiyigeek.top
MYSQL_DATABASE: test
MYSQL_USER: test5
MYSQL_PASSWORD: weiyigeek.top
volumes:
- "/app/mysql5:/var/lib/mysql"
ports:
- 3305:3306
networks:
- database-net
redis:
image: redis
container_name: redis
restart: always
command: redis-server --requirepass weiyigeek.top
volumes:
- "/app/redis:/data"
ports:
- 6379:6379
adminer:
image: adminer
container_name: MySQLManager
restart: always
ports:
- 8888:8080
networks:
- database-net
links:
- db8:mysql8.x
- db5:mysql5.7
depends_on:
- db8
- db5
#新建(注意不在services中)它会自动创建以当前目录+database-net名称的docker网络名称。
# networks:
# database-net:
# driver: bridge
# 指定已存在的网络(推荐使用前必须进行创建否则报错)
# docker network create -d bridge mysql_database-net
networks:
mysql_database-net:
external: true
Tips : 此时若还要docker容器间进行相互通讯需要进行配置防火墙信任 docker 的 ip 地址
,比如:firewall-cmd --zone=trusted --add-source=172.17.0.1/16 --permanent
,如果部署iptable报错请重启docker;
5.学习补充
1) Docker-compose 设置容器数据持久化的方式
描述: 我常常利用 docker-compose 进行容器的创建,它有两种设置方式都是可以持久化的;
第一种情况路径直接挂载到本地,比较直观,但需要管理本地的路径。
1
2
3
4demo1:
image: weiyigeek/nginx:latest
volumes:
- ./ghost/config.js:/var/lib/ghost/config.js第二种使用卷标的方式,比较简洁,但你不知道数据存在本地什么位置需要通过docker命令查看标卷。
1
2
3
4
5
6
7
8
9services:
mysql:
image: mysql
container_name: mysql
volumes:
- mysql:/var/lib/mysql
...
volumes:
mysql: {}
Q: 如何查看docker的卷标?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 1) 查看所有卷标
docker volume ls
# 2) 查看批量的卷标
$ docker volume ls | grep mysql
local vagrant_mysql
# 3) 查看具体的volume对应的真实地址
$ docker volume inspect vagrant_mysql
[
{
"Name": "vagrant_mysql",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/vagrant_mysql/_data"
}
]