@octopus
        
        2020-09-14T10:36:11.000000Z
        字数 29252
        阅读 1342
    docker

bootfs(boot file system):
主要包含 bootloader 和 kernel,bootloader 主要是引导加载 kernel,当 kernel 被加载到内存中后,bootfs 就被 umount 了,此时内存的使用权已由 bootfs 转交给 kernel。
rootfs (root file system) :
在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev、/proc、/bin、/etc 等标准目录和文件。
由此可见对于不同的 linux 发行版,bootfs 基本是一致的,rootfs 会有差别,因此不同的发行版可以公用 bootfs。所以在docker下载一个操作系统image很小,是因为不带 kernel。
文件和meta data的集合(root filesystem)
是分层的,并且每一层都是可以添加改变删除文件,成为一个新的 image。
不同的image之前可以共享分层。
Image本身是只读的
可以通过自定义 dockerfile 生成 image,也可以使用已有的image,从 registry(仓库)中获取。docker hub 是 docker 提供的免费仓库。
// 拉取镜像,14.04版本的ubuntudocker pull ubuntu:14.04

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

可以看到,每创建一个容器,就创建了一个 network namespace,不同容器的网络环境是隔离的,与本地环境也是隔离的,各自有各自的ip。 在 test1 与 test2 中可以互相 ping 通,互相通信,在本地也能与两个 container 互通。原理是什么,怎么实现的?
> ip netns add test1 # 创建一个本地网络命名空间,名字是 test1> ip netns add test2> ip netns list # 列出本地网络命名空间test1test2> ip netns delete test1 # 这步不必运行,只是写出删除ns的方法。> ip netns exec test1 ip a # 查看test1网络环境1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00# 可以看到现在 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
# 使用 ip link 将 lo 启动> ip netns exec test1 ip link set dev lo up> ip netns exec test1 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever# lo 的状态就变为 UNKNOWN ,是因为单个端口是无法完全 up 起来,像网线一样需要有另一端进行连接,是一对。

    veth-pair 就是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连着。 
    它的作用很简单,就是要把从一个 network namespace 发出的数据包转发到另一个 namespace。
# 在本地创建一对 Veth 接口> ip link add veth-test1 type veth peer name veth-test2> ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ffinet 172.21.0.3/20 brd 172.21.15.255 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::5054:ff:fe39:1f8/64 scope linkvalid_lft forever preferred_lft forever3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ffinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0valid_lft forever preferred_lft foreverinet6 fe80::42:17ff:fed2:baaa/64 scope linkvalid_lft forever preferred_lft forever9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group defaultlink/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet6 fe80::882f:81ff:fef0:3dd9/64 scope linkvalid_lft forever preferred_lft forever11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group defaultlink/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1inet6 fe80::82:daff:fe12:b02d/64 scope linkvalid_lft forever preferred_lft forever12: veth-test2@veth-test1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000link/ether d2:6b:17:27:8c:21 brd ff:ff:ff:ff:ff:ff13: veth-test1@veth-test2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff# 12 13 就是刚才新建的一对 veth 接口
# 将 veth 接口的一端添加到 test1 命名空间中> ip link set veth-test1 netns test1# 查看 test1 的网络链路> ip netns exec test1 ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:0013: veth-test1@if12: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff link-netnsid 0# 会发现 test1 中多了一个 veth-test1@if12 接口# 同时查看本地 ip link,发现 veth-test1 已经没有了。> ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group defaultlink/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group defaultlink/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 011: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group defaultlink/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 112: veth-test2@if13: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000link/ether d2:6b:17:27:8c:21 brd ff:ff:ff:ff:ff:ff link-netnsid 2# 同时向 test2 中加入接口 veth-test2> ip link set veth-test2 netns test2
# 这样 veth 的两端就布置好了,接下来把两端接口启动,并为接口分配 ip 地址> netns exec test1 ip link set dev veth-test1 up> ip netns exec test2 ip link set dev veth-test2 up> ip netns exec test1 ip addr add 192.168.1.1/24 dev veth-test1> ip netns exec test2 ip addr add 192.168.1.2/24 dev veth-test2# 查看命名空间 test1 的网络信息> ip netns exec test1 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever13: veth-test1@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff link-netnsid 1inet 192.168.1.1/24 scope global veth-test1valid_lft forever preferred_lft foreverinet6 fe80::6868:78ff:fecc:2e/64 scope linkvalid_lft forever preferred_lft forever# 发现他的 status 是 up 并且已经配置上了 ip 地址,test2 也是同理,并且二者可以互通。> ip netns exec test1 ping 192.168.1.2PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.056 ms64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.044 ms

需求:在容器1中启动mysql服务器,在容器2中连接,容器2并不知道容器1的ip,想通过容器名即可访问,如:host = containet1:3306
# 用 --link 指定绑定ip,相当于 host 名->ip 的绑定> docker run -d --name test2 --link test1 centos /bin/bash -c "while true; do sleep 3600; done"> ping test1PING test1 (172.17.0.2) 56(84) bytes of data.64 bytes from test1 (172.17.0.2): icmp_seq=1 ttl=64 time=0.068 ms64 bytes from test1 (172.17.0.2): icmp_seq=2 ttl=64 time=0.055 ms# 注意 link 是单向的, test2 绑定了 test1的地址,但 test1 没有绑定 test2,所以 test1 中无法用 test2 的名字来直接 ping 通
还有一种情况也可以实现,即自定义桥接接口,将容器都绑定到同一个桥接接口上,则可互相用名字访问,同一个容器可绑定多个桥接接口。
yum install -y bridge-utils # 安装查看桥接网络设备工具
# 查看本地所有的网络设备> docker network lsNETWORK ID NAME DRIVER SCOPE89a165910cb3 bridge bridge local154e5b3bdf2b host host localfef1a552d8c9 none null local# bridge 类型的就是 docker0,前面提到过,容器的 veth 连接本地的一端默认就是连接到 docker0 上的。
host 与 none 类型的接口并不常用,连接到 none 的容器是独立的命名空间,只有 lo 回环接口。连接到 host 是与本地宿主机共享命名空间,ip a 显示的与宿主机的 ip a 一模一样,可能会有端口冲突问题。
# 新建一个 bridge 接口( -d 表示设备类型)> docker network create -d bridge my-bridge> sudo docker network lsNETWORK ID NAME DRIVER SCOPE89a165910cb3 bridge bridge local154e5b3bdf2b host host local91d3b1e839ce my-bridge bridge localfef1a552d8c9 none null local# 用 brctl 命令查看所有桥接接口(brctl需要yum安装)> brctl showbridge name bridge id STP enabled interfacesbr-91d3b1e839ce 8000.0242a89aae58 nodocker0 8000.024217d2baaa no vethd68e0d0vethbaa637c# 可以看到有一个新接口 br-91d3b1e839ce id 和 docker network 中的吻合,它此时没有接口绑定(查看interfaces)。
# 用 --network 指定要绑定的接口,默认会绑定到 docker0 上> docker run -d --name test3 --network my-bridge centos /bin/bash -c "while true; do sleep 3600; done"# 查看桥接设备,br-91d3b1e839ce 上已经有接口绑定> brctl showbridge name bridge id STP enabled interfacesbr-91d3b1e839ce 8000.0242a89aae58 no veth030ca97docker0 8000.024217d2baaa no vethd68e0d0vethbaa637c# 将 test2 (现绑定在docker0上)也绑定到新桥接接口上> sudo docker network connect my-bridge test2# 可以看到 test2 容器有两个网络接口> docker exec test2 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0valid_lft forever preferred_lft forever19: eth1@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.18.0.3/16 brd 172.18.255.255 scope global eth1valid_lft forever preferred_lft forever# 连接在同一自定义桥接接口上的两个容器可以通过名字互ping通> docker exec test2 ping test3PING test3 (172.18.0.2) 56(84) bytes of data.64 bytes from test3.my-bradge (172.18.0.2): icmp_seq=1 ttl=64 time=0.048 ms64 bytes from test3.my-bradge (172.18.0.2): icmp_seq=2 ttl=64 time=0.061 ms
需求:启动一个nginx容器,映射到本地的 80 端口,使访问本地 80 端口时可以转发到容器中的 nginx 进行处理
> docker pull nginx> docker run --name web -d -p 80:80 nginx# -d 后台运行# -p 端口映射到本地。第一个 80 是容器中的 80 端口,第二个是指映射到本地的 80 端口> curl 127.0.0.1<title>Welcome to nginx!</title>
etcd + docker overlay 网络
需求:启动一个 mysql 容器,但是数据表/数据不想因容器的停止删除而消失。
基于本地文件系统的 Volume。可以在执行 docker create 或 docker run 时,通过 -v 参数将主机目录作为容器的数据卷。
基于 plugin 的 Volume。支持第三方存储方案,比如 NAS, AWS
...VOLUME /var/lib/mysql...EXPOSE 3306CMD ["mysqld"]
VOLUME 即把容器中 /var/lib/mysql 目录挂载到宿主机的匿名卷下,实现数据持久化。具体位置因 docker 版本不同而改变。当前测试环境下,docker 会把匿名卷默认绑定到 /var/lib/docker/volumes/ 下。
# 启动一个 mysql 容器,指定 root 密码为 123(-e MYSQL_ROOT_PASSWORD=123)> docker run -d -e MYSQL_ROOT_PASSWORD=123 mysql# 查看本地 volume> docker volume lsDRIVER VOLUME NAMElocal 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c# 查看 volume 详细信息> docker volume inspect 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c[{"CreatedAt": "2020-09-06T11:29:42+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c/_data","Name": "3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c","Options": null,"Scope": "local"}]# 根据 Mountpoint 可以看到容器数据实际挂在到了本地的 /var/lib/docker/volumes/3472...# 停止删除容器后,docker volume 不会被删除。> docker volume rm 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c
假如有一个 mysql 容器,生成了持久化数据,被停止删除后,第二个新的 mysql 容器想使用之前的数据,就要指定挂载位置。
# 先去 mysql dockerfile 中了解到, volumn 挂载目录是 /var/lib/mysql# 然后指定已有的数据卷作为挂载目录docker run -d -v 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql
这样就会发现,匿名卷名字太长,也没有辨识度,所以最好在一开始就指定 -v new_name ,当发现 new_name 不存在,就会将 new_name 作为匿名卷名。
# 启动 mysql 容器1,指定挂载目录名为 mysql> docker run -d -v mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql> docker volume lsDRIVER VOLUME NAMElocal mysql> docker volume inspect mysql[{"CreatedAt": "2020-09-06T12:15:11+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/mysql/_data","Name": "mysql","Options": null,"Scope": "local"}]# 进行建库建表插入数据等等操作后...# 启动 mysql 容器2,使用之前的数据> docker run --name mysql2 -d -v mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 mysql
部署 WordPress
# 启动 mysqldocker run -d --name mysql -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 -e MYSQL_DATABASE=wordpress mysql#启动 wordpressdocker run -d -e WORDPRESS_DB_HOST=mysql:3306 --link mysql -p 8080:80 wordpress
当一个 app 依赖太多容器的时候,在部署/管理/停止都很难维护,如果能将 app 依赖的容器分组,统一管理就很方便,Docker Compose 登场。
- docker compose 是一个工具
 - 这个工具可以通过一个 yml 文件定义多容器的 docker 应用
 - 通过一条命令就可以根据 yml 文件的定义去创建或者管理多个容器
 
services:db: # 容器的名字image: mysql:latest # 依赖外部镜像mysqlvolumes:- "db-data:/var/lib/mysql" # 相当于 -vnetwork:- new-bridge # 相当于 --network
services:worker:build: ./workerlinks: # 通常不会这样使用,仅限于有依赖外部容器时- db- redisnetwork:- new-bridge
volumes:db-data:# 等于运行语句:docker volume create db-data
networks:new-bridge:driver: bridgeother-bridge:driver: bridge# 等于运行语句:docker network create -d bridge new-bridge
不同版本的 copmpse 对应的 version 与 yml 语法不同,详见 http://docs.docker.com/compose/compose-file/#upgrading
# docker-compose.ymlversion: "3.1"services:wordpress:image: wordpressports:- 8080:80environment:WORDPRESS_DB_HOST: mysqlWORDPRESS_DB_PASSWORD: rootnetworks:- new_bridgemysql:image: mysqlenvironment:MYSQL_DATABASE: wordpressMYSQL_PASSWORD: 123volumes:- db_data:/var/lib/mysqlnetworks:- new_bridgevolumes:db_data:networks:new_bridge: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
# 自动在当前目录寻找名为 docker-compose.yml 的配置文件来启动容器和安装服务。> docker-compose up# 名字不为默认的 docker-compose.yml 的配置文件要指定> docker-compose -f xxx.yml up
# 查看所有docker-compose服务> docker-compose ps> docker-compose ps -d # 后台# 停止> docker-compose stop# 开始> docker-compose start# 停止并删除> docker-compose down# 进入容器> docker-compose exec mysql bash
Swarm在调度(scheduler)节点(leader节点)运行容器的时候,会根据指定的策略来计算最适合运行容器的节点,目前支持的策略有:spread, binpack, random.
Random 
顾名思义,就是随机选择一个Node来运行容器,一般用作调试用,spread和binpack策略会根据各个节点的可用的CPU, RAM以及正在运 
行的容器的数量来计算应该运行容器的节点。
Spread 
在同等条件下,Spread策略会选择运行容器最少的那台节点来运行新的容器,binpack策略会选择运行容器最集中的那台机器来运行新的节点。 
使用Spread策略会使得容器会均衡的分布在集群中的各个节点上运行,一旦一个节点挂掉了只会损失少部分的容器。
Binpack 
Binpack策略最大化的避免容器碎片化,就是说binpack策略尽可能的把还未使用的节点留给需要更大空间的容器运行,尽可能的把容器运行在 
一个节点上面。 



相关命令
docker swarm:集群管理,子命令有init, join, leave, update。(docker swarm --help查看帮助)docker service:服务创建,子命令有create, inspect, update, remove, tasks。(docker service--help查看帮助)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> docker swarm init --advertise-addr 192.168.0.1# 提示Swarm initialized: current node (nndlldwxemn7du1hjxzq33myp) is now a manager.To add a worker to this swarm, run the following command:docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
根据提示,如果要部署 work 节点,就在 work 节点上运行
docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
# 节点1> docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377This node joined a swarm as a worker.# 节点2> docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377This node joined a swarm as a worker.
注意 docker node ls 只能在 manager 中运行,查看
> docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONnndlldwxemn7du1hjxzq33myp * node1 Ready Active Leader 19.03.11ghcm5w5mtxmscmh1eauz988tt node2 Ready Active 19.03.11tecycccl80lsub2maw7fyt0jp node3 Ready Active 19.03.11
用 swarm 创建容器,就是创建 Service,因为 swarm 会根据指定的策略来计算最适合运行容器的节点,所以不能用 docker run 来随便启一个容器,要用 swarm 命令来创建Service(容器)
> docker service create --name mysql -d -e MYSQL_ROOT_PASSWORD=123 mysql
可以查看 Service 状态、运行在哪台服务器上
> docker service ps mysql # --name 指定的 service 名ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTSd5m5hnjsfk7e mysql.1 mysql:latest node1 Running Running 57 seconds ago# 服务跑在 node1 上
# 水平扩展 3 个 mysql 服务> docker service scale=3
> docker service lsID NAME MODE REPLICAS IMAGE PORTS9sgbsqhez1j6 mysql replicated 0/1 mysql:latest
docker service rm mysql
# manager> docker network create -d overlay demo# manager 查看网络> docker network lsNETWORK ID NAME DRIVER SCOPE4deb8704d751 bridge bridge localxmr31rtdnhqv demo overlay swarmfae16beabc0f docker_gwbridge bridge local88e0da999c0d host host local63gq7lykqe20 ingress overlay swarmb1b0a7aa9ea7 none null local# node1 & node2 查看网络> docker network lsNETWORK ID NAME DRIVER SCOPE4deb8704d751 bridge bridge localfae16beabc0f docker_gwbridge bridge local88e0da999c0d host host local63gq7lykqe20 ingress overlay swarmb1b0a7aa9ea7 none null local
在 manager 上创建的 overlay 并不会同步到其他节点上。
# manager> 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
# manager> 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
# manager 查看网络> docker network lsNETWORK ID NAME DRIVER SCOPE4deb8704d751 bridge bridge localxmr31rtdnhqv demo overlay swarmfae16beabc0f docker_gwbridge bridge local88e0da999c0d host host local63gq7lykqe20 ingress overlay swarmb1b0a7aa9ea7 none null local# node1> docker network lsNETWORK ID NAME DRIVER SCOPE4deb8704d751 bridge bridge localxmr31rtdnhqv demo overlay swarmfae16beabc0f docker_gwbridge bridge local88e0da999c0d host host local63gq7lykqe20 ingress overlay swarmb1b0a7aa9ea7 none null local# node2> docker network lsNETWORK ID NAME DRIVER SCOPEca3d7f954ec2 bridge bridge local9974b890ab6e docker_gwbridge bridge local7ccd52d74607 host host local63gq7lykqe20 ingress overlay swarm03f3304afa87 none null local
manager 与 node1 同步了 名为 demo 的 overlay 网络(mysql 与 wordpress 分别运行在 manager 与 node1 上),而 node2 没有同步 overlay。但是三个节点服务器都可以正常通过各自 ip:80 访问 wordpress 网站。那么 swarm 集群的通信原理是什么呢?

以请求 3个节点中没有任何服务的 node3节点为例,node3 中没有任何服务,node1 & node2 中启动了水平扩展的 wordpress 服务,node1 中有 mysql 服务。
当请求服务器 node3:80 端口,就会找到 80端口所属服务为 docker。
docker swarm 已经设置好了 iptables 规则,当访问本地 80 端口时,就转发到名为 docker_gwbridge 的命名空间中去。
> iptables -nL -t mangle # 查看 iptabls 的转发规则target prot opt source destinationDNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.20.0.2:80> ip adocker_gwbridge:inet 172.20.0.1/16
> nsenter --net=/var/run/docker/netns/ingress_sbox # 进入命名空间> iptables -nL -t mangletarget prot opt source destinationMARK tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 MARK set 0x10b> ipvsadm -lIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags-> RemoteAddress:Port Forward Weight ActiveConn InActConnFWM 271 rr-> 10.0.0.27:0 Masq 1 0 0-> 10.0.0.28:0 Masq 1 0 0

> nslookup mysql # 10.0.0.1# 查看真实转发 ip> nslookup tasks.mysql # 列出所有 mysql 所在节点的虚拟 ip
VIP + LVS。vip 可以对应多个 ip,用 LVS 可以轮询转发到不同的 ip 上进行请求。
这样就实现了 node3 访问容器服务,容器访问其他容器,最终组合返回正确的请求结果。
总结:
容器与容器之间通过 overlay 网络实现通信
服务与服务间通过 Ingress 实现通信
docker compose 要运行在 swarm 上,有一个重要的配置项:deploy。该配置项是 docker compose 3.0以上版本才支持。deploy 重点参数:
model
global          # 不可横向扩展,只有一个服务
replicated      # 可以横向扩展
replicas
n               # 扩展的个数。
placement
constrints:
    - node.role == manager   # 服务指定部署到 manager 节点
# docker-compose.ymlversion: "3.1"services:web:image: wordpressports:- 8080:80environment:WORDPRESS_DB_HOST: mysqlWORDPRESS_DB_PASSWORD: rootnetworks:- new_networkdeploy:model:replicatedreplicas:3mysql:image: mysqlenvironment:MYSQL_DATABASE: wordpressMYSQL_PASSWORD: 123volumes:- db_data:/var/lib/mysqlnetworks:- new_networkdeploy:model:globalplacement:constrints:- node.role == managervolumes:db_data:networks:new_network:driver: overlay
在 swarm 上用 compose 安装服务要使用 docker stacks 命令
docker stack deploy name --compose-file=docker-compose.yml
docker stack ls 查看所有 stack 启动的任务
docker stack services name 查看任务中启动的所有服务的状态
FROM scratch // 无base image,制作的就是一个base imageFROM centos // 使用 centos 作为 base imageFROM ubuntu:14.04
注意,最好使用官方的 image 作为 base image
LABEL maintainer = "zy@gmail.com"LABEL version = "1.0"LABEL description = "this is description"
RUN yum update && yum -y install vim \python-dev
WORKDIR /test # 如果没有该目录则会自动创建WORKDIR demoRUN pwd # 结合上两条,应该输出 /test/demo
注意最好不要使用 RUN cd,尽量使用绝对目录。
# 当前文件夹下有一个文件 hello.php,和一个 test.tar.gzADD hello.php / # 添加到image的跟目录ADD test.tar.gz / # 解压缩 test.tar.gz 并添加到跟目录WORKDIR /rootADD hello.php test/ # 文件会被放在:/root/test/hello.phpCOPY hello.php / # copy 的使用方式与 add 完全一致
大部分情况下,copy 优于 add。
add 除了有 copy 的功能,还有解压功能
添加远程文件请用 wget 或 curl
ENV MYSQL_VERSION 5.6RUN apt-get install -y mysql-server = "${MYSQL_VERSION}"
尽量多用 ENV ,可以增加可维护性
CMD echo "hello docker"> docker run image # hello docker> docker run image /bin/bash # 不会输出hello docker,因为指定了其他命令
如果docker run 指定了其他命令,CMD命令将被忽略
如果定义了多个 CMD ,则只有最后一个会被执行
COPY entrypoint.sh /usr/local/bin/ENTRYPOINT ["entrypoint.sh"]EXPOSR 27017CMD ["mongod"]
Entrypoint 不会被忽略,一定会被执行
FROM ubuntuRUN apt-get install -y stressENTRYPOINT ["/usr/bin/stress"] # 启动时一定会运行的命令CMD [] # 启动后执行,空则会收集参数> docker run -it xxx/stress --vm 1 --verbose
docker volume ls 与 docker volume inspect volume_id 查看具体挂载位置。
VOLUME /var/lib/mysql
docker run -d -p 5000:5000 --restart=always --name registry registry:2
> vim /etc/docker/daemo.json{"insecure-registries" : ["196.168.0.1:5000"]}> vim /etc/systemd/system/docker.service.dEnvironmentFile=/etc/docker/daemo.json # 新加一行环境文件> systemctl restart docker # 重启docker
docker build -t 196.168.0.1:5000/test .docker push 196.168.0.1:5000/test
> docker pull ubuntu:14.04
> docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEhello-world latest bf756fb1ae65 8 months ago 13.3kB
docker image rm bf756fb1ae65
// vim DockerfileFrom centosRun yum -y install vim// -t 标签 . 表示在当前路径寻找 dockerfile> docker image build -t centos_vim .
docker push cnoctopus/hello-world:latest
- 注意,镜像的 tag 必须以用户名/开头,不然没有权限。
 - 不推荐直接推自己 build 的 image,因为会有安全性问题。建议在 docker hub 绑定 github 仓库,然后提交 dockerfile 到 github,docker会自动从绑定的 github 仓库拉取 dockerfile 并生成 image,其他人可以看到 dockerfile,image又是官方生成,安全性提高。
 
docker container commit container_name image_name:tag
docker run hello-worlddocker run -d hello-world # -d 在后台运行
# 容器使用的总内存数 = memory + memory-swap# memory-swap 默认等于 memory 的大小docker run --memory=200M hello-world # 即总可用内存为400M
docker run --name test -e REDIS_HOST=196.168.0.1 centos ...# 进入容器,使用 env 命令可以查看所有环境变量
# 以 nginx 为例,启动一个 nginx 容器> docker run --name nginx -d -p 80:80 nginx# 查看是否正常运行> curl 127.0.0.1<title>Welcome to nginx!</title># 查看 nginx dockerfile 可知,index.html 等网站文件是放在 /usr/share/nginx/html/ 中,进入 bash 内部查看:> docker exec -it nginx /bin/bash> ls /usr/share/nginx/html/50x.html index.html# 已知开发目录在本地的 /root/tmp/ ,通过 -v 来将目录挂载到 容器的 /usr/share/nginx/html/ 实现数据同步。> docker run --name nginx -v /root/tmp:/usr/share/nginx/html -d -p 80:80 nginx> ls /root/tmp # 空> docker exec nginx ls /usr/share/nginx/html # 空# 所以,挂载后,本地目录会覆盖容器目录,因为本地目录原本是空的,那么容器中的目标目录也会用本地同步,最后变成空目录。> curl 127.0.0.1<title>403 Forbidden</title># 在本地新建 index.html,输出 hello world> echo "hello world" > /root/tmp/index.html# 查看容器挂载的目录> docker exec nginx ls /usr/share/nginx/htmlindex.html> curl 127.0.0.1hello world
docker exec -it container_id /bin/bash # 交互式运行/bin/bash进入容器bashdocker exec -it container_id python # 交互式进入容器运行python命令docker exec -it container_id ip a # 查看运行容器的ip
// 只列出当前在运行容器docker container ls// 在运行与已退出容器全部列出docker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS ...ca8993563d84 hello-world "/hello" a minute ago Exited ...
docker container stop container_id
docker container rm ca8993563d84
docker rm -f ca8993563d84
docker rm $(docker container ls -f "status=exited" -q)
docker inspect container_id
docker logs container_id
> docker network lsNETWORK ID NAME DRIVER SCOPE89a165910cb3 bridge bridge local154e5b3bdf2b host host localfef1a552d8c9 none null local
> docker network inspect 89a165910cb3 # 网络id[{"Name": "bridge","Id": "89a165910cb3bb6dab7a33d922f72204b046bcb11e60c19387661272b84a7ed6","Created": "2020-09-01T11:44:23.590643816+08:00","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": {"14a7a82f5351a018467e9af926640618569b38ebc874832810ca15f8d14de395": {"Name": "test1","EndpointID": "f91fecf0c4fd3d6014bc167101e7e65cb3dfbe17f4c7901273a2f2be7553c143","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"},"Labels": {}}]
> docker volume lsDRIVER VOLUME NAMElocal 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c
# docker volume inspect volume_id> docker volume inspect 3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c[{"CreatedAt": "2020-09-06T11:29:42+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c/_data","Name": "3472d769f942f7de34a8d344553584df900572252807996044d5d3d8d4c2fb3c","Options": null,"Scope": "local"}]