在踩坑无数之后,多次修改后,这篇草稿箱中的文字终于得以成型,撒花。

六月更新架构的时候,去掉了 openresty 作为服务器前端,取而代之的是裸跑 Traefik,因为只暴露网关的 80 / 443,后面所有子容器都是以 expose 方案对内暴露端口到一块虚拟网卡上,安全问题也不大,网关挂载着通配符证书,可以方便的添加删除后面的应用,虽说用起来挺舒服的,但是有两点始终让我不是很爽。

  1. 因为 docker 直接使用 iptable 操作端口转发,在不修改 iptable 的前提下,之前积累了大量的 fail2ban 规则无法使用了,也就是说得忍受大量扫描器污染日志。
  2. 因为应用跑完全隔离的方案,子容器能拿得到的客户端 IP 都是虚拟网卡 IP ,想统计个数据,得用对账的方案,比较麻烦。

问了一下之前出去创业的师傅,他们直接在给容器分配公网,壕无人道,而且直接分配公网 IP 配置项目也不少…

如何能在最少配置的情况下,最少资源消耗的情况下,达到现有使用的便捷程度,便更为了我这篇文字的主要目的。

赶在休假结束之前,重新梳理了服务器运行环境和Traefik的使用,记录下来,或许对折腾“免”运维的服务的你也会有帮助。

在折腾具体配置之前,需要先提供标准的系统环境。

基于 Ubuntu 18.04 系统配置 Docker 运行环境

2018年第三个季度,Ubuntu 18.04 更新了快一个季度了,基本上该更新的软件也都更新了,该兼容的软件也都进行了兼容处理;Docker 在经历改名风波后,版本迭代高歌猛进,一大波编排软件蜂拥而至,现在版本也升级到了 v18 。

考虑到后面软件会做越来越多向后兼容的事情,16.04 的维护周期也近半,那么这次就使用最新的系统和软件来进行基础环境的配置吧,这里没有选择 CoreOS 是因为我这里还有一些其他软件的使用需求,想在一个相对中立的环境中使用。

截止这篇文章写成: 我使用的国外主流云厂商皆支持 Ubuntu 18.04, 国内的云厂商中,阿里云支持,但是腾讯云暂时还不支持。

如果你的云主机厂商支持最新版本的系统,那么可以直接参考我下面给出的命令进行基础环境配置。

如果你的云主机厂商不支持,那么请使用 do-release-upgrade 命令先进行手动升级,升级过程中可以一路 yes,以及使用当前软件维护的最新版本: install the package maintainer's version

下面开始介绍如何配置最基础的系统环境。

先更新软件包列表,升级软件版本到最新的稳定版,然后为避免系统中残留一些老古董影响软件运行,我们要进行尝试性的卸载老版本软件操作,以及安装一些常用软件。

向系统中添加 Docker 官方 GPG Key,然后验证该 Key 有效性,并更新仓库源到系统,Ubuntu 18.04 会直接触发拉取软件包列表的操作,比较人性化,最后直接敲入 install 命令,进行社区版的 docker 安装即可。

https://github.com/docker/compose/releases 找到最新稳定版本安装。

至此,基础的 docker 环境就安装就绪了。

简单的系统加固

基础的修改 ssh 端口,规避扫描器,应该人人都会,就略过不提,如果不会可以百度或者翻阅之前的博客文章,我们来说说 ufw 防火墙的配置。

服务默认会是未激活状态,在激活之前,务必先豁免 ssh 端口,避免再使用 vnc 登录上去补救。

当然,如果你不喜欢修改 SSH 端口的话,可以直接使用下面的命令。

然后激活防火墙状态。

防火墙启动完毕,可以顺便折腾一下 fail2ban ,大幅减少常规的扫描器对于日志的骚扰,和一些初级的猜解、渗透,后面再写一篇详述。

Docker 端口绑定和 UFW 的冲突

在配置完毕防火墙后,接下来可以试验配置是否生效,启动一个映射到 80 端口的 nginx:alpine 镜像,然后浏览器或者命令行访问服务器公网IP,可以看到熟悉的 nginx 默认欢迎页,ufw 并没有什么作用。

这里 ufw 没有生效的原因在于 docker 默认使用了 iptable 添加了一些转发规则,压根没有走到 ufw 的规则中。

一般网上推荐的方案是,关闭这个特性,比如使用类似下面的操作。

但是如果你真的这样做了,接下来你将无法获得客户端访问时,使用的IP信息,各个容器直接访问互联网、以及容器互通方面也会遇到一些小问题,然后你又不得不添加一些 iptable 去修正这个问题。

如果你还是选择关闭 iptable 特性,执行容器,那么如果你想获取客户端IP,便只能使用以下几个方案达成:

  1. 前端启动一个L7的Haproxy / nginx反向代理后面的服务。
  2. 运行模式改为 host ,放弃容器完整虚拟化,将端口直接暴露到 host 上,此时将不再能够通过 docker ps 查看到你的容器端口状态,只能通过 docker network inspect 网卡看到对应的端口开启状态,十分不利于维护。
    • 如果你是使用经典的 docker run 命令,那么需要配合 --net=host 参数。
    • 如果你是使用非 swarm 模式的 compose, 则需要声明 network_mode: "host", compose 版本需要声明 3.2 及以上, port 导出实际并不需要用繁琐的模式定义。
    • 不用尝试创建自定义网络为 host ,截止本文完成时的编排工具版本以及 docker 版本,这个功能不支持。
  3. 修改 ufw 、docker 的 iptable 转发规则,完成你想要的转发方式。

可以看到,不管是哪种方案,搞起来都十分繁琐,而且不利于重复部署,未来调试维护成本太高了。

更好的方案

让 Docker 保持默认配置和行为,但是留出端口控制权给 UFW 以及外层的网关,子容器依旧全部使用 expose 使用私有化的方法导出端口给网关。

这个方案是不是看起来和上面小节中的方案1很相似,但是其实差别还不小,使用 Traefik 可以在不不配 consul / zk 的情况下,自动监听 docker daemon 的状况,做到服务发现、负载均衡、可用性自动切换、甚至自动绑定域名证书。

之前不得不说是过分追求全容器方案,导致我使用 Traefik 都是在容器中。虽说升级相对轻松,只需要修改 compose 配置中的版本字段即可,程序可用性也不需要太过关注,直接交付给 Linux Daemon 去维护,但是这样就面临一个问题,网关拿到数据的时候,已经经过了至少两块虚拟网卡的转发,一来浪费性能,二来丢失客户端IP,三来如果要保障更高级别的安全,还得关闭 docker iptable 转发的特性,这个面临的问题,上面的小节里说的够多了。

在决定“裸”运行 Traefik 后,我们需要对它的配置进行一定的改动,我这里提供一份最简单的配置,相信已经可以满足许多常见场景。

将配置做适当修改,保存之后,运行即可:

当然,此时你是无法访问到你的 Traefik 网关提供的服务的,为什么呢,因为这个软件端口绑定会受限制于 UFW 的规则,所以我们要更新 UFW 规则,允许外网访问我们的 80 和 443 端口。

如果你操作顺利,此刻你已经能够顺利访问你的网站了。

当然,这里少了 docker daemon 的协助,进程管理还是要看护一下的,推荐使用 supervisor 进行辅助管理,之前的博客有介绍过不止一次,有兴趣可以翻阅,这里同样给出一份最基础的配置参考:

你的网关就就绪之后,我们随便找一个目录使用一个叫做 whoami 的软件镜像帮助我们验证:网关能够如期的使用,除了自动服务发现,负载解析,还能提供包括统计、转发、header重写等功能。

将上面的配置保存为 docker-compose.yml,然后后台运行起来。

浏览器或者命令行访问 who.your.com,获得下面的信息:

可以看到服务发现、SSL证书挂载、源站IP转发等功能都能够正确使用,而且不出意料,QPS 也会高不少(毕竟少了至少一层网络转发、至少一层完整的虚拟化)。

另外,由于没有修改 docker 的配置,容器不会出现不允许访问外网的情况,简单验证:

接下来就是逐步升级每台服务器以及做数据迁移了。

资源链接

其实关于容器内获取外部IP,社区有大量讨论,比如:0 等等,涉及不同的网卡模式,不同的编排工具,不同的端口映射模式,又有许多延伸话题。

而 Docker 和 UFW 防火墙的恩怨情仇,其实也是老化长谈,但是不知道为何,网上能看到的资料一边倒到修改 iptable …

其他

希望本文能够给你一些额外的启示,帮到正在使用 Traefik 和 Docker 来做服务化的你。