使用 DaoCloud 管理你的服务器

最近终于对混乱的服务器部署感到厌烦了,便有动力做谋划已久的容器化。

先说一下之前服务器的状况:共有三台,一台东京1机房 Linode,两台低配青岛阿里云。以前只有一台 Linode 来的,后来遇上了国内云们的学生免费,另一方面也为了访问速度,因为上面有跑 BKEngine 的游戏统计服务。于是与 BKEngine 相关的东西,大概是一个 MongoDB 数据库、两个 Node 服务、两个静态站,悉数迁移到国内。Linode 上就只剩下面包工坊内部的私有Git,和一堆乱七八糟的东西。

说是乱七八糟的东西,并不是因为这些站点本身乱七八糟,而是他们分别在不同的时间迁入,又在不同的时间迁出。迁出的时候如果碰上我懒了,可能服务都没去停,只切掉了域名解析。……时间久了,就算是我也搞不清这些站点依赖了什么东西,各自的配置在哪里,也是一件有风险的事情。

比如说呢,光是静态站就长期或临时地放过:BKEngine 主站,NVLMaker 主站,高恋主站……应该还有我已经忘记的。动态站在 Linode 上倒是只剩下一个,但它不但是个随手开 screen 用 nodemon 守护的 Node 应用,还依赖了 MongoDB 和 Redis……你要问我这两个数据库的配置文件放哪里了,我已经只能说「我尽量找找」。

于是这就是原因了。

选择 DaoCloud 是因为……嗯……我只知道 DaoCloud 能支持管理自己的服务器,而不是限定某家云平台。如果以后发现有其他家更好的,也许我会再迁移一次吧。

DCS 与 DCD

Dao 面向个人开发者的免费服务总共有两种,一个是 DaoCloud Service (DCS),另一个则是 DaoCloud Develop (DCD)。这两个服务最大的区别在于 DCS 是服务平台,同时集成了 CI 和镜像构建系统,而 DCD 是能让你部署一个属于你自己的 DCS 的服务端程序。此外,自己部署的多个 DCD 也能够与 DCS 连接进行集中管理。

DCD 既然是要自己部署的,就要消耗一定的系统资源。在我的 2G 内存 Linode 上,它启动后会消耗约 700M 内存,这对我来说有点得不偿失了。对我来说,DCS 就足够。

安装和部署

安装

选择上图中的添加主机,系统会为你自动生成一行 sh 命令,在你的主机中执行即可。(当然,Docker 本身要单独安装)

部署应用

  1. 从 Hub 获取镜像进行部署

点击「发现镜像」即可搜索 Docker Hub 的海量镜像,这里我选择了「nginx-proxy」并点击部署:


在部署页面,你能够通过 GUI 界面配置容器参数,非常直观。同时也支持使用 Docker Compose 的 YAML 文件进行配置。

在部署完成后,服务将自行开启。

  1. 使用 CI 构建自己的应用镜像

除了 Nginx 这类开箱即用的服务,我们还会有静态页面或 Node 应用等需要部署。DCS 提供了 CI 服务,支持从常见的几个 Git 托管站点拉取代码并根据指定的 Dockerfile 构建应用镜像。

要使用这一功能,请点击最上一图中的「项目」。构建后的镜像可在「镜像仓库」中找到,并可在那里选择部署。后续的流程与从 Hub 获取镜像进行部署完全相同。

最后,你将获得 Docker 化的站点集群~

我的实践方案

我的需求是托管几个静态站点和几个 Go/Node 应用站点,所以第一步是选择一个合适的反向代理来作为网关。
我很推荐使用 nginx-proxy 这一镜像,其对标准的 nginx 做了封装,能根据其他容器的环境变量自动生成 vhost 配置,无需自己手工创建,且支持热插拔。配合关联的其他应用镜像,还可以支持自动配置和更新 SSL 证书(使用 Let’s Encrypt)。

Go/Node 应用都会暴露出各自的端口,nginx-proxy 会自动识别并绑定好你通过环境变量设置的域名。静态站点会稍有些麻烦,我们不得不在每个静态站点的容器内另外使用一个 nginx,作为静态文件服务器。当然,使用 link 参数能让各个容器共享同一个 nginx,但这破坏了应用的独立性。何况 nginx 本身的资源消耗非常小,这一点冗余是不值得担心的。

静态站点的推荐 Dockerfile(页面数据应放在仓库的 /app 目录):

1
2
3
4
5
6
FROM nginx:1.12-alpine

RUN rm -rf /usr/share/nginx/html
COPY app /usr/share/nginx/html

EXPOSE 80

注:各种知名应用一般都有其对应的 -alpine 版本镜像,alpine 是一个体积超小的超精简 Linux,现在被广泛用于 Docker 容器环境。

本博客(Hexo)的 Dockerfile:

1
2
3
4
5
6
7
8
9
FROM node:8

WORKDIR /app
COPY . /app
RUN yarn install && yarn run build

EXPOSE 4000

CMD ["yarn", "run", "serve"]

注:hexo 这类包含原生代码编译的应用一般不能使用 alpine 版本,因其使用了特殊版本的 libc 替代 glibc 以获得体积上的优化。很多代码在其上编译或运行会出现问题。

网络隔离

在以前使用 VPS 时,我们经常会做的是:禁止密码登录、配置 iptables 这样的配置步骤,使用 Docker 时也不例外。在默认情况下,Docker 容器暴露的端口会直接绑定在 0.0.0.0/0,这意味着任何人都能够通过外网直接访问你的容器端口,甚至都不会经过 nginx。这是非常危险的,特别是当你使用数据库时。

我常常采用的方式是,用某种方法造出一个内网环境,内网中的各个应用可以随意或稍加限制地访问其他应用的端口,而外网只能通过 80 443 22 这几个端口来访问,80 和 443 指向 nginx。

Docker 会托管你的 iptables 配置,这使得你很难随意地定制它,不过还好,它留给我们了 DOCKER-USER 链来配置自己的内部访问。但它的工作方式很怪异,一通折腾下来我也没有达到我的目的,最终我选择的其他方案。

  1. 修改 Docker 监听地址,由 0.0.0.0 改为 172.17.0.1(Docker 网桥中的宿主机地址,其实就是 127.0.0.1)。在修改后,所有外部访问都会被拒绝,包括 nginx 暴露的 80 端口。它现在只允许 172.17.0.x 访问容器了。这需要修改/增加 /etc/docker/daemon.json 文件的 ip 字段。
  2. 为了让来自外网 IP 的流量进入 Docker 网桥,可以用 iptables 做一次 NAT 转换,让来自外网 80/443 端口的流量正常访问 172.17.0.1 的 80/443 端口。配置如下(假设外网网卡是 eth0):
1
2
3
iptables -t nat -I PREROUTING 1 -p tcp -i eth0 --dport 80 -j DNAT --to-destination 172.17.0.1:80
iptables -t nat -I PREROUTING 1 -p tcp -i eth0 --dport 443 -j DNAT --to-destination 172.17.0.1:443
iptables -t nat -A POSTROUTING -j MASQUERADE

以上,我们就达到了隔离目的。

注:

  • 如果你使用 Debian 系,千万不要忘记设置开关机时 iptables 配置的自动保存/恢复。
  • 这些步骤并不包含对宿主机端口的屏蔽,若需屏蔽宿主机的各种端口,仍需按照以往 VPS 的方式进行配置,但可能要放在 DOCKER-USER 链里,但我并没有尝试过。

其他

其实设置监听 127.0.0.1 并 NAT 或许是个更一劳永逸的办法,但无论如何这有些风险,因为你把外网流量完全指向了宿主机。并且,在大多数宿主上,这样做并不能奏效,因为内核配置禁止 local 作为 NAT 的目标地址。
要解除限制可以执行:sysctl -w net.ipv4.conf.eth0.route_localnet = 1sysctl -w net.ipv4.conf.all.route_localnet = 1 (如果你不知道这是在干什么还是别做了)。

评论