【学习笔记】Pod 概念与容器设计模式

为什么需要 Pod

容器的基本概念

容器的本质实际上是一个进程,是一个视图被隔离,资源受限的进程。

容器里面 PID = 1 的进程就是应用本身,这意味着管理虚拟机等于管理基础设施,因为我们是在管理机器,但管理容器等于直接管理应用本身。

Pod 的类比概念

我们说 K8s 就是云时代的操作系统,我们举个真实操作系统的例子。

例如 Helloworld 程序,这个程序实际上是由一组进程组成的 ({api、main、log、compute}),这些进程等同于 Linux 中的线程。

K8s 类比为一个操作系统,同时容器类比为进程,那么 Pod 就可以类比为进程组。

进程组概念

先提出一个问题:假如用容器把 Helloworld 程序跑起来,会怎么做呢?

最自然的解法,启动一个 Docker 容器,里面运行四个进程。这里会引出一个问题,这个容器里面 PID = 1 的进程该是谁?如果该进程是 main 进程,那么由谁去管理剩下的三个进程?

问题的核心在于,容器设计本身是一种“单进程”模型,不是说容器里只能起一个进程,由于容器的应用等于进程,所以只能去管理 PID = 1 进程,其他再起来的进程其实是一个托管状态。所以说服务应用进程本身具有“进程管理”的能力。

比如说 Helloworld 的程序有 system 的能力,或者直接把容器里 PID = 1 的进程直接改为 systemd,否则这个应用,或者说容器没有办法去管理很多个进程。因为 PID = 1 进程是应用本身,如果现在把这个 PID = 1 的进程给 kill 了,或者它自己运行过程中死掉了,那么剩下三个进程资源无法回收。

反过来,真的把应用本身改为 systemd,或者在容器里运行一个 systemd,这会导致另一个问题:管理容器不是管理应用本身,而是管理 systemd。如果应用退出或者 fail,容器是没有办法知道的。

在 K8s 中,四个进程共同组成的应用 Helloworld,会被定义为一个拥有四个容器的 Pod,四个容器共享 Pod 内资源。需要注意的是,Pod 是一个逻辑单位,没有真实的东西对应 Pod。
进程组

为什么 Pod 必须是原子调度单位?

因为存在调度失败(Task co-scheduling),调度失败指的是分配容器到 Node 时,因为不知道全局分配信息,导致有紧密协作的容器不能被分配到同一个 Node。

调度失败存在如下解决办法:

  • Mesos 的资源囤积:当所有设置了 Affinity 约束的任务都达到时,才开始统一调度;
  • Omega 系统的乐观调度:不管冲突的异常情况,先调度,同时设置一个回滚机制,冲突后利用回滚解决问题;
  • K8s 的 Pod:将紧密协作的容器视为一个 Pod,以 Pod 为单位进行调度。

再次理解 Pod

首先,Pod 里面的容器是 “超亲密关系”。与亲密关系不同的是,亲密关系是可以通过调度解决的,例如两个 Pod 运行在同一台宿主机上,而超亲密关系大致分为如下几类:

  • 文件交换;
  • 需要通过 localhost 或者本地 socket 进行通信;
  • 需要非常频繁的 RPC 调用;
  • 需要共享某些 Linux Namespace。

Pod 的实现机制

Pod 要解决的问题

Pod 要打破容器间的 Linux Namespace 和 cgroups 隔离,具体解法分为两部分:网络和存储。

  1. 共享网络
    在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。
    Infra container 是一个非常小的镜像,大概 100~200KB 左右,是一个汇编语言写的、永远处于“暂停”状态的容器。由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。
    所以说一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实全是一份,这一份都来自于 Pod 第一次创建的这个 Infra container。
  2. 共享存储
    比如说现在有两个容器,一个是 Nginx,另外一个是非常普通的容器,在 Nginx 里放一些文件,让我能通过 Nginx 访问到。所以它需要去 share 这个目录。我 share 文件或者是 share 目录在 Pod 里面是非常简单的,实际上就是把 volume 变成了 Pod level。然后所有容器,就是所有同属于一个 Pod 的容器,他们共享所有的 volume。

详解容器设计模式

InitContainer

可以在 yaml 里首先定义一个 Init Container,完成操作后就退出。这个容器会比用户容器先启动,并且严格按照定义顺序来依次执行。

容器设计模式:Sidecar

Sidecar 的含义是可以定义一些专门的容器,来执行主业务容器所需要的一些辅助工作,例如:

  • 原本需要在容器里面执行 SSH 需要干的一些事情,可以写脚本、一些前置的条件,其实都可以通过像 Init Container 或者另外像 Sidecar 的方式去解决;
  • 日志收集,日志收集本身是一个进程,是一个小容器,那么就可以把它打包进 Pod 里面去做这个收集工作;
  • Debug 应用,实际上现在 Debug 整个应用都可以在应用 Pod 里面再次定义一个额外的小的 Container,它可以去 exec 应用 pod 的 namespace;
  • 查看其他容器的工作状态。不再需要去 SSH 登陆到容器里去看,只要把监控组件装到额外的小容器里面就可以了,然后把它作为一个 Sidecar 启动起来,跟主业务容器进行协作,所以同样业务监控也都可以通过 Sidecar 方式来去做。

这种做法一个非常明显的优势就是在于其实将辅助功能从业务容器解耦了,所以我就能够独立发布 Sidecar 容器,并且更重要的是这个能力是可以重用的,即同样的一个监控 Sidecar 或者日志 Sidecar。

@山东·威海 2020.05.15