手把手教你玩转 Docker(二)

前言

嗯,这其实是一篇弃坑文。

原先打算针对小白用户搞一系列博文,由浅入深地讲解如何玩转 Docker,但考虑到时间和精力实在有限,放弃了这个念头。当然也不排除,后面有时间和精力的时候,会用 readthedocs 再实现这个想法。

不过,本文也不能一点干货都没有,思考了一番,决定把自己自建 Docker 集群的一些经历分享一下,可能对后人会有点帮助。

自建集群目的

比较清楚地认识到,自己是一个爱折腾的家伙,目前在管理的网站及非网站的应用(包括线上环境与测试环境)起码有 15 个,而且总会有一些新的想法冒出来要去尝试。

在自建集群之前,由于不同应用的依赖环境千差万别,每添加一个应用,不得不考虑主机上的现有环境和已经在跑的服务,而且部署和测试也是比较繁琐,没有办法满足我快速尝试新点子的需求。

而 Docker 恰好可以解决应用部署的环境问题,自建 Docker 集群可以充分利用我手上的闲置 VPS,并且提高应用的可用性。

硬件资源

自建集群有 8 台 VPS,其中 4 台性能比较好的 Vultr 中低配小鸡,一台腾讯云低配 VPS,一台搬瓦工 CN2 小鸡,一台 CloudCone 低配小鸡,还有一台朋友送的 VirMach 小鸡。

手上还有几台谷歌云服务器,不过考虑到用不长久,而且流量贵,就没在自建集群的考虑范围内。

1
2
3
4
5
6
7
8
9
10
[root@ctrl-bwh-01 scripts]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
q0kllzk1ezr9h449cjatak17z GLBS-CC-01 Ready Active 18.09.5
ioacz1khl94m01wul1z70qatu * ctrl-bwh-01 Ready Active Reachable 18.09.5
1sj867qzlrgdp72705pcnvleb db-vultr-01 Ready Active 18.09.5
dua8lh9h6yt5b58jwwn5vtlu6 db-vultr-02 Ready Active 18.09.5
rk4newk0r7z51lvgjil564bar web-qq-01 Ready Active 18.09.5
xaq6d5n4kzcxj7phnvc32xw21 web-virmach-01 Ready Active 18.09.5
1e2a4scv0eskgq0og3xqpedco web-vultr-01 Ready Active Reachable 18.09.5
5jw8pe7vkzr2iwo3m1rrr1ef5 web-vultr-02 Ready Active Leader 18.09.5

集群方案

说到 Docker 集群,一般都会提到鼎鼎有名的 k8s。而我觉得 k8s 太重了,我的众多低配 VPS 跑 k8s 之后,能分配给应用的资源就少得可怜,所以 k8s 是直接就被我 pass 掉的。

虽然也有 k3s 之类的轻量级 k8s 解决方案,不过我还是选择了原生的 docker swarm。VPS 安装好 Docker 之后,不需要额外安装软件,就可以马上建立集群。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 集群初始化,节点成为 manager 节点
docker swarm init --advertise-addr=x.x.x.x

# 集群丢失 Leader 时,强制重建集群
docker swarm init --advertise-addr=x.x.x.x --force-new-cluster

# 获取作为 worker 节点加入集群的命令
docker swarm join-token worker

# 获取作为 manager 节点加入集群的命令
docker swarm join-token manager

# 加入集群
docker swarm join --token xxx x.x.x.x:xxx --advertise-addr=x.x.x.x

官方文档中有提到,Docker 会自动设置 --advertise-addr ,该参数非必填。不过根据个人经验来看,还是强烈建议显式指定该参数,尤其当 VPS 有多个网卡时。

统一的服务入口

在集群内部通过 docker service create xxx 的命令创建服务之后,如果有设置对外暴露端口,那么可以向集群中任意一台 VPS 的指定端口请求服务。

手上的应用还是 Web 应用居多,而它们都需要 80 或 443 端口,为了让它们都能正常提供服务,集群需要一个统一的前端应用提供负载均衡服务,根据一定的规则(比如域名)转发给后端应用。

虽然 nginx 也可以比较方便地实现负载均衡,但是我此处选用的是相对专业的、功能更完善的 traefik。traefik 提供服务自动发现、HTTPS 证书自动生成、服务监测指标数据 等功能,感兴趣的同学可以前往 官网 了解详情。

traefik 架构示意

Traefik 还提供了简易的 Web UI,可以看到当前集群的服务数量和服务状态。

traefik 控制台

集群管理面板

虽然可以登录到 manager 节点,敲命令行管理集群节点、服务,但还是稍麻烦些,而且不太希望所有维护人员都有权限直接操作机器。

可以管理 Docker Swarm 集群管理面板也不少,能入法眼的就 Rancher 和 Portainer,然而由于 Rancher 对宿主机配置要求比较高,消耗资源较多,我最终选择了轻量级的 Portainer。

Portainer 官网提供了比较多的管理方式,踩了比较多的坑之后,我采用的是 agent + portainer 这种方式。以 global 模式在每个节点部署 agent 服务,portainer 部署时需要指定连接 agent 服务。欲知详情,请看 官方文档

portainer 面板

Portainer 还提供了服务更新的 WebHook,我现在的博客和部分站点的代码仓库更新后,通过在 GitLab 设置的 CI-CD 配置,自动打包镜像,然后触发 WebHook 自动更新,省时省力。

当然,Portainer 也是有不完善的地方,比如查看集群服务时,会偶尔出现「无法连接」的报错,而且不能连接数据库,所以部署 Portainer 服务时,要限制它固定在某台宿主机上。不然,会出现频繁设置密码等现象。

监控和告警系统

traefik 提供的管理面板是非常简单的,仅能查看一些基础数据,比如某个服务有多少后端,整体的服务状态。为了能够定制化监控集群中的服务,并且在需要的时候触发告警,让物理人介入进行维护,需要一个监控和告警系统。

而 Grafana 恰好可以满足需求,配合 Prometheus 以及 Prometheus 相关的 Exporter,一个五脏俱全的监控告警系统呼之欲出。

因为腾讯云主机的带宽极小、性能又比较一般,提供对外服务不太合适,为了充分利用资源,我便把监控和告警系统相关的大多服务都部署在上面。本来还想搞一个 ES 在腾讯上,无奈配置太低,只好作罢。

有了监控面板,可以清楚地知道具体服务的服务状态和服务数据规律,下图中的柱状图表示了以 5 分钟为统计周期的「每秒处理请求数」。注:该面板的标题 Total requests over 容易产生误解。

Grafana 监控面板

下图是站点升级结束时,Redis 的服务数据。一般情况下,Clients 会在 10 以下,当 Clients 飙升到 50 以上,就要额外关注站点的服务状态。通过合理设置阈值,可以让 Grafana 在超过阈值时发送告警通知,提醒维护人员对服务进行扩容等操作。

Redis 监控数据

原服务改造

原有服务都是单机部署,需要改造并打包 Docker 镜像。改造难点其实在于持久化数据的存储与读写,比如 Web 应用的 Session 存储、图片存储、附件存储等。

对于 Session 存储,可以搭建中心化的数据中心解决,或者是改用 token 方式进行登录验证。对于图片存储、附件存储,我的改造方案是,将全量数据存放在 BackBlaze 云存储中,每个应用按需从云存储中心拉取数据,并定期删除冷门数据。感兴趣的可以看看我另外一篇博文:开源图床 ImageX 介绍

注意事项

Docker 不稳定

通过实践,我发现 Docker 还是挺容易挂的,尤其是长时间跑高之后。为了保证 Docker 服务的持续运行,除了要让 Docker 开机自启动之外,还需要对 Docker 服务进行监控,一旦发现服务挂了就马上重启服务。

可以通过一条简单的 crontab 定时任务解决:

1
2
# 适用于 CentOS 7,如果 Docker 正在服务,不会产生负面影响
* * * * * systemctl start docker

定期清理

时间长了,宿主机会有很多不需要的镜像、停止的容器等,如果有需要,同样可以通过定时任务进行清理。

1
2
3
4
# 每天凌晨 2 点清理容器和镜像
0 2 * * * docker container prune --force && docker image prune --force
# 更凶残地方式
0 2 * * * docker system prune --force

实现细节与踩坑

本文仅大概介绍了集群建设的整体思路和实现方式,实际上要真正搭建和运行起来,踩坑是避免不了的。如果读者有兴趣,我会抽时间说明一些实现细节。有任何问题,也欢迎在博客、公众号留言。