分享人彭哲夫,芒果TV平台部核心技术团队负责人,主要负责Docker和Redis Cluster相关的技术设施开发。之前担任过豆瓣App Engine和金山快盘的主程。在系统工程方面有非常丰富的经验。

我先假定今晚的听众至少小范围的铺开 Docker 容器化技术在线上了,至少熟悉 Docker 的工作原理和 remote API。所以我不会过多的介绍Docker 的基本操作和使用,主要是分享集群化容器中的日志管理和网络管理。

在早期 Docker 实现中,日志这块的功能都不完善,所有容器内的标准输出和错误都会写入到/var/lib/docker/containers/{$cid}/{$cid}-log.json中。因为没有日志自动分卷以及容器绑定,所以一旦到线上就会出现瞬间磁盘打满的情况。而这个文件同时又是 docker logs api 的 data source,加之 docker 1.6 引入的 log-driver 参数,因此对于线上日志的收集管理我们目前有这么几个方法。

监控文件,并通过管道转出数据。这种方案最大的问题是日志文件和容器是绑定的,因此需要有一个 agent 的角色来做这件事,变相的增加了开发成本,还要考虑管道的可靠性问题。另外 CentOS 6系和7系日志地址不一样,如果硬编码则扩展性不佳,如果读取系统配置,那就要考虑跨系统之间的路径问题。 通过docker logs api来远程重定向日志。这种方法最大的问题是你避免不了还是得有 agent 去清理日志这么个操作,否则的话磁盘依然会被打满,当然也可以配合 logrotate 来做这事,不过增加了运维成本。如果是远端调用这个 API 的话,需要考虑连接的可靠性,一旦出现重连,那就要做日志回溯,否则会丢失一部分日志。 容器内进程自己写出日志。这又有两种方案,如下: 进程直接写出,控制权交给了业务方,对业务不透明,可控性降低,毕竟是集群环境。这样一来也要暴露集群结构给上层。 映射日志设备(/dev/log)进容器,容器内进程直接写设备,隔离性减弱,单点问题追踪会很麻烦,因为这时候 stdout 和 stderr 是没有内容的,也就是docker log命令无任何输出。

『进程直接写出』这种方案我们试过,不过要让业务方来改代码,所以整体推进很难了。另外它还暴露了远端日志服务器地址,无论是网络上还是安全上都是有问题的。举个例子,一旦介入 SDN 等管理网络的方式,那么等于就是破坏了整体的隔离性。『映射日志设备进容器』这种方案就是定位问题容器比较麻烦,而且还是要涉及到跟业务方沟通。

使用新版的 log-driver 参数,其中包含支持 syslog,看似很美好,但是在集群环境下要考虑 syslog 单点问题。一般来说会有多个 syslog 或者支持 syslog 协议的远端 server (logstash)。如果使用远程 syslog 接受日志,大量容器日志输出并不平均,从而会产生性能热点和流量热点。如果走单机 syslog 再汇总,那就和上面的方案『进程直接输出』没多大区别了,同样是跟踪问题比较麻烦。我觉得目前这个实现更多的是方便了之前使用 syslog 方案的。 通过attach 方法截获容器输出流重定向。这种方案需要 agent 支持,有一定开发要求。目前我们采用的就是这种方案,通过一个模块实现了 consistent hash,然后把日志流量打到远端收集服务器上。这个方案只需要让业务把日志输出到 stdout/stderr 中即可,并不会增加开发成本。同时Docker 1.6中可以指定日志驱动为 none,避免了 logs 文件的产生。另外一方面可以把容器自己的 meta info 附加到日志流里面,从而实现远端的日志检索分类聚合等操作。但这个方案最大的问题是开发力量的投入,不同的 dockeclient 实现质量也不一样,当然好处也是很明显的,灵活可控,日志流向和分配都在自己受伤。

所以日志方面,从目前 Docker 实现来看,如果开发力量跟得上,agent + attach 方案是灵活性和可控性是最高的。目前 log-driver 对于上规模的集群来说还是不太好用,理想状态下我希望是可以指定多个 log-drivers,通过 hash 方案打到远端。当然具体方案的选取就得看各自公司本身的基础设施和设计目标了。

说完日志来说下网络,目前 Docker 的网络方案主要有这么几个,当然现在大家都在等 1.7,不过我认为对于生产系统而言,已有 SDN 方案的不会太过于在乎 Libnetwork,可能会研究下其和 Docker 是怎样通过 plugin 方式结合的。因为其他它案目前都是 Hook 方式去做的。

默认 NAT/BR/HOST,NAT 有性能损失,BR 有网络闪断,HOST 流控不好做,端口冲突靠业务保证没法做到透明。 网络层方案 a. 隧道方案 I. OVS,主要是有性能方面的损失,基于 VxLAN 和 GRE 协议,类似的方案还有 Kubernetes/Socketplane 的实现。 II. Weave,UDP 广播,本机建立新的 BR,通过 PCAP 互通。 III. Flannel,UDP 广播,VxLan。
隧道方案非常灵活,但是因为太过于灵活,出了网络问题(A-B 链路抖动)跟踪起来比较麻烦,大规模集群情况下这是需要考虑的一个点,毕竟即便是内网也不一定风平浪静。 b. 路由方案 I. Pipework,对于 Docker BR 本身的扩展,当然也支持 OVS macvlan 等方案的结合。现在 libnetwork 出来变相的是废了这个项目了,长远来看后继无人,因此它不是一个很好的选择。 II. Calico,基于 BGP 协议的路由方案,支持很细致的 ACL 控制,对于隔离要求比较严格的场景比较适合,对混合云亲和度比较高,因为它不涉及到二层的支持。 III. Macvlan,从逻辑和 Kernel 层来看隔离性和性能最优的方案,基于二层隔离,所以需要二层路由器支持,大多数云服务商不支持,所以混合云上比较难以实现。
路由方案没那么灵活,大多数情况下需要有一个 agent 在容器主机上去操作,一般是从 3 层或者 2 层实现隔离和跨主机容器互通的,出了问题也很容易排查。但是路由方案对本身物理网络依赖会比隧道方案要重。另外 hook 的话毕竟还是不太优美,所以得看看 libnetwork 是怎样和 Docker 结合的。

目前我们选取的是macvlan 的方案实现的二层隔离,说轻点主要是对容器而言,可以完全当容器为一台虚拟机。说重点,是因为其对物理网络基础设施依赖程度最高。

Q&A

问题:macvlan 是怎么做的?
答案: Linux 内核支持,直接用 agent 在 host 上生成设备塞入到容器的 namespace 中。

问题:为何不直接打日志?
答案: 容器内的话得考虑 docker 本身存储性能问题,如果是容器外得考虑卷管理问问题,如果是远端,得考虑和业务结合的问题。

问题:网络层比较成熟的选择?
答案: macvlan 进了内核,Calico 做了十来年了,这2者都会比较成熟,weave 比较简单和方便。

问题:日志丢失问题
答案: 尽可能的不让业务层去控制日志输出,即便是 socket 文件或者设备都有可能被删除从而导致日志丢失,因此尽量从平台层面控制。

问题:二进制服务跑在 Docker 里面的问题
答案: 目前我们也在做类似的事情,跑Redis。但是因为容器本身没有 init 进程,因此内核参数都是默认的,有些 binary 应用在这方面会比较敏感。我们目前也没找到比较优美的方法解决。Docker 官方仓库有不少类似的 issue。

原文链接: http://dockone.io/article/355