K8S发布系统-阿里有状态服务部署

和之前同一文档,这节是讲在阿里的K8S上部署StatefulSet有状态服务方法,在自建群集上部署的话方法类似;更新的文档上是采用自建+NFS存储类方式。

存储类StorageClass

新测试环境与开发环境(原测试)使用同一个NAS文件系统,以节省资源;原环境配置:

其在nfs格式的NAS根目录下创建了nfsroot目录并在k8s中进行了类配置使用。

点开NAS,可以在“挂载使用”中看到其挂载示例命令:

现在在一台ECS上将其挂载上,并创建另一个目录,以作为新的测试环境挂载点使用:

yum install -y nfs-utils
mount -t nfs -o vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport xxxx.cn-hangzhou.nas.aliyuncs.com:/ /mnt/nas
cd /mnt/nas
mkdir nasroot
touch nasroot/测试环境k8s

创建连接到NAS的存储类StorageClass:

新版的存储插件为CSI,之前老版本的为Flexvolume,相关区别见官网。阿里NAS存储卷在K8S上的挂载使用也可参见文档

mkdir yamls
cd yamls
vim alicloud-nas.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
# https://help.aliyun.com/document_detail/144398.html
metadata:
  name: alicloud-nas
mountOptions:
  - 'nolock,tcp,noresvport'
  - vers=3
parameters:
  volumeAs: subpath
  server: 'xxx.cn-hangzhou.nas.aliyuncs.com:/nasroot/'
provisioner: nasplugin.csi.alibabacloud.com
reclaimPolicy: Retain
volumeBindingMode: Immediate
kubectl create -f alicloud-nas.yml
kubectl get storageclasses.storage.k8s.io

可以查看添加后生成的YAML内容:

存储类是表明k8s群集中支持的存储类型,一般不直接使用;通常会创建“存储声明”PVC(PersistentVolumeClaim),以及“存储卷”PV(PersistentVolume)来使用。

目前的回收策略有:

  • Retain — 手动回收
  • Recycle — 基本擦除 (rm -rf /thevolume/*) 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态供应。
  • Delete — 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除

持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员事先供应,或者 使用存储类(Storage Class)来动态供应。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样,也是使用 卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。

Redis部署

PVC会在部署文件中以volumeClaimTemplates方式进行声明,PV也会自动创建,故不需要单独创建。

下面会把Redis放在public的命名空间中运行,故要先行创建。

按之前方式部署,这里Redis采用主从方式部署。

配置项ConfigMap

由于Pod不会存储数据,而PV也是在Pod启动进挂载使用;程序启动的配置文件一般也都放在ConfigMap之中,创建ConfigMap的YAML:

vim redis-config.yaml
apiVersion: v1
kind: ConfigMap
data:
  master.conf: |-
    dir /data
    appendonly yes
    rename-command FLUSHDB ""
    rename-command FLUSHALL ""
  redis.conf: |-
    dir /data
    appendonly yes
    # Disable RDB persistence, AOF persistence already enabled.
    save ""
  replica.conf: |-
    dir /data
    slave-read-only yes
    rename-command FLUSHDB ""
    rename-command FLUSHALL ""
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default
  namespace: public
kubectl create -f redis-config.yml
kubectl -n public get configmaps

ConfigMap中不光可以用来储存应用的配置文件,也可以用来储存脚本文件,供容器中使用。

vim redis-scripts.yml
apiVersion: v1
kind: ConfigMap
data:
  ping_liveness_local.sh: |-
    response=$(
      timeout -s 9 $1 \
      redis-cli \
        -a $REDIS_PASSWORD --no-auth-warning \
        -h localhost \
        -p $REDIS_PORT \
        ping
    )

    if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading
    the dataset in memory" ]; then
      echo "$response"
      exit 1
    fi
  ping_liveness_local_and_master.sh: |-
    script_dir="$(dirname "$0")"
    exit_status=0
    "$script_dir/ping_liveness_local.sh" $1 || exit_status=$?
    "$script_dir/ping_liveness_master.sh" $1 || exit_status=$?
    exit $exit_status
  ping_liveness_master.sh: >-
    response=$(
      timeout -s 9 $1 \
      redis-cli \
        -a $REDIS_MASTER_PASSWORD --no-auth-warning \
        -h $REDIS_MASTER_HOST \
        -p $REDIS_MASTER_PORT_NUMBER \
        ping
    )

    if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading
    the dataset in memory" ]; then
      echo "$response"
      exit 1
    fi
  ping_readiness_local.sh: |-
    response=$(
      timeout -s 9 $1 \
      redis-cli \
        -a $REDIS_PASSWORD --no-auth-warning \
        -h localhost \
        -p $REDIS_PORT \
        ping
    )
    if [ "$response" != "PONG" ]; then
      echo "$response"
      exit 1
    fi
  ping_readiness_local_and_master.sh: |-
    script_dir="$(dirname "$0")"
    exit_status=0
    "$script_dir/ping_readiness_local.sh" $1 || exit_status=$?
    "$script_dir/ping_readiness_master.sh" $1 || exit_status=$?
    exit $exit_status
  ping_readiness_master.sh: |-
    response=$(
      timeout -s 9 $1 \
      redis-cli \
        -a $REDIS_MASTER_PASSWORD --no-auth-warning \
        -h $REDIS_MASTER_HOST \
        -p $REDIS_MASTER_PORT_NUMBER \
        ping
    )
    if [ "$response" != "PONG" ]; then
      echo "$response"
      exit 1
    fi
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-health
  namespace: public
kubectl create -f redis-scripts.yml
kubectl -n public get configmaps

保密字典Secert

创建yaml文件并应用:

vim redis-secret.yml
apiVersion: v1
kind: Secret
data:
  redis-password: WnMyMDE5MTAwMQo=
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default
  namespace: public
type: Opaque
kubectl create -f redis-secret.yml
kubectl -n public get secrets

密码值需使用base64编码格式;上面的密码解码即为Zs20191001。

Secret的类型:

网络入口Headless服务

Headless service是StatefulSet实现稳定网络标识的基础,需要提前创建。

vim redis-default-headless.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-headless
  namespace: public
spec:
  clusterIP: None
  ports:
    - name: redis
      port: 6379
      protocol: TCP
      targetPort: redis
  selector:
    app: redis
    release: redis-default
kubectl create -f redis-default-headless.yml
kubectl -n public get svc

“普通” 服务(除了无头服务)会以 my-svc.my-namespace.svc.cluster-domain.example 这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于服务的 IP 协议族。 该名称会解析成对应服务的集群 IP。

“无头(Headless)” 服务(没有集群 IP)也会以 my-svc.my-namespace.svc.cluster-domain.example 这种名字的形式被指派一个 DNS A 或 AAAA 记录, 具体取决于服务的 IP 协议族。 与普通服务不同,这一记录会被解析成对应服务所选择的 Pod 集合的 IP。 客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。

主库

创建生成Redis主库的YAML文件:

vim redis-default-master.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-master
  namespace: public
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: redis
      release: redis-default
      role: master
  serviceName: redis-default-headless
  template:
    metadata:
      labels:
        app: redis
        chart: redis-9.5.5
        release: redis-default
        role: master
    spec:
      containers:
        - command:
            - /bin/bash
            - '-c'
            - |
              if [[ ! -f /opt/bitnami/redis/etc/master.conf ]];then
                cp /opt/bitnami/redis/mounted-etc/master.conf /opt/bitnami/redis/etc/master.conf
              fi
              if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then
                cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf
              fi
              ARGS=("--port" "${REDIS_PORT}")
              ARGS+=("--requirepass" "${REDIS_PASSWORD}")
              ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf")
              ARGS+=("--include" "/opt/bitnami/redis/etc/master.conf")
              /run.sh ${ARGS[@]}
          env:
            - name: REDIS_REPLICATION_MODE
              value: master
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: redis-password
                  name: redis-default
            - name: REDIS_PORT
              value: '6379'
          image: 'docker.io/bitnami/redis:5.0.7-debian-9-r0'
          imagePullPolicy: IfNotPresent
          livenessProbe:
            exec:
              command:
                - sh
                - '-c'
                - /health/ping_liveness_local.sh 5
            failureThreshold: 5
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 5
          name: redis-default
          ports:
            - containerPort: 6379
              name: redis
              protocol: TCP
          readinessProbe:
            exec:
              command:
                - sh
                - '-c'
                - /health/ping_readiness_local.sh 5
            failureThreshold: 5
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 1
          resources: {}
          securityContext:
            runAsUser: 1001
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /health
              name: health
            - mountPath: /data
              name: redis-data
            - mountPath: /opt/bitnami/redis/mounted-etc
              name: config
            - mountPath: /opt/bitnami/redis/etc/
              name: redis-tmp-conf
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      securityContext:
        fsGroup: 1001
      serviceAccount: default
      serviceAccountName: default
      volumes:
        - configMap:
            defaultMode: 0530
            name: redis-default-health
          name: health
        - configMap:
            name: redis-default
          name: config
        - emptyDir: {}
          name: redis-tmp-conf
  updateStrategy:
    type: RollingUpdate
  volumeClaimTemplates:
    - metadata:
        labels:
          app: redis
          component: master
          heritage: Tiller
          release: redis-default
        name: redis-data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 8Gi
        storageClassName: alicloud-nas
kubectl create -f redis-default-master.yml
kubectl -n public get statefulsets.apps

可以看到,其已运行。

其会自动创建PVC和PV并且映射:

kubectl -n public get pvc
kubectl get pv

创建服务:

vim redis-default-master-svc.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-master
  namespace: public
spec:
  ports:
    - name: redis
      port: 6379
      protocol: TCP
      targetPort: redis
  selector:
    app: redis
    release: redis-default
    role: master
kubectl create -f redis-default-master-svc.yml
kubectl -n public get svc

从库

用于创建从库的YAML文件:

vim redis-default-slave.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-slave
  namespace: public
spec:
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: redis
      release: redis-default
      role: slave
  serviceName: redis-default-headless
  template:
    metadata:
      labels:
        app: redis
        chart: redis-9.5.5
        release: redis-default
        role: slave
    spec:
      containers:
        - command:
            - /bin/bash
            - '-c'
            - |
              if [[ ! -f /opt/bitnami/redis/etc/replica.conf ]];then
                cp /opt/bitnami/redis/mounted-etc/replica.conf /opt/bitnami/redis/etc/replica.conf
              fi
              if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then
                cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf
              fi

              ARGS=("--port" "${REDIS_PORT}")
              ARGS+=("--slaveof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}")
              ARGS+=("--masterauth" "${REDIS_MASTER_PASSWORD}")
              ARGS+=("--requirepass" "${REDIS_PASSWORD}")
              ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf")
              ARGS+=("--include" "/opt/bitnami/redis/etc/replica.conf")
              /run.sh ${ARGS[@]}
          env:
            - name: REDIS_REPLICATION_MODE
              value: slave
            - name: REDIS_MASTER_HOST
              value: >-
                redis-default-master-0.redis-default-headless.public.svc.cluster.local
            - name: REDIS_PORT
              value: '6379'
            - name: REDIS_MASTER_PORT_NUMBER
              value: '6379'
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: redis-password
                  name: redis-default
            - name: REDIS_MASTER_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: redis-password
                  name: redis-default
          image: 'docker.io/bitnami/redis:5.0.7-debian-9-r0'
          imagePullPolicy: IfNotPresent
          livenessProbe:
            exec:
              command:
                - sh
                - '-c'
                - /health/ping_liveness_local_and_master.sh 5
            failureThreshold: 5
            initialDelaySeconds: 30
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 5
          name: redis-default
          ports:
            - containerPort: 6379
              name: redis
              protocol: TCP
          readinessProbe:
            exec:
              command:
                - sh
                - '-c'
                - /health/ping_readiness_local_and_master.sh 5
            failureThreshold: 5
            initialDelaySeconds: 5
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          securityContext:
            runAsUser: 1001
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /health
              name: health
            - mountPath: /data
              name: redis-data
            - mountPath: /opt/bitnami/redis/mounted-etc
              name: config
            - mountPath: /opt/bitnami/redis/etc
              name: redis-tmp-conf
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      securityContext:
        fsGroup: 1001
      serviceAccount: default
      serviceAccountName: default
      volumes:
        - configMap:
            defaultMode: 0530
            name: redis-default-health
          name: health
        - configMap:
            name: redis-default
          name: config
        - emptyDir: {}
          name: sentinel-tmp-conf
        - emptyDir: {}
          name: redis-tmp-conf
  updateStrategy:
    type: RollingUpdate
  volumeClaimTemplates:
    - metadata:
        labels:
          app: redis
          component: slave
          heritage: Tiller
          release: redis-default
        name: redis-data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 8Gi
        storageClassName: alicloud-nas
kubectl create -f redis-default-slave.yml
kubectl -n public get statefulsets.apps

Tips:这里没有使用其自带的run.sh启动脚本,而是使用生成的命令启动;因为经过反复测试发现使用脚本启动的命令带的args会有断行现象,会导致同步及密码会有问题:

同样其会自动创建相关PVC、PV,通过命令可查看,也可在控制台查看:

创建服务:

vim redis-default-slave-svc.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
    chart: redis-9.5.5
    heritage: Tiller
    release: redis-default
  name: redis-default-slave
  namespace: public
spec:
  ports:
    - name: redis
      port: 6379
      protocol: TCP
      targetPort: redis
  selector:
    app: redis
    release: redis-default
    role: slave
kubectl create -f redis-default-slave-svc.yml
kubectl -n public get svc

error: Content is protected !!