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

前言

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

  • 阿里云轻量一台
  • 腾讯云 ESC 一台
  • 腾讯云轻量两台

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

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

搭建内网

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

1
2
3
4
# 安装
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

1
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

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

1
2
3
4
5
# 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

1
2
3
4
5
# 将worker节点升级为manager节点
sudo docker node promote swarm-node1

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

创建 Swarm 网络

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在所有 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 的后台面板

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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: 'ubuntu@192.168.xxx.xxx:/home/'
password: 'xxxx'

部署一个服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

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

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
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 打上标签
1
2
3
4
# 给 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 的配置

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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

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
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 容器日志
1
2
3
4
5
6
7
# 查看上面 Traefik 的配置
logging:
driver: splunk
options:
splunk-token: xxxx-xxxx-xxxx-xxxx-xxxx
splunk-url: http://192.168.xxx.xxx:8088/
splunk-format: raw

启动

1
2
3
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

补充