文章目录

  • swarm
  • 单节点
  • 集群
  • 网络
  • VIP
  • 多service应用
  • secret
  • volume
  • 小结
  • 练习


swarm

  • 为了解决compose在生产环境的缺点,swarm容器编排技术出现了
  • 基本架构
  • 虽然k8s在容器编排领域处于绝对领先的地位,但和swarm在概念和使用上有类似之处,都是分布式管理,这里作为入门学习

单节点

  • 单节点没法分布式,但是可以先试个水看看样子
  • 初始化单节点,做了以下事情:docker info可以查看swarm状态
  • 创建swarm集群的根证书
  • manager节点的证书
  • 其它节点加入集群需要的tokens
  • 创建Raft数据库用于存储证书,配置,密码等数据
  • 因为是单节点,离开了就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 leavedocker 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-webdocker 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 lsdocker 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 containerdocker service
  • 一键部署的命令是:docker-compose up -ddocker 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