Swarm Mode 集群搭建笔记

之前写过一篇博客 在 Docker Swarm Mode 集群使用 Traefik 反向代理。不过这篇文章是我最开始实践的时候的一些个人总结,其实还是有不少问题没解决的。而且写的比较乱。最近我把集群重新搭建了一遍,写下这篇文章,主要是方便以后自己需要的时候查看。一些技术细节本文就不具体说明了,可以自己去搜索了解。

更新内核使用 BBR

不需要可以跳过。使用脚本自动更换,很方便:

$ wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh && chmod +x bbr.sh && ./bbr.sh

完成之后重启就好了,验证 BBR 可以:

$ lsmod | grep bbr

如果值包含 tcp_bbr 表示已经启动。

安装 Docker 和 Docker Compose

直接使用 Daocloud 的脚本进行安装:

$ curl -sSL https://get.daocloud.io/docker | sh

配置 NFS

集群之间一个需要解决的问题就是容器数据的共享了,解决方案有很多,如果你用阿里云的容器服务的话,它会推荐你购买它的云盘,一个云盘一个月下来也要差不多 30 块钱,而且一个云盘只能被一个服务挂载,着实贵且不方便。

阿里云也有 NFS 提供,但是基本都是几百 G 卖的,特别贵。

服务器有 40G 系统盘,不用真的都是浪费,所以我就自己搭个 NFS 用吧,节省成本。

首先在 master 节点执行(我服务器用的 ubuntu ):

$ apt install nfs-kernel-server

接着在 worker 节点执行:

$ apt install nfs-common

在 master 节点,创建文件夹,我们后续所有数据卷都存在该文件夹下:

$ mkdir /datar

接着编辑 /etc/exports 文件:

# vim /etc/exports
/data xx.xx.xx.xx(rw,sync,no_subtree_check,no_root_squash)

这里的 xx.xx.xx.xx 是你的节点的地址,这样你的节点才能挂载该文件夹。

然后重启服务:

$ systemctl restart nfs-kernel-server

配置防火墙,节点之间互相开放端口,否则 NFS 可能连接会出问题。

接着来到 worker 节点,执行:

$ mount xx.xx.xx.xx:/data /data

这一步是将 master 的 /data 文件夹挂载到 worker 中同样的地方,注意 xx.xx.xx.xx 为 master 的内网 IP。

操作完成,如果希望重启自动挂载,可以修改 worker 中的 /etc/fstab,加入下面这一行,注意 IP 地址。不过这样做有风险,如果挂载失败系统可能无法启动,意味着这个系统盘的镜像可能无法用于其他服务器上。

# vim /etc/fstab
xx.xx.xx.xx:/data     /data    nfs     auto,nofail,noatime,nolock,intr,actimeo=1800 0 0

安装 Convoy(可选)

如果不需要使用命名数据卷,可以略过。我最开始也是使用这个的,但是现在已经不用了。如果你把你的 NFS 文件夹都挂载到相同的地方,可以不需要这一步。

Convoy 是用来管理和创建容器数据卷的,它支持多种 drivers,我主要是利用它来创建 NFS 格式的数据卷,然后利用 NFS 达到跨容器数据卷的目的。

使用以下脚本快速安装:

$ wget https://gist.githubusercontent.com/ruiming/4dd434db6429a448f307fe711f4995db/raw/23951855abe37f0c85a2d5cbbf9974e5fc7b7ed6/ConvoySetup.sh

打开该文件编辑,留意到 [Service] 下方一行:

# vim ConvoySetup.sh
ExecStart=/usr/local/bin/convoy daemon --drivers vfs --driver-opts vfs.path=${VFS_PATH:/mnt}

${VFS_PATH:/mnt} 修改为你前面的目录,我这里就是 /data

然后执行就可以了:

$ chmod +x ConvoySetup.sh
$ ./ConvoySetup.sh

部署 Swarm Mode 集群

其实很简单,首先在 master 节点执行:

$ docker swarm init

屏幕会打印出一条 docker swarm join --token xxx 的信息,复杂该条信息,到 worker 节点执行即可加入。

在开始创建服务时,我们先创建一个网络:

docker network create --driver overlay traefik-net --attachable

TCP 协议转发 Master 的 docker.sock(可选)

Traefik(接下来会介绍) 可以监听 Swarm Mode 服务和容器的变化,在不重启 Traefik 的情况下,重新修改自身配置,做到动态的负载均衡。官方文档中说明了 Traefik 需要运行在 master 节点上面,因为 Docker Swarm Mode 的事件只能通过 master 节点的 docker.sock 捕获。

有一种方法可以解决这个问题,就是将 master 的 docker.sock 通过 TCP 暴露到内网中。如果你不需要,可以略过下面该服务的创建,并且所有 tcp://docker-proxy:2375 都对应修改为 unix:///var/run/docker.sock,并且还需要把 docker.sock 挂载到容器中来。

$ docker service create \
--name docker-proxy \
--global
--mount type=bind,source=/var/run/docker.sock:/var/run/docker.sock \
--network traefik-net \
rancher/socat-docker

我自己是这么用了,这样 Traefik 就不仅仅局限于在 master 部署了。

搭建反向代理服务 Traefik

一开始尝试过使用阿里云的反向代理,但是收费是一方面,另一方面也不是很好用,自己的集群还是以学习为主,经得住折腾,所以就自己部署了。

/data 中创建一个 Traefik 配置文件:

# vim /data/traefik.toml
logLevel = "ERROR"
defaultEntryPoints = ["http", "https"]

# 稍后会把主机 /data/logs 文件夹挂载到容器的 /data/logs 下,控制 Traefik 把日志输出到这里来
[traefikLog]
  filePath = "/data/logs/traefik.log"	

[accesslog]
  filePath = "/data/logs/access.log"

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  compress = true
    [entryPoints.https.tls]
      # 如果你需要指定自己的证书,一般是自己有野卡的情况,可以指定下
      # 当然了,我们也会把主机的 /data/certs 挂载到容器的 /certs 下
      # 我们需要在 /data/certs 下存放与下方对应的同名证书文件
      [[entryPoints.https.tls.certificates]]
      certFile = "/certs/ruiming.me/fullchain.cer"
      keyFile = "/certs/ruiming.me/ruiming.me.key"

[retry]

# 如果你没有自己的证书,希望由 Traefik 帮你申请和维护,可以按下面配置开启
# [acme]
# email = "ruiming.zhuang@gmail.com"
# storage = "acme.json"
# entryPoint = "https"
# onHostRule = true

创建完成之后接着开始创建 Traefik:

$ docker service create \
--name traefik \
--constraint=node.role==manager \
--publish 8080:80 \
--publish 443:443 \
--label traefik.frontend.rule=Host:traefik.ruiming.me \
--label traefik.port=8080 \
--mount type=bind,source=/data/traefik.toml,target=/traefik.toml \
--mount source=/data/certs,target=/certs \
--network traefik-net \
traefik \
--docker \
--docker.swarmmode \
--docker.domain=ruiming.me \
--docker.watch \
--docker.endpoint=tcp://docker-proxy:2375 \
--docker.exposebydefault=false \
--web

我把 Traefik 容器内的 80 端口暴露到了主机的 8080 端口上面了,因为主机的 80 端口我另有用途。如果你没有其他用途,可以设置为 80。

两个 Label 是 Traefik 的标签,作用是指定了一个前端规则,当我们访问 traefik.ruiming.me (当然要做好 IP 解析)的时候,Traefik 会把它转发到这个后端(Traefik 把我们的应用命名为后端)的 8080 端口上面(8080 端口上是一个简单 Traefik UI)。

部署 Portainer

总不能我们一直使用命令来管理容器,所以我们需要一个管理面板,选择同样很多,这里我推荐使用 Portainer。

执行:

$ docker service create \
--name portainer \
--replicas=1 \
--publish 9000:9000 \
--mount src=/data/portainer,dst=/data \
--label traefik.port=9000 \
--label traefik.frontend.rule=Host:xxx.ruiming.me \
--label traefik.docker.network=traefik-net \
--network traefik-net \
portainer/portainer \
-H tcp://docker-proxy:2375

之后访问 xxx.ruiming.me,我们就可以对容器进行可视化的管理和操作了。

不过 Portainer 的 Container 管理只能管理该 Endpoint 本身的,我们需要把其他服务器的 docker.sock 通过 sock 或者 tcp 方式监听,才能查看管理其容器。

这里推荐使用 portainer-agent

$ docker service create \
--name portainer-endpoint \
--global \
--mount src=/var/run/docker.sock:/var/run/docker.sock \
--mount src=/etc/hostname:/etc/host_hostname \
--env PORTAINER_ADDR=portainer:9000 \
--env PORTAINER_USER=YOUR_PORTAINER_USERNAME \
--env PORTAINER_PASS=YOUR_PORTAINER_PASSOWRD \
softonic/portainer-endpoint

日志查看(可选)

关于日志这一块,Docker Swam mode 是可以对日志进行统一管理的,不过我看了下那些驱动都不太懂,所以还是能自己控制日志文件就自己控制下,如果没有或者不知道具体的日志输出到哪里了,可以直接用 docker logs 命令将日志重定向到 logs 卷中的文件。例如我的博客日志:

$ docker service logs -f blog  > /data/logs/blog.log &

它会实时的把容器的 stdout/stderr 输出到对应文件中。

同时 SSR 日志我也在 logs 卷中进行了一个 link 操作。

接着利用创建 port22/tailon 这个容器,把我们的 /data/logs 文件夹挂载进去,就可以看其中所有 .log 结尾的文件。这个镜像是一个很简单的日志查看分析工具,提供了 tail grep awk 三个命令让我们进行日志查看搜索。

当然,安全起见,你可以通过 Traefik 配置一个 http basic auth,加上这个 label :traefik.frontend.auth.basic。配置之前我们需要先执行:

$ htpasswd -nbm username password

会输出一串字符,把它作为这个标签的值配置就可以了。

利用 Docker Stack 一键部署

上面其实都是通过 docker servce create 方式来创建服务,而当我搭建好了 Portainer,我又习惯通过 Portainer 来创建。我需要保存所有创建的命令以方便下次创建,也要避免直接 Portainer 创建而不记录其配置。

我最终自己写了一个 docker-compose.yml 文件,这个文件内包含了我所有的服务,同时这个文件夹内也包含了我所有的数据卷。并且 docker-compose.yml 的挂载路径都是使用的相对路径。如此一来,当我重新搭建一个集群之后,只要前置工作准备好(Swarm 集群搭建起来,NFS 挂载好),我只需要把这个文件夹上传上去,然后运行:

docker stack deploy --compose-file=./docker-compose.yml StackName

恩,就阔以了。什么 MySQL Redis 以及个人博客,SSR,反向代理,其他个人项目,等等,全部都嗖嗖的自动部署上来了。

写到这里的时候,我其实也刚刚把系统重置了,也刚刚执行完这条命令。

1522503450932-1522503454812

我的一个监控服务嗖嗖的一堆邮件发过来了,各种服务 UP。。。

可见 Docker 集群的强大之处,如果你不用 Docker,那么把这些服务,迁移到一个新的集群上面,没有半天时间可能真的搞不定。。。

过去部署一个 SSR 的时间,现在我已经可以把全部项目都部署上来了。

妥善管理和保护好你这个这个 NFS 文件夹,从此远离迁移和部署难题。

你可以为你的域名创建多条 A 记录,分别指向这里的每个服务器 IP。在 DNS 层面实现一层负载均衡。

目前这个文件夹我放在阿里云两台服务器的 Master 节点下,利用阿里云的快照功能实现备份。

其实还有不少可以改进的地方,包括 docker-compose.yml 本身上面其实可以有很多完善的地方,比如加入资源限制,抽离环境变量等等,现在我的这个文件涉及敏感信息我就不拿出来了。还有就是日志上面,也可以有一个更方便查看的地方,Portainer 的日志查看我觉得有点丑和难用,跟阿里云的容器日志一个德性。

折腾这些东西花了挺多时间的,不过还是值得的,也学习到了不少东西,并且总结了这么一套东西,以后迁移也更迅速方便了。