V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhuang
V2EX  ›  Docker

关于 Docker 镜像构建的一些经验

  •  2
     
  •   zhuang ·
    azhuang · 2015-08-19 21:04:32 +08:00 · 4279 次点击
    这是一个创建于 3144 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近半年时间一直在尝试使用 Docker 作为运维技术,总结出一些经验教训,发在这里算是抛砖引玉吧。

    A. 一致性构建

    这个问题的重要性,我在另一个帖子 /t/208790 里谈到过。目前来说没有什么太好的解决办法,实际应用当中可以退一步,保证各种环境一致即可。

    最近我在这个问题上有了比较成功的尝试,虽然应用方面略微复杂,但这是一个可以自动化的流程。相关的技术细节有几个方面:

    • 使用基于源的发行版( Gentoo )
      目的在于利用发行版的构建脚本( ebuild ),获取每个软件包的编译时依赖和运行时依赖。使用发行版的内置工具确定编译参数。

    • 镜像构建采用叠加方式
      最底层是发行版无关的 rootfs 文件系统结构;中间层是应用程序的运行时依赖;最上层是应用程序本身。这种构建方式的好处有两个,一是每个层面可以独立更新,二是不需要在容器镜像中构建完整的系统,可以减小最终镜像体积。

    • 使用交叉编译构建
      交叉编译本身是为跨平台设计的,这里只用到它“宿主无关”的特性。本质上是锁定工具链的版本与参数,在输入(目标应用程序源码)相同的情况下,必定会有相同的输出(二进制程序)。

    • 构建过程在容器内部进行
      这是自动化工作的一部分,首先构建出一套“可以构建其它元件”的 bot 平台镜像,然后用这套平台去生成 rootfs 、工具链、运行依赖和目标应用,最终组装成完整镜像。

    最终成果效果相当好,比如要构建 nginx 镜像,可以指定 nginx 的版本,可以手动启用 luajit 支持,可以指定 gcc 的版本,可以指定使用 glibc 或者是 uclibc ,甚至可以生成 amd64 或者 arm 的版本。更关键的是在不同的机器上,调用同样的 bot 平台可以生成二进制层面相同的镜像。

    B. 日志

    对应用程序而言,输出日志的方式有三种:一是内建如 syslog 支持,自主输出;二是调用系统 logger 模拟写入日志;三是直接输出到 stdout/stderr 。

    第三种方式的日志可以在宿主机上通过 docker logs 来获取,然而这是非常不可取的做法。比如它无法完成最基本的 logrotate 功能,又或者在云平台部署时没有访问权限,同时输出日志到宿主机也是安全隐患。

    前两种输出方式都会面临一个问题,如何从容器内部提取日志并处理。大致有两种方案,一种是将日志存储的部分以卷的形式挂载到集中式的日志处理单元上,另一种是在容器内部运行一个日志转发应用,直接输出到集中式的日志处理单元上。

    我更倾向采用后者。前者的问题主要是,日志处理必须和应用镜像在同一台物理主机上,同时卷快照的实时性不如即时转发的日志流。后者主要是违背 docker 每个镜像一个进程的应用哲学。

    C. Supervision

    Docker 在强调可移植性的时候给出的 Best Practices 包括一条 one process per container 的应用模式。然而在实际生产里,我认为更合理的表述是 one service per container 才对。

    一方面每个容器只运行一个进程本身不现实,除了少数简单应用和 golang 静态链接构建的应用,不可能只发布一个二进制可执行程序到容器中。况且,在日志输出这个问题上,引入独立的日志管理进程才是最合理有效的做法。甚至 Docker 官方都给出了用 supervisord 做 Process Supervision 的例子。

    单进程容器另一个问题是无法处理宿主信号。

    容器启动的 entrypoint 参数的进程将会成为容器内部的 pid 1 进程。原则上任何应用都可以成为 pid 1 ,比如单进程容器的应用自身,一个正常系统中的 pid 1 是 init 进程。两者的区别在于 init 可以正确处理来自于宿主机器的信号。当宿主调用 docker stop 向容器发送 SIGTERM/SIGKILL 信号时,带有 init 的容器可以结束相关子进程之后再退出,而不是直接终止进程。比如子进程正在写入日志,或者触发某个耗时的钩子应用,一个正确的 init 可以等待任务完成之后再退出。

    所以,即使是单进程应用,还是需要一个合理的 Process Supervision 机制。

    D. 配置管理

    Docker 很容易做到应用和数据之间的解耦合,只需要将数据用 data only volumes 的形式独立出来即可。相比之下,配置管理比起传统在物理机上直接部署更加麻烦了。

    如果将配置作为容器镜像的一部分纳入镜像生成当中,不仅会增加镜像的构建成本,还会降低配置变动的灵活度。理想情况是,容器在启动时通过网络获取配置参数,但这已经是 orchestration 层面上的事情了,或许新引入的问题比解决的问题还要多。

    通过环境变量将配置传递到容器中也是方法之一。主要的问题是,现在支持 docker 的云平台对于容器的控制粒度还不够,环境变量的可控性不佳。通过某个参数控制启用 dev/prd profile 是合适的,但传递完整的配置文件有些勉为其难。

    目前我在构建的镜像里设计了一套半自动化的配置管理方案,原理是将配置作为数据的一种,存储到独立的数据卷中。其中包括各种镜像所需的配置文件,以及轻量化的 busybox/dropbear 程序,方便调试使用。在构建镜像时会自动引入一套 init 系统,如果检测到配置卷,就自动更新容器的配置。最终目标是同一个镜像仅仅通过区分配置就可以区分 dev/prd 的功能。

    单就配置管理来说, Docker 本身并不能解决原来不使用容器技术时的问题,在 Docker 领域应该有更好的解决方案,只是还没有被发掘出来。

    4 条回复    2015-11-27 07:36:28 +08:00
    qinglangee
        1
    qinglangee  
       2015-08-20 09:15:52 +08:00
    不加点广告, 都没人来吐槽了
    adrianzhang
        2
    adrianzhang  
       2015-08-22 00:16:56 +08:00
    日志一般使用 splunk 管理。
    zhuang
        3
    zhuang  
    OP
       2015-08-22 13:08:17 +08:00
    @adrianzhang

    Splunk 一样需要暴露文件系统或者有宿主权限才好做日志管理,与 docker 相关的部分只有一个 splunk forwarder 转发。

    Splunk 更接近 Elasticsearch + Logstash + Kibana 的组合,而这里提到的问题是怎么把日志从容器导出到 Splunk/ELK 之中。至于导出之后如何做处理那是另外一回事。
    popu111
        4
    popu111  
       2015-11-27 07:36:28 +08:00 via Android
    感谢楼主,对我启发不小:)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2733 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:47 · PVG 20:47 · LAX 05:47 · JFK 08:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.