NodeJS 服务 Docker 镜像极致优化指北( 四 )

/app/log 目录(如果没有会自动创建)挂载到容器的 /usr/share/log 中 。这样服务在将日志写入该文件夹时,就能持久化存储在宿主机上,不随着 docker 的销毁而丢失了 。
当然,当部署集群变多后,物理宿主机上的日志也会变得难以管理 。此时就需要一个服务编排系统来统一管理了 。从单纯管理日志的角度出发,我们可以进行网络上报,给到云日志服务(如腾讯云 CLS)托管 。或者干脆将容器进行批量管理,例如Kubernetes这样的容器编排系统,这样日志作为其中的一个模块自然也能得到妥善保管了 。这样的方法很多,就不多加赘述了 。
k8s 服务控制器的选择镜像优化之外,服务编排以及控制部署的负载形式对性能的影响也很大 。这里以最流行的Kubernetes的两种控制器(Controller):DeploymentStatefulSet 为例,简要比较一下这两类组织形式,帮助选择出最适合服务的 Controller 。
StatefulSet是 K8S 在 1.5 版本后引入的 Controller,主要特点为:能够实现 pod 间的有序部署、更新和销毁 。那么我们的制品是否需要使用 StatefulSet 做 pod 管理呢?官方简要概括为一句话:

Deployment 用于部署无状态服务,StatefulSet 用来部署有状态服务 。
这句话十分精确,但不易于理解 。那么,什么是无状态呢?在我看来,StatefulSet的特点可以从如下几个步骤进行理解:
  1. StatefulSet管理的多个 pod 之间进行部署,更新,删除操作时能够按照固定顺序依次进行 。适用于多服务之间有依赖的情况,如先启动数据库服务再开启查询服务 。
  2. 由于 pod 之间有依赖关系,因此每个 pod 提供的服务必定不同,所以 StatefulSet 管理的 pod 之间没有负载均衡的能力 。
  3. 又因为 pod 提供的服务不同,所以每个 pod 都会有自己独立的存储空间,pod 间不共享 。
  4. 为了保证 pod 部署更新时顺序,必须固定 pod 的名称,因此不像 Deployment 那样生成的 pod 名称后会带一串随机数 。
  5. 而由于 pod 名称固定,因此跟 StatefulSet 对接的 Service 中可以直接以 pod 名称作为访问域名,而不需要提供Cluster IP,因此跟 StatefulSet 对接的 Service 被称为 Headless Service
通过这里我们就应该明白,如果在 k8s 上部署的是单个服务,或是多服务间没有依赖关系,那么 Deployment 一定是简单而又效果最佳的选择,自动调度,自动负载均衡 。而如果服务的启停必须满足一定顺序,或者每一个 pod 所挂载的数据 volume 需要在销毁后依然存在,那么建议选择 StatefulSet
本着如无必要,勿增实体的原则,强烈建议所有运行单个服务工作负载采用 Deployment 作为 Controller 。
写在结尾一通研究下来,差点把一开始的目标忘了,赶紧将 Docker 重新构建一遍,看看优化成果 。
NodeJS 服务 Docker 镜像极致优化指北

文章插图
可以看到,对于镜像体积的优化效果还是不错的,达到了 10 倍左右 。当然,如果项目中不需要如此高版本的 node 支持,还能进一步缩小大约一半的镜像体积 。
之后镜像仓库会对存放的镜像文件做一次压缩,以 node14 打包的镜像版本最终被压缩到了 50M 以内 。
NodeJS 服务 Docker 镜像极致优化指北

文章插图
当然,除了看得到的体积数据之外,更重要的优化其实在于,从面向物理机的服务向容器化云服务在架构设计层面上的转变 。

经验总结扩展阅读