12

浅谈如何打造对容器友好的应用 - DTeam 团队日志

 4 years ago
source link: https://blog.dteam.top/posts/2019-11/%E6%B5%85%E8%B0%88%E5%A6%82%E4%BD%95%E6%89%93%E9%80%A0%E5%AF%B9%E5%AE%B9%E5%99%A8%E5%8F%8B%E5%A5%BD%E7%9A%84%E5%BA%94%E7%94%A8.html?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

容器化是一种全新的产品交付方式,几乎是颠覆了传统的软件交付方式和交付流程,并且在各大公司都已经有了成熟的应用,自从 k8s 问世之后,容器化的交付方式得到了前所未有的高速发展。k8s 的资源调度有效提高了资源率利用率,充分利用每台 Node 节点的计算能力,大大降低了运维压力,以及降低了基础设施的总体成本。

本文就来谈谈如何打造对容器友好的应用,以便现有的应用可以很容易逐步迁移到容器中,实现容器化的交付,方便未来逐步向 k8s 迁移。

容器运行方式

简述一下 Docker 容器的运行方式,Docker 容器会在系统中运行一个隔离环境,你需要打包所有运行依赖到容器镜像中。同时,Docker 允许向容器内注入运行时需要的环境变量,以便修改应用程序的运行行为而无需重新打包镜像。特别注意 Docker 强调容器镜像应该尽可能轻量级,最佳实践要求一个容器只运行一个进程,并且保持该进程在容器内的 PID 为1

一图说明虚拟机和 Docker 容器的对比:

图片来源: https://www.nakivo.com/blog/docker-vs-kubernetes/

如何让应用程序对容器友好

容器可以帮忙解决程序的运行依赖问题,但同时应用程序本身也必须对容器环境进行适配,否则盲目迁移到容器反而会造成更大的迁移成本,甚至无法适配容器环境导致运行失败。本文就浅谈下如何适配容器环境。

依赖轻量化

容器需要包含整个程序的运行依赖,一个打包好的镜像需要在全网节点分发部署。为了减小容器的容量,加快部署速度,有必要对应用程序本身的依赖进行精简。以下都是比较有效且合理的手段:

  • 前后端分离
  • 打包镜像中尽可能保证每一层容量最小 (参考官方最佳实践)

除了最后一项需要编写 Dockerfile 的人对 Docker 本身有比较好的理解之外,前两项都是应用程序设计本身需要解决的问题。

Docker 容器的最佳实践要求每个容器只运行一个进程,该进程 pid 必须为1(比如通过 exec 就能保证 java 进程的 pid 为 1 CMD exec java ${JAVA_OPTS} -jar xxx.jar),所以要求每个程序都能独立运行,而不是需要一个辅助进程在旁协助。

这就要求应用程序自身就能对运行环境做一些自检和适配,而不是需要后台跑个脚本帮助应用程序稳定运行(尽管很多早期的应用通常这么干)

支持环境变量注入

在容器中运行的应用最简单修改配置的方式就是注入环境变量,因此要求应用程序必须能支持环境变量。这一点很多现代化的开发框架都已经支持,比如 Spring Boot 的配置就已经支持通过环境变量直接覆盖application.yml定义好的配置,参考官方文档,另外像Logback这一类的日志框架,也支持通过环境变量覆盖诸如日志级别之类的可配置项,如果已有的技术栈支持环境变量覆盖,直接复用即可,简单做一些相应的优化配置即可完成

容器对于 无状态 应用非常友好,而对于 有状态 的应用维护起来要麻烦的多。如果你的应用程序**_有状态_**,那么需要考虑状态分离,将无状态的部分和有状态的部分剥离成不同的应用,分别部署。无状态的应用可以部署到容器集群中,有状态的应用考虑单独部署一个独立的集群维护。

例如应用程序需要支持存储,那么可以考虑将存储单独剥离成一个存储服务(例如使用现有的云存储服务,或者使用数据库存储等等);如果应用程序需要支持**_session_**,可以考虑改造为无**_session_**的JWT等等。

这样去状态化之后,就可以使用容器部署无状态的应用了。

处理多时区问题

在本地开发很少有人会关心时区问题,很多人都习惯了本地时区,但是迁移到容器环境中非常容易忽略时区问题,造成时间差。

由于容器环境默认使用UTC时间,和本地时间相差 8 小时,所以应用程序必须对时区进行处理。这通常包含两方面的内容:

  • 应用程序本身需要处理时间日期的部分
  • 数据库的时间日期存储

数据库的最佳实践也是要求存储带时区的日期格式,但是大部分开发人员容易忽略,图省事存储为不带时区的格式。当应用程序和数据库所在时区不同就会造成问题。

当然为了简单起见,应用程序也无需考虑国际化的时候,有 2 种办法可以不用编码规避这个问题:

  • 在构建 Docker 镜像的时候修改镜像的时区,参考这篇问答
  • 注入特殊环境变量或 properties(仅对特定程序有用)

对于第一种方案,会额外增加一个 Layer,并且对于不同的 base image 修改时区的方法也不一样(如 Debian 和 Alpine 修改时区的方法就不一样)。

对于第二种方案,需要考虑具体的应用程序,比如 JVM 支持注入user.timezone这个 system property 修改时区:

java -Duser.timezone=America/New_York

对于 Nodejs 则支持通过TZ环境变量指定时区:

TZ='Europe/Amsterdam' node server.js

本篇文章浅谈了如何打造对容器友好的应用需要注意的一些地方。只是想将应用迁移到容器运行的话还是比较容易的,差不多以上几点注意到之后就可以简单在容器环境启动一个单实例容器镜像跑起来了。如果想做到更加适配容器集群环境的话,通常还需要考虑到以下一些问题:

  • 日志打印到终端,而不是存储日志文件
  • 多实例如何协调一致。如接口幂等,防止用户请求分配到不同实例造成数据处理异常,多实例负载均衡,而不是互相冲突等等
  • 容器探针。需要应用程序自己实现一个活性探测接口(如 http, tcp, 脚本探测等)

容器的出现带来了一种全新的交付方式,k8s 的出现将容器的热度大幅度拉升,基于容器和 k8s 的交付方案层出不穷。为了赶上这波热度,我本人也做了很多实际的体验与调研,特此总结此篇文章,以供其他对容器化有兴趣的技术人员参阅。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK