写点什么

如何 Docker 化任意一个应用

  • 2018-07-28
  • 本文字数:3085 字

    阅读完需:约 10 分钟

网上有很多关于如何将应用 Docker 化的教程,为什么我还要再写一个呢?

我见过的大部分教程都是限定在某种特定技术(例如 Java 或者 Python),可能无法满足读者的需求。同时,这些教程也没有说清楚关于 Dev 和 Ops 团队之间建立明确约定所涉及到的所有相关方面(这正是容器化的精髓所在)。

我根据最近的经验总结了以下一些步骤。它是一份细节清单,包含了其他指南中忽略的内容。

声明:这不是一份新手指南。我建议读者先掌握一些如何设置和使用 docker 的基础知识,并且创建和运行一些容器之后,再来阅读。

让我们开始吧。

一、选择基础镜像

每种对应技术几乎都有自己的基础镜像,例如:

如果不能直接使用这些镜像,我们就需要从基础操作系统镜像开始安装所有的依赖。

外面有很多教程使用的都是 Ubuntu(例如 ubuntu:16.04)作为基础镜像,这不能算有问题,但是我建议优先考虑 Alpine 镜像:

https://hub.docker.com/_/alpine/

它是一个非常小的基础镜像(大约只有 5MB)。

注意:在基于 Alpine 的镜像中无法使用“apt-get”命令,Alpine 系统有自己的软件包仓库和包管理工具。详细请参考:

https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management

https://pkgs.alpinelinux.org/packages

二、安装必要软件包

这个步骤通常比较琐碎,有一些容易忽略的细节:

  • apt-get update 和 apt-get install 命令应该写在一行(如果使用 Alpine 则对应的是 apk 命令)。这不是常见的做法,但是在 Dockerfile 中应该要这么做,否则“apt-get update”命令产出的临时层可能会被缓存,导致构建时没有更新包信息(参见 https://forums.docker.com/t/dockerfile-run-apt-get-install-all-packages-at-once-or-one-by-one/17191 这个讨论)。
  • 确认是否只安装了实际需要的软件(尤其是这个容器会在生产环境中运行)。我看见过有人在他们的镜像中安装了 vim 和其他开发工具。

如果有必要,针对构建、调试和开发环境创建不同的 Dockerfile。这不仅仅关系到镜像大小,还涉及到安全性、可维护性等等。

三、添加自定义文件

一些优化 Dockerfile 的小提示:

四、定义容器运行时的用户权限

现在可以休息一下,阅读下这篇不错的的文章: Understanding how uid and gid work in Docker containers

读完这篇文章,我们会了解:

  • 仅当应用程序需要访问用户或组数据(/etc/passwd 或 /etc/group)时,才需要在容器启动时指定固定的用户 ID。
  • 尽可能避免容器以 root 权限运行。

不幸的是,不少热门应用程序镜像需要用特定的用户 id 来运行(例如 Elastic Search 需要 uid:gid = 1000:1000)。尽量不要在写出这样的镜像……

五、定义暴露的端口

这也是一个微不足道的小操作,但是不要为了暴露特权端口(例如 80)而将容器以 root 权限运行。如果有这样的需求,可以让容器暴露一个非特权端口(例如 8080),然后在启动时进行端口映射。

关于特权端口和非特权端口的不同: https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html

六、定义入口点(entrypoint)

普通方式:直接运行可执行文件。

更好的方式:创建一个“docker-entrypoint.sh”脚本,可以用来通过环境变量来配置容器的入口点(具体请参照下一节)。

这是一个非常普遍的做法,这里有一些例子:

https://github.com/elastic/elasticsearch-docker/tree/master/build/elasticsearch/bin

https://github.com/docker-library/postgres/tree/de8ba87d50de466a1e05e111927d2bc30c2db36d/10

七、定义一种配置方式

基本上每个应用程序都需要参数化。基本上有两条路可以遵循:

  1. 使用应用程序特定的配置文件:该方式需要通过文档说明配置文件的格式、字段、放置位置等等(当运行环境比较复杂,例如应用程序跨越不同的技术,则不太合适)。
  2. 使用(操作系统)环境变量:简单而有效。

如果读者认为这种方式不够现代,记住这也是 12-factors 推荐的方式:

https://12factor.net/zh_cn/config

这并不意味着我们可以抛开所有的配置文件,并对应用程序进行重构,去除配置文件机制。只需要通过 envsubst 命令来替换配置文件模板(这个流程需要在 docker-entrypoint.sh 文件中完成,因为这需要在运行时完成)。

例如:

https://docs.docker.com/samples/library/nginx/#using-environment-variables-in-nginx-configuration

这种方式可以将应用程序的配置文件封装在容器内部,无须让使用者了解这些细节。

八、外部化数据

关于数据存储有一条黄金法则:绝对不要将任何持久化数据保存到容器内。

容器的文件系统被设计成临时和短暂的。因此任何由应用程序生成的内容、数据文件和处理结果都应该保存到挂载的卷或者操作系统绑定挂载点上(既将宿主机操作系统的目录挂载到容器中)。

对于挂载卷我不太有经验,因此我个人更倾向于将数据保存到绑定挂载点(bind mounts)。这些挂载点一般通过类似 Salt Stack 这样的配置管理工具仔细的在宿主机上创建。

这里说的“仔细创建”,主要包括下面几个步骤:

  1. 在宿主机操作系统上创建非特权用户(和组)。
  2. 所有需要绑定目录的所有者都是该用户。
  3. 根据使用场景给授权(仅针对这个特定的用户和组,其他用户无权访问)。
  4. 容器也以该用户运行。
  5. 此时容器就可以完全控制这些目录。

九、确保处理好日志

前面关于“持久性数据”没有一个明确的定义,日志在这里就是灰色地带。我们该如何处理它们呢?

如果这是一个新的应用程序,并且希望它能够坚持 docker 约定,就不应该将日志写入文件。应用程序应该使用标准输出和标准错误输出日志。和之前推荐使用环境变量一样,这也是 12-factors 之一:

https://12factor.net/zh_cn/logs

Docker 会自动捕捉应用程序的标准输出,并可以通过“docker logs”命令查看:

https://docs.docker.com/engine/reference/commandline/logs/

当然还有一些实际场景下会遇到问题。例如运行一个简单的 nginx 容器,至少会有两种不同的日志文件:

  • HTTP 访问日志(Access Logs)
  • 错误日志(Error Logs)

对于这种日志按照特定结构输出的应用,可能不太适合将它们的日志输出到标准输出。这个例子中,只需要按照前面一节中说的处理好持久化问题,并确保正确配置文件的轮转。

十、轮转日志和其他仅追加文件

如果应用程序将日志写到文件,或者会无限追加内容到文件,就需要关注这些文件的轮转(rotation),这对于防止服务器空间耗尽非常有用(尤其是 GDPR 和其他数据安全条例出来之后)。

如果使用绑定挂载,我们可以依靠宿主机的一些工具来实现文件轮转功能,例如 logrotate(文档参见 https://linux.die.net/man/8/logrotate )。

最近我找到的一个简单且完整的例子:

https://www.aerospike.com/docs/operations/configure/log/logrotate.html

另外一个例子:

https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04

查看英文原文: How to dockerize any application

感谢张婵对本文的审校。

2018-07-28 19:554459

评论

发布
暂无评论
发现更多内容

毕业设计So Easy:基于Java Web学生选课系统

不脱发的程序猿

Java web 毕业设计 学生选课系统

五层验证系统,带你预防区块链业务漏洞

华为云开发者联盟

区块链 智能合约 漏洞 可信 自免疫系统

阿里P9分享:基于JDK 8 源码剖析

Java架构师迁哥

野生程序员高考试卷,试试你能打多少分?

北游学Java

程序员 高考

拼搏26天刷完了阿里大佬的Java面试合集1000题,拿到了月薪30K的offer

Java 程序员 架构 面试

☕️【Java 技术之旅】深入分析JDK动态代理的分析(源码深入)

码界西柚

Java JVM 动态代理 6月日更

信息流动过程中的聚类问题

Ryan Zheng

流程即代码:低代码 & 云研发 IDE —— Uncode

Phodal

ide 云开发 云研发

阿里分享:全网最详细的一篇SpringCloud总结

Java架构师迁哥

Apache Calcite:异质数据源优化查询框架

余生

sql Apache Calcite

QQ春节红包活动如何应对10亿级流量?看看大佬的复盘总结

TakinTalks稳定性社区

活动 系统运维 高并发优化 高可用系统的架构 高可用架构

限流篇,欣赏guava的RateLimiter

下雨喽

Java 架构 设计 限流 Guava

搞定研发知识管理,你的企业就能跑快一步

华为云开发者联盟

知识管理 华为云 devcloud 研发团队 研发知识

助力初创企业加速升级,华为云初创扶持计划微光训练营南京站开营仪式成功举办

科技热闻

云钉一体应用创新:音视频如何带来灵活高效的协同体验

阿里云CloudImagine

阿里云 音视频

一口气了解【2021 阿里云峰会】重磅发布

阿里云CloudImagine

阿里云

怒肝最新保姆级前端学习路线,速成贴心全面!

程序员鱼皮

CSS JavaScript Vue 大前端 React

HarmonyOS IoT首著,走进万物互联的世界!

博文视点Broadview

2021上半年1000道大厂高频面试题汇总(Java岗)

Java架构师迁哥

10次面试9次被刷?吃透这500道大厂Java高频面试题后,怒斩offer

Java 程序员 架构 面试

牛掰!阿里首席架构师用7部分讲明白了Java百亿级高并发系统(全彩版小册开源)

Java架构追梦

Java 学习 阿里巴巴 架构 百亿级并发架构设计

如何快速分类整理电脑文件

TroyLiu

文件管理 文件整理 电脑文件 文件分类 快速整理文件

并发编程概览-从Lock和Synchronized说起

追风少年

Java 并发编程

一文讲懂服务的优雅重启和更新

万俊峰Kevin

微服务 web开发 Go 语言 优雅停机

Apache APISIX 开源 2 周年!

API7.ai 技术团队

开源 架构 后端 网关

从原理到实践,手把手带你轻松get数仓双集群容灾

华为云开发者联盟

容灾 集群 数仓 集群容灾 双集群

来自Linux老学员的经验分享,新生必看!

学神来啦

Linux 运维 安全 虚拟机

质量基础设施(NQI)“一站式”服务平台开发搭建

源中瑞-龙先生

NQI 质量基础设施“一站式”

Java 并发编程—— Semaphore

Antway

6月日更

计算机专业的应届生想进大厂做开发有多难?

Java架构师迁哥

机器学习- 吴恩达Andrew Ng 编程作业技巧 -John 易筋 ARTS 打卡 Week 50

John(易筋)

ARTS 打卡计划

如何Docker化任意一个应用_语言 & 开发_Henrique Souza_InfoQ精选文章