0%

深度学习 Pod

关于 Kubernetes 系列其他文章的传送门

  1. 《 部署 Kubernetes 也能如此简单吗? 》

  2. 《 初识 k8s,新时代的宠儿 》

  3. 《 深度学习 Pod 》     您当前所在位置

  4. 《 学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!! 》

  5. 《 k8s 中的网络入口 – Service 资源管理 》

  6. 《 k8s 中的 7 层调度 – Ingress 》

  7. 《 k8s 中的存储系统 – Volumes 》

  8. 《 k8s 中强大的配置中心 – ConfigMap 》

  9. 《 k8s 中的部署神器 – Statefulset 控制器 》

  10. 《 k8s 中的安全守护神 – 你不知道不代表它不在 》

  11. 《 k8s 中的图形接口 – Dashboard 》

  12. 《 k8s 中的网络插件 – flannel 和 Calico 》

  13. 《 k8s 中的调度器 – 亲和性、污点和容忍性 》

  14. 《 k8s 中对资源的监控 – 资源指标、Prometheus、弹性伸缩器 HPA 》

  15. 《 k8s 中的云原生应用管理利器 – Helm 》

  16. 《 理解 k8s 高可用,让你的集群稳如泰山 》



logo

一、什么是 Pod ?

同一个 Pod 中的容器共享 6 个名称空间中的 3 个:

  • IPC(系统级的进程间通信、消息队列)
  • UTS(主机名,域名)
  • Network(网络设备、网络协议栈、端口)

思考:为什么设计 Pod?

问题:一般情况下,一个 Pod 中是不建议运行多个容器,那为什么要设计 Pod 这个概念,还让其内部可以运行多个容器呢?

有些时候,一个单一进程可能无法完成我们期望的额外任务,例如:如果想在一个 Nginx 中使用 ELK 收集日志,就需要在 Nginx 主机中部署 Filebeat,但是一个容器中原则上只能运行一个进程,这时就可以在同一个 Pod 中另外启动一个容器来运行 Filebeat,使其能够紧密的辅助主容器完成任务。

所以在 Pod 中,通常只能有一个主容器,其他的容器是为了辅助主容器的任务而设定的,这些辅助容器也被称为 side car(边车)

并不是让你在 Pod 中运行一整套 “nmp” 的。

二、学习 Pod 前必须了解的 k8s 网络

k8s 集群中的网络类型

  • Node Network:节点真正与外部网络通信的接口;
  • Service Network:这个网络所拥有的 IP 地址仅会出现在 Iptables/IPVS 规则当中,不会配置在任何一个网络接口上,也没有办法直接去 ping 通,也不可能有一个协议栈来响应 ping 请求,它仅仅出现在规则当中,是用来路由和调度发给 Service IP 地址的请求流量的;
  • Pod Network:属于 k8s 的内部网络,每一个 Pod 都处于这个网络当中,Pod 的 IP 地址也都是这个网络当中被动态分配的的一个 IP 地址。因此各 Pod 彼此之间无论跑在哪个节点上,都是可以直接互相通信的。

Pod 是动态的,随时可能被 Pod 控制器所重建,IP 地址就会发生变化,如果这时 Pod 之间是直接使用 IP 地址通信反而不是一种理想的状态,而应该借助于 Service 的地址来通信。因此在 k8s 上跑的应用程序有一个特点,为了让客户端能够稳定的访问 Pod,必须在 Pod 前端加一个抽象层,也就是 Service。

将 Pod 网络对外开放

有三种方法可以使 Pod 能被集群外的客户端访问到:

  • hostNetwork:让 Pod 直接共享它所在主机的网络名称空间,因此 Pod 的地址和端口也就是主机的地址和端口。直接访问 Pod 所在物理节点的 IP:Port 即可直接与 Pod 进行通信。
  • NodePort:在集群内的每一个物理节点上打开一个端口。访问物理节点的端口时,会被映射到 Service 上,再由 Service 映射到 Pod 上;
  • hostPort(DNAT):使容器可以使用其所在的物理节点的端口,会被映射到 Pod 上。和 NodePort 不同的是,访问时只能使用 Pod 所在的物理节点的地址和端口;

三、Label 标签

为什么要有标签 ?

Pod 控制器对应到 Pod,既不是通过 IP 地址,也不是通过名称,因为当 Pod 重建后,这两个信息都会发生变化。所以在 k8s 中,所有的资源都可以被附加一个标签(Label),而 Pod 控制器就是通过标签选择器去找到 Pod。

标签其实就是“键值”类型的数据:

  • 一个对象可拥有不止一个标签,而同一个标签也可被添加到多个资源之上;
  • 实践中,可以为资源附加多个不同维度的标签以实现灵活的资源分组管理功能,例如版本标签、环境标签、分层标签等;
  • 标签中的键名称通常由键前缀和键名组成,其中键前缀可选,其格式形如:KEY_PREFIX/KEY_NAME

标签的增删改查

使用 --show-labels 可以显示标签:

1
2
3
4
5
~]# kubectl get pods --show-label
NAME READY STATUS LABELS
myapp-5c6976696c-mwp7w 1/1 Running app=myapp,pod-template-hash=5c6976696c
myapp-5c6976696c-p7lf2 1/1 Running app=myapp,pod-template-hash=5c6976696c
ngx-dep-855f7b4bc8-p9rjt 1/1 Running app=ngx-dep,pod-template-hash=855f7b4bc8

标签属于元数据,所以在 metadata 中就可以自定义标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~]# vim /root/pod-demo.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels: # 添加两个标签
app: pod-demo
rel: stable
spec:
containers:
- image: nginx:latest
name: nginx


~]# kubectl apply -f /root/demo.yaml

查询刚刚创建的 Pod 标签:

1
2
3
~]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
pod-demo 1/1 Running 0 14s app=pod-demo,rel=stable

也可以使用 kubectl label... 来直接为资源新增一个标签。

例如:为 pod-demo 添加一个 color 值为 red 的标签;

  • kubectl label pods pod-demo -n default color=red

替换已存在的标签。

  • kubectl label pods pod-demo -n default color=black --overwrite
    • 如果资源已经有一个同名的标签,使用 --overwrite 参数才能替换原有标签;

删除一个标签的方法。

  • kubectl label pods pod-demo -n default color-
    • color- 代表删除此标签

标签选择器

通过 -l 参数,按照特定的规则过滤资源的标签,例如:

  • kubectl get pods --show-labels -l app=myapp:只显示包含 app 标签且值等于 myapp 的资源;
  • kubectl get pods --show-labels -l app!=myapp:只显示不包含 app 标签或 app 的值不等于 myapp 的资源;
  • kubectl get pods --show-labels -l "app in (myapp,ngx-dep)":显示包含 app 标签,且值等于 myapp 或者 ngx-dep 的资源;
  • kubectl get pods --show-labels -l "app":显示包含 app 标签,可以是任何值;
  • kubectl get pods --show-labels -l '!app':显示不包含 app 标签,注意这时有特殊符号,需要使用强引用;

标签还有另一种显示的方法:

  • 在不使用 --show-labels 的情况下,用 -L app 明确要显示的标签,会以独立标签的形式显示 app 的值;
1
2
3
4
5
~]# kubectl get pods -l "app in (myapp,ngx-dep)" -L app
NAME READY STATUS RESTARTS AGE APP
myapp-5c6976696c-mwp7w 1/1 Running 1 23h myapp
myapp-5c6976696c-p7lf2 1/1 Running 1 23h myapp
ngx-dep-855f7b4bc8-p9rjt 1/1 Running 1 24h ngx-dep

资源注解(annotation)

注解和标签很像,也是“键值”类型的数据,不过它不能用于标签及挑选 k8s 对象,只是为用户提供一个自定义元数据的接口;

注解中的元数据不受字符数量的限制,它可大可小,可以为结构化或非结构化的形式,也支持使用在标签中禁止使用的其他字符;

注解在 k8s 中很重要,后期很多管理工具是通过注解来管理资源;

注解的使用方法

注解也属于元数据,所以同样可以在 metadata 中自定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~]# vim /root/pod-demo.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels:
app: pod-demo
rel: stable
annotations: # 添加一个注解
ik8s.io/project: hello
spec:
containers:
- image: nginx:latest
name: nginx


~]# kubectl apply -f /root/demo.yaml

查看刚刚创建的注解:

1
2
3
4
5
6
7
8
~]# kubectl describe pods pod-demo -n default |grep Annotations -C 3
Start Time: Thu, 31 Oct 2019 20:15:25 +0800
Labels: app=pod-demo
rel=stable
Annotations: ik8s.io/project: hello
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{"ik8s.io/project":"hello"},"labels":{"app":"pod-demo","rel":"stable"},"name":"p...
Status: Running

可以发现,在注解中,自动帮我们保存了在执行 kubectl apply 时指定的配置文件中的所有配置信息,这样下次使用 apply 重新构建时,会自动对比两次的不同,新加的部分会以补丁的形式补充上去。这就是为什么使用 kubectl apply 可以重复构建资源的原因,而使用 kubectl create 则不行

四、Pod 的生命周期

live

流程

  1. 首先启动一个 Infra 容器(又叫 Pause 容器),用来和 Pod 中的其他容器共享 linux 命名空间,并开启 init 进程。(上图中忽略了这一步)
  2. 然后启动 Init 容器,它是一种专用的容器,在应用程序容器启动之前运行,用来对 Pod 进行一些初始化操作,并包括一些应用镜像中不存在的实用工具和安装脚本;
  3. 之后,应用程序容器和 post-start hook 同时启动;
  4. 开始启动 livenessreadiness 探针;
  5. 手动停止 Pod 时,pre-stop hook 执行,优雅删除期限过期后(默认是 30 秒),应用程序容器停止。实际的 Pod 终止过程要更复杂,具体参考 Pod 的终止。

livenessProbe 健康状态检测

livenessProbe 的作用是检测容器健康状态,如果不健康,会重启容器。

常用的有 3 种检测方式:

  • exec:执行指定的命令来做健康状态检测,要执行的命令一定是容器内包含的命令;
  • httpGet:请求一个 URL;
  • tcpSocket:请求一个套接字;

其他参数:

  • failureThreshold:检测健康状态变为失败时,需要确认几次,默认为 3;
  • successTHreshold:检测健康状态变为成功时,需要确认几次,默认且只能为 1;
  • periodSeconds:检测周期,默认 10s;
  • initialDelaySeconds:当容器启动后延迟多久后才开始检查;默认容器一启动就检查;
  • timeoutSeconds:每次检查时如果对方无响应,最多等待多久后自动断开,以减少连接数;默认 1s;

1. 通过命令来检测的示例

示例配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~]# vim liveness-exec.yaml 

apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness-exec
name: liveness-exec
spec:
containers:
- name: liveness-demo
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 60; rm -rf /tmp/healthy; sleep 600 # 容器启动时会创建一个文件
livenessProbe:
exec:
command: # 检测这个文件是否存在,如果存在就正常。
- test
- -e
- /tmp/healthy

启动 Pod:

1
kubectl apply -f /root/liveness-exec.yaml

之后在描述信息里能看到检测状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
~]# kubectl describe pods liveness-exec
Name: liveness-exec
Namespace: default
Priority: 0
Node: node1.monster.com/192.168.50.11
Start Time: Fri, 01 Nov 2019 13:31:51 +0800
Labels: test=liveness-exec
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"test":"liveness-exec"},"name":"liveness-exec","namespace":"default...
Status: Running
IP: 10.244.1.8
IPs:
IP: 10.244.1.8
Containers:
liveness-demo:
Container ID: docker://a3fbb09b01551abd5710997729465373f347710c841385687091d766fe85ce94
Image: busybox
Image ID: docker-pullable://busybox@sha256:1303dbf110c57f3edf68d9f5a16c082ec06c4cf7604831669faf2c712260b5a0
Port: <none>
Host Port: <none>
Args:
/bin/sh
-c
touch /tmp/healthy; sleep 60; rm -rf /tmp/healthy; sleep 600
State: Running
Started: Fri, 01 Nov 2019 13:37:56 +0800 # 启动容器的时间
Last State: Terminated # 上次容器的状态
Reason: Error
Exit Code: 137 # 退出的错误码
Started: Fri, 01 Nov 2019 13:35:56 +0800
Finished: Fri, 01 Nov 2019 13:37:51 +0800
Ready: True
Restart Count: 3 # 重启了几次
Liveness: exec [test -e /tmp/healthy] delay=0s timeout=1s period=10s #success=1 #failure=3 # Liveness 的检测方式
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-mzmwh (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-mzmwh:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-mzmwh
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/liveness-exec to node1.monster.com
Normal Created 3m43s (x3 over 7m42s) kubelet, node1.monster.com Created container liveness-demo
Normal Started 3m43s (x3 over 7m42s) kubelet, node1.monster.com Started container liveness-demo
Warning Unhealthy 2m18s (x9 over 6m38s) kubelet, node1.monster.com Liveness probe failed:
Normal Killing 2m18s (x3 over 6m18s) kubelet, node1.monster.com Container liveness-demo failed liveness probe, will be restarted
Normal Pulling 108s (x4 over 7m47s) kubelet, node1.monster.com Pulling image "busybox"
Normal Pulled 103s (x4 over 7m42s) kubelet, node1.monster.com Successfully pulled image "busybox"

2. 通过 http 请求来检测的示例

先使用 lifecycle 在容器启动后,创建一个页面文件 healthz,之后定义健康状态检查时,通过 URI 请求这个页面。

示例配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
~]# vim liveness-http.yaml 

apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness-demo
image: nginx:1.12-alpine
ports:
- name: http # 注释 1-1 定义了 80 端口的别名为 http
containerPort: 80
lifecycle: # 注释 2-1:先使用 lifecycle 在容器启动后,创建一个页面文件 healthz
postStart:
exec:
command:
- /bin/sh
- -c
- 'echo Healty > /usr/share/nginx/html/healthz'
livenessProbe: # 注释 2-2: 定义健康状态检查时,通过 URI 请求 /healthz 这个页面
httpGet:
path: /healthz
port: http # 注释 1-2:调用之前定义的别名 http,就代表 80 端口。
scheme: HTTP
periodSeconds: 2 # 定义检测周期
failureThreshold: 2 # 检测健康状态变为失败时,需要确认几次
initialDelaySeconds: 5 # 当容器启动后延迟多久后才开始检查

readinessProbe 就绪状态检测

readinessProbe 就绪检测的配置方式和健康状态检测的配置方式是相像的,但不同的是,就绪状态检测在检测到容器还未就绪时,是不会重启容器的。

  • 当就绪检查成功了,Service 就会把 Pod 当做后端用了,这是 Service 非常依赖的评估标准;
  • 如果检查不成功,就会把 Pod 从后端移走,无论 Pod 运行是否正常。

配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~]# vim readiness-exec.yaml 

apiVersion: v1
kind: Pod
metadata:
labels:
test: readiness-exec
name: readiness-exec
spec:
containers:
- name: readiness-demo
image: busybox
args: ["/bin/sh", "-c", "while true; do rm -f /tmp/ready; sleep 30; touch /tmp/ready; sleep 300; done"]
readinessProbe:
exec:
command: ["test", "-e", "/tmp/ready"]
initialDelaySeconds: 5
periodSeconds: 5

五、Pod 对象的相位

无论是手动创建还是通过控制器创建 pod,pod 对象总是应该处于其生命进程中以下几个相位之一:

  • pending:apiserver 创建了 pod 资源对象并存入 etcd 中,但它尚未被调度完成或者仍处于下载镜像的过程中;
  • running:pod 已经被调度至某节点,并且所有容器都已经被 kubelet 创建完成;
  • succeeded:pod 中的所有容器都已经成功终止并且不会被重启;
  • failed:所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非 0 值的退出状态或已经被系统终止;
  • unknown:apiserver 无法正常获取到 pod 对象的状态信息,通常是由于其无法于所在工作节点的 kubelet 通信所致;

Pod 对象的创建过程
creat

容器的重启策略

容器程序发生崩溃或者容器申请超出限制的资源等原因都会导致 Pod 对象的终止,此时是否应该重建该 Pod 对象取决于其重启策略

  • Always:但凡 Pod 对象终止就将其重启,此为默认设定;
  • onfailure:仅在容器出现错误时将其重启;
  • Never:从不重启;

Pod 的终止过程
stop

六、Pod 资源限制需求及资源限制

容器的计算资源配额:

  • 相比较来说,CPU 属于可压缩(compressible)资源,即资源额度可按需收缩,而内存(当前)则是不可压缩型资源,对其执行收缩操作可能会导致某种程度的问题。
    • CPU 资源的计量方式
      • 在 k8s 系统上,1 个单位的 CPU 相当于虚拟机上的 1 颗 CPU(vcpu)或物理机上的一个超线程或逻辑 CPU,它支持分数计量方式,一个核心相当于 1000 个微(m)核心,因此 500m 相当于是 0.5 个核心;
    • 内存资源的计量方式
      • 内存的计量方式与日常使用方式相同,默认单位是字节,也可以使用 EPTGMK 作为单位后缀;

如果不满足下限,则拒绝启动容器,影响调度;
如果不满足上限,不会影响调度;

Pod 资源限制的示例

使用 CPU 密集型镜像,来测试对 CPU 的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

~]# vim stress-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: stress-pod
spec:
containers:
- name: stress
image: ikubernetes/stress-ng
command: ["/usr/bin/stress-ng", "-c 1", "-m 1", "--metrics-brief"]
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "400m"

使用内存密集型镜像,此镜像会不停的申请内存而不释放,来测试对内存的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~]# vim memleak-pod.yaml 

apiVersion: v1
kind: Pod
metadata:
name: memleak-pod
spec:
containers:
- name: simmemleak
image: saadali/simmemleak
resources:
requests:
memory: "64Mi"
cpu: "1"
limits:
memory: "64Mi"
cpu: "1"

Pod 服务指令类别

根据 Pod 的 requests 和 limits 设定,kubernetes 在创建 Pod 时,会将 Pod 归类到 BestEffortBurstableGuaranteed 三个服务质量(Quality of Service,QoS)类别的其中一个:

  • Guaranteed:当每个容器 cpu 和内存资源的 requests 和 limits 定义的值相等时,Pod 资源会自动归属于此类别,这类 pod 资源具有最高优先级;
  • Burstable:Pod 内至少有一个容器设置了 cpu 或内存资源的 requests 属性,但不满足 Guaranteed 类别要求的 pod,将自动归属此类别,它们具有中等优先级;
  • BestEffort:未为任何一个容器设置 requests 和 limits 属性的 pod 资源将自动归属于此类别,它们的优先级为最低级别;

使用以下命令可以查看容器当前被归为哪一类别:

1
2
~]# kubectl describe pod pod-demo | grep "QoS Class"
QoS Class: BestEffort