容器的本质实际上是一个进程,是一个视图被隔离,资源受限的进程。
容器里面 PID = 1 的进程就是应用本身,这意味着管理虚拟机等于管理基础设施,因为我们是在管理机器,但管理容器等于直接管理应用本身。
我们说 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。
因为存在调度失败(Task co-scheduling),调度失败指的是分配容器到 Node 时,因为不知道全局分配信息,导致有紧密协作的容器不能被分配到同一个 Node。
调度失败存在如下解决办法:
首先,Pod 里面的容器是 “超亲密关系”。与亲密关系不同的是,亲密关系是可以通过调度解决的,例如两个 Pod 运行在同一台宿主机上,而超亲密关系大致分为如下几类:
Pod 要打破容器间的 Linux Namespace 和 cgroups 隔离,具体解法分为两部分:网络和存储。
可以在 yaml 里首先定义一个 Init Container,完成操作后就退出。这个容器会比用户容器先启动,并且严格按照定义顺序来依次执行。
Sidecar 的含义是可以定义一些专门的容器,来执行主业务容器所需要的一些辅助工作,例如:
这种做法一个非常明显的优势就是在于其实将辅助功能从业务容器解耦了,所以我就能够独立发布 Sidecar 容器,并且更重要的是这个能力是可以重用的,即同样的一个监控 Sidecar 或者日志 Sidecar。
@山东·威海 2020.05.15