6-1 Kubernetes 的 StatefulSet 和 DaemonSet

更新时间:2023年3月

Headless Services

参考:服务(Service)- Headless Services | Kubernetes

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service

对于 Headless Services ,kubernetes 不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selectors(选择器)

带有 selectors

对于带有 selectorsHeadless Services,,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象, 并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址),通过这个地址直接到达 Service 的后端 Pod 上

不带 selectors

对没有定义选择算符的无头服务,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会查找和配置以下之一:

  • 对于 type: ExternalName 的服务,查找和配置其 CNAME 记录
  • 对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS 的 A / AAAA 记录
    • 对于 IPv4 端点,DNS 系统创建 A 记录
    • 对于 IPv6 端点,DNS 系统创建 AAAA 记录

StatefulSet

参考-1:StatefulSet | Kubernetes

参考-2:有状态的应用 | Kubernetes

StatefulSet 是用来管理有状态应用的工作负载 API 对象

和 Deployment 不同的是, StatefulSet 为它创建的每个 Pod 维护了一个永久 ID(身份表示)。虽然这些 Pod 是基于相同的规则和约定来创建的, 但不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID

应用场景

StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:

  • 稳定的、唯一的网络标识符
  • 稳定的、持久的存储
  • 有序的、优雅的部署和扩缩
  • 有序的、自动的滚动更新

常见使用场景例如:MySQL 的主从搭建

特点

Pod 标识

StatefulSet 创建的 Pod 都具有唯一的标识,该标识包括顺序序号、稳定的网络 ID 和稳定的存储。 该标识和 Pod 是一直绑定的,与该 Pod 调度到哪个节点上无关

唯一的顺序序号

StatefulSet 中的每个 Pod 都会分配一个整数序号, 该序号在这个 StatefulSet 上是唯一的。假设有 N 个 Pod,默认情况下,这些 Pod 将被从 0 到 N-1 的序号

固定网络 ID

  • StatefulSet 中的每个 Pod 的主机名是固定的,格式为$( StatefulSet 名称 )-$( 序号 )
  • StatefulSet 可以使用 Headless Services 来管理 Pod 的网络域。这个服务的格式为: $( 服务名称 ).$( 命名空间 ).svc.$(CLUSTER_DNS_DOMAIN)
  • 每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为: $( pod 名称 ).$( 所属服务的 DNS 域名 )

示例

集群域名服务(名字空间/名字)StatefulSet(命名空间/名字)StatefulSet 域名Pod DNSPod 主机名
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0…N-1}.nginx.default.svc.cluster.localweb-{0…N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0…N-1}.nginx.foo.svc.cluster.localweb-{0…N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0…N-1}.nginx.foo.svc.kube.localweb-{0…N-1}

固定存储

当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,PersistentVolumeClaims不会被删除,因此与 PersistentVolumeClaims 相关联的 PersistentVolume 也不会被删除。要删除它必须通过手动方式来完成。

pod 标签

当 StatefulSet 创建 Pod 时, 它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签值设置为 Pod 名称。 这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。常用场景:MySQL 读写分离

部署及扩缩容保证
  • 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1
  • 当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0
  • 在将扩容缩容操作应用到 Pod 之前,在它前面的所有 Pod 必须是 Running 和 Ready 状态
  • 在一个 Pod 终止之前,在它之后的所有 Pod 必须完全关闭。

Pod 管理策略

StatefulSet 允许放宽其排序保证, 同时通过它的 .spec.podManagementPolicy 域保持其唯一性和身份保证

  • OrderedReady,(默认设置)
  • Parallel, StatefulSet 控制器并行的启动或终止所有的 Pod, 启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 Ready 或者完全停止状态。 这个选项只会影响扩缩操作的行为,更新则不会被影响

限制

  • 给定的存储必须是持久性存储(PV),可以提前制备,也可以通过 storage class 来制备
  • 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。kubernetes 1.23 之后,可以通过 .spec.persistentVolumeClaimRetentionPolicy 配置是否删除PVC(alpha 版本),参考:StatefulSet - PersistentVolumeClaim 保留 | Kubernetes
  • 当前版本的 kubernetes,StatefulSet 需要 Headless Services 来负责 Pod 的网络标识
  • 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且正常地终止,可以在删除之前将 StatefulSet 缩容到 0
  • 在默认的 Pod 管理策略OrderedReady)下,使用滚动更新时,如果进入无法运行或就绪的状态,需要手动干预,详见:StatefulSet - 强制回滚 | Kubernetes

示例- StatefulSet 部署 Zookeeper

参考 - 1:运行一个有状态的应用程序 | Kubernetes

参考 - 2:运行 ZooKeeper,一个分布式协调系统 | Kubernetes

部署

创建 Service

创建两个 Service,包含 3 个端口,分别为客户端访问端口 2181、服务之间数据同步端口 2888、服务选举端口 3888

$ vim svc-zookeeper-ensemble.yaml
# Headless Service
apiVersion: v1
kind: Service
metadata:
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
# 正常 Service,type 为默认的 ClusterIP
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zk

应用 Service 的声明文件

$ kubectl apply -f svc-zookeeper-ensemble.yaml

创建 PDB(可选)

创建 PodDisruptionBudget,用于限制最大不可用 Pod 数为 1,超过 1 时服务即宕机

$ vim zk-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  selector:
    matchLabels:
      app: zk
  maxUnavailable: 1

应用 Service 的声明文件

$ kubectl apply -f zk-pdb.yaml

创建 StorageClass

# 请先提前安装配置好 NFS 服务和 k8s 的 NFS 制备器
$ vim sc-zookeeper-ensemble.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner 
reclaimPolicy: Retain # PV 的删除策略
mountOptions: 
  - soft
  - nfsvers=4.2
  - noatime # 访问文件时不更新文件 inode 中的时间戳,高并发环境可提高性能
parameters:
  archiveOnDelete: "true"  # 删除 pod 时保留 pod 数据,默认为 false,不保留数据 

应用 StorageClass 的声明文件

$ kubectl apply -f ./sc-zookeeper-ensemble.yaml

创建 ConfigMap

$ vim zk-configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: zk-configmap
data:
  zoo_servers: "server.1=zk-0.zk-hs:2888:3888;2181 server.2=zk-1.zk-hs:2888:3888;2181 server.3=zk-2.zk-hs:2888:3888;2181"
  # zoo_data_dir: "/zk/data"
  # zoo_conf_dir: "/zk/conf"
  

应用 ConfigMap 的声明文件

$ kubectl apply -f ./zk-configmap.yaml

创建 StatefulSet

注:可以配置的变量参考:zookeeper - Official Image | Docker Hub

该 StatefulSet 不支持动态扩容缩容,如果有扩容缩容需求可以用 zkCli.shreconfig 命令,并配合 SERVER_JVMFLAGS 变量和 addauth 实现鉴权

$ vim zk-statefulset.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zk
spec:
  selector:
    matchLabels:
      app: zk
  serviceName: zk-hs
  replicas: 3
  template:
    metadata:
      labels:
        app: zk
    spec:
      terminationGracePeriodSeconds: 10
      containers:    
      - name: zookeeper
        image: "zookeeper:3.7.1-temurin"
        imagePullPolicy: IfNotPresent
        # 就绪探针和存活探针
        readinessProbe:
          httpGet:
            path: /commands/ruok
            port: 8080
          initialDelaySeconds: 10
          timeoutSeconds: 5
          periodSeconds: 3
        livenessProbe:
          httpGet:
            path: /commands/ruok
            port: 8080
          initialDelaySeconds: 30
          timeoutSeconds: 5
          periodSeconds: 3
        # 
        ports:
          - containerPort: 2181
            name: client
          - containerPort: 2888
            name: peer
          - containerPort: 3888
            name: leader-election  
        # 设置环境变量 ZOO_SERVERS,用于配置集群节点和端口信息
        env:
        - name: ZOO_SERVERS
          valueFrom:
            configMapKeyRef:
              name: zk-configmap
              key: zoo_servers 
        # 若配置了 ZOO_DATA_DIR 变量,则必须重写 docker-entrypoint.sh
        # 或使用 command 覆盖 ENTRYPOINT 指令
        #- name: ZOO_DATA_DIR
        #  valueFrom:
        #    configMapKeyRef:
        #      name: zk-configmap
        #      key: zoo_data_dir
        # 若配置了 ZOO_CONF_DIR 变量,则一些其他变量不会自动配置,需自行配置
        # 详见 docker-entrypoint.sh
        #- name: ZOO_CONF_DIR
        #  valueFrom:
        #    configMapKeyRef:
        #      name: zk-configmap
        #      key: zoo_conf_dir
        #  
        volumeMounts:
          - name: datadir
            mountPath: /data
            # mountPath: $(ZOO_DATA_DIR)
        # 使用 args 替换官方 Dockerfile 的 CMD 指令,添加一些 server.${id} 的配置操作
        # 也可以使用 command 替换官方 Dockerfile 的 ENTRYPOINT 指令
        args:
        - bash
        - "-c"
        - |
          # 基于 Pod 序号生成 Zookeeper 服务的 ID
          ## 根据主机名获取 Pod 序号
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          ## 添加偏移量,使 ID 符合 zookeeper 要求的 1~254 范围
          ZOO_MY_ID=$(( 1 + ${ordinal} ))
          ## 写入 ID
          echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"
          
          # 需要替换本机的 server.id 对应的 IP 地址为全部监听
          sed -i "s/server\.$ZOO_MY_ID\=[a-z0-9.-]*/server.$ZOO_MY_ID=0.0.0.0/" "$ZOO_CONF_DIR/zoo.cfg"
          
          # 最后加上官方 Dockerfile 原本的 CMD 启动参数
          zkServer.sh start-foreground
          
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: nfs-client
      resources:
        requests:
          storage: 0.5Gi
---

应用声明文件

$ kubectl apply -f ./zk-statefulset.yaml
检查

检查 Pod 运行情况

$ kubectl get pods -o wide
kubectl get pods -o wide
NAME                                      READY   STATUS    RESTARTS       AGE     IP               NODE              NOMINATED NODE   READINESS GATES
zk-0                                      1/1     Running   0              8m8s    172.20.195.44    192.168.111.187   <none>           <none>
zk-1                                      1/1     Running   0              7m54s   172.20.119.216   192.168.111.188   <none>           <none>
zk-2                                      1/1     Running   0              7m40s   172.20.195.45    192.168.111.187   <none>           <none>
......

检查角色情况

# pod zk-0
$ kubectl exec -it zk-0 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower


# pod zk-1
$ kubectl exec -it zk-1 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader

# pod zk-2
$ kubectl exec -it zk-2 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

检查集群运行情况

选择一台有 Zookeeper 环境的宿主机,通过 Pod IP 连接 pod

$ zkCli.sh -server 172.20.195.44
# 查询配置信息,初次查询会比较慢
[zk: 172.20.195.44(CONNECTING) 0] config
......
WatchedEvent state:SyncConnected type:None path:null
server.1=0.0.0.0:2888:3888:participant;0.0.0.0:2181
server.2=zk-1.zk-hs:2888:3888:participant;0.0.0.0:2181
server.3=zk-2.zk-hs:2888:3888:participant;0.0.0.0:2181
version=0

# 测试写入数据
[zk: 172.20.195.44(CONNECTED) 7] create /test-str "hello cluster"
Created /test-str


# 另一台 zk 上查询数据
$ echo "get /test-str" | zkCli.sh -server 172.20.119.216
......
WatchedEvent state:SyncConnected type:None path:null
hello cluster
......

DaemonSet

参考:DaemonSet | Kubernetes

参考:DaemonSet - 定义 | Kubernetes

DaemonSet 可以确保全部(或者选择一部分)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod

应用场景

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

选择节点

  • 如果指定了 .spec.template.spec.nodeSelector,DaemonSet 控制器将在能够与 Node 选择算符 匹配的节点上创建 Pod
  • 也可以指定 .spec.template.spec.affinity,之后 DaemonSet 控制器将在能够与 节点亲和性 匹配的节点上创建 Pod
  • 如果没有指定以上字段,则 DaemonSet 控制器将会在所有节点上创建 Pod

污点和容忍度

可以在 DaemonSet 的 Pod 模板中定义自己的容忍度并将其添加到 DaemonSet Pod。DaemonSet 控制器会自动将一组容忍度添加到 DaemonSet Pod

容忍度键名效果描述
node.kubernetes.io/not-readyNoExecuteDaemonSet Pod 可以被调度到不健康或还不准备接受 Pod 的节点上。在这些节点上运行的所有 DaemonSet Pod 将不会被驱逐。
node.kubernetes.io/unreachableNoExecuteDaemonSet Pod 可以被调度到从节点控制器不可达的节点上。在这些节点上运行的所有 DaemonSet Pod 将不会被驱逐。
node.kubernetes.io/disk-pressureNoScheduleDaemonSet Pod 可以被调度到具有磁盘压力问题的节点上。
node.kubernetes.io/memory-pressureNoScheduleDaemonSet Pod 可以被调度到具有内存压力问题的节点上。
node.kubernetes.io/pid-pressureNoScheduleDaemonSet Pod 可以被调度到具有进程压力问题的节点上。
node.kubernetes.io/unschedulableNoScheduleDaemonSet Pod 可以被调度到不可调度的节点上。
node.kubernetes.io/network-unavailableNoSchedule仅针对请求主机联网的 DaemonSet Pod 添加此容忍度,即 Pod 具有 spec.hostNetwork: true。这些 DaemonSet Pod 可以被调度到网络不可用的节点上。

示例-使用 DaemonSet 部署 node-exporter

参考-1:Monitoring Linux host metrics with the Node Exporter | Prometheus

参考-2:k8s 监控(四)监控宿主机 - 掘金 (juejin.cn)

要点

  • 使用 DaemonSet,确保每个 kubernetes 节点都部署了 node-exporter
  • 要将宿主机的 /proc/sys 等目录挂载至容器
  • 使用宿主机网络、进程号等资源
部署 node-exporter
$ vim daemonset-prom-node-exporter.yaml
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app.kubernetes.io/component: exporter
    app.kubernetes.io/name: node-exporter
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/component: exporter
      app.kubernetes.io/name: node-exporter
  template:
    metadata:
      labels:
        app.kubernetes.io/component: exporter
        app.kubernetes.io/name: node-exporter
    spec:
      hostNetwork: true
      hostPID: true
      hostIPC: true
      containers:
      - name: prometheus-node-exporter
        image: prom/node-exporter:v1.4.1
        imagePullPolicy: IfNotPresent
        
        ports:
        - name: metrics
          containerPort: 9100
          hostPort: 9100
          protocol: TCP
            
        resources:
          limits:
            cpu: 250m
            memory: 180Mi
          requests:
            cpu: 100m
            memory: 50Mi  
            
        volumeMounts:
        - mountPath: /host/sys
          mountPropagation: HostToContainer
          name: sys
          readOnly: true
        - mountPath: /host/proc
          mountPropagation: HostToContainer
          name: proc
          readOnly: true
        - mountPath: /host/root
          mountPropagation: HostToContainer
          name: root
          readOnly: true
        args:
        - "--no-collector.wifi"
        - "--no-collector.hwmon"
        - "--collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/pods/.+)($|/)"
        - "--collector.netclass.ignored-devices=^(veth.*)$"
        - "--path.sysfs=/host/sys"
        - "--path.procfs=/host/proc"
        - "--path.rootfs=/host/root"
      
      volumes:
      - hostPath:
          path: /sys
        name: sys
      - hostPath:
          path: /proc
        name: proc
      - hostPath:
          path: /
        name: root
---

应用声明

$ kubectl apply -f daemonset-prom-node-exporter.yaml
Prometheus 配置
# prometheus 新增 job    
    - job_name: 'kubernetes-node'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

注:10250 是 kubelet 的 API 端口

检查

检查是否正常访问 metrics

$ curl http://192.168.111.185:9100/metrics -I
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4; charset=utf-8
Date: Mon, 20 Mar 2023 19:03:55 GMT

登录 prometheus 的 web 页面查看是否正常采集数据

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值