“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 生命周期
- 执行初始化容器init container,其实在这之上还有一个infra container,pod 的生命周期是跟infra container一致的,然后才开始 init container, init container 可以定义多个, 依次执行,只有当前 init container 创建成功完成退出之后再执行下一个 init container
- 开始初始化main container,再初始化main container 如果用户设置了 hook 则会执行hook的操作 post start hook, 然后开始执行初始化main container
- 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。
- 当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 是一致的。