什么是 StatefulSet

StatefulSet 是用来管理有状态的应用,例如数据库。前面我们部署的应用,都是不需要存储数据,不需要记住状态的,可以随意扩充副本,每个副本都是一样的,可替代的。而像数据库、Redis 这类有状态的,则不能随意扩充副本。StatefulSet 会固定每个 Pod 的名字

部署 StatefulSet 类型的 redis

一主多从redis,动态扩缩容redis从库
redis-configmap.yaml

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: redis
  5. namespace: demo
  6. data:
  7. redis.conf: |-
  8. daemonize no
  9. protected-mode no
  10. bind 0.0.0.0
  11. port 6379
  12. timeout 300
  13. databases 16
  14. save 900 1
  15. save 300 10
  16. save 60 10000
  17. loglevel notice
  18. logfile /data/redis.log
  19. ################### RDB ####################
  20. dir /data
  21. stop-writes-on-bgsave-error yes
  22. rdbcompression yes
  23. dbfilename dump.rdb
  24. ################## REPL ###################
  25. slave-read-only yes
  26. repl-diskless-sync no
  27. repl-timeout 120
  28. ################## SECURITY ###################
  29. requirepass 123456
  30. masterauth 123456
  31. ################## AOF ####################
  32. appendonly no
  33. appendfilename "appendonly.aof"
  34. appendfsync no

redis-statefulset.yaml

apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: demo
spec:
  selector:
    app: redis
  ports:
  - name: redis
    port: 6379
    targetPort: 6379
    protocol: TCP
  clusterIP: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: demo
spec:
  replicas: 2                                   # 默认是一主一从
  selector:
    matchLabels:
      app: redis
  serviceName: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:5
        imagePullPolicy: IfNotPresent
        command:
        - /bin/sh
        - -c
        - |
          set -e
          hostname=$(echo `hostname` | grep -oE '\-([0-9]+)$')
          if [ "$hostname" != "-0" ]; then  #如果不是第一个节点,则向第一个节点同步
            redis-server  /etc/redis.conf slaveof redis-0.redis.demo.svc.cluster.local 6379    # namespace为demo,所以主机名.demo.svc.cluster.local
          else
            redis-server /etc/redis.conf
          fi
        volumeMounts:
#        - name: redis-data
#          mountPath: /data
        - name: redis-conf
          mountPath: /etc/redis.conf
          subPath: redis.conf
      volumes:
      - name: redis-conf
        configMap:
          name: redis
  volumeClaimTemplates:
  - metadata:
      name: redis-data
      namespace: demo
    spec:
      accessModes: ["ReadWriteMany"]
      storageClassName: nfs-client
      resources:
        requests:
          storage: 500Mi

部署

# 部署configmap
$ kubectl apply -f redis-configmap.yaml 

# 部署statefulset
$ kubectl apply -f redis-statefulset.yaml 

# 查看pod
$ kubectl get pods -n demo
NAME                                READY   STATUS    RESTARTS   AGE
redis-0                             1/1     Running   0          41s
redis-1                             1/1     Running   0          16s

# 查看redis-1复制情况
$ kubectl exec redis-1 -n demo -- redis-cli -a 123456 info replication
# Replication
role:slave
master_host:redis-0.redis.demo.svc.cluster.local
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:98
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4762f4b0a19cb5c31433078041b5e93bd12217d0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98

扩容

扩容从redis-0->redis-N

# 扩容
$ kubectl scale statefulset redis --replicas=4 -n demo

# 查看pod
$ kubectl get pods -n demo
NAME                                      READY   STATUS    RESTARTS   AGE
redis-0                             1/1     Running   0          2m13s
redis-1                             1/1     Running   0          2m11s
redis-2                             1/1     Running   0          27s
redis-3                             1/1     Running   0          25s

# 查看复制信息,可见同步正常
$ kubectl exec redis-3 -n demo -- redis-cli -a 123456 info replication
# Replication
role:slave
master_host:redis-0.redis.demo.svc.cluster.local
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:210
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4762f4b0a19cb5c31433078041b5e93bd12217d0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:141
repl_backlog_histlen:70

缩容

缩容从redis-N->redis-0

# 缩容
$ kubectl scale statefulset redis --replicas=1 -n demo

# 查看pod
$ kubectl get pods -n demo
NAME                                      READY   STATUS    RESTARTS   AGE
redis-0                                   1/1     Running   0          11m

StatefulSet 特性

  • Service 的 CLUSTER-IP 是空的,Pod 名字也是固定的。
  • Pod 创建和销毁是有序的,创建是顺序的,销毁是逆序的。
  • Pod 重建不会改变名字,除了IP,所以不要用IP直连 ```shell $ kubectl get svc -n demo NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE redis ClusterIP None 6379/TCP 4m37s

$ kubectl get pods -n demo NAME READY STATUS RESTARTS AGE redis-0 1/1 Running 0 41s redis-1 1/1 Running 0 16s


Endpoints 会多一个 hostname
```yaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2022-07-05T08:23:31Z"
  creationTimestamp: "2022-07-05T08:17:28Z"
  labels:
    service.kubernetes.io/headless: ""
  name: redis
  namespace: demo
  resourceVersion: "593328"
  uid: 880e7bd6-cdb2-4442-a837-20c55a2b1f89
subsets:
- addresses:
  - hostname: redis-1
    ip: 172.16.126.17
    nodeName: k8s.lechuang.com
    targetRef:
      kind: Pod
      name: redis-1
      namespace: demo
      resourceVersion: "593307"
      uid: a7888f6d-ce37-42b8-80db-646a0eaf3c4b
  - hostname: redis-2
    ip: 172.16.126.22
    nodeName: k8s.lechuang.com
    targetRef:
      kind: Pod
      name: redis-2
      namespace: demo
      resourceVersion: "593326"
      uid: 277aa568-6e59-4ed4-8bfa-3b011003d6ab
  - hostname: redis-0
    ip: 172.16.126.9
    nodeName: k8s.lechuang.com
    targetRef:
      kind: Pod
      name: redis-0
      namespace: demo
      resourceVersion: "592620"
      uid: 8c566a38-8029-418d-abce-2180b44e954b
  ports:
  - name: redis
    port: 6379
    protocol: TCP

访问时,如果直接使用 Service 名字连接,会随机转发请求要连接指定 Pod,可以通过pod-name.service-name连接到指定pod。

# 临时启动一个redis-client容器
$ kubectl run -n demo redis-client --restart='Never' --image redis:5 --command -- bash

# pod-name.service-name连接到指定pod
$ kubectl exec --tty -i redis-client -n demo -- redis-cli -h redis-1.redis -a 123456 info replication
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Replication
role:slave
master_host:redis-0.redis.demo.svc.cluster.local
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:1092
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4762f4b0a19cb5c31433078041b5e93bd12217d0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1092
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:365
repl_backlog_histlen:728

问题

pod 重建后,数据库持久化的内容丢失了,接下来解决这个问题。