集群规模下日志处理和网络层的经验分享

jopen 10年前

原文  http://www.dockerone.com/article/355

我先假定今晚的听众至少小范围的铺开 docker 容器化技术在线上了,至少熟悉 docker 的工作原理和 remote api。所以我不会对简单的 docker 操作和使用做太多的分享,主要是分享集群化容器线上管理这块中的日志管理和网络管理。

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

  1. 监控这个文件,通过管道把数据转出。这种方案最大的问题是日志文件和容器是绑定的,因此需要有一个 agent 的角色来做这件事,变相的增加了开发成本,还要考虑管道的可靠性问题。另外 CentOS 6系和7系日志地址不一样,如果硬编码则扩展性不佳,如果读取系统配置,那么要考虑跨系统之间的路径问题。
  2. 通过 docker logs api 来远程重定向日志,这种方法最大的问题是你避免不了还是得有 agent 去清理日志这么个操作,否则的话依然磁盘会被打满,当然也可以配合 logrotate 来做这事,不过增加了运维成本。如果是远端调用这个 API 的话,要考虑连接可靠性,一旦出现重连,那么显而易见的是要做日志回溯,否则会丢失一部分日志。
  3. 容器内进程自己把日志写出
    a. 进程直接写出,控制权交给了业务方,对业务不透明,可控性降低,毕竟是集群环境。这样一来也要暴露集群结构给上层。
    b. 映射日志设备(/dev/log)进容器,容器内进程直接写设备,隔离性减弱,单点问题追踪会很麻烦,因为这时候 stdout 和 stderr 是没有内容的,也就是 docker log 命令无任何输出。
    3.a 的话我们试过,不过要让业务方来改代码整体推进进度就不太号控制了。另外暴露了远端日志服务器地址,无论是网络上还是安全上都是有问题的。举个栗子一旦介入 SDN 等管理网络的方式,那么等于就是破坏了整体的隔离性。3.b 就是定位问题 container 比较麻烦,而且还是要涉及到跟业务方沟通。
  4. 使用新版的 log-driver 参数,其中包含支持 syslog,看似很美好,但是在集群环境下要考虑 syslog 单点问题。一般来说会有多个 syslog 或者支持 syslog 协议的远端 server (logstash)。如果使用远程 syslog 接受日志,大量 containers 日志输出并不平均,从而会产生性能热点和流量热点。如果走单机 syslog 再汇总,就跟3.b 没多大区别了,跟踪问题比较麻烦。我觉得目前这个实现更多的是方便了之前使用 syslog 方案的。
  5. attach 方法截获容器输出流重定向,需要 agent 支持,有一定开发要求。目前我们采用这种方案,通过一个模块实现了 consistent hash 把日志流量打到远端收集服务器上。这个方案只需要让业务把日志输出到 stdout/stderr 中即可,并不会增加开发成本。同时 1.6 docker 可以指定日志驱动为 none,避免了 logs 文件的产生。另外一方面可以把 container 自己的 meta info 给附加到日志流里面,从而实现远端的日志检索分类聚合等操作。这个方案最大的问题是开发力量的投入,不同个 dockeclient 实现质量也不一样,当然好处也是很明显的,灵活可控,日志流向和分配都在自己受伤。

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

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

  1. 默认 NAT/BR/HOST,NAT 有性能损失,BR 有网络闪断,HOST 流控不好做,端口冲突靠业务保证没法做到透明。
  2. 网络层方案
    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 在 container host 上去操作,一般是从 3 层或者 2 层实现隔离和跨机 container 互通的,出了问题也很容易排查。但是路由方案对本身物理网络依赖会比隧道方案要重。另外 hook 的话毕竟还是不太优美,所以得看看 libnetwork 是怎样和 docker 结合的。
    目前我们选取的是 macvlan 的方案实现的二层隔离,说轻量主要是对 container 而言,可以完全当 container 为一台虚拟机。说重量,是因为其对物理网络基础设施依赖程度最高。

Q&A

Q. macvlan 是怎么做的

A. linux 内核支持,直接用 agent 在 host 上生成设备塞入到 container 的 namespace 中。

Q. 为何不直接打日志

A. 容器内的话得考虑 docker 本身存储性能问题,如果是容器外得考虑卷管理问问题,如果是远端,得考虑和业务结合的问题

Q. 网络层比较成熟的选择

A. macvlan 进了内核,Calico 做了十来年了,这2者都会比较成熟,weave 比较简单和方便

Q. 日志丢失问题

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

Q. 二进制服务跑在 docker 里面的问题

A. 目前我们也在做类似的事情,把 redis 跑入。但是因为 container 本身没有 init 进程,因此内核参数都是默认的,有些 binary 应用在这方面会比较敏感。我们目前也没找到比较优美的方法解决。docker 官方 github 有不少类似的 issue

</div>