目录

DockerSwarm实践及原理

基本原理

Swarm 是使用Docker 引擎内置的集群管理和编排工具。Swarm集群的框架与Hadoop集群或其他分布式系统类似,它也是由节点构成,每一个节点就是一台主机或者虚拟机。工作的机制也是主从模式(master/slaver),节点分为两种,一种是负责管理的Manager另一种是具体干活的Worker。

  • 管理节点: 用于 Swarm 集群的管理,docker swarm 命令基本只能在管理节点执行(节点退出集群命令 docker swarm leave 可以在工作节点执行)。为了避免单点故障,一个Swarm 集群可以有多个管理节点,但只有一个管理节点可以成为 leader,leader 通过 raft 协议实现。

  • 工作节点: 是任务执行节点,管理节点将任务 (Task) 下发至工作节点执行。管理节点默认也作为工作节点,也可以通过配置让服务只运行在管理节点。

https://tc.ctq6.cn/tc/docker_swarm.png

多个 Docker 主机就被抽象为单个大型的虚拟 Docker 主机,在管理节点上,用户可以像在单机一样在集群上操作容器或服务

基本概念

Swarm集群中管理的对象主要由三个,Task、Service与Node,其中Node上面已经介绍过,这里解释下Task与Service的概念

任务

Swarm 中的最小的调度单位,目前一个Task就是一个容器

服务

Service一般是由一组相同的Task组成,Service是这组Task要完成的任务的一个抽象。按照其包含的Task的布署方式分为两种:

  • replicated services 按照一定规则在各个工作节点上运行指定个数的任务
  • global services 每个工作节点上运行一个任务

这两种模式是在服务创建时通过创建命令docker service create的 –mode 参数指定的

Service与Task以及容器的关系如下:

https://tc.ctq6.cn/tc/docker_swarm_jg.jpg

总结成一句话就是,swarm集群(cluster)是由节点(node)组成;服务(service)一般包含若干个任务(Task),一个Task就是运行一个容器,所有这些Task都是在节点上执行的,具体在那个个节点上执行是由管理节点调度的。

初始化swarm 集群

安装docker环境

1
curl -sL https://gitee.com/YunFeiGuoJi/Cnblog/raw/master/shell/Scripts/docker_install.sh|sh -x -

修改docker配置文件

1
2
3
4
# 修改 /etc/docker/damean.json 添加配置
"live-restore": false
# 重启
systemctl restart dockerd

初始化docker swarm manager

1
2
docker swarm init --advertise-addr 192.168.56.101
--advertise-addr 指定与其他 node 通信的地址 

docker swarm init 输出告诉我们:

① swarm 创建成功,swarm-manager 成为 manager node。

② 添加 worker node 需要执行的命令。

③ 添加 manager node 需要执行的命令。

如果当时没有记录下 docker swarm init 提示的添加 worker 的完整命令,可以通过 docker swarm join-token worker 查看

管理docker swarm

节点管理

  • 查看帮助命令
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[root@master ~]# docker node --help

Usage:	docker node COMMAND

Manage Swarm nodes

Commands:
  demote      Demote one or more nodes from manager in the swarm
  inspect     Display detailed information on one or more nodes
  ls          List nodes in the swarm
  promote     Promote one or more nodes to manager in the swarm
  ps          List tasks running on one or more nodes, defaults to current node
  rm          Remove one or more nodes from the swarm
  update      Update a node

Run 'docker node COMMAND --help' for more information on a command.
  • 管理节点
1
2
3
4
5
6
7
# 查看节点
docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
8x5iogo0c2d7cl9edjvbacxio *   master              Ready               Active              Leader              19.03.12
shihkplhlozcttq3m8va4129c     node01              Ready               Active                                  19.03.12
# 删除节点
docker node rm shihkplhlozcttq3m8va4129c

服务管理

  • 创建服务
1
2
3
4
5
6
docker service create --name app1  --network yfgj --replicas 2 --entrypoint "sleep 1d" busybox
docker service create --name app2  --network yfgj --replicas 2 nginx
# 查看生成的vip
docker service inspect --format='{{json .Endpoint.VirtualIPs}}' app1
[{"NetworkID":"9ttgtpwyvnssbfnw33wchbgwi","Addr":"10.0.15.5/24"}]
docker service inspect --format='{{json .Endpoint.VirtualIPs}}' app2
  • 扩容副本
1
docker service scale web1=2
  • 删除服务
1
docker service rm service_name

Gui管理界面安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# docker-compose yml文件
version: "3.8"

services:
  portainer:
    image: portainer/portainer-ce:latest
    ports:
      - "9000:9000"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    networks:
      - "tarfik-public"
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=traefik-public"
        - "traefik.http.routers.portainer.rule=Host(`portainer.local.cluster`)"
        - "traefik.http.routers.portainer.entrypoints=websecure"
        - "traefik.http.routers.portainer.tls=true"
        - "traefik.http.routers.portainer.tls.certresolver=foo"
        - "traefik.http.routers.portainer.tls.domains[0].main=local.cluster"
        - "traefik.http.routers.portainer.tls.domains[0].sans=*.local.cluster" 
        - "traefik.http.services.portainer.loadbalancer.server.port=9000"
      replicas: 1
      placement:
        constraints: [node.role == manager]
networks:
  traefik-public:
    external: true
# 执行命令
docker stack deploy -c docker-compose.yaml docker-swarm-manager
# 访问
## 本地访问
http://localhost:9000

服务发现

实现功能:

  • 让 service 通过简单的方法访问到其他 service。
  • 当 service 副本的 IP 发生变化时,不会影响访问该 service 的其他 service。
  • 当 service 的副本数发生变化时,不会影响访问该 service 的其他 service

从使用者角度看,一个Service相当于它的所有Task的一个反向代理,它主要使用了 Linux 内核 iptables 和 IPVS 的功能来实现服务发现和负载均衡

  • iptables:Linux 内核中的包过滤技术,它可用于根据数据包的内容进行分类、修改和转发决策。
  • IPVS :Linux 内核中传输级负载均衡器

Swarm支持三种模式的负载均衡,它们的使用方式如下:

  • 基于 DNS 的负载均衡:DNS server 内嵌于 Docker 引擎,Docker DNS 解析服务名并安装随机次序返回容器 ID 地址列表,客户端通常会挑第一个 IP 访问。在服务启动时,通过指定–endpoint-mode参数为dnsrr来设定,另外服务需要加入一个覆盖网,例如
1
docker service create --endpoint-mode dnsrr --network overlay1  --replicas 3 --name nginx nginx
  • 基于 VIP 的负载均衡:默认时这种模式,在服务启动时可以指定或被分配一个 IP 地址,该 IP 地址可以映射到与该服务关联的多个容器的 IP 地址。示例如下
1
docker service create --network overlay1  --replicas 3 --name nginx nginx
  • 路由网格 (Routing mesh):这种模式服务暴露的端口会暴露在 Swarm 集群中的所有工作节点,通过访问任何一台主机的ip或域名加暴露的端口号就可以访问到该服务。使用也很简单,只需要在服务创建时添加端口号映射就可以了,如下
1
docker service create --network overlay1  --replicas 3 -p 80:80 --name nginx nginx

创建覆盖网络

1
2
3
4
5
6
docker network create \
  --driver overlay \
  --gateway 10.0.4.1 \
  --subnet 10.0.4.0/22 \
  --ip-range 10.0.4.0/24 \
  --attachable yfgj_net

暴露服务外部访问

安装traefik代理服务

  • 编写traefik yml文件
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
version: "3.8"

secrets:
  aliyun_region_id:
    #external: true
    file: "/root/.ssl/aliyun_region_id"
  aliyun_access_key:
    #external: true
    file: "/root/.ssl/aliyun_access_key"
  aliyun_secret_key:
    #external: true
    file: "/root/.ssl/aliyun_secret_key"

services:
  traefik:
    image: traefik:v2.4.8
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      #- "--entrypoints.webnossl.address=:8443"
      - "--api=true"
      - "--api.dashboard=true"
      - "--api.debug=true"
      - "--accesslog=true"
      - "--log=true"
      - "--log.filepath=/tmp/traefik.err.log"
      - "--accesslog.filepath=/tmp/traefik.access.log"
      - "--providers.docker.network=yfgj_net"
      - "--certificatesresolvers.foo.acme.dnschallenge=true"
      - "--certificatesresolvers.foo.acme.dnschallenge.provider=alidns"
      - "--certificatesresolvers.foo.acme.dnschallenge.resolvers=114.114.114.114:53,8.8.8.8:53"
      - "--certificatesresolvers.foo.acme.keytype=EC256" #RSA4096
      #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.foo.acme.email=xxxx"
      - "--certificatesresolvers.foo.acme.storage=/letsencrypt/acme.json"
      - "--metrics.prometheus=true"
      - "--metrics.prometheus.buckets=0.100000, 0.300000, 1.200000, 5.000000"
      - "--metrics.prometheus.addEntryPointsLabels=true"
      - "--metrics.prometheus.addServicesLabels=true"
      - "--entryPoints.metrics.address=:8082"
      - "--metrics.prometheus.entryPoint=metrics"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
      - "8020:8020"
    secrets:
      - "aliyun_region_id"
      - "aliyun_access_key"
      - "aliyun_secret_key"
    environment:
      - "ALICLOUD_REGION_ID_FILE=/run/secrets/aliyun_region_id"
      - "ALICLOUD_ACCESS_KEY_FILE=/run/secrets/aliyun_access_key"
      - "ALICLOUD_SECRET_KEY_FILE=/run/secrets/aliyun_secret_key"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/root/letsencrypt:/letsencrypt"
    networks:
      - "yfgj_net"
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=yfgj_net"
        - "traefik.http.services.traefik_traefik.loadbalancer.server.port=8080"
        # http 80
        - "traefik.http.routers.traefik-ui-80.rule=Host(`traefik.ctq6.cn`)" 
        - "traefik.http.routers.traefik-ui-80.entrypoints=web"
        # http 8443
        - "traefik.http.routers.traefik-ui-8443.rule=Host(`traefik-8443.ctq6.cn`)" 
        - "traefik.http.routers.traefik-ui-8443.entrypoints=webnossl"
        - "traefik.http.routers.traefik-ui-8443.tls=true"
        - "traefik.http.routers.traefik-ui-8443.tls.certresolver=foo"
        - "traefik.http.routers.traefik-ui-8443.tls.domains[0].main=ctq6.cn"
        - "traefik.http.routers.traefik-ui-8443.tls.domains[0].sans=*.ctq6.cn"
        - "traefik.http.routers.traefik-ui-8443.middlewares=redirect-https"
        # https 443
        - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
        - "traefik.http.routers.traefik-ui-443.rule=Host(`traefik-443.ctq6.cn`)"
        - "traefik.http.routers.traefik-ui-443.tls=true"
        - "traefik.http.routers.traefik-ui-443.tls.certresolver=foo"
        - "traefik.http.routers.traefik-ui-443.tls.domains[0].main=ctq6.cn"
        - "traefik.http.routers.traefik-ui-443.tls.domains[0].sans=*.ctq6.cn"
        - "traefik.http.routers.traefik-ui-443.middlewares=redirect-https"
        - "traefik.http.routers.traefik-ui-443.entrypoints=websecure"
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
networks:
  yfgj_net:
    external: true
    name: yfgj_net
  • 安装traefik服务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 配置域名
阿里云或者腾讯云上配置域名指向安装traefik所在节点,并将443,80,8443防火强配置为允许公网访问,配置需要访问的服务域名
# 部署traefik 服务
docker stack deploy -c docker-compose-traefik.yml traefik
# 测试traefik 域名是否生成
curl -v  localhost:8080/api/http/routers|jq .
# 测试是否能访问对应的服务
curl -v -H "Host:traefik.ctq6.cn" localhost
curl -v -H "Host:traefik-443.ctq6.cn" localhost:443
curl -v -H "Host:traefik-8443.ctq6.cn" localhost:8443
# 浏览器访问
http://traefik.ctq6.cn
https://traefik-443.ctq6.cn
http://traefik-8443.ctq6.cn:8443

安装nginx web服务

  • 编写nginx web服务 yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
version: "3.8"

services:
  nginx:
    image: nginx:latest
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
    ports:
      - 8088:80
    networks:
      - yfgj_net
    volumes:
      - /root/yunfeiguoji:/usr/share/nginx/html/blog/yunfeiguoji:rw
      - ./default.conf:/etc/nginx/conf.d/default.conf:rw
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=yfgj_net"
        - "traefik.http.services.nginx_nginx.loadbalancer.server.port=80"
        # https 443
        - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
        - "traefik.http.routers.nginx-ui-443.rule=Host(`www.ctq6.cn`)"
        - "traefik.http.routers.nginx-ui-443.tls=true"
        - "traefik.http.routers.nginx-ui-443.tls.certresolver=foo"
        - "traefik.http.routers.nginx-ui-443.tls.domains[0].main=ctq6.cn"
        - "traefik.http.routers.nginx-ui-443.tls.domains[0].sans=*.ctq6.cn"
        - "traefik.http.routers.nginx-ui-443.middlewares=redirect-https"
        - "traefik.http.routers.nginx-ui-443.entrypoints=websecure"
      placement:
        constraints: [node.role == manager]
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
        reservations:
          cpus: '0.25'
          memory: 20M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
networks:
  yfgj_net:
    external: true
    name: yfgj_net
  • 启动服务
1
docker stack deploy nginx -c docker-conpose-nginx.yml