8.1 DaemonSet 简介
DaemonSet 资源对象可以保证 Kubernetes 集群中所有或部分 Node 都只能够运行一份 Pod 副本实例。当有新的 Node 加入集群时,kubernetes 将会在新 Node 上创建新的 Pod 副本;如果有 Node 被集群移除,节点上对应的 Pod 也会被清除。
DaemonSet 会管理所有 Node 上的 Pod 副本,保持集群中的 Pod 和 Node 为一一对应的关系,同时也负责对它们进行更新和删除。

DaemonSet 类似于计算机中的守护进程,运行集群中必备的基础服务,常见的用法如下:
- 集群存储:比如 GlusterFS 存储、Ceph 存储等
- 日志收集:比如 Fluentd、Logstash 等
- 性能监控:用于采集 Node 的运行数据,比如 Prometheus Node Exporter、collectd、New Relic agent、Ganglia gmond 等
- 系统程序:比如 kube-proxy、kube-dns、glusterd、ceph 等
运行示例程序
在每个 Node 节点都运行一个 fluentd 容器,并且每个容器都需要挂载物理机的两个目录 /var/log 和 /var/lib/docker/containers
新建 fluentd-ds.yaml、fluentd-rbac.yaml 文件,并写入如下内容:
# fluentd-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-cloud-logging
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: fluentd-cloud-logging
template:
metadata:
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
serviceAccount: fluentd-account
imagePullSecrets:
- name: mydockerhubsecret
containers:
- name: fluentd-cloud-logging
image: stakater/fluentd-elasticsearch
resources:
limits:
cpu: 100m
memory: 200Mi
env:
- name: FLUENTD_ARGS
value: -q
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: false
- name: containers
mountPath: /var/lib/docker/containers
readOnly: false
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/lib/docker/containers
# fluentd-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd-account
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: fluentd-account
roleRef:
kind: ClusterRole
name: view
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd-account
namespace: kube-system
执行创建:
$ kubectl create -f fluentd-rbac.yaml
serviceaccount/fluentd-account created
$ kubectl create -f fluentd-ds.yaml
daemonset.apps/fluentd-cloud-logging created
查看创建好的 DaemonSet 和 Pod:
$ kubectl get daemonset -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluentd-cloud-logging 2 2 2 2 2 <none> 2m58s
分别在 kubesphere02 和 kubesphere03 两个节点上创建了一个 fluentd-cloud Pod
$ kubectl get pods -n kube-system -o wide|grep fluentd-cloud-logging
fluentd-cloud-logging-bk69k 1/1 Running 0 3m30s 10.20.138.62 kubesphere02 <none> <none>
fluentd-cloud-logging-jm67f 1/1 Running 0 3m30s 10.20.177.95 kubesphere03 <none> <none>
执行删除:
$ kubectl delete daemonsets.app fluentd-cloud-logging -n kube-system
daemonset.apps "fluentd-cloud-logging" deleted
# 也可以通过 YAML 文件删除
$ kubectl delete -f fluentd-ds.yaml
daemonset.apps/fluentd-cloud-logging deleted
8.2 滚动更新
DaemonSet 可以使用字段 spec.updateStrategy.type 设置更新策略,目前支持两种策略:
- RolingUpdate:更新 DaemonSet 模板后,自动删除旧的 Pod 并创建新的 Pod
- OnDelete:默认策略,更新 DaemonSet 模板后,只有手动删除了旧的 Pod 才会创建新的 Pod
在使用 RolingUpdate 策略时,还可以设置字段:
- spec.updateStrategy.rollingUpdate.maxUnavailable,默认值为 1
- spec.minReadySeconds,默认值为 0
具体的设置信息可以如下所示:
---
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
minReadySeconds: 30
8.3 StatefulSet 简介
有状态应用在 Kubernetes 里对应的对象就是 StatefulSet。跟 Deployment 一样,StatefulSet 也是一种控制器,区别是一个控制无状态应用,一个控制有状态应用。当一个有状态的 Pod 挂掉之后(或者是所在节点出现故障),这个 Pod 实例必须在其它的节点上重建,但是新的实例必须和被替换的实例拥有相同的名称、网络标识和状态,StatefulSet 可以保证 Pod 在重新调度以后保留它们之前的标识和状态,这样就可以方便的扩缩容。
应用一旦牵扯到状态,控制起来就会比较麻烦,因此 StatefulSet 要比 Deployment 复杂许多。具体来说,StatefulSet 适合具有以下需求的场景:
- 稳定的持久化存储 Pod 在重新调度之后还能继续访问之前的存储数据
- 稳定的网络标志 Pod 在重新调度后其名字和主机名保持不变
- 有序部署:构成应用的多个 Pod 启动顺序是固定的,适合 Pod 之间有依赖关系的情况
- 有序删除:删除应用时 Pod 销毁顺序跟启动顺序正好相反
StatefulSet 中每个 Pod 具有固定的名字,Pod 的 DNS 域名格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local。跟普通域名一样,子域在前,主域在后,只不过节数比较多而已。各部分含义如下:
- statefulSetName:StatefulSet 名字
- 0..N-1 Pod 的序号:从 0 到 N-1
- serviceName:Headless Service(没有 ClusterIP 的 Service) 名字,StatefulSet 无法使用普通的 Service(有一个 ClusterIP 作为服务入口) 来提供应用服务(包括负载均衡)。这是因为 StatefulSet 里的各个 Pod 不是对等的,只能由用户自己来实现服务内部的网络路由
- namespace:服务所在的命名空间,Headless Service 和 StatefulSet 必须在相同的命名空间下
- svc.cluster.local:集群域名
StatefulSet 的设置由以下 3 部分组成:
- 用于定义网络标志的 Headless Service
- 用于创建 PV(持久存储卷)的 volumeClaimTemplates
- 定义具体应用的 StatefulSet
Headless Service(无头服务)
Headless Service 是指不为 Service 设置 ClusterIP(入口 IP 地址),只通过标签选择器将后端的 Pod 列表返回给调用的客户端,客户端自行决定如何处理这个 Pod 列表。
在前面讲解 Deployment 时,我们知道每个创建的 Pod 的名称都是没有顺序的,名称最后有一截 Hash 值,因此这样创建的 Pod 是无序的。但是 StatefulSet 中要求 Pod 名称必须有序,每个 Pod 名称具有唯一性,并且当 Pod 重建后名称依然要保持一致,Pod 的名称就是唯一性的标志,必须持久有效,因此会使用 Headless Service 服务。
volumeClaimTemplates(卷申请模板)
有状态自然是需要持久化的,所以 StatefulSet 引入了 PV(持久存储卷) 和 PVC(持久存储卷声明) 对象来持久存储服务产生的状态,这样即便服务可能被 kill 掉或是重启,但是由于数据保存在 PV 中,所以这些状态也不会丢失。它们之间的关系如下图所示:

在前面讲解 Deployment 时,直接在 Pod template 中定义存储卷,所有副本集使用同一个存储卷,数据都是相同的。但是 StatefulSet 中要求有状态存储,也就意味着数据不一样,每个节点必须有专有存储卷,不能使用相同存储卷,因此需要使用 volumeClaimTemplates,为每个 Pod 生成不同的 PVC,并绑定 PV,这样就实现了 Pod 分别有专属的存储卷。
8.4 运行 nginx 实例
整个的流程为:
- 创建本地持久存储卷
- 将本地持久存储卷绑定到 Node 上
- 创建 StatefulSet
创建 local PV,新建 local-storage.yaml 文件,并向其中写入如下代码:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
# provisioner 的值为 kubernetes.io/no-provisioner,因为 local pv 不支持 Dynamic Provisioning,不能在创建 pvc 的时候自动创建 pv
provisioner: kubernetes.io/no-provisioner
# 使用延迟卷绑定功能,将 volume binding 延迟至 pod scheduling 阶段执行
volumeBindingMode: WaitForFirstConsumer
执行创建:
$ kubectl create -f local-storage.yaml
storageclass.storage.k8s.io/local-storage created
然后在每个 Node 上创建一个 PV,新建 local-pv.yaml 文件,并向其中写入如下内容:
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
# local.path 指明对应的磁盘路径
local:
path: /tmp
# spec.nodeAffinity 指定对应的 node
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kubesphere02
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv2
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /tmp
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kubesphere03
上面的配置会在 kubesphere02 和 kubesphere03 两个 Node 上分别创建一个 PV。其中,存储类 spec.storageClassName 用的是前面启用的 local-storage, spec.capacity.storage 指定了容量为 1G,挂载路径 spec.local.path 为 /tmp(该路径需要在 Node 上存在)。
执行创建:
$ kubectl create -f local-pv.yaml
persistentvolume/local-pv1 created
persistentvolume/local-pv2 created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv1 1Gi RWO Retain Available local-storage 17s
local-pv2 1Gi RWO Retain Available local-storage 17s
可以看到两个 PV 都已创建成功,其状态为可用(Available),底层资源提供者为 local-storage。
创建有状态集合,新建 web.yaml 文件,并向其中写入如下代码:
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:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: mirrorgooglecontainers/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 10Mi
其中 Service 的 clusterIP 指定为 None,表示它是一个 Headless Service,集群不用为它分配 IP。StatefulSet 包含 2 个 Pod 副本,它们的名字分别为 web-0 和 web-1,并且都挂载了一个 PV。不管 Pod 被重新调度到哪个节点,Pod 的名字和挂载的 PV 都将跟之前保持一致。
在执行创建之前可以监听 Pod 的创建过程,在一个新的终端执行如下命令:
$ kubectl get pods -w -l app=nginx
然后执行创建:
$ kubectl create -f web.yaml
service/nginx created
statefulset.apps/web created
这个时候可以看到整个的创建过程:
$ kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 1s
web-0 0/1 ContainerCreating 0 1s
web-0 0/1 ContainerCreating 0 2s
web-0 1/1 Running 0 14s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 11s
查看创建的 Headless Service 和 StatefulSet:
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 100s
$ kubectl get statefulset web
NAME READY AGE
web 2/2 108s
查看根据 volumeClaimTemplates 自动创建 PVC:
# PVC 名称的格式为:$(volumeClainTemplates.name)-(pod_name),这里 volumeClainTemplates.name 为 www,pod_name 为 web-[0-1]
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound local-pv2 1Gi RWO local-storage 2m43s
www-web-1 Bound local-pv1 1Gi RWO local-storage 2m29s
查看创建的 Pod,它们都是有序的:
# Pod 名称的格式为:$(StatefulSet 名称)-$(序号),这里为:web-0、web-1
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m43s
web-1 1/1 Running 0 3m29s
8.5 扩缩容 StatefulSet
StatefulSet 和 Deployment、DaemonSet 一样,也可以手动进行扩缩容。主要通过修改配置中的 replicas 字段实现。
扩容 StatefulSet:
$ kubectl scale sts web --replicas=5
statefulset.apps/web scaled
缩容 StatefulSet:
$ kubectl patch sts web -p '{"spec":{"replicas":1}}'
statefulset.apps/web patched
8.6 更新 StatefulSet
StatefulSet 可以使用字段 spec.updateStrategy.type 设置更新策略,可以用于更新 StatefulSet 中 Pod 的 container images、resource requests、limits、labels、以及 annotations 等,目前支持两种更新策略:
- RolingUpdate:默认策略,更新 StatefulSet 模板后,自动删除旧的 Pod 并创建新的 Pod,并且更新顺序与序号索引相反
- OnDelete:更新 StatefulSet 模板后,只有手动删除了旧的 Pod 才会创建新的 Pod
比如使用默认策略更新容器的镜像,首先新开一个终端监控 Pod 的变化过程:
$ kubectl get pods -w -l app=nginx
更新 Pod 的镜像为 nginx-slim:0.7:
$ kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"mirrorgooglecontainers/nginx-slim:0.7"}]'
statefulset.apps/web patched
再来看 Pod 的变化过程:
$ kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 11m
# 先更新 web-1 Pod
web-1 1/1 Terminating 0 15m
web-1 0/1 Terminating 0 15m
web-1 0/1 Terminating 0 15m
web-1 0/1 Terminating 0 15m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 22s
# 然后更新 web-0 Pod
web-0 1/1 Terminating 0 15m
web-0 0/1 Terminating 0 15m
web-0 0/1 Terminating 0 15m
web-0 0/1 Terminating 0 15m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 20s
来查看新创建的 Pod 的容器镜像,可以发现都已经更新为新镜像了:
$ kubectl get pod -l app=nginx -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
web-0 mirrorgooglecontainers/nginx-slim:0.7
web-1 mirrorgooglecontainers/nginx-slim:0.7
$ kubectl rollout status sts/web
partitioned roll out complete: 2 new pods have been updated...
8.7 删除 StatefulSet
删除 StatefulSet 分为两种方式:
- 非级联删除:删除 StatefulSet 时,由 StatefulSet 创建的 Pod 不会被删除
- 级联删除:删除 StatefulSet 时,StatefulSet 本身及其所创建的 Pod 都会被删除,并且删除顺序与序号索引相反
非级联删除
使用 --cascade=false 参数就表示非级联删除:
$ kubectl delete statefulset web --cascade=false
statefulset.apps "web" deleted
# 由 StatefulSet 创建的 Pod 并没有被删除,依然是运行状态
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m41s
web-1 1/1 Running 0 8m16s
尝试手动删除 web-0 Pod:
$ kubectl delete pod web-0
pod "web-0" deleted
# 由于删除了 web StatefulSet,所以 web-0 Pod 没有被继续创建了
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 9m9s
重新创建 web StatefulSet:
# 由于没有删除 nginx 服务,所以重新创建的时候有报错提醒,可以忽略
$ kubectl create -f web.yaml
statefulset.apps/web created
Error from server (AlreadyExists): error when creating "web.yaml": services "nginx" already exists
查看当前环境中运行的 Pod:
# 当前环境运行的都是新创建的 Pod,原来单独剩下的 web-1 Pod 被删除了
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 48s
web-1 1/1 Running 0 44s
需要注意的是:删除 StatefulSet 时,并不会删除与 Pod 相关联的 PersistentVolumes,当重新构建 StatefulSet 后,新创建的 Pod 会挂载原来的 PersistentVolumes。
级联删除
省略参数 --cascade 就表示级联删除:
$ kubectl delete statefulset web
statefulset.apps "web" deleted
最后还需要手动删除 nginx 服务:
$ kubectl delete service nginx
service "nginx" deleted
8.8 Pod 管理策略
对于某些分布式的系统,有的时候不强制要求 Pod 的启动必须有顺序,只是为了使用 StatefulSet 中的唯一性和身份标志的特性,可以通过 spec.podManagementPolicy 字段设置启动顺序。主要分为两种顺序:
- OrderedReady:默认选项,表示按顺序启动
- Parallel:可以并行启动/终止所有 Pod,在启动/终止一个 Pod 时不需要等待其它 Pod 必须先启动/终止
现在尝试并行启动 Pod,修改 web.yaml 文件,在其中增加 podManagementPolicy: "Parallel" 字段如下所示:
---
spec:
serviceName: "nginx"
podManagementPolicy: "Parallel"
replicas: 2
监控 Pod 的创建过程,并执行创建:
$ kubectl create -f web.yaml
service/nginx created
statefulset.apps/web created
# 可以看到同时启动了 web-0 和 web-1 Pod
$ kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 0/1 ContainerCreating 0 1s
web-1 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 2s
web-1 1/1 Running 0 2s
评论区