文章目录
- swarm
- 单节点
- 集群
- 网络
- VIP
- 多service应用
- secret
- volume
- 小结
- 练习
swarm
- 为了解决compose在生产环境的缺点,swarm容器编排技术出现了
- 基本架构
- 虽然k8s在容器编排领域处于绝对领先的地位,但和swarm在概念和使用上有类似之处,都是分布式管理,这里作为入门学习
单节点
- 单节点没法分布式,但是可以先试个水看看样子
- 初始化单节点,做了以下事情:
docker info
可以查看swarm状态
- 因为是单节点,离开了就swarm就崩了,只能强制
- 初始化swarm后可以创建service(容器),这里为什么是service的概念呢?
- 执行:
docker service create nginx:latest
,service、service里面的进程(其实就是container)、container是不一样的
- compose中的scale,扩展出来的其实也是container,service是大概念
- 扩展service,删掉某个容器,立马重新启一个
- 可以直接删除service,或者离开swarm;开始玩多节点
集群
- 创建集群的几个方法
- 在这个网站玩:但是环境只能用四小时
- 在本地用VMware创建三个虚拟机(或者Vagrant+VirualBox)
- 在云上使用云主机, 亚马逊,Google,微软Azure,阿里云,腾讯云等;注意要设置安全组
- 我三台虚机的IP
node01: 192.168.109.131
node02: 192.168.109.129
node03: 192.168.109.132
- 初始化swarm:
docker swarm init --advertise-addr=192.168.109.131
- 会出现这么个提示:
docker swarm join --token SWMTKN-1-6ddq0dydkopalzkubp5u60tc67eo3ul7eype0swvf7ccqaut89-4eb4c8nqf17lwdozyc7pfyqdv 192.168.109.131:2377
- 在其他两个节点执行上述命令:This node joined a swarm as a worker.
- 这里有个问题:三个节点的hostname和没设置,补充一首
# hostname用来设置主机名,就是terminal中:用户@hostname,一般用来服务发现
# 可以在/etc/hostname中修改,例如写为node01;;可能设置网需要重启terminal
hostname -F /etc/hostname # root用户下
# hosts文件用于设置 IP hostname alias,相当于设置域名,网络互连(这个和hostname没啥关系,但是一般设置成hostname,方便记)
# 编辑/etc/hosts,其他几个node都这么写
192.168.109.133 node01
192.168.109.129 node02
192.168.109.132 node03
# 此时ping其他机器就通了
ping node02
- 如果是改了hostname,其他节点会变为DOWN,需要执行:
docker swarm leave
,docker swarm join --token SWMTKN-1-6ddq0dydkopalzkubp5u60tc67eo3ul7eype0swvf7ccqaut89-4eb4c8nqf17lwdozyc7pfyqdv node01:2377
重新加入 - 这就有了包含三个node的集群,接下来需要搞明白node——service——container的关系了
- 创建service:
docker service create --name swarm-web nginx
,会在某个 节点上创建一个容器支持服务
- 注意看
replicas
,其实就是指container的状态;此时其他节点还没使用
- 扩展service,其实就是多启几个容器:
docker service update swarm-web --replicas 3
- 之前有两个node起不来,因为hostname设置吗?重启虚拟机重新join解决
- 类似的指令:
docker service scale swarm-web=4
,这会在某个node上再启一个容器 - 此时如果在某个node上rm掉支持服务的容器,会快速自动创建新的(在原节点)
- 总结:service只有一个,对应的容器可以多个,分布在不同的node,swarm监控管理服务和容器;服务的接口怎么暴露呢?每个node都提供接口,数据一致性呢?怎么同步?
- 分布式的解决方案有很多需要了解,但需求+方案,不能空想方案
- 还有两个常用的命令:
docker service inspect swarm-web
,docker service logs swarm-web
网络
- 虚拟机全部关机之后service就没了,查看node还在,但连个节点一直处于DOWN,inspect发现是心跳失败
# 有说法是删掉冲突的文件
systemctl stop docker
rm -rf /var/lib/docker/swarm/worker/tasks.db
systemctl start docker
# 建议先关闭manager防火墙试试
systemctl stop firewalld.service
systemctl disable firewalld.service
# 最后还是swarm leave再join
- 这里网络的driver是
overlay
类型
- 同时我们发现在每个node上还有一个
docker_gwbridge
网络
- 因为之前的基础镜像不方便查看ip,自己搭建网络试一下;这里service的创建是基于swarm的(overlay网络也只能swarm使用)
-
docker network create -d overlay myoverlay
,指定driver -
docker run -idt --network myoverlay --name box1 busybox
直接用这个网络创建容器是不行的 -
docker service create --network myoverlay --name box-overlay --replicas 2 busybox ping 8.8.8.8
只能建立service docker service ps box-overlay
,这里manager(leader)还是node01,和swarm init有关- 情况是这样的:
docker network inspect docker_gwbridge
# node01
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
# node02
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
docker network inspect myoverlay
# node01 node02
"Subnet": "10.0.1.0/24",
"Gateway": "10.0.1.1"
- 也不必多说特点了,docker_gwbridge这套网络用来和外部通信,overlay这套用来容器之间通信(东西通信/横向通信)
- service网络示意图
- overlay使用了VxLAN技术,在容器之间通信
- 如图所示:主要原理是引入一个UDP格式的外层隧道作为数据链路层,而原有数据报文内容作为隧道净荷加以传输
- node02/03又Down了,心跳检测失败
- 前面介绍了两个基本模型,用于容器对外和互相通信,还有一种比较复杂的ingress网络
Ingress Routing Mesh
,实现把service的服务端口对外发布出去,让其能够被外部网络访问到;主要解决入方向流量的问题
- iptables的 Destination NAT流量转发
- Linux bridge, network namespace
- 使用IPVS技术做负载均衡
- 包括容器间的通信(overlay)和入方向流量的端口转发
- ingress网络测试
# 创建服务
docker network create -d overlay mynet
docker service create --name web --network mynet -p 8080:80 --replicas 2 containous/whoami # 这个镜像会返回容器通信的信息
# 这里做了端口映射,访问这swarm节点的8080,都能访问
curl 192.168.109.129:8080
# 在任意节点查看转发规则
sudo iptables -nvL -t nat
- 可以看到,将外部的8080都映射到了
docker_gwbridge
网络 - 查看这个网络,发现有个容器
ingress-sbox
连在上面,实则这只是个network namespace - 除了我们自己创建的
mynet
网络(没有ingress-sbox),还有一个名为ingress
的overlay型网络,也有这个命名空间ingress-sbox
- OK,都连了这个ingress-sbox
- 如何查看这个namespace呢?
docker run -it --rm -v /var/run/docker/netns:/netns --privileged=true nicolaka/netshoot nsenter --net=/netns/ingress_sbox sh
,进入 - 查看它的转发规则,这里给数据包做了个标记:
0x101
,即257 - 使用
ipvs
做负载均衡:ipvsadm
,按道理讲,这里应该是数据包的转发列表
- 这里是三层转发,外部可能访问任意一个node,在路由层进行均衡转发 ;如果我们想像NGINX那样在四层,直接均衡可访问的机器,需要停掉这个ipvs
- 下面是ingress网络示意图:也就是说,这个namespace解决了负载转发的问题(iptables+ipvs)
- 小结:外部流量通过docker_gwbridge将数据包转发给ingress,在namespace中打标记做负载均衡;因为ingress也是overlay类型,所以可直接转发给各容器
- 这里使用的mynet网络用于容器间通信(默认使用ingress overlay)
- 总结
- 从网络流量来理解上述三类网络的分工,分三个流向
- 容器内部访问外网,使用bridge转发
- 外部访问service,使用ingress负载均衡,让外面看起来就像是一个服务器在处理请求
- 集群内部需要管理,设置各种角色,搞各种策略,这需要通信,overlay解决这个问题
- 这里搭建的这个例子也能看到负载均衡现象,在任意节点curl访问会随机返回server hostname,就像一个节点在提供服务(透明)
- 可以看出,了解了网络的原理基本就明白了node——service——container直接的关系
- 集群的一个service可以基于多个不同node的container
- 每个node,对一个service可以有多个container(三个node,scale=4时就可以)
VIP
- VIP网络用于swarm内部负载均衡,即内部多个service之间通信时
- OK,先搞个service,三个replicas;再搞一个client,访问前面的service
docker service create --name web --network mynet --replicas 3 containous/whoami
docker service create --name client --network mynet xiaopeng163/net-box:latest ping 8.8.8.8
- 去client连我们的web服务,这里有个IP
- inspect这三个容器,属于mynet的IP是
10.0.2.11 / 10.0.2.16 / 10.0.2.12
(ingress肯定是要接的,均衡外部请求) docker service inspect web
这就是那个VIP- 这次研究的问题是用于内部通信的mynet网络,inspect看一下
- 下面列出的
lb_pp...
就是lb-mynet的network namespace,进去看看怎么搞的 sudo nsenter --net=/var/run/docker/netns/lb_ppjtzo226 sh
这里的2.14网络是?- 如图所示,会将数据包给VIP打上标记后负载均衡,使用
ipvsadm
能看到转发表
- 小结:内部service之间通过此网络的VIP访问,实际上是将网络下的容器IP通过
iptables+ipvs
做了负载均衡
多service应用
- swarm可以和compose一样,创建多个service,整起来
- 但swarm在生产环境,不能build镜像,所以要提前准备好image
- 按照之前的代码,分别手动创建redis和flask的service就属于多service应用(集群环境),就不用配置文件了:
# 清理一下系统
docker system prune -a -f
# 切换到docker-compose目录,build好镜像
docker-compose build
docker image ls
# 登录docker hub,推上去
docker login
docker-compose push # 肯定是推image,根据什么呢?看yml文件猜一猜吧,roykun的前缀?
# 之前使用过 docker image push roykun/hello:1.0,image名称前加上username,方便管理
# 手动创建service
docker service create --network mynet --name redis redis:latest redis-server --requirepass ABC123
docker service create --network mynet --name flask --env REDIS_HOST=redis --env REDIS_PASS=ABC123 -p 8080:5000 roykun/flask-redis:latest
version: "3.8"
services:
flask:
build:
context: ./
dockerfile: Dockerfile
image: roykun/flask-redis:latest
ports:
- "8080:5000"
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
- 测试使用:
curl http://127.0.0.1:8080
- 通过
docker stack
部署多service(stack基于swarm)
- 需要先build镜像,可以推到自己的dockerhub
- stack仍然使用docker-compose.yml,但针对的是多机环境(改了改hosts,重新init了一下docker swarm)
# 使用stack构建,Ignoring unsupported options: build
env REDIS_PASSWORD=ABC123 docker stack deploy --compose-file docker-compose.yml flask-demo
# 当然,刚创建replicas都是1
- 查看:
docker stack ls
,docker stack ps flask-demo
- stack自动创建了一个overlay网络,和compose类似(bridge)
- 因为使用的还是compose的文件,里面如果指定了networks也只适用于单机环境
- 所以,一般情况下建议区分用于开发环境和生产环境的yml文件
- 比如带上
dev/prod
后缀
- 小结
- 使用
docker run
在单节点创建container - 使用
docker compose
在单节点创建多个container/service(可以NGINX做均衡) - 使用
docker stack
在集群创建多个container/service(属于swarm,一个service可能多个container)
secret
- secret可以用来保存密码,在我们设置env时指定密码的路径即可,更安全
- 创建的两种方式
# 1 直接用echo的输出作为mysql的秘钥
echo abc123 | docker secret create mysql_pass -
docker secret rm mysql_pass
# 2 将密码存在文件里
docker secret create mysql_pass mysql_pass.txt
docker secret ls
docker secret inspect mysql_pass
- 这两种方式都有缺点,可以通过history看到密码,也可以通过文件看到;需要及时清理
- 密码最终是存在swarm的raft数据库
- 使用
docker service create --name mysql-demo --secret mysql_pass --env MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pass mysql:5.7
- 指定secret后,会将密码存raft拿出来放到
/run/secrets/mysql_pass
文件 - 这里用MySQL举例,有对应的env可以指定mysql需要的
ROOT_PASSWORD
,所以不同的镜像还需要查看docker官网的定义
- 当然,也可以自定义compose文件,在compose中设置好ENV和secret,deploy时会全部创建;下面是官网的例子
version: "3.9"
services:
db:
image: mysql:latest
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: db_password.txt
db_root_password:
file: db_root_password.txt
volumes:
db_data:
volume
- swarm中使用local volume,各node把数据持久化到本地
# 还是使用compose文件
version: "3.8"
services:
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pass
secrets:
- mysql_pass
volumes:
- data:/var/lib/mysql
volumes:
data:
secrets:
mysql_pass:
file: mysql_pass.txt
# 记得新建mysql的密码文件
- deploy:
docker stack deploy --compose-file docker-compose.yml db-demo
,你就能看到secret,也能看到volume:docker volume ls
小结
- 到目前为止,docker的主要操作基本OK(主要是概念),可以分这么几部分
- image
- 镜像可以从远处pull,也可以自己写Dockerfile构建
docker image build
,这是应用的基础
- deploy
- 部署这部分本质是拿着镜像创建容器,容器是核心,但又有三种方式,就是上一个小结里说的
- 使用时可以分这几步考虑:手动部署还是通过compose文件?单节点还是集群?
- 基本的容器命令是:
docker container
和docker service
- 一键部署的命令是:
docker-compose up -d
和docker stack
- 集群用的比较广泛,可以从网络入手
docker network
,就可以理清关系
练习
- 投票APP的stack部署配置文件(集群环境)
version: "3"
services:
redis:
image: redis:alpine
networks:
- frontend
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
db:
image: postgres:9.4
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
placement:
constraints: [node.role == manager]
vote:
image: dockersamples/examplevotingapp_vote:before
ports:
- 5000:80
networks:
- frontend
depends_on:
- redis
deploy:
replicas: 2
update_config:
parallelism: 2
restart_policy:
condition: on-failure
result:
image: dockersamples/examplevotingapp_result:before
ports:
- 5001:80
networks:
- backend
depends_on:
- db
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
worker:
image: dockersamples/examplevotingapp_worker
networks:
- frontend
- backend
depends_on:
- db
- redis
deploy:
mode: replicated
replicas: 1
labels: [APP=VOTING]
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
placement:
constraints: [node.role == manager]
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
frontend:
backend:
volumes:
db-data:
- 部署:
docker stack deploy --compose-file docker-compose.yml vote-demo
- 这个compose文件有一些字段还没用过,可以学习;如果有问题可以
docker service logs
查看
- 可能需要在db service中加一句
POSTGRES_HOST_AUTH_METHOD: "trust"
- 再次执行docker stack即可update