K8s 浅谈Pod

“Pod” 这一词是我们经常听到的一个词语,我们知道Pod 是k8s 最基本的单元对象,Pod里边包含着容器,容器里边运行着我们的服务,也就是进程,那么我们知道为何k8s 要有pod的概念它的作用都有什么,接下来我们要分析pod的存在意义。

什么是Pod

Pod 的中文含义叫 ‘豆荚’,豆荚里边放的都是豌豆,豌豆可以理解为容器,Pod 可以理解为“虚拟机”的概念,所以Pod里边运行着一组容器

Pod,其实是一组共享了某些资源的容器,是k8s最基础的一个对象,关于 Pod 最重要的一个事实是:它只是一个逻辑概念。

为什么有Pod

Pod是一组容器的逻辑概念,根据容器的设计理念一个容器里边只运行一个主进程,也就是说容器的本质就是进程,Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个 Volume。

接下来我们思考一下Pod 里的容器是如何做到共享Network Namespace、和Volume 等资源的 ?

其实在创建容器的时候,k8s会默认创建一个中间容器,这个容器叫作 Infra 容器,这个容器永远都是第一个被创建的,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。这样的组织关系,可以用下面这样一个示意图来表达:


如上图所示,这个 Pod 里有两个用户容器 A 和 B,还有一个 Infra 容器。很容易理解,在 Kubernetes 项目里,Infra 容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右。

而在 Infra 容器“Hold 住”Network Namespace 后,用户容器就可以加入到 Infra 容器的 Network Namespace 当中了。所以,如果你查看这些容器在宿主机上的 Namespace 文件,它们指向的值一定是完全一样的。

这也就意味着,对于 Pod 里的容器 A 和容器 B 来说:

  • 它们可以直接使用 localhost 进行通信;
  • 它们看到的网络设备跟 Infra 容器看到的完全一样;
  • 一个 Pod 只有一个 IP 地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址;
  • 当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;
  • Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。

有了这个设计之后,共享 Volume 就简单多了:Kubernetes 项目只要把所有 Volume 的定义都设计在 Pod 层级即可。

Pod 这种“超亲密关系”容器的设计思想,实际上就是希望,当用户想在一个容器里跑多个功能并不相关的应用时,应该优先考虑它们是不是更应该被描述成一个 Pod 里的多个容器。

最典型的例子是:WAR 包与 Web 服务器。

假如现在我们用docker的方式来处理,

第一种方式就是WAR 包 放到 Tomat webapps 的目录下,制作一个镜像然后发布部署,每次更新WAR 包内容 或者升级 Tomat 的时候都需要重新制作一个新的镜像

第二种方式 只发布一个Tomat 镜像 ,然后把WAR 包的地址挂载到Tomat 里,但是问题是如何让每一台宿主机,都预先准备好这个存储有 WAR 包的目录呢?这样来看,你只能独立维护一套分布式存储系统了。

实际上,有了 Pod 之后,这样的问题就很容易解决了。我们可以把 WAR 包和 Tomcat 分别做成镜像,然后把它们作为一个 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 中,Tomcat 容器是我们要使用的主容器,而 WAR 包容器的存在,只是为了给它提供一个 WAR 包而已。所以,我们用 Init Container 的方式优先运行 WAR 包容器,扮演了一个 sidecar 的角色。

Pod 生命周期

  1. 执行初始化容器init container,其实在这之上还有一个infra container,pod 的生命周期是跟infra container一致的,然后才开始 init container, init container 可以定义多个, 依次执行,只有当前 init container 创建成功完成退出之后再执行下一个 init container
  2. 开始初始化main container,再初始化main container 如果用户设置了 hook 则会执行hook的操作 post start hook, 然后开始执行初始化main container
  3. main container 创建成功之后 如果用户设置了容器的探活设置(livenessProbe 和 readinessProbe), kubelet 会根据 设置的类型来访问Pod容器,根据返回的结果决定是否重启或者是否能被访问到。

容器探测的类型

ExecAction:在容器中执行命令,状态码为0表示成功,否则即为不健康状态。
TCPSocketAction:与容器TCP端口建立连接进行诊断,端口能够成功即为正常,否则为不健康状态
HTTPGetAction: 向容器内指定IP 和 端口发送 http 请求,响应码为2xx 或者 3xx即为成功

  • livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器进行重启。如果容器不提供存活探针,则默认状态为Success。
  • readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。
  1. 当Pod 被删除的时候 如果用户设置了 hook 则会执行hook的操作 post stop hook, 执行完毕之后删除Pod

总结

这遍文章知道了Pod 对象是k8s 最基础的原子单位,它里边可以运行多个容器,多个容器之间的网络是存储都是共享的,做到共享的实现方式是引用了一个中间容器 infra container, 在创建Pod 之前首先创建infra container, 然后然后再创建Pod里的容器,如果有init container 那么先创建init container 再创建main container,创建好的container 则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起, Pod 的生命周期与Infra 是一致的。

你可能感兴趣的:(K8s 浅谈Pod)