(注:本文转载自阿里巴巴云原生课堂理解 Pod 和容器设计模式)
在基本了解什么是Pod的基础上,详细介绍一下 Kubernetes 非常提倡的一个概念,叫做容器设计模式。
接下来将会用一个例子来给大家进行讲解。
比如我现在有一个非常常见的一个诉求:我现在要发布一个应用,这个应用是 JAVA 写的,有一个 WAR 包需要把它放到 Tomcat 的 web APP 目录下面,这样就可以把它启动起来了。可是像这样一个 WAR 包或 Tomcat 这样一个容器的话,怎么去做,怎么去发布?这里面有几种做法。
第一种方式:可以把 WAR 包和 Tomcat 打包放进一个镜像里面。但是这样带来一个问题,就是现在这个镜像实际上揉进了两个东西。那么接下来,无论是我要更新 WAR 包还是说我要更新 Tomcat,都要重新做一个新的镜像,这是比较麻烦的;
第二种方式:就是镜像里面只打包 Tomcat。它就是一个 Tomcat,但是需要使用数据卷的方式,比如说 hostPath,从宿主机上把 WAR 包挂载进我们 Tomcat 容器中,挂到我的 web APP 目录下面,这样把这个容器启用起来之后,里面就能用了。
但是这时会发现一个问题:这种做法一定需要维护一套分布式存储系统。因为这个容器可能第一次启动是在宿主机 A 上面,第二次重新启动就可能跑到 B 上去了,容器它是一个可迁移的东西,它的状态是不保持的。所以必须维护一套分布式存储系统,使容器不管是在 A 还是在 B 上,都可以找到这个 WAR 包,找到这个数据。
注意,即使有了分布式存储系统做 Volume,你还需要负责维护 Volume 里的 WAR 包。比如:你需要单独写一套 Kubernetes Volume 插件,用来在每次 Pod 启动之前,把应用启动所需的 WAR 包到这个 Volume 里,然后才能被应用挂载使用到。
这样操作带来的复杂程度还是比较高的,且这个容器本身必须依赖于一套持久化的存储插件(用来管理 Volume 里的 WAR 包内容)。
InitContainer在 Kubernetes 里面,像这样的组合方式,叫做 Init Container。
还是同样一个例子:在上图的 yaml 里,首先定义一个 Init Container,它只做一件事情,就是把 WAR 包从镜像里拷贝到一个 Volume 里面,它做完这个操作就退出了,所以 Init Container 会比用户容器先启动,并且严格按照定义顺序来依次执行。
然后,这个关键在于刚刚拷贝到的这样一个目的目录:APP 目录,实际上是一个 Volume。而我们前面提到,一个 Pod 里面的多个容器,它们是可以共享 Volume 的,所以现在这个 Tomcat 容器,只是打包了一个 Tomcat 镜像。但在启动的时候,要声明使用 APP 目录作为我的 Volume,并且要把它们挂载在 Web APP 目录下面。
而这个时候,由于前面已经运行过了一个 Init Container,已经执行完拷贝操作了,所以这个 Volume 里面已经存在了应用的 WAR 包:就是 sample.war。等到第二步执行启动这个 Tomcat 容器的时候,去挂这个 Volume,一定能在里面找到前面拷贝来的 sample.war。
像上面这样的做法,在 Kubernetes 里面就是一个非常经典的容器设计模式,叫做:“Sidecar”。
容器设计模式:Sidecar什么是 Sidecar?就是说在 Pod 里面,可以定义一些专门的容器,来执行主业务容器所需要的一些辅助工作,比如我们前面举的例子,其实就干了一个事儿,这个 Init Container,它就是一个 Sidecar,它只负责把镜像里的 WAR 包拷贝到共享目录里面,以便被 Tomcat 能够用起来。回顾xx中的infra container,不就是一个init container吗?
其它有哪些操作呢?比如说:
原本需要在容器里面执行 SSH 需要干的一些事情,可以写脚本、一些前置的条件,其实都可以通过像 Init Container 或者另外像 Sidecar 的方式去解决;
日志收集,日志收集本身是一个进程,是一个小容器,那么就可以把它打包进 Pod 里面去做这个收集工作;
Debug 应用,实际上现在 Debug 整个应用都可以在应用 Pod 里面再次定义一个额外的小的 Container,它可以去 exec 应用 pod 的 namespace;
查看其他容器的工作状态,这也是它可以做的事情。不再需要去 SSH 登陆到容器里去看,只要把监控组件装到额外的小容器里面就可以了,然后把它作为一个 Sidecar 启动起来,跟主业务容器进行协作,所以同样业务监控也都可以通过 Sidecar 方式来去做。
这种做法一个非常明显的优势就是将辅助功能从业务容器解耦了,所以能够独立发布 Sidecar 容器,并且更重要的是这个能力是可以重用的。事实上,任何设计模式的优势归结起来不就是解耦和复用吗!
下面介绍 Sidecar 设计模式的几种常用场景。
Sidecar:应用与日志收集业务容器将日志写在一个 Volume 里面,而由于 Volume 在 Pod 里面是被共享的,所以日志容器 —— 即 Sidecar 容器一定可以通过共享该 Volume,直接把日志文件读出来,然后存到远程存储里面,或者转发到其他容器。现在业界常用的 Fluentd 日志进程或日志组件,基本上都是这样的工作方式。
Sidecar:代理容器proxy假如现在有个 Pod 需要访问一个外部系统,或者一些外部服务,但是这些外部系统是一个集群,那么这个时候如何通过一个统一的、简单的方式,用一个 IP 地址,就把这些集群都访问到呢?这个时候,就可以通过 Sidecar 代理容器。
Sidecar:适配器容器现在业务暴露出来的 API,比如说有个 API 的一个格式是 A,但是现在有一个外部系统要去访问我的业务容器,它只知道的一种格式是 API B ,所以要做一个工作,就是把业务容器怎么想办法改掉,要去改业务代码。但实际上,你可以通过一个 Adapter 帮你来做这层转换。
现在有个例子:原先业务容器暴露出来的监控接口是 /metrics,可是现在,这个监控系统升级了,它访问的 URL 是 /health,新系统只认得暴露出 /health 的 URL,才能去做监控,/metrics 不认识。那这个怎么办?那就需要改代码了,但可以不去改代码,而是额外写一个 Adapter,用来把所有对 /health 的这个请求转发给 /metrics 就可以了,所以这个 Adapter 对外暴露的是 /health 这样一个监控的 URL,这就可以了,你的业务就又可以工作了。
本文主要讲解的就是容器的设计模式:Sidecar。Sidecar 模式(或者说Sidecar容器)的主要作用就是把业务容器与非业务容器解耦,使得应用的打包、发布工作能够更加user-friendly。在定义Pod的yaml文件中,我们可以设置相应的initContainer字段,这就是所谓的Sidecar容器,它总是比其他应用容器先启动。
本文还介绍了几种常见的使用Sidecar设计模式的场景,比如日志的收集、做代理、适配等等。