V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
juzisang
V2EX  ›  分享发现

记一次跨云服务商&跨网络搭建 Docker Swarm 集群

  •  
  •   juzisang · 2021-04-23 15:26:03 +08:00 · 3568 次点击
    这是一个创建于 1301 天前的主题,其中的信息可能已经有所发展或是发生改变。

    跨云服务商&网络 Docker Swarm 集群搭建

    前言

    由于隔一段时间各个云服务商都会搞活动,然后就会剁手入一个,手上已经有 4 个云服务器了。

    • 阿里云轻量一台

    • 腾讯云 ESC 一台

    • 腾讯云轻量两台

    然后家里有用 PVE 搞了个虚拟机化,来运行软路由,NAS 之类的家庭服务。由于有高配强迫症,组了台 16 核 32 线程的服务器,导致性能严重过剩,就琢磨着能不能和云服务器组网,来组建一个小集群

    最终选定方案是用 zerotier 搭建 VPN 组内网,docker swarm 来组建集群,基于此安装管理面板,以及 https 证书,网关服务日志记录搜索之类的,当然还有服务滚动更新,期间遇到一些坑,记录一下

    Zerotier 搭建内网

    首先请去 zerotier 组成账号,以及创建一个网络,这里网上教程很多,搜一下就有了。我给个简单的安装以及加入网络的代码。

    # 安装
    curl -s https://install.zerotier.com | sudo bash
    # 加入 zerotier 后台自己创建的网络
    sudo zerotier-cli join xxx
    

    安装 Docker 并配置加速镜像源

    可以按照 腾讯云 的文档,来配置,这里就不赘述了

    https://cloud.tencent.com/document/product/1207/45596?from=information.detail.腾讯云加速 docker

    初始化集群管理节点&加入 Worker

    初始化集群 Manager

    注意把192.168.xxx.xx 替换成你自己 zerotier 后台中的 ip

    sudo docker swarm init --advertise-addr=192.168.xxx.xx:2377 --data-path-addr=192.168.xxx.xx --data-path-port 5789
    

    可以注意到我指定了--data-path-addr=192.168.xxx.xx --data-path-port 5789

    这是因为云服务的网络也是基于 vxlan, 占用了 docker 默认的 4789 端口,导致如果不指定端口,会导致集群虽然能组建成功,但是 docker 容器之间的网络不通。如加入了同一个 network,node1 中的容器,ping 不通 node2 中的容器,这就失去了组建集群的意义了。

    这是需要特别注意,踩了好久最后通过搜索才发现,我一度以为是不是这是厂商为了卖自己的集群服务,禁止了用户自建的可能。来源可以参考

    加入 Worker

    在其他服务器中运行,加入到集群当中

    # manager 节点中运行,获取加入集群的命令
    sudo docker swarm join-token worker
    
    # 在 manager 以外的节点中运行,加入到集群当中
    sudo docker swarm join --token xxx 192.168.xxx.xx:2377 
    

    在 manager 节点运行 sudo docker node ls 查看加入的 node 状态

    Node 提权降权操作

    我将我所有的云服务器都作为流量的出入口节点,家里虚拟机的流量将会通过域名指定的云服务器来对外开放。 我是用的是 traefik 作为网关及容器内的负载均衡, 由于 treafik 需要监听 docker 的 event 事件,节点必须是 manager 才能有权限,所以我将所有的云服务器都提升为 manager

    # 将 worker 节点升级为 manager 节点
    sudo docker node promote swarm-node1
     
    # 将 manager 节点降级为 worker 节点
    sudo docker node demote swarm-node1
    

    创建 Swarm 网络

    所有需要跨 Node 通信的容器,都需要加入该网络

    # 创建一个名为 proxy 的网络
    sudo docker network create -d overlay --attachable proxy
    

    测试集群容器网络是否互通

    # 在所有 Node 中都起一个容器
    sudo docker service create --mode global --network proxy --name web srampal/nginx-netutils:2
    
    # 在任意节点中获取到 nginx-netutils 容器的 ip
    sudo docker network inspect proxy
    "Containers": {
      "39a532786c2c23a1033f7899afe0973bdac9100191b2077306477129f78eafe4": {
        "Name": "nginx-netutils.1.atc36jt29aidgbtgqx95hfefu",
        "EndpointID": "8368996ff2921687ec57ce51412a987c95390b5cb9bd757c6094a74e48ca6640",
        "MacAddress": "02:42:0a:00:01:68",
        "IPv4Address": "10.0.1.104/24",
        "IPv6Address": ""
      }
    }
    
    # 在其他节点的容器中 ping 上面的 ip,检测网络是否通
    sudo docker exec xxxId ping 10.0.1.104
    

    Traefik网关及负载均衡

    由于配置过多,我这里直接贴上我现在的配置+注释,这是 Treafik 的后台面板

    version: '3.4'
    
    services:
      proxy:
        image: traefik:v2.4
        environment:
          - TZ=Asia/Shanghai
          # 用于 acme.sh 获取 https 证书
          - ALICLOUD_ACCESS_KEY=xxx
          - ALICLOUD_SECRET_KEY=xxx
        command:
          # 开启监听 Docker 事件
          - '--providers.docker.endpoint=unix:///var/run/docker.sock'
          # 开启集群模式
          - '--providers.docker.swarmMode=true'
          # 忽略没有 traefik.enable=true 标签的容器
          - '--providers.docker.exposedbydefault=false'
          # 使用 proxy 网络,proxy 为上面创建的 swarm overlay 网络
          - '--providers.docker.network=proxy'
          # 定一个一个名为 http 的入口,端口为 80
          - '--entrypoints.http.address=:80'
          - '--entrypoints.https.address=:443'
          # 开启 https 入口的 tls
          - '--entrypoints.https.http.tls=true'
          # 定义 mysql 的入口
          - '--entrypoints.mysql.address=:3306'
          - '--api'
          # 开启请求日志,明确不使用 UTC,采用容器时区
          - '--accesslog=true'
          - '--accesslog.fields.names.StartUTC=drop'
          # - '--accesslog.filepath=/var/log/traefik/access.log'
          
          # - '--log.level=DEBUG'
          # - '--log.filePath=/var/log/traefik/traefik.log'
          # 具体域名证书的申请,域名必须指向当前机器
          # - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true'
          # - '--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web'
          # 泛域名证书
          - '--certificatesresolvers.letsencryptresolver.acme.dnschallenge.provider=alidns'
          - '--certificatesresolvers.letsencryptresolver.acme.email=xxx@gmail.com'
          - '--certificatesresolvers.letsencryptresolver.acme.storage=/www/config/acme.json'
          # 使用 letsencrypt 的测试环境
          # - '--certificatesresolvers.letsencryptresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory'
        ports:
          # 为了解决流量在 node 节点中跳两次的问题
          # https://github.com/traefik/traefik/issues/1880
          - target: 80
            published: 80
            protocol: tcp
            mode: host
          - target: 443
            published: 443
            protocol: tcp
            mode: host
          - target: 3306
            published: 3306
            protocol: tcp
            mode: host
          # 这样写会导致 node1 入口的流量被 docker 负载均衡到 node2,就算服务只在 node1 上部署
          # - 80:80
          # - 443:443
          # - 3306:3306
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock:ro
          # letsencrypt-config 为远程卷,为了解决多机共享证书
          - letsencrypt-config:/www/config/:ro
          # 将本机时区映射到容器,解决日志时间错乱的问题
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
        # 日志上报到 splunk
        logging: 
          driver: splunk
          options:
            splunk-token: xxxxx-xxx-xxxx-xxx-xxxxx
            splunk-url: http://192.168.xxx.xx:8088/
            splunk-format: raw
        networks:
          - proxy
        deploy:
          # 部署到所有节点当中
          mode: global
          update_config:
            # 更新时将会一个一个更新
            parallelism: 1
            # 更新失败将会回滚
            failure_action: rollback
          restart_policy:
            # 如果不是非 0 状态退出,这回执行重启
            condition: on-failure
            # 重启间隔时间
            delay: 5s
            # 第一次启动失败之后,继续重试 3 次
            max_attempts: 3
            # 检测容器是否启动成功的等待时间
            window: 120s
          placement:
            # 只在 manager 节点中部署
            constraints: [node.role == manager]
          labels:
            # 开启 traefik 监听
            - 'traefik.enable=true'
            # 定一个名为 traefik 的节点,入口为上面定义的 http 端口 80
            - 'traefik.http.routers.traefik.entrypoints=http'
            # 路由到 traefik.xxx.com
            - 'traefik.http.routers.traefik.rule=Host(`traefik.xxx.com`)'
            # 定义一个名为 traefik-https-redirect 的中间件,将会吧 http 302 到 https
            - 'traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https'
            # 使用 traefik-https-redirect
            - 'traefik.http.routers.traefik.middlewares=traefik-https-redirect'
            # 定一个名为 traefik-secure 的节点,入口为上面定义的 https 端口 443
            - 'traefik.http.routers.traefik-secure.rule=Host(`traefik.xxx.com`)'
            - 'traefik.http.routers.traefik-secure.entrypoints=https'
            # 使用内置中间件 authtraefik,访问需要账号密码
            - 'traefik.http.routers.traefik-secure.middlewares=authtraefik'
            # 下面的设置将会申请 返回码证书
            - 'traefik.http.routers.traefik-secure.tls=true'
            - 'traefik.http.routers.traefik-secure.tls.certresolver=letsencryptresolver'
            - 'traefik.http.routers.traefik-secure.tls.domains[0].main=xxx.com'
            - 'traefik.http.routers.traefik-secure.tls.domains[0].sans=*.xxx.com'
            # 使用 traefik 内置的服务
            - 'traefik.http.routers.traefik-secure.service=api@internal'
            # Swarm 模式下必须手动指定对外端口
            - 'traefik.http.services.traefik-secure.loadbalancer.server.port=80'
            # 设置 authtraefik 中间件密码,所有的单个 $ 需要替换为 $$ ,生成密码 echo $(htpasswd -nb user yourpassword) | sed -e s/\\$/\\$\\$/g
            - 'traefik.http.middlewares.authtraefik.basicauth.users=user:&&xxxxx&&xxxx'
    
    # 使用外部手动创建的 proxy 网络
    networks:
      proxy:
        external: true
    
    volumes:
      # sudo docker plugin install vieux/sshfs 安装。注意,所有 node 都要执行安装
      # 在集群中共享数据,比如证书
      letsencrypt-config:
        driver: vieux/sshfs:latest
        driver_opts:
          sshcmd: '[email protected]:/home/'
          password: 'xxxx'
    

    部署一个服务

    version: '3.4'
    
    services:
      helloworld:
        image: traefik/whoami
        networks:
          - proxy
        deploy:
          labels:
            - 'traefik.enable=true'
            - 'traefik.http.routers.helloworld.entrypoints=http'
            - 'traefik.http.routers.helloworld.rule=Host(`helloworld.xxx.top`)'
            - 'traefik.http.middlewares.helloworld-https-redirect.redirectscheme.scheme=https'
            - 'traefik.http.routers.helloworld.middlewares=helloworld-https-redirect'
            - 'traefik.http.routers.helloworld-secure.entrypoints=https'
            - 'traefik.http.routers.helloworld-secure.rule=Host(`helloworld.xxx.top`)'
            - 'traefik.http.routers.helloworld-secure.tls=true'
            - 'traefik.http.routers.helloworld-secure.service=helloworld'
            # 注意,Swarm 模式下必须手动指定对外端口
            - 'traefik.http.services.helloworld.loadbalancer.server.port=80'
    networks:
      proxy:
        external: true
    
    

    滚动更新、回滚、重启策略,及资源限制

      appserver
        image: juzisang/xxx
        networks:
          - proxy
        deploy:
          # 生成的副本数量
          replicas: 2
          # 升级时的配置
          update_config:
            # 每次更新两个
            parallelism: 2
            # 每组更新的间隔时间
            delay: 10s
            # 升级失败则回滚 pause rollback continue,默认 pause 
            failure_action: rollback
          resources:
            # 限制内存最高占用 1024M,单核 cpu 的 50%
            limits:
              cpus: '0.50'
              memory: 1024M
            # 最低保留 512M 内存,单核 0.25
            reservations:
              cpus: '0.25'
              memory: 512M
          placement:
            constraints:
              # 部署到管理机
              - 'node.role == worker'
              # 部署到对应标签的
              - 'node.labels.role==node1' 
          # 容器异常退出之后的重启策略
          restart_policy:
            # 以非 0 返回值退出
            condition: on-failure
            # 间隔 5s 重启
            delay: 5s
            # 重试 3 次
            max_attempts: 3
            # 等待至多 120s 来检测是否启动成功
            window: 120s
    
    
    • 任何给 Node 打上标签
    # 给 node 打上对应标签
    sudo docker node update --label-add role=node1 swarm-node1
    # 删除标签
    sudo docker node update --label-rm node1 swarm-node1 
    

    安装 swarmpit 面板

    swarmpit 可以用于监控集群状态,操纵节点回滚,升级,已经查看日志等操作

    这是我的配置,也是基于官方 docker-compose.yml 基础,加上了 traefik 的配置

    version: '3.3'
    
    services:
      app:
        image: swarmpit/swarmpit:latest
        environment:
          - TZ=Asia/Shanghai
          - SWARMPIT_DB=http://db:5984
          - SWARMPIT_INFLUXDB=http://influxdb:8086
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          - /var/run/docker.sock:/var/run/docker.sock:ro
        healthcheck:
          test: ['CMD', 'curl', '-f', 'http://localhost:8080']
          interval: 60s
          timeout: 10s
          retries: 3
        networks:
          - proxy
        deploy:
          resources:
            limits:
              cpus: '0.50'
              memory: 1024M
            reservations:
              cpus: '0.25'
              memory: 512M
          placement:
            constraints: 
              - node.labels.role==node2
          labels:
            - 'traefik.enable=true'
            - 'traefik.http.routers.swarmpit.entrypoints=http'
            - 'traefik.http.routers.swarmpit.rule=Host(`swarmpit.xxx.com`)'
            - 'traefik.http.middlewares.swarmpit-https-redirect.redirectscheme.scheme=https'
            - 'traefik.http.routers.swarmpit.middlewares=swarmpit-https-redirect'
            - 'traefik.http.routers.swarmpit-secure.entrypoints=https'
            - 'traefik.http.routers.swarmpit-secure.rule=Host(`swarmpit.xxx.com`)'
            - 'traefik.http.routers.swarmpit-secure.tls=true'
            - 'traefik.http.routers.swarmpit-secure.service=swarmpit'
            - 'traefik.http.services.swarmpit.loadbalancer.server.port=8080'
    
      db:
        image: couchdb:2.3.0
        environment:
          - TZ=Asia/Shanghai
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          - db-data:/opt/couchdb/data
        networks:
          - proxy
        deploy:
          placement:
            constraints: 
              - node.labels.role==node2
          resources:
            limits:
              cpus: '0.30'
              memory: 256M
            reservations:
              cpus: '0.15'
              memory: 128M
    
      influxdb:
        image: influxdb:1.7
        environment:
          - TZ=Asia/Shanghai
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          - influx-data:/var/lib/influxdb
        networks:
          - proxy
        deploy:
          placement:
            constraints: 
              - node.labels.role==node2
          resources:
            limits:
              cpus: '0.60'
              memory: 512M
            reservations:
              cpus: '0.30'
              memory: 128M
    
      agent:
        image: swarmpit/agent:latest
        environment:
          - TZ=Asia/Shanghai
          - DOCKER_API_VERSION=1.35
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          - /var/run/docker.sock:/var/run/docker.sock:ro
        networks:
          - proxy
        deploy:
          mode: global
          labels:
            swarmpit.agent: 'true'
          resources:
            limits:
              cpus: '0.10'
              memory: 64M
            reservations:
              cpus: '0.05'
              memory: 32M
    
    networks:
      proxy:
        external: true
    
    volumes:
      db-data:
        driver: local
      influx-data:
        driver: local
    
    

    安装 splunk

    version: '3.4'
    
    services:
      splunk:
        image: splunk/splunk:latest
        networks:
          - proxy
        environment:
          - TZ=Asia/Shanghai
          - SPLUNK_START_ARGS=--accept-license
          - SPLUNK_PASSWORD=xxxx
          # - SPLUNK_UPGRADE=true
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          # 导出配置,防止重启丢配置
          - splunk-var:/opt/splunk/var
          - splunk-etc:/opt/splunk/etc
        ports:
          # 用于外部服务上传日志
          - target: 8088
            published: 8088
            protocol: tcp
            mode: host
        deploy:
          replicas: 1
          restart_policy:
            condition: on-failure
            delay: 5s
            max_attempts: 3
            window: 120s
          placement:
            constraints: 
              - node.labels.role==node3
          labels:
            - 'traefik.enable=true'
            - 'traefik.http.routers.splunk.entrypoints=http'
            - 'traefik.http.routers.splunk.rule=Host(`splunk.xxx.com`)'
            - 'traefik.http.middlewares.splunk-https-redirect.redirectscheme.scheme=https'
            - 'traefik.http.routers.splunk.middlewares=splunk-https-redirect'
            - 'traefik.http.routers.splunk-secure.entrypoints=https'
            - 'traefik.http.routers.splunk-secure.rule=Host(`splunk.xxx.com`)'
            - 'traefik.http.routers.splunk-secure.tls=true'
            - 'traefik.http.routers.splunk-secure.service=splunk'
            - 'traefik.http.services.splunk.loadbalancer.server.port=8000'
    networks:
      proxy:
        external: true
    
    volumes:
      splunk-var:
        driver: local
      splunk-etc:
        driver: local
    
    
    • 上传 docker 容器日志
        # 查看上面 Traefik 的配置
        logging: 
          driver: splunk
          options:
            splunk-token: xxxx-xxxx-xxxx-xxxx-xxxx
            splunk-url: http://192.168.xxx.xxx:8088/
            splunk-format: raw
    

    启动

    sudo docker stack deploy -c proxy-compose.yml proxy
    sudo docker stack deploy -c splunk-compose.yml splunk 
    sudo docker stack deploy -c swarmpit-compose.yml swarmpit 
    

    补充

    13 条回复    2023-02-10 16:09:57 +08:00
    maniaccn
        1
    maniaccn  
       2021-04-23 16:36:04 +08:00
    NB
    akirarika
        2
    akirarika  
       2021-04-23 16:54:05 +08:00 via Android
    好耶!是橘子
    learningman
        3
    learningman  
       2021-04-23 16:55:15 +08:00 via Android
    zerotier 为啥不自建一个 moon
    juzisang
        4
    juzisang  
    OP
       2021-04-23 16:57:49 +08:00
    @learningman #3 因为我家里的虚拟机,云服务器都有公网,用 zerotier 就第一次延迟高,后面就直接直通了。所以暂时不需要 moon,后面看需要在自建吧
    juzisang
        5
    juzisang  
    OP
       2021-04-23 16:58:01 +08:00
    @akirarika #2 啊这
    maxat20xx
        6
    maxat20xx  
       2021-04-23 17:10:43 +08:00 via Android
    …还可以这么玩,受教了
    Flamie
        7
    Flamie  
       2021-04-23 17:18:18 +08:00
    谢谢楼主,学习了!
    zvcs
        8
    zvcs  
       2021-04-23 17:26:28 +08:00 via iPhone
    非常值得学习。
    ringwraith
        9
    ringwraith  
       2021-04-23 17:43:33 +08:00
    学习一下
    Skmgo
        10
    Skmgo  
       2021-04-24 01:21:23 +08:00
    正好用的上,多云环境冗余。
    labulaka521
        11
    labulaka521  
       2021-04-24 16:13:48 +08:00
    嘿嘿 我搭了一个 k3s 集群
    suifengdang666
        12
    suifengdang666  
       2021-04-25 09:44:27 +08:00
    学习大佬操作~
    craftx
        13
    craftx  
       2023-02-10 16:09:57 +08:00
    很全啊。但貌似 docker stack deploy 不支持 compose 中的 configs ,正在找解决方案
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1852 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 16:37 · PVG 00:37 · LAX 08:37 · JFK 11:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.