Pod是K8S在项目中的最小API对象,换一个更加专业的说法,Pod,是Kubernetes的原子调度单位
我们为何需要Pod,是接下来的一个问题
我们知道了Docker容器的本质,Namesapce作为隔离,Cgroups作为限制,rootfs做文件系统,这样已经做好了Docker的整体结构,那么Kubernetes的Pod有何意义?
首先说,容器的本质是什么?
是进程,容器就好比云计算系统中的进程,镜像就好比是.exe的安装包,Kubernetes呢?
Kubernetes作为管理这些进程的系统,就好比是我们的操作系统
再者说,利用pstree -g查看进程树的时候,可以看到对应操作系统的进程数据图
进程也并非自己独自运行,而是具有进程组的方式,组织在一起,比如有一个rsyslogd的程序,负责的是Linux操作系统的日志处理,rsyslogd的主进程main,和其用到的日志模块imklog,同属一个进程组,共同协作,完成rsyslogd程序的职责
而Kubernetes做的编排,就是一种进程组概念的延伸
往往部署应用存在着类似进程和进程组的关系,因为具有密切的协作关系,必须部署在同一台机器上
如果没有组的概念,这样运维关系就会很难处理
我们以rsyslogd为例子,已知rsyslogd中有是三个进程,imklog模块,imuxsock模块,自己的main函数主进程,三个进程一定要运行在同一台机器上,不然,其之间基于Socket的通信和文件交换,会出现问题
我们将rsyslogd这个应用给容器化,受限于容器的单进程模型,这三个模块必须要分别制作为三个不同的容器,三个容器运行时,内存配额都是1GB
容器的单进程,并不是是只能运行一个进程,而是容器没有管理多个进程的能力,容器里PID=1的进程就是应用本身,其他的进程都是这个进程的子进程,用户编写的应用,并不能够像是正常操作系统的init或者systemd具有进程管理,所以如果容器内还有子进程,那么当子进程突然死亡的时候,如何去处理呢?
假如,我要用Docker Swarm来运行这个rsyslogd,加入我们需要三个容器都运行在一台机器上,就必须要在另外两个容器上设置一个affinity=main的约束,即 这两个必须和main容器在一个机器上
即时设置好了,还需要顺序执行 docker run main
docker run imklog
docker run imuxsock
这三个容器都会进入Swarm的待调度队列,然后,main容器和imklog容器都先后出队并被调度到了node-2上,当imuxsock容器出队开始被调度的时候,Swarm可能会遇到资源不够的问题,
这就是成组调度中可能出现的问题
Mesos给出的方法,是资源囤积的机制,在设置了Affinity约束的任务都到达的时候,才开始统一的调度,而在Google Omega论文中,使用乐观调度进行冲突调节,不管这些冲突,而是利用了冲突出现后,利用精心准备的回滚机制进行解决
这些方法都谈不上完美,资源囤积带来了不可避免的调度效率和死锁的可能性,乐观调度的复杂程度,呵呵了
而在Kubernetes项目进行调度的时候,看到了三个容器组成的Pod,会先考虑内存等于3GB的node节点去绑定,而不是考虑node2
像这种容器间的亲密协作,称为超亲密关系,这种超亲密关系的容器的典型特征包含 互相的文件交换
使用localhost或者socket文件进行通信,频繁的远程通信,共享Linux Namespace
那么仅仅是调度上的问题吗?
如果仅仅是调度上可能存在的调度问题,有必要将Pod设置为一等公民吗?
那么必须说Pod在项目中更重要的意义了,容器设计模式
Pod其实本质上,就是一个逻辑概念
并不是真正的存在,K8S调度的最小的单位,还是Linux上的Namespace和Cgroups,而不是所谓的Pod边界或者隔离环境
那么,Pod如何被创建的呢?
Pod本质上,是一组共享某些资源的容器
Pod中所有的容器,共享的是同一个Network Namespace,并且可以共享同一个Volume
那么一个Pod,假设有A B两个容器,那么这个Pod本质上就是A容器共享B容器的网络和Volume
甚至可以通过docker run –net –volumnes-from这种命令实现
docker run –net-B –volumes-from=B –name=A image-A…
但是,如果是原生的两个容器之间的挂载,必须要有一个先启动,那么一个Pod中多个容器就不是对等关系,而是拓补关系
在Kubernetes项目中,Pod实现需要利用一个中间容器,这个容器名为Infra,这是Pod中第一个被创建的容器,其他由用户定义的容器,通过join Network Namespace的方式,与Infra容器关联在一起,这种组织方式
如上所示,这个Pod中有两个用户容器A和B,还有一个Infra容器,在Kubernetes项目中,Infra容器占用的资源极少,使用的是一种特殊的资源,叫做k8s.gcr.io/pause
使用汇编语言编写,用于在暂停,解压后的大小也只有100-200KB左右
在Infra容器开启Network Neamspace之后,用户自定义的容器就可以加入到Infra容器的Network Namespace中了,这就意味着对于Pod里容器A和容器B来说
可以直接使用localhost进行通信
看到的网络设备和Infra容器一样
一个Pod只有一个IP地址,也就是这个Pod的Network Namespace对应的IP地址
当然,其他的网络资源,都是一个Pod一份,并且被Pod内容器共享
Pod生命周期之和Infra容器一致,和容器A和B无关
对于同一个Pod里面的所有用户容器来说,进出流量可以认识通过Infra容器完成的,这样为一个Kubernetes开发一个网络插件的时候,考虑的是Pod的Network Namespace,而不是每一个用户容器的网络配置,是没有意义的
这就是网络插件的接口化含义,网路插件只需要关心如何配置Pod中Infra容器的Network Namespace即可
有了这个设计,共享Volume就简单多了,Kubernetes只要将所有的Volume设计在Pod层级即可
一个Volume对应的宿主机目录对于Pod来说,只有一个,一个Pod下的容器只要挂载这个Volume,就可以共享这个Volume对应的宿主机的目录
apiVersion: v1
kind: Pod metadata: name: two-containers spec: restartPolicy: Never volumes: – name: shared-data hostPath: path: /data containers: – name: nginx-container image: nginx volumeMounts: – name: shared-data mountPath: /usr/share/nginx/html – name: debian-container image: debian volumeMounts: – name: shared-data mountPath: /pod-data command: [“/bin/sh”] args: [“-c”, “echo Hello from the debian container > /pod-data/index.html”] |
在其中
debian-container和nginx-container都声明了挂载了shared-data和Volume,而shared-data是hostpath类型,就是对应的/pod-data
这个目录,同时被绑定挂载进了上述两个容器中,相当于一个中介
这就是nginx-container可以从/usr/share/nginx/html目录中,读取到debain-container生成的index.html文件的原因
那么这种Pod内交流的方式,可以映射为一个虚拟机
也是开发者希望用户在一个容器内跑多个功能并不相关的应用的时候,优先考虑他们是不是可以被描述为一个Pod内的多个容器
我们来看一个简单的实例
现在有一个Java Web应用的WAR包,需要被放在Tomcat的webapps目录下运行起来,现在只能用Docker做这件事,如何处理这个关系呢?
一种方式是,将WAR包直接放在TOMCAT镜像的webapps下,做成一个新的镜像,但是要更新WAR内的内容,或者升级TOMCAT镜像,就需要重新打包镜像
另一种方式是,不管WAR包,只发布一个Tomcat的容器,这个容器的webapps目录,挂载到宿主机上,让宿主机将WAR包放在挂载的目录中,不过这样就需要维护每个宿主机上的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: {} |
第一个容器,我们将WAR包放在根目录下,并且在启动的时候,进行拷贝到/app目录下
然后第二个容器从对应的目录下进行读取即可
在这个Pod中,WAR类型的容器不再是一个普通容器,而是一个Init Container类型的容器
在Pod中,所有的Init Container定义的容器,都会比spec.containers定义的用户容器先启动,并且Init Container容器会逐一启动,直到启动并且完成了,用户容器才会启动
这个Init Container类型的WAR包启动之后,执行了一句 cp /sample.war/app,将应用的WAR包拷贝到/app目录下,然后退出
然后这个/app,挂载了一个名为app-volume的Volume,然后Tomcat容器也在了这个volume到自己的webapps目录下
等Tomcat容器启动时候,webapps就会存在这个sample.war文件,正是一开始就被拷贝进去的
这样,就是一种组合的方式,解决了WAR和tomcat容器之间耦合关系的问题
这种组合的操作,正式容器设计中的常用模式 sidecar
就是一个Pod中,启动辅助容器,完成一些独立于主容器之外的工作
或者是容器的日志收集
我们有一个应用,需要源源不断的将日志文件输出到容器的/var/log目录上
我们将一个Pod里的Volume挂载到容器的/var/log上
在Pod中运行一个sidecar容器,也声明挂载同一个Volume到自己的/var/log目录上
这个sidecar就只需要将自己/var/log目录中读取日志文件,转发到MonogoDB或者Elasticsearch中存储起来
Pod的一个重要特性,就是和这个Pod的所有容器都共享一个Network Namespace,这就使得很多和Pod网络相关的配置和管理,都交给sidecar完成,这完全无须干涉用户容器
Istio项目就是使用sidecar容器完成微服务治理
本章中,我们看了Kubernetes项目中Pod的实现原理
Pod就是Kubernetes和其他单容器项目最大的不同,
而很多人,喜欢将容器和虚拟机相提并论,将容器当做更好性能的虚拟机
而且,无论是从具体的实现原理,还是使用方式,特性等方面,容器和虚拟机几乎没有任何相似的地方,也不存在于一种普世的方式,可以将虚拟机中的应用无缝切换到容器之中,因为容器的性能优势,必然伴随着对应缺陷
一个运行在虚机中的应用,其本质上,也是一个进程组,而不是容器所描述的单一进程
而Pod,就是扮演传统的虚拟机的角色,将容器作为用户程序,多个串在一起的捆绑式运行
所以一个Pod,一般用来管理有关联关系的容器,将这些进程分别做成容器镜像,将由顺序关系的容器,定义为Init Container,这才是由传统的应用架构到微服务架构的合适转变
那么本章问题是
除了Network Namespace之外,Pod容器还可以共享哪些Namespace呢?
那么基本上可以实现的Namespace,Kubernetes中基本都可以实现共享吧
就如同之前所讲的Namesapce
“除了我们刚刚用到的 PID Namespace,Linux 操作系统还提供了 Mount、UTS、IPC、Network 和 User 这些 Namespace”
这些Namesacpe理论上都可以支持