kubernetes/NEW/kubernetes工作负载资源StatefulSet.md
2024-12-22 22:26:50 +08:00

20 KiB
Raw Blame History

kubernetes工作负载资源StatefulSet

著作:行癫 <盗版必究>


StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符;和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但不同的是 StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。

1.特点

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

稳定的、唯一的网络标识符

稳定的、持久的存储

有序的、优雅的部署和缩放

有序的、自动的滚动更新

稳定的意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用 由一组无状态的副本控制器提供的工作负载来部署应用程序比如Deployment或者ReplicaSet 可能更适用于你的无状态应用部署需要。

2.限制

给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供

删除或者收缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全

StatefulSet 当前需要无头服务来负责 Pod 的网络标识。你需要负责创建此服务

当删除 StatefulSets 时StatefulSet 不提供任何终止 Pod 的保证

为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止,可以在删除之前将 StatefulSet 缩放为 0

注意:

无头服务Headless Services

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

使用无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起

无头 Service 并不会分配 Cluster IPkube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。

3.创建StatefulSet

[root@master xingdian]# cat Statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  type: NodePort
  ports:
  - port: 80
    name: web
    targetPort: 80
    nodePort: 30010
  selector:
    app: nginx
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: xingdian
provisioner: example.com/external-nfs
parameters:
  server: 10.0.0.230
  path: /kubernetes-1
  readOnly: "false"
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: xingdian-1
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: xingdian
  nfs:
    path: /kubernetes-1
    server: 10.0.0.230
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: xingdian-2
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: xingdian
  nfs:
    path: /kubernetes-1
    server: 10.0.0.230
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.20.1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "xingdian"
      resources:
        requests:
          storage: 1Gi

名为 nginx 的 Headless Service 用来控制网络域名

名为 web 的 StatefulSet 有一个 Spec它表明将在独立的2个 Pod 副本中启动 nginx 容器

volumeClaimTemplates 将通过 PersistentVolumes 驱动提供的 PersistentVolumes 来提供稳定的存储

4.Pod 选择算符

你必须设置 StatefulSet 的 .spec.selector 字段,使之匹配其在 .spec.template.metadata.labels 中设置的标签。在 Kubernetes 1.8 版本之前, 被忽略 .spec.selector 字段会获得默认设置值。 在 1.8 和以后的版本中,未指定匹配的 Pod 选择器将在创建 StatefulSet 期间导致验证错误。

5.Pod 标识

StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。 该标识和 Pod 是绑定的,不管它被调度在哪个节点上。

6.有序索引

对于具有 N 个副本的 StatefulSetStatefulSet 中的每个 Pod 将被分配一个整数序号, 从 0 到 N-1该序号在 StatefulSet 上是唯一的。

7.稳定的网络 ID

StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。 上例将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。

StatefulSet 可以使用无头服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(命名空间).svc.cluster.local,其中 cluster.local 是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为: $(pod 名称).$(所属服务的 DNS 域名),其中所属服务由 StatefulSet 的 serviceName 域来设定。

$(pod name).$(service name).$(namespace).svc.cluster.local

取决于集群域内部 DNS 的配置,有可能无法查询一个刚刚启动的 Pod 的 DNS 命名。 当集群内其他客户端在 Pod 创建完成前发出 Pod 主机名查询时,就会发生这种情况。 负缓存 (在 DNS 中较为常见) 意味着之前失败的查询结果会被记录和重用至少若干秒钟, 即使 Pod 已经正常运行了也是如此。

如果需要在 Pod 被创建之后及时发现它们,有以下选项:

直接查询 Kubernetes API比如利用 watch 机制)而不是依赖于 DNS 查询

缩短 Kubernetes DNS 驱动的缓存时长(通常这意味着修改 CoreDNS 的 ConfigMap目前缓存时长为 30 秒)

集群域名 服务(名字空间/名字) StatefulSet名字空间/名字) StatefulSet 域名 Pod DNS Pod 主机名
cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1}
cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1}
kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{0..N-1}

8.稳定的存储

对于 StatefulSet 中定义的每个 VolumeClaimTemplate每个 Pod 接收到一个 PersistentVolumeClaim。在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class 提供的 1 Gib 的 PersistentVolume。 如果没有声明 StorageClass就会使用默认的 StorageClass。 当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。

9.部署和扩缩保证

对于包含 N 个 副本的 StatefulSet当部署 Pod 时,它们是依次创建的,顺序为 0..N-1

当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0

在将缩放操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态

在 Pod 终止之前,所有的继任者必须完全关闭

注意:

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 设置为 0;参数指定Kubernetes在强制终止pod之前等待pod正常终止的时间。 这种做法是不安全的,要强烈阻止。

在上面的 nginx 示例被创建后,会按照 web-0、web-1 的顺序部署2个 Pod。 在 web-0 进入Running 和 Ready状态前不会部署 web-1。要等到 web-0 部署完成并进入 Running 和 Ready 状态后,才会部署 web-1。

如果用户想将示例中的 StatefulSet 收缩为 replicas=1,首先被终止的是 web-1。 在 web-1没有被完全停止和删除前如果在此期间发生 web-0 运行失败, 那么就不会终止 web-1必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。

10.无状态服务

  • 是指该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的。

  • 多个实例可以共享相同的持久化数据。例如nginx 实例tomcat 实例等

  • 相关的 k8s 资源有ReplicaSet、ReplicationController、Deployment等由于是无状态服务所以这些控制器创建的 pod 序号都是随机值。并且在缩容的时候并不会明确缩容某一个pod而是随机的因为所有实例得到的返回值都是一样所以缩容任何一个pod都可以。

1、 pod 之间没有依赖,数据或者配置

2、 相同服务所有pod响应的结果都是一致的

3、 相同服务的不同pod 启动,停止,更新,删除没有顺序要求,

11.有状态服务

  • 宠物和牛的类比农场主的牛如果病了可以丢掉再重新买一头如果宠物主的宠物病死了是没法找到一头一模一样的宠物的。有状态服务可以说是需要数据存储功能的服务、或者指多线程类型的服务队列等。mysql数据库、kafka、zookeeper等

  • 每个实例都需要有自己独立的持久化存储,并且在 k8s 中是通过申明模板来进行定义。持久卷申明模板在创建 pod 之前创建,绑定到 pod 中,模板可以定义多个。

    • 1、pod 对数据或者配置有严格的依赖,
    • 2、相同服务的每个pod都是唯一的功能不同相应的结果不同。
    • 3、相同服务的不同pod启动停止更新删除有严格顺序要求一旦顺序错乱服务不可用同一个时间点只能对一个pod操作
    • 4、pod的启动的时候有明确的序号

二:案例

1.利用Statefulset构建Mysql主从集群

准备工作:

NFS共享存储为Kubernetes集群提供持久化存储

配置Kubernetes集群链接企业私有Harbor仓库

Kubernetes集群安装NFS插件Provisioner,利用NFS存储在Kubernetes集群创建StorageClass使用

注意:

部署NFS插件Provisioner方式

Kuboard方式

yaml文件方式见新文档

创建共享目录

略 共享目录为 /data/ha-mysql

创建命名空间
[root@xingdiancloud-native-master-a mysql]# kubectl create ns mysql
创建存储类

image-20241221163057079

image-20241221163210967

image-20241221163251629

创建ConfigMap

Kuboard方式

image-20241221231643049

image-20241221231712133

Yaml文件方式

[root@xingdiancloud-native-master-a mysql]# cat 01-configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: mysql
  name: mysql
  labels:
    app: mysql
data:
  master.cnf: |
    [mysqld]
    log-bin
    log_bin_trust_function_creators=1
    lower_case_table_names=1    
  slave.cnf: |
    [mysqld]
    super-read-only
    log_bin_trust_function_creators=1
        
[root@xingdiancloud-native-master-a mysql]# kubectl apply -f 01-configmap.yaml
创建Service

Kuboard方式

Yaml文件方式

[root@xingdiancloud-native-master-a mysql]# cat 02-service.yaml 
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  namespace: mysql
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  namespace: mysql
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

[root@xingdiancloud-native-master-a mysql]# kubectl apply -f 02-service.yaml
创建StatefulSet控制器
[root@xingdiancloud-native-master-a mysql]# cat 03-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:5.7.26
        env:
        - name: TZ
          value: Asia/Shanghai
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/slave.cnf /mnt/conf.d/
          fi          
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/google-samples/xtrabackup:1.0
        env:
        - name: TZ
          value: Asia/Shanghai
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on master (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          xtrabackup --prepare --target-dir=/var/lib/mysql          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:5.7.26
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        - name: TZ
          value: Asia/Shanghai 
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql
          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing slave.
            mv xtrabackup_slave_info change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from master. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm xtrabackup_binlog_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi
          # Check if we need to complete a clone by starting replication.
          if [[ -f change_master_to.sql.in ]]; then
            echo "Waiting for mysqld to be ready (accepting connections)"
            until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
            echo "Initializing replication from clone position"
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
            mysql -h 127.0.0.1 <<EOF
          $(<change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          fi
          # Start a server to send backups when requested by peers.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: mysql
      resources:
        requests:
          storage: 1Gi

[root@xingdiancloud-native-master-a mysql]# kubectl apply  -f  03-statefulset.yaml
验证集群状态

注意:因服务运行需要时间,需要耐心等待一段时间后在验证,验证的结果如下

[root@xingdiancloud-native-master-a mysql]# kubectl get pod -n  mysql
NAME      READY   STATUS    RESTARTS   AGE
mysql-0   2/2     Running   0          37m
mysql-1   2/2     Running   0          38m
mysql-2   2/2     Running   0          39m

2.利用Statefulset构建ES集群

见文档《基于Kubernetes构建ES集群》