Kubernetes 常用运维技巧

这篇文章对日常工作中常用的 Kubernetes 系统运维操作和技巧进行总结和详细说明。(不定期更新!)

Node 的隔离和恢复

在硬件升级、维护等情况下,我们需要将某些 Node 进行隔离,脱离 Kubernetes 集群的调度范围。 Kubernetes 提供了一种机制,既可以将 Node 纳入调度范围,也可以将 Node 脱离调度范围。

创建配置文件 unschedule_node.yaml,在 spec 部分指定 unschedulable 为 true:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Node
metadata:
name: kubernetes-minion1
labels:
kubernetes.io/hostname: kubernetes-minion1
spec:
unschedulable: true

然后,通过kubectl replace命令完成对 Node 状态的修改:

1
$ kubectl replace -f unschedule_node.yaml

查看 Node 状态,可以观察到在 Node 的状态中增加了一项 SchedulingDisabled。

对于后续创建的 Pod,系统将不会再向该 Node 进行调度。

另一种方法时不使用配置文件,直接使用kubectl patch命令完成:

1
$ kubectl patch node kubernetes-minion1 -p '{"spec": {"unschedulable": true}}'

需要注意的是,将某个 Node 脱离调度范围时,在其上运行的 Pod 并不会自动停止,需要手动停止在该 Node 上运行的 Pod。。

同样,如果需要将某个 Node 重新纳入集群调度范围,则将 unschedulable 设置为 false,再次执行kubectl replacekubectl patch命令就能恢复系统对该 Node 的调度。

Node 的扩容

在实际生产系统中会经常遇到服务器容量不足的情况,这时就需要购买新的服务器,然后将应用系统进行水平扩展来完成对系统的扩容。

在 Kubernetes 集群中,对于一个新 Node 的加入是非常简单的。可以在 Node 节点上安装 Docker、Kubelet 和 kube-proxy服务,然后将 Kubelet 和 kube-proxy 的启动参数中的 Master URL 指定为当前 Kubernets 集群 Master 的地址,最后启动这些服务。基于 Kubelet 的自动注册机制,新的 Node 将会自动加入现有的 Kubetnetes 集群中。

Kubenetes Master 在接受了新 Node 的注册后,会自动将其纳入当前集群的调度范围内,在之后创建容器时,就可以向新的 Node 进行调度了。

Pod 动态扩容和缩放

在实际生产系统中,我们经常会遇到某个服务需要扩容的场景,也可能会遇到由于资源紧张或者工作负责降低需要减少服务实例数的场景。此时我们可以利用命令kubectl scale rc来完成这些任务。比如通过执行以下命令将 redis-slave RC 控制的 Pod 副本数量更新为 3:

1
$ kubectl scale rc redis-slave --replicas=3

将 –replicas 设置为比当前 Pod 副本数量更小的数字,系统将会“杀掉”一些运行中的 Pod,即可实现应用集群缩容。

更新资源对象的 Label

Label(标签)作为用户可灵活定义的对象属性,在已创建的对象上,仍然可以随时通过kubectl label命令对其进行增加、修改、删除等操作。

例如,我们要给已创建的 Pod “redis-master-bobr0”添加一个标签 role=backend:

1
$ kubectl label pod redis-master-bobr0 role=backend

删除一个 Label,只需在命令后最后指定 Label 的 key 名并与一个减号相连即可:

1
$ kubectl label pod redis-master-bobr0 role-

修改一个 Label 的值,需要加上 –overwrite 参数:

1
$ kubectl lable pod redis-master-bobr0 role=master --overwrite

将 Pod 调度到指定的 Node

Kubernetes 的 Scheduler 服务(kube-scheduler 进程)负责实现 Pod 的调度,整个调度过程通过一系列复杂的算法最终为每个 Pod 计算出一个最佳的目标节点,这一过程是自动完成的,我们无法知道 Pod 最终会被调度到哪个节点上。有时我们可能需要将 Pod 调度到一个指定的 Node 上,此时,我们可以通过 Node 的标签(Label)和 Pod 的 nodeSelector 属性相匹配,来达到上述目的。

首先,我们可以通过kubectl label命令给目标 Node 打上一个特定的标签,下面是此命令的完整用法:

1
$ kubectl label nodes <node-name> <label-key>=<label-value>

这里,我们为 kubenetes-minion1 节点打上一个 zone=north 的标签,表明它是“北方”的一个节点:

1
$ kubectl label nodes kubernetes-minion1 zone=north

上述命令行操作也可以通过修改资源定义文件的方式,并执行kubectl replace -f xxx.yaml命令来完成。

然后,在 Pod 的配置文件中加入 nodeSelector 定义,以 redis-master-controller.yaml 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master
labels:
name: redis-master
spec:
replicas: 1
selector:
name: redis-master
template:
metadata:
labels:
name: redis-master
spec:
containers:
- name: master
image: kubeguide/redis-master
ports:
- containerPort: 6379
nodeSelector:
zone: north

运行kubectl create -f命令创建 Pod,scheduler 就会将该 Pod 调度到拥有 zone=north 标签的 Node 上去。

使用kubectl get pods -o wide命令可以验证 Pod 所在的 Node。

如果我们给多个 Node 都定义了相同的标签,则 scheduler 将会根据调度算法从这组 Node 中挑选一个可用的 Node 进行 Pod 调度。

这种基于 Node 标签的调度方式灵活性很高,比如我们可以把一组 Node 分别贴上“开发环境”“测试环境”“正式环境”这三组标签中的一种,此时一个 Kubernetes 集群就承载了 3 个环境,这将大大提高开发效率。

需要注意的是,如果我们指定了 Pod 的 nodeSelector 条件,且集群中不存在包含相应标签的 Node 时,即使还有其他可供调度的 Node,这个 Pod 也最终会调度失败。

应用的滚动升级

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有 Pod,然后重新拉取镜像并启动。如果集群规模比较大,则这个工作就变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes 提供了 rolling-update(滚动升级)功能来解决上述问题。

滚动升级通过执行kubectl rolling-update命令一键完成,该命令创建了一个新的 RC,然后自动控制旧的 RC 中的 Pod 副本数量逐渐减少到 0,同时新的 RC 中的 Pod 副本数量从 0 逐步增加到目标值,最终实现了 Pod 的升级。需要注意的是,系统要求新的 RC 需要与旧的 RC 在相同的命名空间(Namespace)内,即不能把别人的资产偷偷转移到自家名下。

以 redis-master 为例,假设当前运行的 redis-master Pod 是 1.0 版本,则现在需要升级到 2.0 版本。

创建 redis-master-controller-v2.yaml 的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master-v2
version: v2
spec:
replicas: 1
selector:
name: redis-master
version: v2
template:
metadata:
labels:
name: redis-master
version: v2
spec:
containers:
- name: master
image: kubenetes/redis-master:2.0
ports:
- containerPort: 6379

在配置文件中有几处需要注意:

  1. RC 的名字(name)不能与旧的 RC 的名字相同;
  2. 在 selector 中应至少有一个 Label 与旧的 RC 的 Label 不同,以标识其为新的 RC。本例中新增了一个名为 version 的 Label,以与旧的 RC 进行区分。

运行kubectl rolling-update命令完成 Pod 的滚动升级:

1
$ kubectl rolling-update redis-master -f redis-master-controller-v2.yaml

等所有新的 Pod 启动完成后,旧的 Pod 也被全部销毁,这样就完成了容器集群的更新。

另一种方法是不使用配置文件,直接用kubectl rolling-update命令,加上--image参数指定新版镜像名称来完成 Pod 的滚动升级:

1
$ kubectl rolling-update redis-master --image=redis-master:2.0

如果在更新过程中发现配置有误,则用户可以中断更新操作,并通过执行kubectl rolling-update --rollback完成 Pod 版本的回滚:

1
$ kubectl rolling-update redis-master --image=redis-master:2.0 --rollback