容器-8-Kubernetes实战-k8s核心概念之Node,Pod与Deployment

本篇我们开始用k8s的方式部署静态网站镜像,并通过这个过程了解k8s为什么会抽象出那么多概念。

1. Node

k8s首先需要选定在哪一台或哪几台服务器上部署。
如我们在kubeadm部署k8s集群的时候就已知的,k8s集群是由1-N台Master节点和N个Worker节点组成的。
Kubernetes节点

k8s的设计原则之一就是不挑Worker节点的硬件配置。毕竟当初Google搭Borg集群的时候淘到的服务器硬件各式各样都有。(想起来当初搭Hadoop的时候也有人问过我是不是什么硬件都可以。。。以Hadoop MapReduce的吃硬盘和网络程度,实体机+专用万兆宽带跑出来的性能比虚拟机快出好几倍。当然配置低也的确能跑起来不死。)
你手头的机器可能是什么歪瓜裂枣都有:
Kubernetes Nodes物理硬件
不管是实体机还是虚拟机,不管什么硬件配置,不管高的矮的胖的瘦的,k8s都将其一视同仁地抽象为一个Node(曾经也有一个专有名词minion)。
Kubernetes Nodes抽象

一个典型的Node信息如下(部分删减):

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
Name:               docker-7
Roles: worker
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
MemoryPressure False Fri, 21 Jun 2019 16:58:54 +0800 Fri, 21 Jun 2019 16:57:54 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Fri, 21 Jun 2019 16:58:54 +0800 Fri, 21 Jun 2019 16:57:54 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Fri, 21 Jun 2019 16:58:54 +0800 Fri, 21 Jun 2019 16:57:54 +0800 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Fri, 21 Jun 2019 16:58:54 +0800 Fri, 21 Jun 2019 16:57:54 +0800 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 10.16.34.59
Hostname: docker-7
Capacity:
cpu: 4
ephemeral-storage: 101729776Ki
hugepages-2Mi: 0
memory: 8010576Ki
pods: 110
Allocatable:
cpu: 4
ephemeral-storage: 93754161407
hugepages-2Mi: 0
memory: 7908176Ki
pods: 110
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 320m (8%) 300m (7%)
memory 150Mi (1%) 150Mi (1%)
ephemeral-storage 0 (0%) 0 (0%)

Master节点不断轮询更新每个Node的状态信息:“你还活着么?压力大不大?CPU/内存/存储已经分配了多少?还剩下多少?”
这样当Master节点为了新的容器需求征兵时,就能很容易地知道哪几个节点还够压榨。

2. Pod

Pod是Kubernetes中的原子调度单位。最简单的Pod就等于一个容器。这么表述也就意味着Pod里也可以放多个容器。
看为什么Kubernetes会发明出来Pod这个概念?因为有些容器之间有共享网络和存储的需求。

举个最典型的例子:日志收集器。例如我们的静态网站容器会在被访问的时候生成access.log文件。如果是在虚机里部署,那么就会在服务器上另外起一个logstash,收集这些日志文件后汇总到Elasticsearch。
如果logstash和Nginx不在同一台宿主机上部署,虽然也不是不可行,但就会很折腾:后文会提到挂载PV,可以在logstash和Nginx的Pod上都挂载同一个PV。但这个折腾毫无必要。日志文件并不那么重要,不值得永久存储,生命周期跟着容器即可。当容器被销毁时,日志也可以跟着被销毁。

那么我们是不是可以把Nginx和logstash打包到同一个容器中呢?这会产生一个问题:当logstash进程挂掉时,k8s的监控怎么表示容器的状态?如果显示Failure然后重启容器,则无辜的Nginx进程也受到了牵连;如果显示正常,那么要单独重启容器里的某一个进程就会变得很麻烦。
所以我们需要将Nginx和logstash单独打包为容器,部署成一个Pod。k8s会将一个Pod里的容器都部署在同一台宿主机上。

Kubernetes Pod

k8s的官方博客将多容器Pod的类型列为三种:

  • Sidecar(边车)容器
  • Ambassador(大使)容器
  • Adapter(适配器)容器
    k8s Pod的实现方式特别适合我们将一些控制平面的功能放到Sidecar中。近几年很知名的Service Mesh项目就是完全通过Sidecar模式支撑起来的。我们以后再详细讨论这个话题。

其实对于Pod不用想得太复杂,可以认为它就是逻辑上的一台虚机的概念。只有必须部署在同一台虚机上的,才会被并到一个Pod里。
例如虽然MySQL和Java应用虽然也可以部署在同一台虚机上,但从best practice考量,一般不会这么部署,所以就不是一个Pod。MySQL的Master和Slave一般是部署在两台服务器上,所以也是两个独立的Pod。

2.1 Pod YAML

要部署一个Pod,我们需要先写一个YAML描述文件,然后用k8s的命令部署。
我从Spring Boot的配置开始就已经接触了挺久的YAML,所以这边就不详细介绍YAML的语法了。如果有对语法部署的可以参考Wiki
一个最简单的Nginx Pod部署YAML如下:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest

这个YAML第一层的元素有四个:apiVersion, kind, metadata和spec。
apiVersion的v1表示是稳定版。(像之前kubeadm的apiVersion就是kubeadm.k8s.io/v1beta1,还在快速迭代中)。
kind就是想要创建的对象的类型。
metadata是用来识别对象的唯一性。
spec字段是对象规约,内容随着每个Kubernetes对象而不同。对于该Nginx Pod来说,需要指定使用的唯一镜像以及镜像的版本。关于镜像和镜像版本可以在Docker Hub上查到。

2.2 部署命令

我们可以使用如下的命令部署:

1
kubectl apply -f nginx-pod.yaml

然后就可以通过如下命令查看Pod创建进展:

1
kubectl get pod

以及通过如下命令查看Pod详细信息:

1
kubectl describe pod <Pod名>

想进入Pod,就是用exec -it命令:

1
kubectl exec -it nginx-pod /bin/bash

如果不需要这个Pod了,可以用以下命令取消部署:

1
kubectl delete -f nginx-pod.yaml

k8s的命令虽然很多,但都非常有规律。基本格式都是:

1
kubectl 动作 对象类型

1
kubectl 动作 [-参数] 对象类型 对象名

对于非default namespace的对象,再加一个namespace参数:

1
kubectl 动作 对象类型 对象名 -n namespace名

基本不怎么需要特别记忆。

另外提一下,在使用了k8s后,最好不要再打Docker命令了。当然自己本地开发机上跑容器的时候还是需要拍Docker命令。
在本次POC中,我们不将Nginx部署为Pod。原因在下一章中说明。

2.3 验证

此时Pod已部署成功,但我们还没法从容器外部访问。想验证的话可以稍微修改一下YAML,增加一个验证的shell容器,然后共享PID Namespace:

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

上述配置在spec下增加了shareProcessNamespace: true,表示PID Namespace共享。最底下还增加一个镜像为busybox的容器。(busybox和alpine由于体积比较小,在k8s部署的时候有广泛的用途)
要部署之前,我们需要先kubectl delete -f nginx-pod.yaml把pod删除,不然会得到告警:

1
spec.containers: Forbidden: pod updates may not add or remove containers

在重新apply后使用如下的命令进入Pod

1
kubectl attach -it nginx-pod -c shell

进入busybox容器后执行ps aux,就可以看到Nginx的进程了:

1
2
3
4
5
6
7
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /pause
6 root 0:00 nginx: master process nginx -g daemon off;
11 101 0:00 nginx: worker process
12 root 0:00 sh
17 root 0:00 ps aux

2.4 infra容器:pause

上一节进程的查询结果中有一个pause进程需要说明一下。
当我们使用docker ps查看容器时,会发现有相当多的pause容器在启动。该容器和我们前几节提到的多容器Pod有关。
以我们上面修改后的nginx-pod范例为例。当我们需要让两个容器共享Namespace(不仅仅是IPC,还包括Network等其他Namespace),方法之一是让一个容器先启动,然后将另外一个容器的Namespace设置为前一个容器的Namespace。从技术上可行,但这个会导致这两个容器之间的关系不再是对等拓扑关系。
所以需要有个中间容器,也就是pause容器存在。pause容器非常小,也基本干不了什么事。它唯一的作用就是在Pod启动的最开始就启动,然后在其他容器启动后和它们共享自己的Namespace。在所有的容器启动完成后就暂停自己,不再消耗资源。
Kubernetes Infra Container

3. Deployment

Pod的副本实例数是在Deployment中定义的。
假设我们从负载和高可用的角度考虑,想要在k8s中部署2个Nginx的实例,那么只需要在Deployment的配置中定义replicas: 2即可。

我们实际写k8s yaml配置的时候,不推荐直接部署为Pod。即使是只有一个副本,也推荐部署为replicas为1的Deployment。这是由于k8s的调度机制是通过Deployment来确保副本数量。万一Pod所在的服务器挂了,k8s会检测到副本数不足1,于是将Pod调度到健康的Node上。

Kubernetes Deployment

3.1 Deployment YAML

一个Nginx Deployment的精简版的YAML如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
name: web

可以看到spec下有一个replicas,表述副本数量。

看到这个YAML,我第一反应是有些晕:为什么spec套了一层spec,template下还有一层metadata。
这篇matchLabels, labels, and selectors explained in detail, for beginners解释了为什么这个YAML有点绕的原因。关键点是这个Deployment YAML中的template其实是podTemplate。这样你就会发现template中的部分只要加上apiVersion和kind,基本就是Pod的YAML。
Kubernetes YAML template

与template平级的还有一个selector选择器属性。这个selector选择器表示deployment部署的是label里带app的podTemplate。
label作为一个非唯一的标签属性,可以使Kubernetes的运维更加灵活。这个我们后续再详细研究。

我们现在可以总结出一个最简单版的deployment yaml模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: <deployment-name>
spec:
replicas: <replicas-number>
selector:
matchLabels:
app: <app-name>
template:
metadata:
labels:
app: <app-name>
spec:
containers:
- name: <app-name>
image: <image-name>:<tag-name>
ports:
- containerPort: <container-port>
name: <container-port-name>

3.2 部署命令

部署命令和Pod基本一样:

1
kubectl apply -f nginx-deployment.yaml

然后查看到ready的deployment了:

1
2
3
[root@docker-4 pod]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/2 2 2 16h

3.3 部署POC Nginx镜像

要部署POC的Nginx镜像,只需要将image修改为私有仓库的镜像即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: poc-web
labels:
app: poc-web
spec:
replicas: 2
selector:
matchLabels:
app: poc-web
template:
metadata:
labels:
app: poc-web
spec:
containers:
- name: poc-web
image: 10.16.34.197:5000/staticsite:1.0
ports:
- containerPort: 80
name: web

如果使用Docker Hub作为Docker Registry,则首先创建secret(什么是secret会在容器-12-Kubernetes实战-静态网站部署优化1:ConfigMap,Secret与TLS | Galaxy 中详细说明):

1
kubectl create secret docker-registry regcred --docker-username=账号 --docker-password=密码 --docker-email=Docker邮箱 -n 命名空间默认default

然后在pod定义的同一级加上imagePullSecrets,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
name: poc-web
labels:
app: poc-web
spec:
replicas: 2
selector:
matchLabels:
app: poc-web
template:
metadata:
labels:
app: poc-web
spec:
containers:
- name: poc-web
image: galaxyyao/demosite:1.0
ports:
- containerPort: 80
name: web
imagePullSecrets:
- name: regcred

4. 参考资料

Kubernetes Node的官方文档概念介绍
Nodes - Kubernetes

Kubernetes Pod的官方文档概念介绍
Pod Overview - Kubernetes

Kubernetes Deployment的官方文档概念介绍
Deployments - Kubernetes

这是一篇关于Pause容器的详细介绍
The Almighty Pause Container - Ian Lewis

本篇部分有趣的图来自这篇。很简要易懂的科普文。
Kubernetes & Traefik 101— When Simplicity Matters – Gérald Croës – Medium

如果想补习YAML的话可以参考这篇:
YAML basics in Kubernetes – IBM Developer

本文永久链接 [ https://galaxyyao.github.io/2019/06/24/容器-8-Kubernetes实战-k8s核心概念之Node-Pod与Deployment/ ]