[关闭]
@octopus 2020-09-14T10:36:11.000000Z 字数 29252 阅读 870

docker

docker


Image

1

1. 操作系统的组成

由此可见对于不同的 linux 发行版,bootfs 基本是一致的,rootfs 会有差别,因此不同的发行版可以公用 bootfs。所以在docker下载一个操作系统image很小,是因为不带 kernel。

2. 什么是 Image

3. 获取 Image

docker hub

可以通过自定义 dockerfile 生成 image,也可以使用已有的image,从 registry(仓库)中获取。docker hub 是 docker 提供的免费仓库。

  1. // 拉取镜像,14.04版本的ubuntu
  2. docker pull ubuntu:14.04

Container

1

1. 什么是 container

  1. docker run hello-world

2. docker image build 原理

Docker 网络

linux 网络的命名空间(network namespace)

  1. # 启动一个容器 test1,在后台一直运行
  2. > docker run -d --name test1 centos /bin/sh -c "while true;do sleep 3600;done"
  3. # 交互式进入容器中
  4. > docker exec -it 14a7a82f5351 /bin/sh
  5. # 查看网络信息
  6. > ip a
  7. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  8. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  9. inet 127.0.0.1/8 scope host lo
  10. valid_lft forever preferred_lft forever
  11. 8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  12. link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  13. inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
  14. valid_lft forever preferred_lft forever
  1. # 启动一个容器 test2,后台运行
  2. > docker run -d --name test2 centos /bin/sh -c "while true;do sleep 3600;done"
  3. # 查看网络信息
  4. > docker exec a566bb8 ip a
  5. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  6. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  7. inet 127.0.0.1/8 scope host lo
  8. valid_lft forever preferred_lft forever
  9. 10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  10. link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  11. inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
  12. valid_lft forever preferred_lft forever
  13. # 退出容器
  14. > exit
  1. # 查看本地网络
  2. > ip a
  3. lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. inet 127.0.0.1/8 scope host lo
  6. valid_lft forever preferred_lft forever
  7. inet6 ::1/128 scope host
  8. valid_lft forever preferred_lft forever
  9. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
  10. link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
  11. inet 172.21.0.3/20 brd 172.21.15.255 scope global eth0
  12. valid_lft forever preferred_lft forever
  13. inet6 fe80::5054:ff:fe39:1f8/64 scope link
  14. valid_lft forever preferred_lft forever
  15. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  16. link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
  17. inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
  18. valid_lft forever preferred_lft forever
  19. inet6 fe80::42:17ff:fed2:baaa/64 scope link
  20. valid_lft forever preferred_lft forever
  21. 9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  22. link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  23. inet6 fe80::882f:81ff:fef0:3dd9/64 scope link
  24. valid_lft forever preferred_lft forever
  25. 11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  26. link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
  27. inet6 fe80::82:daff:fe12:b02d/64 scope link
  28. valid_lft forever preferred_lft forever

1

可以看到,每创建一个容器,就创建了一个 network namespace,不同容器的网络环境是隔离的,与本地环境也是隔离的,各自有各自的ip。 在 test1 与 test2 中可以互相 ping 通,互相通信,在本地也能与两个 container 互通。原理是什么,怎么实现的?

模拟两个命名空间的通信 / 容器与宿主机的通信

1. 创建本地的网络命名空间

  1. > ip netns add test1 # 创建一个本地网络命名空间,名字是 test1
  2. > ip netns add test2
  3. > ip netns list # 列出本地网络命名空间
  4. test1
  5. test2
  6. > ip netns delete test1 # 这步不必运行,只是写出删除ns的方法。
  7. > ip netns exec test1 ip a # 查看test1网络环境
  8. 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
  9. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  10. # 可以看到现在 test1 的网络只有一个 lo 回环口,状态是 down,没有运行
关于 ip link:
- 1.查看
    ip link 操作网络7层中的链路层,只能看链路层的状态,看不到ip地址
- 2.启用、禁用接口
    ip link set device down   禁用指定接口
    ip link set device up    启用指定接口
    比如禁用eth0就是ip link set eth0 down
  1. # 使用 ip link 将 lo 启动
  2. > ip netns exec test1 ip link set dev lo up
  3. > ip netns exec test1 ip a
  4. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  5. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  6. inet 127.0.0.1/8 scope host lo
  7. valid_lft forever preferred_lft forever
  8. inet6 ::1/128 scope host
  9. valid_lft forever preferred_lft forever
  10. # lo 的状态就变为 UNKNOWN ,是因为单个端口是无法完全 up 起来,像网线一样需要有另一端进行连接,是一对。

2. 准备 veth-pair 接口

1

    veth-pair 就是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连着。 
    它的作用很简单,就是要把从一个 network namespace 发出的数据包转发到另一个 namespace。
  1. # 在本地创建一对 Veth 接口
  2. > ip link add veth-test1 type veth peer name veth-test2
  3. > ip a
  4. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  5. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  6. inet 127.0.0.1/8 scope host lo
  7. valid_lft forever preferred_lft forever
  8. inet6 ::1/128 scope host
  9. valid_lft forever preferred_lft forever
  10. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
  11. link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
  12. inet 172.21.0.3/20 brd 172.21.15.255 scope global eth0
  13. valid_lft forever preferred_lft forever
  14. inet6 fe80::5054:ff:fe39:1f8/64 scope link
  15. valid_lft forever preferred_lft forever
  16. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  17. link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
  18. inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
  19. valid_lft forever preferred_lft forever
  20. inet6 fe80::42:17ff:fed2:baaa/64 scope link
  21. valid_lft forever preferred_lft forever
  22. 9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  23. link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  24. inet6 fe80::882f:81ff:fef0:3dd9/64 scope link
  25. valid_lft forever preferred_lft forever
  26. 11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  27. link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
  28. inet6 fe80::82:daff:fe12:b02d/64 scope link
  29. valid_lft forever preferred_lft forever
  30. 12: veth-test2@veth-test1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
  31. link/ether d2:6b:17:27:8c:21 brd ff:ff:ff:ff:ff:ff
  32. 13: veth-test1@veth-test2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
  33. link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff
  34. # 12 13 就是刚才新建的一对 veth 接口

3. 将 veth-pair 接口分别添加到两个命名空间中

  1. # 将 veth 接口的一端添加到 test1 命名空间中
  2. > ip link set veth-test1 netns test1
  3. # 查看 test1 的网络链路
  4. > ip netns exec test1 ip link
  5. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
  6. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  7. 13: veth-test1@if12: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
  8. link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff link-netnsid 0
  9. # 会发现 test1 中多了一个 veth-test1@if12 接口
  10. # 同时查看本地 ip link,发现 veth-test1 已经没有了。
  11. > ip link
  12. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
  13. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  14. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
  15. link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
  16. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
  17. link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
  18. 9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
  19. link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  20. 11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
  21. link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
  22. 12: veth-test2@if13: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
  23. link/ether d2:6b:17:27:8c:21 brd ff:ff:ff:ff:ff:ff link-netnsid 2
  24. # 同时向 test2 中加入接口 veth-test2
  25. > ip link set veth-test2 netns test2

4. 启动 veth-pair 接口,并分配 ip 地址,互相请求

  1. # 这样 veth 的两端就布置好了,接下来把两端接口启动,并为接口分配 ip 地址
  2. > netns exec test1 ip link set dev veth-test1 up
  3. > ip netns exec test2 ip link set dev veth-test2 up
  4. > ip netns exec test1 ip addr add 192.168.1.1/24 dev veth-test1
  5. > ip netns exec test2 ip addr add 192.168.1.2/24 dev veth-test2
  6. # 查看命名空间 test1 的网络信息
  7. > ip netns exec test1 ip a
  8. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  9. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  10. inet 127.0.0.1/8 scope host lo
  11. valid_lft forever preferred_lft forever
  12. inet6 ::1/128 scope host
  13. valid_lft forever preferred_lft forever
  14. 13: veth-test1@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
  15. link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff link-netnsid 1
  16. inet 192.168.1.1/24 scope global veth-test1
  17. valid_lft forever preferred_lft forever
  18. inet6 fe80::6868:78ff:fecc:2e/64 scope link
  19. valid_lft forever preferred_lft forever
  20. # 发现他的 status 是 up 并且已经配置上了 ip 地址,test2 也是同理,并且二者可以互通。
  21. > ip netns exec test1 ping 192.168.1.2
  22. PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
  23. 64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.056 ms
  24. 64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.044 ms

总结

1

用容器名访问其他容器

需求:在容器1中启动mysql服务器,在容器2中连接,容器2并不知道容器1的ip,想通过容器名即可访问,如:host = containet1:3306

  1. # 用 --link 指定绑定ip,相当于 host 名->ip 的绑定
  2. > docker run -d --name test2 --link test1 centos /bin/bash -c "while true; do sleep 3600; done"
  3. > ping test1
  4. PING test1 (172.17.0.2) 56(84) bytes of data.
  5. 64 bytes from test1 (172.17.0.2): icmp_seq=1 ttl=64 time=0.068 ms
  6. 64 bytes from test1 (172.17.0.2): icmp_seq=2 ttl=64 time=0.055 ms
  7. # 注意 link 是单向的, test2 绑定了 test1的地址,但 test1 没有绑定 test2,所以 test1 中无法用 test2 的名字来直接 ping 通

新建桥接网络

还有一种情况也可以实现,即自定义桥接接口,将容器都绑定到同一个桥接接口上,则可互相用名字访问,同一个容器可绑定多个桥接接口。

yum install -y bridge-utils # 安装查看桥接网络设备工具

  1. # 查看本地所有的网络设备
  2. > docker network ls
  3. NETWORK ID NAME DRIVER SCOPE
  4. 89a165910cb3 bridge bridge local
  5. 154e5b3bdf2b host host local
  6. fef1a552d8c9 none null local
  7. # bridge 类型的就是 docker0,前面提到过,容器的 veth 连接本地的一端默认就是连接到 docker0 上的。

host 与 none 类型的接口并不常用,连接到 none 的容器是独立的命名空间,只有 lo 回环接口。连接到 host 是与本地宿主机共享命名空间,ip a 显示的与宿主机的 ip a 一模一样,可能会有端口冲突问题。

  1. # 新建一个 bridge 接口( -d 表示设备类型)
  2. > docker network create -d bridge my-bridge
  3. > sudo docker network ls
  4. NETWORK ID NAME DRIVER SCOPE
  5. 89a165910cb3 bridge bridge local
  6. 154e5b3bdf2b host host local
  7. 91d3b1e839ce my-bridge bridge local
  8. fef1a552d8c9 none null local
  9. # 用 brctl 命令查看所有桥接接口(brctl需要yum安装)
  10. > brctl show
  11. bridge name bridge id STP enabled interfaces
  12. br-91d3b1e839ce 8000.0242a89aae58 no
  13. docker0 8000.024217d2baaa no vethd68e0d0
  14. vethbaa637c
  15. # 可以看到有一个新接口 br-91d3b1e839ce id 和 docker network 中的吻合,它此时没有接口绑定(查看interfaces)。

启动新容器,并绑定新的桥接网络

  1. # 用 --network 指定要绑定的接口,默认会绑定到 docker0 上
  2. > docker run -d --name test3 --network my-bridge centos /bin/bash -c "while true; do sleep 3600; done"
  3. # 查看桥接设备,br-91d3b1e839ce 上已经有接口绑定
  4. > brctl show
  5. bridge name bridge id STP enabled interfaces
  6. br-91d3b1e839ce 8000.0242a89aae58 no veth030ca97
  7. docker0 8000.024217d2baaa no vethd68e0d0
  8. vethbaa637c
  9. # 将 test2 (现绑定在docker0上)也绑定到新桥接接口上
  10. > sudo docker network connect my-bridge test2
  11. # 可以看到 test2 容器有两个网络接口
  12. > docker exec test2 ip a
  13. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  14. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  15. inet 127.0.0.1/8 scope host lo
  16. valid_lft forever preferred_lft forever
  17. 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  18. link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  19. inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
  20. valid_lft forever preferred_lft forever
  21. 19: eth1@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  22. link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  23. inet 172.18.0.3/16 brd 172.18.255.255 scope global eth1
  24. valid_lft forever preferred_lft forever
  25. # 连接在同一自定义桥接接口上的两个容器可以通过名字互ping通
  26. > docker exec test2 ping test3
  27. PING test3 (172.18.0.2) 56(84) bytes of data.
  28. 64 bytes from test3.my-bradge (172.18.0.2): icmp_seq=1 ttl=64 time=0.048 ms
  29. 64 bytes from test3.my-bradge (172.18.0.2): icmp_seq=2 ttl=64 time=0.061 ms

端口映射

需求:启动一个nginx容器,映射到本地的 80 端口,使访问本地 80 端口时可以转发到容器中的 nginx 进行处理

  1. > docker pull nginx
  2. > docker run --name web -d -p 80:80 nginx
  3. # -d 后台运行
  4. # -p 端口映射到本地。第一个 80 是容器中的 80 端口,第二个是指映射到本地的 80 端口
  5. > curl 127.0.0.1
  6. <title>Welcome to nginx!</title>

多机容器通信

etcd + docker overlay 网络

Docker 持久化数据存储

需求:启动一个 mysql 容器,但是数据表/数据不想因容器的停止删除而消失。

mysql 默认挂载的匿名卷

  1. ...
  2. VOLUME /var/lib/mysql
  3. ...
  4. EXPOSE 3306
  5. CMD ["mysqld"]
VOLUME 即把容器中 /var/lib/mysql 目录挂载到宿主机的匿名卷下,实现数据持久化。具体位置因 docker 版本不同而改变。当前测试环境下,docker 会把匿名卷默认绑定到 /var/lib/docker/volumes/ 下。
  1. # 启动一个 mysql 容器,指定 root 密码为 123(-e MYSQL_ROOT_PASSWORD=123)
  2. > docker run -d -e MYSQL_ROOT_PASSWORD=123 mysql
  3. # 查看本地 volume
  4. > docker volume ls
  5. DRIVER VOLUME NAME
  6. local 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c
  7. # 查看 volume 详细信息
  8. > docker volume inspect 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c
  9. [
  10. {
  11. "CreatedAt": "2020-09-06T11:29:42+08:00",
  12. "Driver": "local",
  13. "Labels": null,
  14. "Mountpoint": "/var/lib/docker/volumes/3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c/_data",
  15. "Name": "3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c",
  16. "Options": null,
  17. "Scope": "local"
  18. }
  19. ]
  20. # 根据 Mountpoint 可以看到容器数据实际挂在到了本地的 /var/lib/docker/volumes/3472...
  21. # 停止删除容器后,docker volume 不会被删除。
  22. > docker volume rm 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c

假如有一个 mysql 容器,生成了持久化数据,被停止删除后,第二个新的 mysql 容器想使用之前的数据,就要指定挂载位置。

  1. # 先去 mysql dockerfile 中了解到, volumn 挂载目录是 /var/lib/mysql
  2. # 然后指定已有的数据卷作为挂载目录
  3. docker run -d -v 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql

这样就会发现,匿名卷名字太长,也没有辨识度,所以最好在一开始就指定 -v new_name ,当发现 new_name 不存在,就会将 new_name 作为匿名卷名。

  1. # 启动 mysql 容器1,指定挂载目录名为 mysql
  2. > docker run -d -v mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql
  3. > docker volume ls
  4. DRIVER VOLUME NAME
  5. local mysql
  6. > docker volume inspect mysql
  7. [
  8. {
  9. "CreatedAt": "2020-09-06T12:15:11+08:00",
  10. "Driver": "local",
  11. "Labels": null,
  12. "Mountpoint": "/var/lib/docker/volumes/mysql/_data",
  13. "Name": "mysql",
  14. "Options": null,
  15. "Scope": "local"
  16. }
  17. ]
  18. # 进行建库建表插入数据等等操作后...
  19. # 启动 mysql 容器2,使用之前的数据
  20. > docker run --name mysql2 -d -v mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql

Docker Compose

为什么要使用 docker compose

部署 WordPress

  1. # 启动 mysql
  2. docker run -d --name mysql -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 -e MYSQL_DATABASE=wordpress mysql
  3. #启动 wordpress
  4. docker run -d -e WORDPRESS_DB_HOST=mysql:3306 --link mysql -p 8080:80 wordpress

当一个 app 依赖太多容器的时候,在部署/管理/停止都很难维护,如果能将 app 依赖的容器分组,统一管理就很方便,Docker Compose 登场。

  • docker compose 是一个工具
  • 这个工具可以通过一个 yml 文件定义多容器的 docker 应用
  • 通过一条命令就可以根据 yml 文件的定义去创建或者管理多个容器

docker compose 三大概念

Services

  1. services:
  2. db: # 容器的名字
  3. image: mysql:latest # 依赖外部镜像mysql
  4. volumes:
  5. - "db-data:/var/lib/mysql" # 相当于 -v
  6. network:
  7. - new-bridge # 相当于 --network
  1. services:
  2. worker:
  3. build: ./worker
  4. links: # 通常不会这样使用,仅限于有依赖外部容器时
  5. - db
  6. - redis
  7. network:
  8. - new-bridge

Volume

  1. volumes:
  2. db-data:
  3. # 等于运行语句:docker volume create db-data

Network

  1. networks:
  2. new-bridge:
  3. driver: bridge
  4. other-bridge:
  5. driver: bridge
  6. # 等于运行语句:docker network create -d bridge new-bridge

通过 docker compose 部署 WordPress

不同版本的 copmpse 对应的 version 与 yml 语法不同,详见 http://docs.docker.com/compose/compose-file/#upgrading

  1. # docker-compose.yml
  2. version: "3.1"
  3. services:
  4. wordpress:
  5. image: wordpress
  6. ports:
  7. - 8080:80
  8. environment:
  9. WORDPRESS_DB_HOST: mysql
  10. WORDPRESS_DB_PASSWORD: root
  11. networks:
  12. - new_bridge
  13. mysql:
  14. image: mysql
  15. environment:
  16. MYSQL_DATABASE: wordpress
  17. MYSQL_PASSWORD: 123
  18. volumes:
  19. - db_data:/var/lib/mysql
  20. networks:
  21. - new_bridge
  22. volumes:
  23. db_data:
  24. networks:
  25. new_bridge:
  26. driver: bridge

启动

windows 与 mac 默认已经安装了docker-compose,linux 需要手动下载安装。

# linux
> sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 授权
> chmod 777 /usr/local/bin/docker-compose
  1. # 自动在当前目录寻找名为 docker-compose.yml 的配置文件来启动容器和安装服务。
  2. > docker-compose up
  3. # 名字不为默认的 docker-compose.yml 的配置文件要指定
  4. > docker-compose -f xxx.yml up

开始 & 停止 & 停止并删除 & 进入容器

  1. # 查看所有docker-compose服务
  2. > docker-compose ps
  3. > docker-compose ps -d # 后台
  4. # 停止
  5. > docker-compose stop
  6. # 开始
  7. > docker-compose start
  8. # 停止并删除
  9. > docker-compose down
  10. # 进入容器
  11. > docker-compose exec mysql bash

Swarm 集群

相关概念

Swarm在调度(scheduler)节点(leader节点)运行容器的时候,会根据指定的策略来计算最适合运行容器的节点,目前支持的策略有:spread, binpack, random.

相关命令

  1. docker swarm:集群管理,子命令有init, join, leave, update。(docker swarm --help查看帮助)
  2. docker service:服务创建,子命令有create, inspect, update, remove, tasks。(docker service--help查看帮助)
  3. docker node:节点管理,子命令有accept, promote, demote, inspect, update, tasks, ls, rm。(docker node --help查看帮助)

部署

为了方便学习和实验,环境可以使用 play with docker ,缺点是环境只能保存4小时

准备环境

IP:192.168.0.1 主机名:manager 担任角色:swarm manager
IP:192.168.0.2 主机名:node1 担任角色:swarm node
IP:192.168.0.3 主机名:node2 担任角色:swarm node

初始化 manager

  1. # manager
  2. > docker swarm init --advertise-addr 192.168.0.1
  3. # 提示
  4. Swarm initialized: current node (nndlldwxemn7du1hjxzq33myp) is now a manager.
  5. To add a worker to this swarm, run the following command:
  6. docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
  7. To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

部署 work 节点

根据提示,如果要部署 work 节点,就在 work 节点上运行

  1. docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
  1. # 节点1
  2. > docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
  3. This node joined a swarm as a worker.
  4. # 节点2
  5. > docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
  6. This node joined a swarm as a worker.

在 manager 上查看所有节点

注意 docker node ls 只能在 manager 中运行,查看

  1. > docker node ls
  2. ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
  3. nndlldwxemn7du1hjxzq33myp * node1 Ready Active Leader 19.03.11
  4. ghcm5w5mtxmscmh1eauz988tt node2 Ready Active 19.03.11
  5. tecycccl80lsub2maw7fyt0jp node3 Ready Active 19.03.11

Service

创建 Service

用 swarm 创建容器,就是创建 Service,因为 swarm 会根据指定的策略来计算最适合运行容器的节点,所以不能用 docker run 来随便启一个容器,要用 swarm 命令来创建Service(容器)

  1. > docker service create --name mysql -d -e MYSQL_ROOT_PASSWORD=123 mysql

查看 Service 信息

可以查看 Service 状态、运行在哪台服务器上

  1. > docker service ps mysql # --name 指定的 service 名
  2. ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
  3. d5m5hnjsfk7e mysql.1 mysql:latest node1 Running Running 57 seconds ago
  4. # 服务跑在 node1 上

水平扩展 sevice

  1. # 水平扩展 3 个 mysql 服务
  2. > docker service scale=3
  1. > docker service ls
  2. ID NAME MODE REPLICAS IMAGE PORTS
  3. 9sgbsqhez1j6 mysql replicated 0/1 mysql:latest

删除 Service

  1. docker service rm mysql

部署 wordpress

创建 overlay 网络

  1. # manager
  2. > docker network create -d overlay demo
  3. # manager 查看网络
  4. > docker network ls
  5. NETWORK ID NAME DRIVER SCOPE
  6. 4deb8704d751 bridge bridge local
  7. xmr31rtdnhqv demo overlay swarm
  8. fae16beabc0f docker_gwbridge bridge local
  9. 88e0da999c0d host host local
  10. 63gq7lykqe20 ingress overlay swarm
  11. b1b0a7aa9ea7 none null local
  12. # node1 & node2 查看网络
  13. > docker network ls
  14. NETWORK ID NAME DRIVER SCOPE
  15. 4deb8704d751 bridge bridge local
  16. fae16beabc0f docker_gwbridge bridge local
  17. 88e0da999c0d host host local
  18. 63gq7lykqe20 ingress overlay swarm
  19. b1b0a7aa9ea7 none null local

在 manager 上创建的 overlay 并不会同步到其他节点上。

创建 mysql 服务

  1. # manager
  2. > docker service create --name mysql --env MYSQL_ROOT_PASSWORD=123 --env MYSQL_DATABASE=wordpress --network demo --mount type=volume,source=mysql-data,destination=/var/lib/mysql mysql

注意用 service 创建的服务,以前的 docker run -e xxx -v xxx 中设置环境变量用 -e 就要变成 --env ,设置卷使用的 -v mysql-data:/var/lib/mysql 要变成 --mount type=volume,source=mysql-data,destination=/var/lib/mysql

创建 wordpress 服务

  1. # manager
  2. > docker service create --name wordpress -p 80:80 --network=demo --env WORDPRESS_DB_HOST=mysql --env WORDPRESS_DB_PASSWORD=123 wordpress

访问网站

manager_ip:80   ok
node1_ip:80     ok
node2_ip:80     ok

查看各个节点的网络

  1. # manager 查看网络
  2. > docker network ls
  3. NETWORK ID NAME DRIVER SCOPE
  4. 4deb8704d751 bridge bridge local
  5. xmr31rtdnhqv demo overlay swarm
  6. fae16beabc0f docker_gwbridge bridge local
  7. 88e0da999c0d host host local
  8. 63gq7lykqe20 ingress overlay swarm
  9. b1b0a7aa9ea7 none null local
  10. # node1
  11. > docker network ls
  12. NETWORK ID NAME DRIVER SCOPE
  13. 4deb8704d751 bridge bridge local
  14. xmr31rtdnhqv demo overlay swarm
  15. fae16beabc0f docker_gwbridge bridge local
  16. 88e0da999c0d host host local
  17. 63gq7lykqe20 ingress overlay swarm
  18. b1b0a7aa9ea7 none null local
  19. # node2
  20. > docker network ls
  21. NETWORK ID NAME DRIVER SCOPE
  22. ca3d7f954ec2 bridge bridge local
  23. 9974b890ab6e docker_gwbridge bridge local
  24. 7ccd52d74607 host host local
  25. 63gq7lykqe20 ingress overlay swarm
  26. 03f3304afa87 none null local

manager 与 node1 同步了 名为 demo 的 overlay 网络(mysql 与 wordpress 分别运行在 manager 与 node1 上),而 node2 没有同步 overlay。但是三个节点服务器都可以正常通过各自 ip:80 访问 wordpress 网站。那么 swarm 集群的通信原理是什么呢?

RoutingMesh - Ingress 网络

以请求 3个节点中没有任何服务的 node3节点为例,node3 中没有任何服务,node1 & node2 中启动了水平扩展的 wordpress 服务,node1 中有 mysql 服务。

  1. > iptables -nL -t mangle # 查看 iptabls 的转发规则
  2. target prot opt source destination
  3. DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.20.0.2:80
  4. > ip a
  5. docker_gwbridge:
  6. inet 172.20.0.1/16
  1. > nsenter --net=/var/run/docker/netns/ingress_sbox # 进入命名空间
  2. > iptables -nL -t mangle
  3. target prot opt source destination
  4. MARK tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 MARK set 0x10b
  5. > ipvsadm -l
  6. IP Virtual Server version 1.2.1 (size=4096)
  7. Prot LocalAddress:Port Scheduler Flags
  8. -> RemoteAddress:Port Forward Weight ActiveConn InActConn
  9. FWM 271 rr
  10. -> 10.0.0.27:0 Masq 1 0 0
  11. -> 10.0.0.28:0 Masq 1 0 0

DNS + VIP + iptables + LVS

  1. > nslookup mysql # 10.0.0.1
  2. # 查看真实转发 ip
  3. > nslookup tasks.mysql # 列出所有 mysql 所在节点的虚拟 ip

总结:
容器与容器之间通过 overlay 网络实现通信
服务与服务间通过 Ingress 实现通信

swarm + docker compose 部署

docker-compose.yml

docker compose 要运行在 swarm 上,有一个重要的配置项:deploy。该配置项是 docker compose 3.0以上版本才支持。deploy 重点参数:

  1. # docker-compose.yml
  2. version: "3.1"
  3. services:
  4. web:
  5. image: wordpress
  6. ports:
  7. - 8080:80
  8. environment:
  9. WORDPRESS_DB_HOST: mysql
  10. WORDPRESS_DB_PASSWORD: root
  11. networks:
  12. - new_network
  13. deploy:
  14. model:replicated
  15. replicas:3
  16. mysql:
  17. image: mysql
  18. environment:
  19. MYSQL_DATABASE: wordpress
  20. MYSQL_PASSWORD: 123
  21. volumes:
  22. - db_data:/var/lib/mysql
  23. networks:
  24. - new_network
  25. deploy:
  26. model:global
  27. placement:
  28. constrints:
  29. - node.role == manager
  30. volumes:
  31. db_data:
  32. networks:
  33. new_network:
  34. driver: overlay

docker stacks

在 swarm 上用 compose 安装服务要使用 docker stacks 命令

  1. docker stack deploy name --compose-file=docker-compose.yml

Dockerfile

1. From

  1. FROM scratch // 无base image,制作的就是一个base image
  2. FROM centos // 使用 centos 作为 base image
  3. FROM ubuntu:14.04

注意,最好使用官方的 image 作为 base image

2. Label

  1. LABEL maintainer = "zy@gmail.com"
  2. LABEL version = "1.0"
  3. LABEL description = "this is description"

3. Run

  1. RUN yum update && yum -y install vim \
  2. python-dev

4. Workdir

  1. WORKDIR /test # 如果没有该目录则会自动创建
  2. WORKDIR demo
  3. RUN pwd # 结合上两条,应该输出 /test/demo

注意最好不要使用 RUN cd,尽量使用绝对目录。

5. Add & Copy

  1. # 当前文件夹下有一个文件 hello.php,和一个 test.tar.gz
  2. ADD hello.php / # 添加到image的跟目录
  3. ADD test.tar.gz / # 解压缩 test.tar.gz 并添加到跟目录
  4. WORKDIR /root
  5. ADD hello.php test/ # 文件会被放在:/root/test/hello.php
  6. COPY hello.php / # copy 的使用方式与 add 完全一致

大部分情况下,copy 优于 add。
add 除了有 copy 的功能,还有解压功能
添加远程文件请用 wget 或 curl

6. ENV

  1. ENV MYSQL_VERSION 5.6
  2. RUN apt-get install -y mysql-server = "${MYSQL_VERSION}"

尽量多用 ENV ,可以增加可维护性

7. CMD

  1. CMD echo "hello docker"
  2. > docker run image # hello docker
  3. > docker run image /bin/bash # 不会输出hello docker,因为指定了其他命令

如果docker run 指定了其他命令,CMD命令将被忽略
如果定义了多个 CMD ,则只有最后一个会被执行

8. Entrypoint

  1. COPY entrypoint.sh /usr/local/bin/
  2. ENTRYPOINT ["entrypoint.sh"]
  3. EXPOSR 27017
  4. CMD ["mongod"]

Entrypoint 不会被忽略,一定会被执行

9. Entrypoint + CMD 实现启动容器时传参数

  1. FROM ubuntu
  2. RUN apt-get install -y stress
  3. ENTRYPOINT ["/usr/bin/stress"] # 启动时一定会运行的命令
  4. CMD [] # 启动后执行,空则会收集参数
  5. > docker run -it xxx/stress --vm 1 --verbose

10. VOLUME

  1. VOLUME /var/lib/mysql

内网私有docker仓库搭建

1. 前期准备

2. 搭建 docker 仓库

  1. docker run -d -p 5000:5000 --restart=always --name registry registry:2

3. 用户主机配置信任私有仓库

  1. > vim /etc/docker/daemo.json
  2. {
  3. "insecure-registries" : ["196.168.0.1:5000"]
  4. }
  5. > vim /etc/systemd/system/docker.service.d
  6. EnvironmentFile=/etc/docker/daemo.json # 新加一行环境文件
  7. > systemctl restart docker # 重启docker

4. 用户主机创建镜像并推送

  1. docker build -t 196.168.0.1:5000/test .
  2. docker push 196.168.0.1:5000/test

命令

docker pull

  1. > docker pull ubuntu:14.04

docker image ls / docker images

  1. > docker image ls
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. hello-world latest bf756fb1ae65 8 months ago 13.3kB

docker image rm image_id / docker rmi image_id

  1. docker image rm bf756fb1ae65

docker image build / docker build

  1. // vim Dockerfile
  2. From centos
  3. Run yum -y install vim
  4. // -t 标签 . 表示在当前路径寻找 dockerfile
  5. > docker image build -t centos_vim .

docker image push / docker push

  1. docker push cnoctopus/hello-world:latest
  • 注意,镜像的 tag 必须以用户名/开头,不然没有权限。
  • 不推荐直接推自己 build 的 image,因为会有安全性问题。建议在 docker hub 绑定 github 仓库,然后提交 dockerfile 到 github,docker会自动从绑定的 github 仓库拉取 dockerfile 并生成 image,其他人可以看到 dockerfile,image又是官方生成,安全性提高。

docker container commit / docker commit

  1. docker container commit container_name image_name:tag

docker run

  1. docker run hello-world
  2. docker run -d hello-world # -d 在后台运行
  1. # 容器使用的总内存数 = memory + memory-swap
  2. # memory-swap 默认等于 memory 的大小
  3. docker run --memory=200M hello-world # 即总可用内存为400M
  1. docker run --name test -e REDIS_HOST=196.168.0.1 centos ...
  2. # 进入容器,使用 env 命令可以查看所有环境变量
  1. # 以 nginx 为例,启动一个 nginx 容器
  2. > docker run --name nginx -d -p 80:80 nginx
  3. # 查看是否正常运行
  4. > curl 127.0.0.1
  5. <title>Welcome to nginx!</title>
  6. # 查看 nginx dockerfile 可知,index.html 等网站文件是放在 /usr/share/nginx/html/ 中,进入 bash 内部查看:
  7. > docker exec -it nginx /bin/bash
  8. > ls /usr/share/nginx/html/
  9. 50x.html index.html
  10. # 已知开发目录在本地的 /root/tmp/ ,通过 -v 来将目录挂载到 容器的 /usr/share/nginx/html/ 实现数据同步。
  11. > docker run --name nginx -v /root/tmp:/usr/share/nginx/html -d -p 80:80 nginx
  12. > ls /root/tmp # 空
  13. > docker exec nginx ls /usr/share/nginx/html # 空
  14. # 所以,挂载后,本地目录会覆盖容器目录,因为本地目录原本是空的,那么容器中的目标目录也会用本地同步,最后变成空目录。
  15. > curl 127.0.0.1
  16. <title>403 Forbidden</title>
  17. # 在本地新建 index.html,输出 hello world
  18. > echo "hello world" > /root/tmp/index.html
  19. # 查看容器挂载的目录
  20. > docker exec nginx ls /usr/share/nginx/html
  21. index.html
  22. > curl 127.0.0.1
  23. hello world

docker exec

  1. docker exec -it container_id /bin/bash # 交互式运行/bin/bash进入容器bash
  2. docker exec -it container_id python # 交互式进入容器运行python命令
  3. docker exec -it container_id ip a # 查看运行容器的ip

docker container ls -a / docker ps -a

  1. // 只列出当前在运行容器
  2. docker container ls
  3. // 在运行与已退出容器全部列出
  4. docker container ls -a
  5. CONTAINER ID IMAGE COMMAND CREATED STATUS ...
  6. ca8993563d84 hello-world "/hello" a minute ago Exited ...

docker container stop / docker stop

  1. docker container stop container_id

docker container rm container_id / docker rm container_id

  1. docker container rm ca8993563d84
  1. docker rm -f ca8993563d84
  1. docker rm $(docker container ls -f "status=exited" -q)

docker inspect

  1. docker inspect container_id

docker logs

  1. docker logs container_id

docker network ls

  1. > docker network ls
  2. NETWORK ID NAME DRIVER SCOPE
  3. 89a165910cb3 bridge bridge local
  4. 154e5b3bdf2b host host local
  5. fef1a552d8c9 none null local

docker network inspect

  1. > docker network inspect 89a165910cb3 # 网络id
  2. [
  3. {
  4. "Name": "bridge",
  5. "Id": "89a165910cb3bb6dab7a33d922f72204b046bcb11e60c19387661272b84a7ed6",
  6. "Created": "2020-09-01T11:44:23.590643816+08:00",
  7. "Scope": "local",
  8. "Driver": "bridge",
  9. "EnableIPv6": false,
  10. "IPAM": {
  11. "Driver": "default",
  12. "Options": null,
  13. "Config": [
  14. {
  15. "Subnet": "172.17.0.0/16"
  16. }
  17. ]
  18. },
  19. "Internal": false,
  20. "Attachable": false,
  21. "Ingress": false,
  22. "ConfigFrom": {
  23. "Network": ""
  24. },
  25. "ConfigOnly": false,
  26. "Containers": {
  27. "14a7a82f5351a018467e9af926640618569b38ebc874832810ca15f8d14de395": {
  28. "Name": "test1",
  29. "EndpointID": "f91fecf0c4fd3d6014bc167101e7e65cb3dfbe17f4c7901273a2f2be7553c143",
  30. "MacAddress": "02:42:ac:11:00:02",
  31. "IPv4Address": "172.17.0.2/16",
  32. "IPv6Address": ""
  33. }
  34. },
  35. "Options": {
  36. "com.docker.network.bridge.default_bridge": "true",
  37. "com.docker.network.bridge.enable_icc": "true",
  38. "com.docker.network.bridge.enable_ip_masquerade": "true",
  39. "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
  40. "com.docker.network.bridge.name": "docker0",
  41. "com.docker.network.driver.mtu": "1500"
  42. },
  43. "Labels": {}
  44. }
  45. ]

docker volume ls

  1. > docker volume ls
  2. DRIVER VOLUME NAME
  3. local 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c

docker volume inspect

  1. # docker volume inspect volume_id
  2. > docker volume inspect 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c
  3. [
  4. {
  5. "CreatedAt": "2020-09-06T11:29:42+08:00",
  6. "Driver": "local",
  7. "Labels": null,
  8. "Mountpoint": "/var/lib/docker/volumes/3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c/_data",
  9. "Name": "3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c",
  10. "Options": null,
  11. "Scope": "local"
  12. }
  13. ]
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注