K8S-Pod
基本概念
Pod是kubernetes中最重要的概念,Pod不是容器,是kubernetes中最小的编排单位,这个设计落实到API对象上,容器Container变成了Pod的一个属性,Pod扮演的是传统部署中的“虚拟机”的角色,这样设计是为了让用户从传统虚拟机环境迁移到kubernetes更加顺畅。
Pod就好比现在的虚拟机,Container就是虚拟机里面的进程,比如:存储、网络、调度的Pod。
一个很简单的Pod声明式文件如下:
1 | apiVersion: v1 |
可以保存为xxx.yaml,使用如下命令即可运行起来
1 | kubectl apply -f xx.yaml |
可以使用下面的命令在master上查看你运行的所有pod
1 | kubectl get pods |
为什么需要Pod而不是Container
首先,容器的本质就是进程,Kubernetes就好像是操作系统
我们现在随便找到一台Linux机器,执行一条如下命令:
1 | pstree -g |
从下图运行的结果来看,进程点多都会以进程组的方式来运行,而不是孤苦伶仃的(连进程都基本脱单了),好比图中最清晰的teamviewerd来说,后面都有个数字,而且都一样,这个就是这个进程组的id (Process Group ID)
从上面的分析中或许你能够察觉到为什么Kubernetes中最小的调度单位是Pod了,早在Borg的工程师开发Borg的时候就发现,某些应用之间就存在着类似”进程和进程组“的关系,也就是一些协作特别密切的应用,使得他们必须部署在同一台机器上。
举例
rsyslogd负责Linux操作系统的日志处理,rsyslogd中的主程序main,和需要使用到的内核日志模块imklog以及imuxsock都属于同一个进程组,这三个进程一定要运行在一台机器上,不然它们之间的基于Socket通信和文件交换都会出现问题。
当前使用Container方式也能做,一个Container还是可以运行多个进程,只是容器中只有一个PID=1的进程,这个PID=1的进程退出了这个容器也就停止了,可是这三个模块PID不为1的那两个某一个退出都不会影响其他两个进程的运行,但是功能却早已无效
在kubernetes项目中,Pod可以直接解决这个问题,里面运行多个Container,并且这三个紧密协作,这个Pod一重启他们仨都重启了,他们三个直接通过localhost或者Socket文件进行本地通信。
但是也不是所有的应用都应该有这种亲密关系,就好像我们的web应用和数据库,如果这俩部署在一个Pod里面,万一哪天web应用真挂了,这数据库好像跟着一起jump不是太好。
Pod的实现原理
关于Pod最重要的一个事实是Pod只是一个逻辑概念,Kubernetes真正处理的还是宿主机操作系统Linux容器的Namespace和Cgroups,并不存在所谓的Pod的便捷或者隔离环境,Pod其实是一组共享了某些资源的容器,Pod里面所有的容器共享的是同一个Network Namespace,并且可以声明为一个Volume
这么看来其实仿佛跟平时简单的使用Docker没什么区别,就好像是先run了一个容器A
然后使用 docker run –net=A –volume-from=A这种运行方式,这样在Docker下两个容器也能共享一个Network Namespace,一个Volume,但是这样的一个问题就是某一个容器必须率先启动。
Kubernetes中Pod有一个中间容器,叫做Infra容器,Infra一定是这个Pod中第一个被创建的,其他容器都加入到这个容器的Network Namespace、Volume等,这个容器使用pause镜像,占用非常小。
所以对于Pod中的A、B容器来说:
- A、B容器可以直接使用localhost进行通信
- 他们看到的网络设备跟Infra容器看到的一样
- 一个Pod只有一个IP地址,也就是这个Pod对应的Network Namespace对应的IP地址
- Pod的生命周期只跟Infra一样,与容器A和B无关
声明式文件
字段介绍
NodeSelector:一个将Pod与Node进行绑定的字段
kubectl api-versions
查看所有可用的apiVersion
1 | apiVersion: v1 |
上面这个nodeSelector的作用是,这个Pod只运行在打上了”disktype: ssd“标签的Node上,否则直接调度失败。
HostAliases:定义了Pod的hosts文件(相当于 /etc/hosts)里的内容
1 | apiVersion: v1 |
这样定义的Pod启动后,我们通过exec进入pod,能看到/etc/hosts文件中多出这么几行
最下面两行就是通过HostAliases为Pod设置的,kubernetes项目如果想要设置hosts文件的内容必须通过这种方式,如果进入Pod去手动修改hosts文件,Pod一旦重建就没了。
shareProcessNamespace=true
Pod的设计就是让里面容器尽可能多的共享Linux Namespace,仅保留必要的隔离和限制能力。
1 | apiVersion: v1 |
上面这个文件定义了两个Pod,一个是nginx容器,一个是开启了tty和stdin的shell容器。tty和stdin相当于docker run的-it,tty是Linux提供给用户的一个常驻小程序,接收用户的标准输入,返回操作系统标准输出,为了能在tty中输入信息,需要开启stdin。
使用如下命令进入可以观察到
1 | kubectl attach -ti nginx -c shell |
没有这个shareProcessNamespace属性可以看到输出如下
1 | 进入shell这个容器 |
Containers字段
- image 制定镜像
- command 启动命令
- workingDir 容器工作目录
- ports 容器要开发的端口
- volumeMounts 容器挂载的Volume
- imagePullPolicy 容器拉取镜像策略 Always, Never, IfNotPresent. Defaults to Always
- lifecycle 定义的是容器的Container Lifecycle Hooks,就像我们的Spring容器一样,创建完和销毁之前都可以监听到
lifecycle有两个生命周期
postStart和preStop两种
- postStart 容器启动之后立即执行一个指定的操作,但是只是Docker容器的ENTRYPOINT执行之后,也就是并不是一定是ENTRYPOINT执行完成之后
这俩生命周期你能干啥呢? - preStop 容器被杀死之前执行,但是这是个同步操作,也就是这个操作执行完了之后才会去停止容器
- exec command 执行命令 /usr/sbin/nginx xxxx
- httpGet请求
3.tcpSocket
Pod的声明周期-Status
- Pending Pod的YAML文件已经提交给kuber,API对象已经被创建并保存在Etcd中,但是可能因为某些原因不能创建,比如资源不足没Node可调度,就会出现一直在Pending状态
- Running Pod已经调度成功,包含的容器都已经创建成功,并且至少有一个在运行
- Succeeded Pod所有容器正常运行完毕,并且已经退出了,这种情况在运行一次性任务最常见
- Failed Pod中至少有一个容器以不正常状态退出,就是容器的退出码不是0,这个状态你就得想办法看看Pod的日志了
- Unknown Pod的状态不能支持被kubelet报告给kube-apiserver,多半是Master和Kubelet通信出现了问题
Status还可以细分为Conditions,包括PodScheduled、Ready、Initialized,Unschedulable
比如Status是Pending,Condition是Unschedulable,就是调度出现了问题
执行如下命令
1 | kubectl describe pod pod-name |
看到下面这四个Ready、Initialized、ContainersReady、PodScheduled都为True才是真的Pod成功启动
livenessProbe
livenessProbe还有http请求,tcpSocket等多种方式
1 | apiVersion: v1 |
这个Pod容器大概做了些啥妖呢。。启动之后创建一个healthy文件,30s后删了
livenessProbe作为健康状态探测指针,我们指定为exec即执行命令
linux命令命令执行成功返回值为0,不成功为非0
这个探针如果发现cat文件存在,就认为Pod不但已经启动,而且还是健康的,initialDelaySeconds代表启动5秒后执行第一次,后面每5s执行一次。
将上面文件使用如下命令运行起来
1 | kubectl apply -f xxx.yaml |
前面几分钟都能检测到虽然restart次数在增加,但是还是Running状态
过了几分钟使用kubectl describe pod test-liveness-exec
看到如下的events
livenessProbe探测到容器不健康会一直重启
restartPolicy
Pod也有缺点,Pod如果真的出现异常,你可以看到pod的restart次数更改,因为Pod有个字段叫做restartPolicy,默认是Always。
容器发生异常就会重新创建,Pod的恢复过程,永远会发生在当前节点上,即便是节点宕机,除非pod.spec.node发生改变,不然不会发生改变,这也就是单个Pod和由Deployment创建的Pod区别。
restartPolicy:
- Always: 任何情况下,只要容器不在运行状态就自动重启 默认值
- OnFailure 容器异常时重启
- Never 从来不重启
比如某些任务只运行一下不需要一直运行,就可以设置为Never