系列目录
容器经常是为了解决单一的, 窄范围的问题, 比如说微服务. 然而现实中, 一些复杂问题的完成往往需要多个容器. 这里我们讨论一下如何把多个容器放在同一个 pod 里以及容器间的通信
什么是 pod
pod 是 kubernetes 里的一个基本概念, 可能我们从一开始接触 kubernetes 的时候就开始接触 pod, 并被灌输 pod 是 kubernetes 里最小的不可分割的工作单元, 这里再从多容器的角度对其进行一些基本阐释.
简言之, pod 是 kubernetes 可以部署和管理的最小单元, 换言之也就是说如果你想要运行一个容器, 你先要为这个容器创建一个 pod. 同时, 一个 pod 也可以包含多个容器, 之所以多个容器包含在一个 pod 里, 前面讲到过的初始容器为普通应用容器准备环境是一种使用场景, 往往由于业务上的紧密耦合, 一组容器也需要放在同一个 pod 里.
需要注意的是, 以上场景都非必须把不同的容器放在同一个 pod 里, 但是这样往往更便于管理, 甚至后面会讲到的, 紧密耦合的业务容器放置在同一个容器里通信效率更高. 具体怎么使用还要看实际情况, 综合权衡.
kubernetes 为什么使用 pod 作为最小单元, 而不是 container
直接部署一个容器看起来更简单, 但是这里也有更好的原因为什么在容器基础上抽象一层. 容器是一个存在的实体, 并指向一个具体的事物. 这个具体的事物可能是一个 docker 容器, 但也可能是一个 rtk 容器, 或者一个 Virtlet 管理的虚拟机. 它们有不同的要求.
更深层的原因是为了管理容器, kubernetes 需要更多的信息, 比如重启策略, 它定义了容器终止后要采取的策略; 或者是一个可用性探针, 从应用程序的角度去探测是否一个进程还存活着.
基于这些原因, kubernetes 架构师决定使用一个新的实体, 也就是 pod, 而不是重载容器的信息添加更多属性, 用来在逻辑上包装一个或者多个容器的管理所需要的信息
kubernetes 为什么允许一个 pod 里有多个容器
pod 里的容器运行在一个逻辑上的 "主机" 上, 它们使用相同的网络名称空间 (也就是说, 同一 pod 里的容器使用相同的 ip 和相同的端口段区间) 和相同的 IPC 名称空间. 它们也可以共享存储卷. 这些特性使它们可以更有效的通信. 并且 pod 可以使你把紧密耦合的应用容器作为一个单元来管理.
因此一个应用如果需要多个运行在同一主机上的容器时, 为什么为把它们放在同一个容器里呢? 首先, 这样何故违反了一个容器只负责一个应用的原则. 这点非常重要, 如果我们把多个应用放在同一个容器里, 这将使解决问题变得非常麻烦因为它们的日志记录混合在了一起, 并且它们的生命周期也很难管理. 因此一个应用使用多个容器将更简单, 更透明, 并且使应用依赖解偶. 并且粒度更小的容器更便于不同的开发团队共享和复用.
这里说到为了解偶把应用分别放在不同容器里, 前面我们也强调为了便于管理管紧耦合的应用把它们的容器放在同一个 pod 里. 一会强调耦合, 一个强调解偶看似矛盾, 实际上普遍存在, 高内聚低耦合是我们的追求, 然而一个应用的业务逻辑模块不可能完全完独立不存在耦合, 这就需要我们从实际上来考量, 做出决策.
多容器 pod 的使用场景举例
Sidecar containers "帮助" 主容器, 比如日志文件监视器. 一个日志监视器构建完成以后, 可以由不同的应用来使用. 另一个示例是 sidecar 容器为主容器加载文件和运行需要的数据
代理, 桥接和适配器 使主容器与外部世界联通. 比如 Apache http 服务器或者 nginx 可承载静态文件. 也可以做为一个 web 的反向代理服务器.
你可以使用一个 pod 来承载一个多层应用(比如 WordPress), 但是更建议使用不同的 pod 来承载不同的层, 因这这样你可以为每一个层单独扩容并且把它们分布到集群的不同节点上.
同一 pod 间的容器通信
把不同的容器放在同一个 pod 里让它们之间的通信变得非常直接和简单, 它们可以通过以下几种方法达到通信目的.
同一 pod 内的容器共识存储卷
你可以使用一个共享的存储卷来简单高效的地在容器间共享数据. 大多数情况下, 使用一个共享目录在同一 pod 里的不同容器间共享数据就够了.
一个标准的同一 pod 内容器共享存储卷的用例是一个容器往共享存储卷里写入数据, 其它的则从共享目录里读取数据
- apiVersion: v1
- kind: Pod
- metadata:
- name: mc1
- spec:
- volumes:
- - name: html
- emptyDir: {}
- containers:
- - name: 1st
- image: nginx
- volumeMounts:
- - name: HTML
- mountPath: /usr/share/nginx/HTML
- - name: 2nd
- image: debian
- volumeMounts:
- - name: HTML
- mountPath: /HTML
- command: ["/bin/sh", "-c"]
- args:
- - while true; do
- date>> /HTML/index.HTML;
- sleep 1;
- done
这个示例里我们定义了一个存储卷叫作 HTML, 它是 emptyDir 类型的. 当一个 pod 在一个节点上创建的时候, 它就被分配, 只要 pod 一直运行在这个节点上它就一直存在(emptyDir 生命周期和 pod 相同). 就像它的名字所暗示的一样, 它是一个空的目录, 第一个容器运行了一个 nginx server 并且把它挂载到 / usr/share/nginx/HTML, 第二个容器使用了一个 Debian 镜像并把 emptyDir 挂载到 / HTML. 每一秒钟, 第二个容器就会把当前日期写入到 index.HTML 里, 它位于共享存储卷里. 当用户发起一个 http 请求, nginx 就会读取它并响应给用户.
你可以通过把 nginx 的端口暴露出去然后通过浏览器查看或者查看共享目录里的文件来检测以上是否有效果
- kubectl exec mc1 -c 1st -- /bin/cat /usr/share/nginx/HTML/index.HTML
- ...
- Fri Aug 25 18:36:06 UTC 2017
- $ kubectl exec mc1 -c 2nd -- /bin/cat /HTML/index.HTML
- ...
- Fri Aug 25 18:36:06 UTC 2017
- Fri Aug 25 18:36:07 UTC 2017
进程间通信(IPC)
同一个 pod 里的容器共享 IPC 名称空间, 这就意味着他们可以通过进程间通信的手段来进行通信, 比如使用 SystemV semaphores 或者 POSIX 共享内存
以下示例中, 我们定义一个包含了两个容器的 pod. 它们使用相同的 docker 镜像, 第一个容器是一个生产者, 创建一个标准的 Linux 消息队列, 写一些随机的消息, 然后写一个特殊的退出消息. 第二个容器是一个消费者, 打开同一个消息队列来读取数据直到读到退出消息, 我们把重启策略设置为 Never, 这样当两个 pod 都中止的时候 pod 就会停止.
- apiVersion: v1
- kind: Pod
- metadata:
- name: mc2
- spec:
- containers:
- - name: producer
- image: allingeek/ch6_ipc
- command: ["./ipc", "-producer"]
- - name: consumer
- image: allingeek/ch6_ipc
- command: ["./ipc", "-consumer"]
- restartPolicy: Never
然后通过 kubectl create 来创建 pod, 用下面命令来查看状态
- $ kubectl get pods --show-all -w
- NAME READY STATUS RESTARTS AGE
- mc2 0/2 Pending 0 0s
- mc2 0/2 ContainerCreating 0 0s
- mc2 0/2 Completed 0 29
这时候你可以检测每一个容器的日志来检测第二个队列是否消费了第一个队列生产的所有消息包括退出消息
- $ kubectl logs mc2 -c producer
- ...
- Produced: f4
- Produced: 1d
- Produced: 9e
- Produced: 27
- $ kubectl logs mc2 -c consumer
- ...
- Consumed: f4
- Consumed: 1d
- Consumed: 9e
- Consumed: 27
- Consumed: done
这里面存在一个问题, 就是生产者所在容器要早于消费者所在容器, 我们必须处理容器的启动顺序问题
容器的依赖关系和启动顺序
当前, 同一个 pod 里的所有容器都是并行启动并且没有办法确定哪一个容器必须早于哪一个容器启动. 就上面的 IPC 示例, 我们不能总保证第一个容器早于第二个容器启动. 这时候就需要使用到初始容器 (init container) 来保证启动顺序, 关于初始容器, 可以查看官方文档, 本系列也对其进行了翻译, 以便查看.
同一 pod 的容器间网络通信
同一 pod 下的容器使用相同的网络名称空间, 这就意味着他们可以通过'localhost'来进行通信, 它们共享同一个 Ip 和相同的端口空间
同一个 pod 暴露多个容器
通常 pod 里的容器监听不同的端口, 想要被外部访问都需要暴露出去. 你可以通过在一个服务里暴露多个端口或者使用不同的服务来暴露不同的端口来实现
来源: https://www.cnblogs.com/tylerzhou/p/11009412.html