k8s pod三问

k8s pod三问

Scroll Down

aliyun kubernetes观后记与部分博客观后记:

一、有了容器,为什么要用Pod呢?

在操作系统中,一个程序的运行实际上由多个线程组成,这多个线程共享进程的资源,相互协作,完成程序的工作,这也是操作系统中进程组的概念
1.jpg

2.jpg

在容器中,PID = 1 的进程就是该应用本身,比如 main 进程。

如果我们通过创建一个容器,在容器中启动 Helloworld 的4个进程这种方法来跑这个Helloworld程序。那么问题来了。容器是“单进程模型”,这个单进程并不是指容器只能运行一个进程,而是说 容器 = 应用 = 进程,如果容器中有多个进程,只有一个可以作为该容器的 PID = 1 的进程。容器本身也只能管理 PId=1 的进程,其它启动的进程都是托管的状态。

容器本身不具有同时管理多个进程的能力。诸如,上述 Helloworld 程序其有4个进程,分别是main、api、log、compute,如果 PID = 1 的进程是main进程,那么谁来管理剩余的3个进程呢?而如果在程序运行过程中,PID = 1 的进程死掉了,HelloWorld 的剩余三个进程是不会被回收的,这是一个非常严重的问题.

3.jpg
那么上述的 HelloWorld程序应该如何以容器的形式跑起来呢?

1、使应用程序本身具备“进程管理”的功能(这意味着该程序需要具备systemed的能力)。这就平白增添了开发者的负担

2、直接将容器 PID = 1 的进程改为systemed或者在容器中运行一个systemed来达到管理多进程的目的。这将会导致管理容器 = 管理systemed != 管理应用本身.这样做的问题是,如果容器中实际run的进程是systemed,那么再接下来的过程中,提供服务的应用是不是退出了,是不是出现异常失败了,都是无法直接感知的,因为实际上管理的是systemed.这也就是为什么在容器中运行一个复杂容器困难程度很大的原因。

简述第二点的问题就是,如果将容器 PID =1 的程序设置为systemed,那么管理容器就相当于管理systemed,那么这时候实际提供服务的应用的生命周期就不与容器的生命周期一致了。

4.jpg
在K8S中,Pod就相当于一个进程组,比如 具有四个进程的HelloWorld程序就会被定义为一个具有四个容器的Pod。也就是说 职责不同、相互协作的进程需要放在容器内运行的这样的一个场景下,k8s并不会把它们塞到一个容器中,因为这会导致上述提到的两个问题。k8s会把诸如 Helloworld 的 4个独立的进程 放到4个不同的容器中,然后把他们定义在一个Pod中。同时也就是说在Kubernetes中,Pod只是一个逻辑概念,真正在物理上存在的东西是真实运行的那几个容器。

二、Pod的一般创建过程是什么?

5.jpg
用户首先通过cli或ui的方式提交一个POD请求到kubernetes集群Master层面的API Server,然后API Server会将用户的这次Pod创建请求写入到Etcd数据库中,随后Scheduler调度器会通过Watch监听机制得到(有一个Pod需要被调度的)信息,Scheduler会根据Pod所请求的资源大小或相应要求(比如节点亲和性、Pod亲和性)完成一次调度决策,在完成此次调度决策之后,Scheduler会告知API Server该Pod会被调度到某一个节点上。而当API Server接收到调度信息之后,会将这次调度结果再次写入到Etcd中。这时候通过watch机制一直在监听API Server的该节点上的kubelet组件,会将API Server的这次创建Pod的请求所理解并转换为具体创建Pod的操作,也就是调用Container Runtime去完成Pod的创建。

三、容器的设计模式是什么?

我最开始学kubernetes的时候,从b站看的教育机构的培训视频,里面讲了辅助容器、讲了Infra容器、讲了initContainer,但是没有提到SideCar的概念,后来在很多地方都看到了SideCar这个概念。

很长一段时间我都认为SideCar就是Infra容器的另一种叫法,后来发现并不是这样,SideCar 是一个独立于业务容器之外的辅助容器,它不是Infra容器( Infra容器有它独立的使命,比如初始化Pod的共享存储与网络等 )

下面就结合阿里云视频和一位博主的讲述理解下SideCar

假如现在有一个Java Web的WAR包,它需要被放在Tomcat的webapps目录下运行起来

一种方法是,把 WAR 包直接放在 Tomcat 镜像的 webapps 目录下,做成一个新的镜像运行起来。可是,这时候,如果你要更新 WAR 包的内容,或者要升级 Tomcat 镜像,就要重新制作一个新的发布镜像,非常麻烦

另一种方法是,你压根儿不管 WAR 包,永远只发布一个 Tomcat 容器。不过,这个容器的 webapps 目录,就必须声明一个 hostPath 类型的 Volume,从而把宿主机上的 WAR 包挂载进 Tomcat 容器当中运行起来。不过,这样你就必须要解决一个问题,即:如何让每一台宿主机,都预先准备好这个存储有 WAR 包的目录呢?这样来看,你只能独立维护一套分布式存储系统了

实际上,有了 Pod 之后,这样的问题就很容易解决了。我们可以把 WAR 包和 Tomcat 分别做成镜像,然后把它们作为一个 Pod 里的两个容器“组合”在一起。这个 Pod 的配置文件如下所示

apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  initContainers:
  - image: geektime/sample:v2
    name: war
    command: ["cp", "/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: geektime/tomcat:7.0
    name: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001
  volumes:
  - name: app-volume
    emptyDir: {}

在这个 Pod 中,我们定义了两个容器,第一个容器使用的镜像是 geektime/sample:v2,这个镜像里只有一个 WAR 包(sample.war)放在根目录下。而第二个容器则使用的是一个标准的 Tomcat 镜像

不过,你可能已经注意到,WAR 包容器的类型不再是一个普通容器,而是一个 Init Container 类型的容器。在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动(也就是辅助容器的启动早于业务容器)。并且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动

所以,这个 Init Container 类型的 WAR 包容器启动后,我执行了一句"cp /sample.war /app",把应用的 WAR 包拷贝到 /app 目录下,然后退出。而后这个 /app 目录,就挂载了一个名叫 app-volume 的 Volume。

接下来就很关键了。Tomcat 容器,同样声明了挂载 app-volume 到自己的 webapps 目录下

所以,等 Tomcat 容器启动时,它的 webapps 目录下就一定会存在 sample.war 文件:这个文件正是 WAR 包容器启动时拷贝到这个 Volume 里面的,而这个 Volume 是被这两个容器共享的。

像这样,我们就用一种“组合”方式,解决了 WAR 包与 Tomcat 容器之间耦合关系的问题

实际上,这个所谓的“组合”操作,正是容器设计模式里最常用的一种模式,它的名字叫:sidecar顾名思义,sidecar 指的就是我们可以在一个 Pod 中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作

Pod 的另一个重要特性是,它的所有容器都共享同一个 Network Namespace。这就使得很多与 Pod 网络相关的配置和管理,也都可以交给 sidecar 完成,而完全无须干涉用户容器。这里最典型的例子莫过于 Istio 这个微服务治理项目了

参考:
1、https://www.cnblogs.com/fuyuteng/p/10623245.html