8 个构建容器应用的最佳实践
====

![](https://opensource.com/sites/default/files/styles/image-full-size/public/images/business/containers_2015-2-osdc-lead.png?itok=0yid3gFY)

容器是未来在共有云和私有云进行应用开发的主要趋势，但是容器到底是什么，为什么它们成为了一种广受欢迎的部署机制，而且你需要怎样来修改你的应用来为容器化的环境优化它？

### 什么是容器？

容器技术的历史始于 2000 年的 SELinux 和 2005 年的 Solaris zones。今天，容器是由包括 SELinux、Linux 命名空间和控制组（cgroup）等几项内核特性构成，提供了用户进程、网络空间和文件系统空间的隔离。

### 为什么它们如此流行？

最近容器技术大规模的应用在很大程度上是由于旨在使容器更加易于使用的标准的发展，例如 Docker 镜像格式和分布模型，这个标准使用不可变镜像（immutable image），这正是容器运行时环境的起点，不可变镜像可以保证开发团队发布的镜像就是经过测试的，和部署到生产环境中的镜像是同样的镜像。

容器所提供的轻量级隔离为一个应用组件提供了一个更好的抽象。在容器中运行的组件将不会干扰其它可能直接运行在虚拟机上的应用。它们可以避免对系统资源的争夺，而且除非它们共享一个持久卷，否则不会阻止对同一个文件的写请求。容器使得日志和指标采集的实践得以标准化，而且它们可以在物理机和虚拟机上支持更大的用户密度，所有的这些优点将导致更低的部署成本。

### 我们应该如何构建一个基于容器的应用呢？

将应用改为运行在容器中并不是什么很高的要求。主要的 Linux 发行版都有提供了基础镜像，任何可以在虚拟机上运行的程序都可以在上面运行。但是容器化应用的趋势是遵循如下最佳实践：

#### 1. 实例是一次性的

你的应用的任何实例都不需要小心地保持运行。如果你的一个运行了许多容器的系统崩溃了，你还能够转移到其它可用的系统去创建新的容器。

#### 2. 重试而不是崩溃

当你的应用的一个服务依赖于另一个服务的时候，在另一个服务不可用的时候它应该不会崩溃。例如，你的 API 服务正在启动而且监测到数据库不能连接。你应该设计它使得其不断重试连接，而不是运行失败和拒绝启动。当数据库连接断开的时候 API 可以返回 503 状态码，告诉客户端服务现在不可用。应用应该已经遵守了这个实践，但是如果你正在一个一次性实例的容器环境中工作，那么对这个实践的需要会更加明显。

#### 3. 持久性数据是特殊的

容器是基于共享镜像启动，它使用了写时复制（COW）文件系统。如果容器的进程选择写入文件，那么这些写的内容只有在直到容器存在时才存在。当容器被删除的时候，写时复制文件系统中的那一层会被删除。提供给容器一个挂载的文件系统目录，使之在容器存活之外也能持久保存，这需要另外的配置，而且会额外消耗物理存储。明确的抽象定义了什么存储是持久的，催生出了实例是一次性的观点。拥有一个抽象层也使得容器编制引擎可以处理挂载和卸载持久卷的复杂请求，以便这些持久卷可以用于容器。

#### 4. 使用 stdout 而不是日志文件

现在你或许会思考，如果持久的数据是特殊的，那么我用日志文件来做什么事情？容器运行时环境和编制引擎项目所采用的方法是进程应该[写入 stdout/stderr][1]，而且具有归档和维护[容器日志][2]的基础设施。

#### 5. 敏感信息（以及其它配置信息）也是特殊的

你绝不应该将敏感信息例如密码、密钥和证书硬编码到你的镜像中。通常在你的应用与开发服务、测试服务，或者生产服务相交互时，这些敏感信息通常都是不同的。大多数开发者并没有访问生产环境的敏感信息的权限，所以如果敏感信息被打包到镜像中，那么必须创建一个新的镜像层来覆盖这个开发服务的敏感信息。基于这一点来看，你再也不能使用与你们开发团队所创建的和质量测试所测试的相同的镜像了，而且也失去了不可修改的镜像的好处。相反的，这些值应该被存储在环境变量中文件中，它们会在容器启动时导入。

#### 6. 不要假设服务的协同定位

在一个编排好的容器环境中，你会希望让编排器将你的容器发送到任何最适合的节点。最适合意味着很多事情：它应该基于那个节点现在拥有最多的空间、容器所需的服务质量、容器是否需要持久卷，等等。这可能意味这你的前端、API 和数据库容器最终都会放在不同的节点。尽管给每个节点强制分配一个 API 容器是可以做到的（参考 Kubernetes 的  [DaemonSets][3]），但这种方式应该留给执行监控节点自身这类任务的容器。

#### 7. 冗余/高可用计划

即使你没有那么多负载需要高可用性的配置，你也不应该以单路方式编写服务，否则会阻止它运行多份拷贝。这将会允许你运用滚动式部署，使得将负载从一个节点移动到另外一个节点非常容易，或者将服务从一个版本更新到下一个版本而不需要下线。

#### 8. 实现就绪检查和灵活性检查

应用在响应请求之前会有一定的启动时间是一件很正常的事情，例如，一个 API 服务器需要填充内存数据缓存。容器编排引擎需要一种方法来检测你的容器是否准备好服务用户请求。为一个新的容器提供就绪检查可以允许我们进行滚动式部署，使得旧容器可以继续运行直到不再需要它，这可以防止服务宕机。类似的，一个存活检查也是一种容器编排引擎持续检查容器是否在健康可用状态的方法。决定容器健康或者说“存活”应该由容器应用的创建者说了算。一个不再存活的容器将会被结束，而且一个新的容器会被创建来替代它。

### 想查找更多资料？

我将会出席十月份的格雷丝霍普计算机女性峰会（Grace Hopper Celebration of Women in Computing），你可以在这里来看一下关于我的访谈：[应用的容器化：是什么，为什么，和如何实现][4]。今年不去 GHC 吗？那你可以在 [OpenShift][5] 和 [Kubernetes][6] 的项目站点来了解关于容器、编排和应用的相关内容。

--------------------------------------------------------------------------------

via: https://opensource.com/life/16/9/8-best-practices-building-containerized-applications

作者：[Jessica Forrester][a]
译者：[LinuxBars](https://github.com/LinuxBars)
校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

[a]: https://opensource.com/users/jwforres
[1]: https://docs.docker.com/engine/reference/commandline/logs/
[2]: http://kubernetes.io/docs/getting-started-guides/logging/
[3]: http://kubernetes.io/docs/admin/daemons/
[4]: https://www.eiseverywhere.com/ehome/index.php?eventid=153076&tabid=351462&cid=1350690&sessionid=11443135&sessionchoice=1&
[5]: https://www.openshift.org/
[6]: http://kubernetes.io/
