deployment in kubernetes

注: 本文是学习《kubernetes in Action中文版》的学习笔记.

这里主要是了解kubernets如何做到服务的多副本高可用,一个是部署的时候,一个是发布更新的时候。

部署的时候我们最基础的是需要多个副本,然后最好是每个副本都在不同的物理机,rack,或者az里,这样可以最大限度的保证高可用。

所以我们这里就是朝着这个方向去努力。

后面我们再分析kubernetes里pod的资源限制和使用的问题。

ReplicationController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ReplicationController #表示使用rc
metadata:
name: kubia
spec:
replicas: 3 # replica count
selector: # label select
app: kubia
template: # 以下都是pod template, 这里的labels app名字要和上面selector的app名字相同,否则控制器将无休止地创建新的容器。
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
ports:
- containerPort: 8080

ReplicationController 这个现在已经不怎么推荐使用了。它会持续监控正在运行的pod列表,确保pod的数量始终与其标签选择器匹配。

ReplicationController主要有4个部分:

  1. label select, 这个主要是确定ReplicationController的作用域有哪些pod
  2. replica count, 指定应运行的pod数量
  3. pod template: 用于创建新的pod副本
  4. minReadySeconds: 表示启动多久了算ready成功,默认为0,表示一启动就绪就表示可用了。

我们通常可以使用如下命令来获取ReplicationController的信息

1
kubectl get rc

上面的rc就是ReplicationController的缩写。

如果我们要查看pod的rc详细信息

1
kubectl describe rc kubia

作者在这个时候把其中一个pod所在服务器的网络给中断了,那rc就会再拉起一个新的pod。但是这个是间隔多久说是大概有一分钟的时间。这个应该是有地方可以配置的,不然这个时间也太长了。

但是我们也不能依赖kubernets的健康检查,我们还是需要负载均衡上做相应的检测和重试。    

其他的也不用深入研究了,我们直接去到ReplicaSet吧

ReplicaSet

https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicaset/

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: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# 按你的实际情况修改副本数
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5

我们可以清楚的看到ReplicaSet比ReplicationController多了一个matchLabels的概念,不需要在selector属性里直接列出pod需要的标签,而是通过matchLabels来指定。

这个matchLabels我个人感觉是是上面的selector属性指定没有区别,ReplicaSet里更有意思的是matchExpressions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: timo-k8s
spec:
selector:
matchExpressions:
- key: app
operator: Exists
template:
metadata:
labels:
app: timo-k8s
spec:
containers:
- name: timo-k8s
image: timoseven/timo-k8s:frontend
ports:
- containerPort: 8080
1
2
3
4
5
6
selector:
matchExpressions:
- key: app
operator: In
values:
- kubia

这两个例子里可以看到matchExpressions的语法可以有Exists,In,还有NotIn,DoesNotExists

  1. In: label的值必须与其中的一个指定的values匹配

  2. NotIn: label的值愈任何指定的values不匹配

  3. Exists: pod必须包含一个指定名称的标签(值不重要),使用此运算符时,不应该指定values字段

  4. DoesNotExist:pod不得包含含有指定名称的标签,values属性不得指定。

总体来说现在如果还在用ReplicationController的话就把它替换成ReplicaSet吧。

DaemonSet

前面2个Replica都是用来指定部署pod的个数的,但是很多时候我们需要指定某些pod在一些node上执行,那这个就需要使用DaemonSet。

其实以上这些mesos+marathon也可以完成的。

在kubernets中这个是根据node亲和性来的,并不是只是在DaemonSet中使用

将 Pod 指派给节点 | Kubernetes

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
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0

其中requiredDuringSchedulingIgnoredDuringExecution 这个是必须要有的key,它对应的value是antarctica-east1和antarctica-west1

而preferredDuringSchedulingIgnoredDuringExecution 这个是尽量要有的,但是没有也没关系。

同时原先书中写的nodeSelector 这个已经不建议使用,因为它的语法表达能力比较弱。上面亲和性的2个语法中operator可以有In,NotIn,Exists,DoesNotExist,Gt,Lt

其中Gt和Lt是这里新加的,根据名字也很容易理解就是大于和小于的意思。

nodeSelector和亲和性可以同时使用,但是同时使用的就是要两者都满足才行。

所以我们以后还是就用亲和性就可以了。

以上都是我们在做首次部署的应该注意的,但是在实际生产环境中,我们还需要注意发布的时候的行为。

在mesos+marathon中我们可以设定更新的时候同时更新百分比。

Deployments

在kubernets里当然也有关于更新的设置。

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1

其中strategy.type 这里可以是Recreate和RollingUpdate。

Recreate的含义是在创建新的Pod之前,所有现有的Pod都会被杀死。

RollingUpdate的含义就是滚动升级,其中maxUnavailable表示是最大不可用的数量,这里可以写绝对数字,也可以写百分比,比如33% 这样。当然如果我们写0,那就表示我们必须起新的Pod之后再关闭现有的Pod。

maxSurge这个表示可以启动多少个新的Pod,这里也可以是绝对数字或者是百分比。这里如果写100%,那就是说我会再新启动3个nginx Pod,然后再关闭现有的nginx Pod。

502&504问题

我们在部署的时候可以使用minReadySeconds来指定Pod就绪多久才算成功。我们可以把这个值调整一下,有些Pod可能一health check后就表示就绪了,有些Pod并不是这样的,需要有流量了它才会去建立对应连接和请求。

在kubernets里可以使用启动探针保护慢启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
startupProbe:  # 启动探测,如果失败就会重启
httpGet:
path: /healthz # 直接使用健康检查接口即可
port: 8080
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 20 # 最多提供给服务 5s * 20 的启动时间
successThreshold: 1
livenessProbe: # 存活探测
httpGet:
path: /healthz # spring 的通用健康检查路径
port: 8080
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 5
successThreshold: 1
readinessProbe: # 就绪探针的配置,这个才是真正表示真正的就绪
httpGet:
path: /healthz # 简单起见可直接使用 livenessProbe 相同的接口,当然也可额外定义
port: 8080
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 5
successThreshold: 1

当然我们也可以自定义header来请求

1
2
3
4
5
6
7
8
9
10
11
livenessProbe:
httpGet:
httpHeaders:
- name: Accept
value: application/json

startupProbe:
httpGet:
httpHeaders:
- name: User-Agent
value: MyUserAgent

我们再来看看终止的问题:

探索 Pod 及其端点的终止行为 | Kubernetes

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 120 # 超长优雅期
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
lifecycle:
preStop:
exec:
# 实际生产环境中的 Pod 终止可能需要执行任何时长,但不会超过 terminationGracePeriodSeconds。
# 在本例中,只需挂起至少 terminationGracePeriodSeconds 所指定的持续时间,
# 在 120 秒时容器将被强制终止。
# 请注意,在所有这些时间点 nginx 都将继续处理请求。
command: [
"/bin/sh", "-c", "sleep 180"
]

我们可以看到terminationGracePeriodSeconds和preStop这2个。terminationGracePeriodSeconds表示优雅退出,这个默认是30s,最小值为1s,也就是表示无论如何这个nginx超过120s后都会被强制终止。

那这里的preStop又是干什么呢?

需要注意的是,这个terminationGracePeriodSeconds是与 PreStop同步开始的,而且它也不会等待preStop结束。

所以这里就是执行sleep 180s而已,而且并不会执行完毕,因为到120s的时候Pod就被强制终止了。