@octopus
2020-09-14T18:36:11.000000Z
字数 29252
阅读 960
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版本的ubuntu
docker 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 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_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 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 退出容器
> exit
# 查看本地网络
> ip a
lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
inet 172.21.0.3/20 brd 172.21.15.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe39:1f8/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:17ff:fed2:baaa/64 scope link
valid_lft forever preferred_lft forever
9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::882f:81ff:fef0:3dd9/64 scope link
valid_lft forever preferred_lft forever
11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::82:daff:fe12:b02d/64 scope link
valid_lft forever preferred_lft forever
可以看到,每创建一个容器,就创建了一个 network namespace,不同容器的网络环境是隔离的,与本地环境也是隔离的,各自有各自的ip。 在 test1 与 test2 中可以互相 ping 通,互相通信,在本地也能与两个 container 互通。原理是什么,怎么实现的?
> ip netns add test1 # 创建一个本地网络命名空间,名字是 test1
> ip netns add test2
> ip netns list # 列出本地网络命名空间
test1
test2
> ip netns delete test1 # 这步不必运行,只是写出删除ns的方法。
> ip netns exec test1 ip a # 查看test1网络环境
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/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 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_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 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
inet 172.21.0.3/20 brd 172.21.15.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe39:1f8/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:17ff:fed2:baaa/64 scope link
valid_lft forever preferred_lft forever
9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::882f:81ff:fef0:3dd9/64 scope link
valid_lft forever preferred_lft forever
11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::82:daff:fe12:b02d/64 scope link
valid_lft forever preferred_lft forever
12: veth-test2@veth-test1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether d2:6b:17:27:8c:21 brd ff:ff:ff:ff:ff:ff
13: veth-test1@veth-test2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/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 link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
13: veth-test1@if12: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/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 link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:39:01:f8 brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:17:d2:ba:aa brd ff:ff:ff:ff:ff:ff
9: vethbaa637c@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 8a:2f:81:f0:3d:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
11: vethd1ba402@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 02:82:da:12:b0:2d brd ff:ff:ff:ff:ff:ff link-netnsid 1
12: veth-test2@if13: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/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 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
13: veth-test1@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 6a:68:78:cc:00:2e brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 192.168.1.1/24 scope global veth-test1
valid_lft forever preferred_lft forever
inet6 fe80::6868:78ff:fecc:2e/64 scope link
valid_lft forever preferred_lft forever
# 发现他的 status 是 up 并且已经配置上了 ip 地址,test2 也是同理,并且二者可以互通。
> ip netns exec test1 ping 192.168.1.2
PING 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 ms
64 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 test1
PING 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 ms
64 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 ls
NETWORK ID NAME DRIVER SCOPE
89a165910cb3 bridge bridge local
154e5b3bdf2b host host local
fef1a552d8c9 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 ls
NETWORK ID NAME DRIVER SCOPE
89a165910cb3 bridge bridge local
154e5b3bdf2b host host local
91d3b1e839ce my-bridge bridge local
fef1a552d8c9 none null local
# 用 brctl 命令查看所有桥接接口(brctl需要yum安装)
> brctl show
bridge name bridge id STP enabled interfaces
br-91d3b1e839ce 8000.0242a89aae58 no
docker0 8000.024217d2baaa no vethd68e0d0
vethbaa637c
# 可以看到有一个新接口 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 show
bridge name bridge id STP enabled interfaces
br-91d3b1e839ce 8000.0242a89aae58 no veth030ca97
docker0 8000.024217d2baaa no vethd68e0d0
vethbaa637c
# 将 test2 (现绑定在docker0上)也绑定到新桥接接口上
> sudo docker network connect my-bridge test2
# 可以看到 test2 容器有两个网络接口
> docker exec test2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
19: eth1@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.18.0.3/16 brd 172.18.255.255 scope global eth1
valid_lft forever preferred_lft forever
# 连接在同一自定义桥接接口上的两个容器可以通过名字互ping通
> docker exec test2 ping test3
PING 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 ms
64 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 3306
CMD ["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 ls
DRIVER VOLUME NAME
local 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 ls
DRIVER VOLUME NAME
local 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
# 启动 mysql
docker run -d --name mysql -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123 -e MYSQL_DATABASE=wordpress mysql
#启动 wordpress
docker 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 # 依赖外部镜像mysql
volumes:
- "db-data:/var/lib/mysql" # 相当于 -v
network:
- new-bridge # 相当于 --network
services:
worker:
build: ./worker
links: # 通常不会这样使用,仅限于有依赖外部容器时
- db
- redis
network:
- new-bridge
volumes:
db-data:
# 等于运行语句:docker volume create db-data
networks:
new-bridge:
driver: bridge
other-bridge:
driver: bridge
# 等于运行语句:docker network create -d bridge new-bridge
不同版本的 copmpse 对应的 version 与 yml 语法不同,详见 http://docs.docker.com/compose/compose-file/#upgrading
# docker-compose.yml
version: "3.1"
services:
wordpress:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- new_bridge
mysql:
image: mysql
environment:
MYSQL_DATABASE: wordpress
MYSQL_PASSWORD: 123
volumes:
- db_data:/var/lib/mysql
networks:
- new_bridge
volumes:
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:2377
To 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:2377
This node joined a swarm as a worker.
# 节点2
> docker swarm join --token SWMTKN-1-2gmziwwne7h4tfbt4eh4j3z5x7fa9r9gtg53pe1a1ffg1wszu1-0bamqmlqejn48ncp5ghk6gpns 192.168.0.1:2377
This node joined a swarm as a worker.
注意 docker node ls 只能在 manager 中运行,查看
> docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
nndlldwxemn7du1hjxzq33myp * node1 Ready Active Leader 19.03.11
ghcm5w5mtxmscmh1eauz988tt node2 Ready Active 19.03.11
tecycccl80lsub2maw7fyt0jp 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 PORTS
d5m5hnjsfk7e mysql.1 mysql:latest node1 Running Running 57 seconds ago
# 服务跑在 node1 上
# 水平扩展 3 个 mysql 服务
> docker service scale=3
> docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
9sgbsqhez1j6 mysql replicated 0/1 mysql:latest
docker service rm mysql
# manager
> docker network create -d overlay demo
# manager 查看网络
> docker network ls
NETWORK ID NAME DRIVER SCOPE
4deb8704d751 bridge bridge local
xmr31rtdnhqv demo overlay swarm
fae16beabc0f docker_gwbridge bridge local
88e0da999c0d host host local
63gq7lykqe20 ingress overlay swarm
b1b0a7aa9ea7 none null local
# node1 & node2 查看网络
> docker network ls
NETWORK ID NAME DRIVER SCOPE
4deb8704d751 bridge bridge local
fae16beabc0f docker_gwbridge bridge local
88e0da999c0d host host local
63gq7lykqe20 ingress overlay swarm
b1b0a7aa9ea7 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 ls
NETWORK ID NAME DRIVER SCOPE
4deb8704d751 bridge bridge local
xmr31rtdnhqv demo overlay swarm
fae16beabc0f docker_gwbridge bridge local
88e0da999c0d host host local
63gq7lykqe20 ingress overlay swarm
b1b0a7aa9ea7 none null local
# node1
> docker network ls
NETWORK ID NAME DRIVER SCOPE
4deb8704d751 bridge bridge local
xmr31rtdnhqv demo overlay swarm
fae16beabc0f docker_gwbridge bridge local
88e0da999c0d host host local
63gq7lykqe20 ingress overlay swarm
b1b0a7aa9ea7 none null local
# node2
> docker network ls
NETWORK ID NAME DRIVER SCOPE
ca3d7f954ec2 bridge bridge local
9974b890ab6e docker_gwbridge bridge local
7ccd52d74607 host host local
63gq7lykqe20 ingress overlay swarm
03f3304afa87 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 destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.20.0.2:80
> ip a
docker_gwbridge:
inet 172.20.0.1/16
> nsenter --net=/var/run/docker/netns/ingress_sbox # 进入命名空间
> iptables -nL -t mangle
target prot opt source destination
MARK tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 MARK set 0x10b
> ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 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.yml
version: "3.1"
services:
web:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- new_network
deploy:
model:replicated
replicas:3
mysql:
image: mysql
environment:
MYSQL_DATABASE: wordpress
MYSQL_PASSWORD: 123
volumes:
- db_data:/var/lib/mysql
networks:
- new_network
deploy:
model:global
placement:
constrints:
- node.role == manager
volumes:
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 image
FROM centos // 使用 centos 作为 base image
FROM 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 demo
RUN pwd # 结合上两条,应该输出 /test/demo
注意最好不要使用 RUN cd,尽量使用绝对目录。
# 当前文件夹下有一个文件 hello.php,和一个 test.tar.gz
ADD hello.php / # 添加到image的跟目录
ADD test.tar.gz / # 解压缩 test.tar.gz 并添加到跟目录
WORKDIR /root
ADD hello.php test/ # 文件会被放在:/root/test/hello.php
COPY hello.php / # copy 的使用方式与 add 完全一致
大部分情况下,copy 优于 add。
add 除了有 copy 的功能,还有解压功能
添加远程文件请用 wget 或 curl
ENV MYSQL_VERSION 5.6
RUN 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 27017
CMD ["mongod"]
Entrypoint 不会被忽略,一定会被执行
FROM ubuntu
RUN apt-get install -y stress
ENTRYPOINT ["/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.d
EnvironmentFile=/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 ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest bf756fb1ae65 8 months ago 13.3kB
docker image rm bf756fb1ae65
// vim Dockerfile
From centos
Run 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-world
docker 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/html
index.html
> curl 127.0.0.1
hello world
docker exec -it container_id /bin/bash # 交互式运行/bin/bash进入容器bash
docker exec -it container_id python # 交互式进入容器运行python命令
docker exec -it container_id ip a # 查看运行容器的ip
// 只列出当前在运行容器
docker container ls
// 在运行与已退出容器全部列出
docker container ls -a
CONTAINER 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 ls
NETWORK ID NAME DRIVER SCOPE
89a165910cb3 bridge bridge local
154e5b3bdf2b host host local
fef1a552d8c9 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 ls
DRIVER VOLUME NAME
local 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"
}
]