k8s系列-Pod

K8S-Pod

基本概念

Pod是kubernetes中最重要的概念,Pod不是容器,是kubernetes中最小的编排单位,这个设计落实到API对象上,容器Container变成了Pod的一个属性,Pod扮演的是传统部署中的“虚拟机”的角色,这样设计是为了让用户从传统虚拟机环境迁移到kubernetes更加顺畅。

Pod就好比现在的虚拟机,Container就是虚拟机里面的进程,比如:存储、网络、调度的Pod。

一个很简单的Pod声明式文件如下:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Pod
metadata:
name: host-pod
spec:
containers:
- name: nginx-container
image: nginx:1.8

可以保存为xxx.yaml,使用如下命令即可运行起来

1
kubectl apply -f xx.yaml

可以使用下面的命令在master上查看你运行的所有pod

1
2
3
kubectl get pods
# -o wide 可以看到运行的节点,Pod的IP等
# -w 可以实时查看到pod的状态,你在apply一个文件的时候就能看到

为什么需要Pod而不是Container

首先,容器的本质就是进程,Kubernetes就好像是操作系统
我们现在随便找到一台Linux机器,执行一条如下命令:

1
pstree -g

从下图运行的结果来看,进程点多都会以进程组的方式来运行,而不是孤苦伶仃的(连进程都基本脱单了),好比图中最清晰的teamviewerd来说,后面都有个数字,而且都一样,这个就是这个进程组的id (Process Group ID)
image

从上面的分析中或许你能够察觉到为什么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
2
3
4
5
6
apiVersion: v1
kind: Pod
···
spec:
nodeSelector:
disktype: ssd

上面这个nodeSelector的作用是,这个Pod只运行在打上了”disktype: ssd“标签的Node上,否则直接调度失败。

HostAliases:定义了Pod的hosts文件(相当于 /etc/hosts)里的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: host-pod
spec:
containers:
- name: nginx-container
image: nginx:1.8
hostAliases:
- ip: "10.0.0.0"
hostnames:
- "foo.remote"
- "bar.remote"

这样定义的Pod启动后,我们通过exec进入pod,能看到/etc/hosts文件中多出这么几行
image

最下面两行就是通过HostAliases为Pod设置的,kubernetes项目如果想要设置hosts文件的内容必须通过这种方式,如果进入Pod去手动修改hosts文件,Pod一旦重建就没了。

shareProcessNamespace=true

Pod的设计就是让里面容器尽可能多的共享Linux Namespace,仅保留必要的隔离和限制能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
shareProcessNamespace: true
containers:
- name: nginx
image: nginx:1.8
- name: shell
image: busybox
stdin: true
tty: true

上面这个文件定义了两个Pod,一个是nginx容器,一个是开启了tty和stdin的shell容器。tty和stdin相当于docker run的-it,tty是Linux提供给用户的一个常驻小程序,接收用户的标准输入,返回操作系统标准输出,为了能在tty中输入信息,需要开启stdin。
使用如下命令进入可以观察到

1
2
3
4
5
kubectl attach -ti nginx  -c shell

ps ax

可以看到三个Container所有的正在运行的进程,包括nginx,sh,以及Infra的/pause进程

image

没有这个shareProcessNamespace属性可以看到输出如下

1
2
#进入shell这个容器
kubectl attach -ti nginx -c shell

image

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 容器被杀死之前执行,但是这是个同步操作,也就是这个操作执行完了之后才会去停止容器
  1. exec command 执行命令 /usr/sbin/nginx xxxx
  2. httpGet请求
    image
    3.tcpSocket
    image

Pod的声明周期-Status

  1. Pending Pod的YAML文件已经提交给kuber,API对象已经被创建并保存在Etcd中,但是可能因为某些原因不能创建,比如资源不足没Node可调度,就会出现一直在Pending状态
  2. Running Pod已经调度成功,包含的容器都已经创建成功,并且至少有一个在运行
  3. Succeeded Pod所有容器正常运行完毕,并且已经退出了,这种情况在运行一次性任务最常见
  4. Failed Pod中至少有一个容器以不正常状态退出,就是容器的退出码不是0,这个状态你就得想办法看看Pod的日志了
  5. 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成功启动
image

livenessProbe

livenessProbe还有http请求,tcpSocket等多种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: test-liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5

这个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
image

livenessProbe探测到容器不健康会一直重启

restartPolicy

Pod也有缺点,Pod如果真的出现异常,你可以看到pod的restart次数更改,因为Pod有个字段叫做restartPolicy,默认是Always。

容器发生异常就会重新创建,Pod的恢复过程,永远会发生在当前节点上,即便是节点宕机,除非pod.spec.node发生改变,不然不会发生改变,这也就是单个Pod和由Deployment创建的Pod区别。

restartPolicy:

  • Always: 任何情况下,只要容器不在运行状态就自动重启 默认值
  • OnFailure 容器异常时重启
  • Never 从来不重启

比如某些任务只运行一下不需要一直运行,就可以设置为Never