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都提供接口,数据一致性呢?怎么同步?
- 分布式的解决方案有很多需要了解,但需求+方案,不能空想方案
- 之前有两个node起不来,因为hostname设置吗?重启虚拟机重新join解决
- 还有两个常用的命令:
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
网络
- 同时我们发现在每个node上还有一个
- 因为之前的基础镜像不方便查看ip,自己搭建网络试一下;这里service的创建是基于swarm的(overlay网络也只能swarm使用)
docker network create -d overlay myoverlay
,指定driverdocker run -idt --network myoverlay --name box1 busybox
直接用这个网络创建容器是不行的docker service create --network myoverlay --name box-overlay --replicas 2 busybox ping 8.8.8.8
只能建立servicedocker 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
- 可以看到,将外部的8080都映射到了
- 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)
- 如何查看这个namespace呢?
- 总结
- 从网络流量来理解上述三类网络的分工,分三个流向
- 容器内部访问外网,使用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
能看到转发表
- inspect这三个容器,属于mynet的IP是
- 小结:内部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
- 当然,也可以自定义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
,这是应用的基础
- 镜像可以从远处pull,也可以自己写Dockerfile构建
- deploy
- 部署这部分本质是拿着镜像创建容器,容器是核心,但又有三种方式,就是上一个小结里说的
- 使用时可以分这几步考虑:手动部署还是通过compose文件?单节点还是集群?
- 基本的容器命令是:
docker container
和docker service
- 一键部署的命令是:
docker-compose up -d
和docker stack
- image
- 集群用的比较广泛,可以从网络入手
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
- 可能需要在db service中加一句