k8s中部署有状态应用

公众号:yunops

一、场景描述

一般来讲,不建议在 k8s 中部署 Redis、Kafka 或者 MySQL 等需要持久化数据等有状态应用,因为数据卷数据安全问题、IO 性能问题、非最佳实践导致隔离不彻底、资源争用问题等问题的存在,导致容器化部署的可靠性不如非容器化部署;其次,中间件、数据库类应用通常也不会很频繁的版本变动或者弹性伸缩。但也不是说数据库一定不要部署在容器里,在安全、性能、SLA 要求不高的场景下(如数据丢失不敏感的业务或者环境),容器化部署也是节省成本、提高效率的好办法。

二、可选方案

  1. 根据官方提供或者自编写的 dockerfile 文件,自行实现 redis 集群方案、设计 k8s 编排文件
  1. 使用开源 helm 仓库中的成品部署,如 stable 或者 Bitnami 版本
  1. 三方开源的 redis-operator,如 ucloud 开源产品
  1. 企业级产品,如 Kubedb

三、实践

选型说明

目的是容器化非生产环境的 Redis 部署,结合自身需求对比以上几种方案。如果不差钱,可以用 Ucloud 开源的 operator,这个简便快捷,但是最小规格的集群拓扑仍需启动 6 个 pod(3master 节点 + 3slave),比较耗资源;暂定使用 Bitnami 指定版本的 redis(非 HA 版)部署,说明如下:

操作步骤

  1. 配置 Chart 仓库:helm repo add bitnami https://charts.bitnami.com/bitnami
  2. 下载指定版本 redis Chart:helm pull bitnami/redis --version=10.6.12
  3. 解压:tar -zxf redis-10.5.7.tgz
  4. 编辑 redis/value.yaml 文件内容进行定制,改动点如下:
global:
  imageRegistry: registry-vpc.cn-hangzhou.aliyuncs.com
  storageClass: alicloud-nas-subpath
  redis:
    password: "cx5swxbr05MhIXcj"
image:
  registry: registry-vpc.cn-hangzhou.aliyuncs.com
  repository: mars-xxx/redis
  tag: 5.0.9-debian-10-r4
  pullPolicy: IfNotPresent
cluster:
  enabled: true
  slaveCount: 1
sentinel:
  enabled: false
  usePassword: true
  image:
    registry: registry-vpc.cn-hangzhou.aliyuncs.com
    repository: mars-xxx/redis-sentinel
    tag: 5.0.9-debian-10-r2
    pullPolicy: IfNotPresent
  masterSet: mymaster
  initialCheckTimeout: 5
  quorum: 2
  downAfterMilliseconds: 60000
  failoverTimeout: 18000
  parallelSyncs: 1
  port: 26379
  configmap:
  staticID: false
  livenessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 5
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 5
  readinessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 5
    timeoutSeconds: 1
    successThreshold: 1
    failureThreshold: 5
  customLivenessProbe: {}
  customReadinessProbe: {}
  service:
    type: ClusterIP
    sentinelPort: 26379
    redisPort: 6379
    annotations: {}
    labels: {}
    loadBalancerIP:
clusterDomain: cluster.local
networkPolicy:
  enabled: false
  ingressNSMatchLabels: {}
  ingressNSPodMatchLabels: {}
serviceAccount:
  create: false
  name:
rbac:
  create: false
  role:
    rules: []
securityContext:
  enabled: true
  fsGroup: 1001
  runAsUser: 1001
usePassword: true
password: ""
usePasswordFile: false
persistence:
  existingClaim:
redisPort: 6379
master:
  command: "/run.sh"
  configmap:
  extraFlags: []
  disableCommands:
    - FLUSHDB
    - FLUSHALL
  podLabels: {}
  podAnnotations: {}
  resources:
    requests:
      memory: 256Mi
      cpu: 100m
    limits:
      memory: 1024Mi
      cpu: 500m
  livenessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 5
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 5
  readinessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 5
    timeoutSeconds: 1
    successThreshold: 1
    failureThreshold: 5
  customLivenessProbe: {}
  customReadinessProbe: {}
  affinity: {}
  service:
    type: ClusterIP
    port: 6379
    annotations: {}
    labels: {}
    loadBalancerIP:
  persistence:
    enabled: true
    path: /data
    subPath: ""
    accessModes:
      - ReadWriteOnce
    size: 10Gi
    matchLabels: {}
    matchExpressions: {}
  statefulset:
    updateStrategy: RollingUpdate
  priorityClassName: {}
slave:
  service:
    type: ClusterIP
    port: 6379
    annotations: {}
    labels: {}
    loadBalancerIP:
  port: 6379
  command: "/run.sh"
  configmap:
  extraFlags: []
  disableCommands:
    - FLUSHDB
    - FLUSHALL
  affinity: {}
  livenessProbe:
    enabled: true
    initialDelaySeconds: 30
    periodSeconds: 10
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 5
  readinessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 10
    timeoutSeconds: 10
    successThreshold: 1
    failureThreshold: 5
  customLivenessProbe: {}
  customReadinessProbe: {}
  podLabels: {}
  podAnnotations: {}
  persistence:
    enabled: true
    path: /data
    subPath: ""
    accessModes:
      - ReadWriteOnce
    size: 10Gi
    matchLabels: {}
    matchExpressions: {}
  statefulset:
    updateStrategy: RollingUpdate
metrics:
  enabled: false
  image:
    registry: registry-vpc.cn-hangzhou.aliyuncs.com
    repository: mars-xxx/redis-exporter
    tag: 1.5.3-debian-10-r21
    pullPolicy: IfNotPresent
  podAnnotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9121"
  serviceMonitor:
    enabled: false
    selector:
      prometheus: kube-prometheus
  prometheusRule:
    enabled: false
    additionalLabels: {}
    namespace: ""
    rules: []
  service:
    type: ClusterIP
    annotations: {}
    labels: {}
volumePermissions:
  enabled: false
  image:
    registry: registry-vpc.cn-hangzhou.aliyuncs.com
    repository: mars-xxx/minideb
    tag: buster
    pullPolicy: Always
  resources: {}
configmap: |-
  appendonly yes
  save ""
  databases 256
sysctlImage:
  enabled: false
  command: []
  registry: registry-vpc.cn-hangzhou.aliyuncs.com
  repository: mars-xxx/minideb
  tag: buster
  pullPolicy: Always
  mountHostSys: false
  resources: {}
podSecurityPolicy:
  create: false
  1. 在 default namespace 部署:helm install dev-cache -n default ./redis
  2. 检查部署情况:
  1. 功能验证:

补充说明

该集群会默认创建一个无头服务:dev-cache-redis-ms-headless,本意是用于分布式高可用集群场景下集群内节点交互、master 选取等场景;不要将该无头服务用于业务访问 redis 的连接地址,因为无头服务不像普通 Service,不使用集群内置负载均衡功能、访问 pod 不经过 service 转发;而是直接通过 Endpoints 控制器创建 Endpoints 记录,并在 CoreDNS 服务器中为该服务创建两条 A 型解析记录,使用服务名访问无头服务时会从 DNS 服务器中获取其中一条记录对应的 IP 使用(可能对应 master 节点的 pod、也可能对应 slave 节点的 pod),而不同的节点又对应不同的读写权限,故而不可控。

四、总结

以上是以 Redis 部署为例说明各开源应用工具在 k8s 集群中部署方案;要部署其他应用的思路可以参考这个来,都差不多~~