由于隔一段时间各个云服务商都会搞活动,然后就会剁手入一个,手上已经有 4 个云服务器了。
阿里云轻量一台
腾讯云 ESC 一台
腾讯云轻量两台
然后家里有用 PVE 搞了个虚拟机化,来运行软路由,NAS 之类的家庭服务。由于有高配强迫症,组了台 16 核 32 线程的服务器,导致性能严重过剩,就琢磨着能不能和云服务器组网,来组建一个小集群
最终选定方案是用 zerotier 搭建 VPN 组内网,docker swarm 来组建集群,基于此安装管理面板,以及 https 证书,网关服务日志记录搜索之类的,当然还有服务滚动更新,期间遇到一些坑,记录一下
首先请去 zerotier 组成账号,以及创建一个网络,这里网上教程很多,搜一下就有了。我给个简单的安装以及加入网络的代码。
# 安装
curl -s https://install.zerotier.com | sudo bash
# 加入 zerotier 后台自己创建的网络
sudo zerotier-cli join xxx
可以按照 腾讯云 的文档,来配置,这里就不赘述了
https://cloud.tencent.com/document/product/1207/45596?from=information.detail.腾讯云加速 docker
注意把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 中的容器,这就失去了组建集群的意义了。
这是需要特别注意,踩了好久最后通过搜索才发现,我一度以为是不是这是厂商为了卖自己的集群服务,禁止了用户自建的可能。来源可以参考
在其他服务器中运行,加入到集群当中
# 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 状态
我将我所有的云服务器都作为流量的出入口节点,家里虚拟机的流量将会通过域名指定的云服务器来对外开放。 我是用的是 traefik 作为网关及容器内的负载均衡, 由于 treafik 需要监听 docker 的 event 事件,节点必须是 manager 才能有权限,所以我将所有的云服务器都提升为 manager
# 将 worker 节点升级为 manager 节点
sudo docker node promote swarm-node1
# 将 manager 节点降级为 worker 节点
sudo docker node demote swarm-node1
所有需要跨 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
由于配置过多,我这里直接贴上我现在的配置+注释,这是 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: 'ubuntu@192.168.xxx.xxx:/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 打上对应标签
sudo docker node update --label-add role=node1 swarm-node1
# 删除标签
sudo docker node update --label-rm node1 swarm-node1
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
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
# 查看上面 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
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.