ReplicaSet
ReplicaSet 副本控制器。用 Deployments 控制 ReplicaSet ,用 ReplicaSet 控制 Pod 的副本数量。
ReplicaSet 的作用是维护集群中 Pod 数量的稳定。
工作原理
ReplicaSet 是通过一组字段来定义的,包括一个用来可识别可获得的 Pod 集合的运算符,一个用来标明应该维护的副本个数,一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板。每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值,进而实现其存在价值。当 ReplicaSet 需要创建新 Pod 时,会使用所提供的 Pod 模板。
ReplicaSet 通过 Pod 上的 metadata.ownerReferences 字段连接到附属 Pod ,该字段给出当前对象的属主资源。ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet 的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态,并据此计划其操作行为。
如何使用
虽然 ReplicaSet 可以独立使用,但它主要被 Deployments 用作协调 Pod 创建、删除和更新的机制。当创建 Deployments 时,会自动创建 ReplicaSet 。
创建一个 ReplicaSet
vim replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: nginx
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: nginx
image: nginx
kubectl create -f replicaset.yaml
可以看到
replicaset.apps/frontend created
查看
kubectl get all
NAME READY STATUS RESTARTS AGE
pod/frontend-4t9sl 1/1 Running 0 28s
pod/frontend-c85k7 1/1 Running 0 28s
pod/frontend-j4vd9 1/1 Running 0 28s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontend 3 3 3 28s
可以看到,创建了一个 replicaset 和 三个 Pod 。这时如果删除一个 Pod ,那么 replicaset 会维持三个 Pod ,所以会创建一个新的 Pod 。
Deployment
一个 Deployment 控制器为 Pods 和 ReplicaSet 提供声明式的更新能力。
Deployment 的使用场景
- 创建 Deployment 以将 ReplicaSet 上线。ReplicaSet 在后台创建 Pods 。检查 ReplicaSet 的上线状态,查看是否成功。
- 通过更新 Deployment 的 PodTemplateSpec ,声明 Pod 的新状态。新的 RepicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet 。每个新的 ReplicaSet 都会更新 Deployment 的修订版本。
- 如果 Deployment 的当前状态不稳定,回滚到较早的 Deployment 版本。每次回滚都会更新 Deployment 的修订版本。
- 扩大 Deployment 规模以承担更多负载。
- 暂停 Deployment 以应用对 PodTemplateSpec 所做的多项修改,然后恢复其执行以启动新的上线版本。
- 使用 Deployment 状态来判断上线过程是否出现停滞。
- 清理不再需要的 ReplicaSet 。
创建 Deployment
vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
# deployment 的名称
name: nginx-deployment
labels:
app: nginx
spec:
# deployment 由 replicas 指定创建 pod 的数量
replicas: 3
# deployment 匹配要管理的 Pods
selector:
# 根据标签匹配
matchLabels:
app: nginx
# pod 模板
template:
metadata:
# 标签,deployment 会根据这个标签来匹配要管理的 pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
创建
kubectl apply -f deployment.yaml
kubectl get all
可以看到,创建了三个 pod
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-66b6c48dd5-h85lf 1/1 Running 0 3m3s
pod/nginx-deployment-66b6c48dd5-rtm28 1/1 Running 0 3m3s
pod/nginx-deployment-66b6c48dd5-xxjcm 1/1 Running 0 3m3s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 3m3s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-66b6c48dd5 3 3 3 3m3s
调整 pod 的数量,只需要修改 replicas
字段的值。
有状态的应用 StatefulSets
StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理 Deployment 和扩展一组 Pod ,并且能为这些 Pod 提供序号和唯一性保证。和 Deployment 相同的是,StatefulSet 管理了基于相同容器定义的一组 Pod。不同的是,StatefulSet 为每个 Pod 维护了一个固定的 ID 。这些 Pod 是基于相同的声明来创建的,但是不能相互替换,无论怎么调度,每个 Pod 都有一个永久不变的 ID 。
StatefulSet 和其他控制器使用相同的工作模式。
vim statefulset.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: datadir1
labels:
type: local
spec:
storageClassName: my-storage-class
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/data1"
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: datadir2
labels:
type: local
spec:
storageClassName: my-storage-class
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/data2"
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: datadir3
labels:
type: local
spec:
storageClassName: my-storage-class
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/data3"
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: my-storage-class
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
创建
kubectl apply -f statefulset.yaml
kubectl get all
可以看到
NAME READY STATUS RESTARTS AGE
pod/web-0 1/1 Running 0 2m2s
pod/web-1 1/1 Running 0 119s
pod/web-2 1/1 Running 0 116s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx ClusterIP None <none> 80/TCP 2m2s
NAME READY AGE
statefulset.apps/web 3/3 2m2s
查看 pod
kubectl get pod web-0 -o widr
可以看到
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 8m26s 10.244.1.72 node1 <none> <none>
DaemonSet 后台任务
DaemonSet 确保所有(或者某些)节点上运行一个 Pod 副本。当有节点加入集群时,也会为它们新增一个 Pod 。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它所创建的所有 Pod 。
DaemonSet 的典型用法
- 在集群的每个节点上运行 Daemon ,比如 glusterd 或 ceph
- 在每个节点上运行日志收集 Daemon ,比如 flunentd 或 logstash
- 在每个节点上运行监控 Daemon ,比如 Prometheus Node Exporter 或 collected
编写 DaemonSet Spec
创建 DaemonSet
vim daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: registry.cn-beijing.aliyuncs.com/qingfeng666/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
创建
kubectl apply -f daemonset.yaml
可以看到
daemonset.apps/fluentd-elasticsearch created
查看运行的 daemonset
kubectl -n kube-system get po -o wide | grep fluentd
可以看到
fluentd-elasticsearch-7b8jx 1/1 Running 0 5m32s 10.244.0.22 master <none> <none>
fluentd-elasticsearch-wfm9v 1/1 Running 0 5m32s 10.244.2.12 node2 <none> <none>
fluentd-elasticsearch-x2mn9 1/1 Running 0 5m32s 10.244.1.78 node1 <none> <none>
Daemon Pods 是如何调度的
DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。通常,运行 Pod 的节点有 kubernetes 调度器选择。不过 DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:
- Pod 行为的不一致性。正常 Pod 在被创建后等待调度时处于 Pending 状态,DaemonSet Pod 创建后不会处于 pending 状态下
- Pod 抢占由默认调度器处理。启用抢占后,DaemonSet 控制器将不再不考虑 Pod 优先级和抢占的情况下制定调度决策
ScheduleDaemonSetPods 允许使用默认调度器而不是 DaemonSet 控制器来调度 DaemonSets ,方法是将 NodeAffinity 条件不是 .spec.nodeName 条件添加到 DaemonSet Pods 。默认调度器接下来将 Pod 绑定到模板主机。如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换。DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作,并且不会更改 DaemonSet 的 .spec.template 。
与 Daemon Pods 通信
与 DaemonSet 中的 Pod 通信的几种模式
- NodeIP 和已知端口:DaemonSet 中的 Pod 可以使用 hostPort ,从而可以通过节点 IP 访问到 Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到响应的端口。
- DNS:创建具有相同 Pod 选择器的无头服务通过使用 endpoints 资源或从 DNS 中检索到多个 A 记录来发现 DaemonSet。
- Service:创建具有相同 Pod 选择器的服务,并使用该服务随机访问到某个节点上的守护进程(没有办法访问到特定节点)
更新 DaemonSet
如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod ,同时删除不匹配的节点上的 Pod。
和 Deployments 的区别
DaemonSet 和 Deployment 非常类似,它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(比如 web 服务器、存储服务器)。建议为无状态的服务使用 Deployment ,比如前段服务。对于这些服务而言,对副本的数量进行扩容、缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要的多。当需要 Pod 副本总是运行在全部或特定主机上,并需要它们先于其他 Pod 启动时,应该使用 DaemonSet。
Job
Job 会创建一个或多个 Pod ,并确保指定数量的 Pod 成功终止。随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。当数量达到指定的成功个数阈值时,任务(即 Job)结束。删除 Job 的操作会清除所创建的所有 Pod。
Job 可以用来执行一次或多次有限次数的,或者定期每天执行的业务。
定义一个 job ,计算 π 到小数点后100位,并将结果打印出来
vim job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: registry.cn-beijing.aliyuncs.com/google_registry/perl:5.26
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(100)"]
restartPolicy: Never
backoffLimit: 4
kubectl apply -f job.yaml
可以看到
job.batch/pi created
kubectl get job
可以看到
NAME COMPLETIONS DURATION AGE
pi 0/1 46s 46s
kubectl describe pod pi-h9ww5
可以看到
Name: pi-h9ww5
Namespace: default
Priority: 0
Node: node2/192.168.190.133
Start Time: Thu, 25 Mar 2021 15:15:26 +0800
Labels: controller-uid=16ba5e81-8b9e-4515-904b-013535baa2a6
job-name=pi
Annotations: <none>
Status: Succeeded
IP: 10.244.2.14
IPs:
IP: 10.244.2.14
Controlled By: Job/pi
Containers:
pi:
Container ID: docker://573f0764e80f87bd457a06ff81c65d3982b6478a452d6ad3535718601bc9eb1a
Image: registry.cn-beijing.aliyuncs.com/google_registry/perl:5.26
Image ID: docker-pullable://registry.cn-beijing.aliyuncs.com/google_registry/perl@sha256:97821e418498785bb995b2c7609841ac57df0cc0156a761875db60403be0e1d8
Port: <none>
Host Port: <none>
Command:
perl
-Mbignum=bpi
-wle
print bpi(100)
State: Terminated
Reason: Completed
Exit Code: 0
Started: Thu, 25 Mar 2021 15:18:38 +0800
Finished: Thu, 25 Mar 2021 15:18:38 +0800
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-c9dkq (ro)
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
default-token-c9dkq:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-c9dkq
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m36s default-scheduler Successfully assigned default/pi-h9ww5 to node2
Normal Pulling 3m36s kubelet Pulling image "registry.cn-beijing.aliyuncs.com/google_registry/perl:5.26"
Normal Pulled 26s kubelet Successfully pulled image "registry.cn-beijing.aliyuncs.com/google_registry/perl:5.26" in 3m10.737708126s
Normal Created 25s kubelet Created container pi
Normal Started 25s kubelet Started container pi
从所有 pod 里面选择 jobname 是 pi 的 pod
pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
可以看到
pi-h9ww5
kubectl logs pi-h9ww5
可以看到
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068