UCS双集群高可用部署
应用场景
大企业场景提供多集群多活方案,做小故障域降低逻辑层面故障的风险,提供原有生态的兼容,最大限度降低在业务发布、运维等方面的适配工作量。
通过UCS提供双集群多活容灾,可以确保在任何一个可用区或集群发生故障时,不影响服务整体可用性。
约束限制
- 您需要拥有至少两个Kubernetes版本为1.21及以上的可用CCE turbo集群(如下文中ucs01与ucs02),并且集群分布在不同的AZ。
方案架构简介
- UCS控制面:
- CCE集群:
- 2个CCE turbo集群,集群控制面节点3AZ部署,创建集群详情可参见购买Standard/Turbo集群;
- 集群计算节点分别创建AZ1、AZ2节点池,创建节电池详情可参见创建节点池。
- 集群内分别安装集群弹性引擎(Cluster AutoScaler),容器弹性引擎(HPA Controller)、域名解析(CoreDNS)等插件,详情可参见插件概述。
- 弹性负载均衡ELB:
- ELB实例多AZ部署,详情可参见ELB资源使用多AZ部署。
- ELB将访问流量根据路由策略分发到后端多个Pod实例,同时结合健康检查功能,流量只分发后端正常工作的Pod实例,详情可参见配置流量分配策略分发流量。
- 应用部署:通过CICD流水线对接UCS控制面,创建应用deployment、弹性伸缩策略HPA以及分发策略PropagationPolicy均衡部署到双集群,并通过MultiClusterIngress发布应用,详情可参见使用kubectl命令实现UCS高可用部署操作步骤。
容器级容错实施建议
容器级容错旨在通过配置健康检查和自动重启机制,确保容器应用的高可用性和可靠性。应用部署需要遵守以下规范:
项目 |
描述 |
说明 |
---|---|---|
应用无状态化 |
应用必须做无状态和幂等处理 |
服务的无状态化是部署的多个服务模块(进程),使其完全对等。也就是部署多个Pod实例,请求到任一实例的处理结果是一样的。这些Pod实例不存储业务的上下文信息,比如session、登录、业务上下文相关的信息。只会根据每次请求携带的数据进行相应的业务处理。 幂等指的是使用相同的参数多次调用相同的API,对后端产生的影响是一致的。 |
应用副本数 |
每个应用负载Deployment实例数满足业务容量规划和可用性要求。
|
配合多集群方案,满足生产中应用高可用性要求。为集群级别和可用区(AZ)级别故障域隔离创造条件。 |
应用健康检查 |
每个应用必须配置: |
容器出现故障或无法正常工作,系统可以自动重启该容器,从而提高应用的可用性和可靠性。 |
弹性伸缩 |
业务支持自动扩缩容的能力即配置指标弹性HPA,并且要求:
|
业务按需使用资源,最大程度地减少资源浪费。在业务流量突增以及集群级故障时,应用能够自动扩容,保障业务不受损 |
优雅停机 |
应用必须支持优雅停机 |
优雅停机是在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。应用接收到停止指令之后的步骤应该是,停止接收访问请求,等待已经接收到的请求处理完成,并能成功返回,这时才真正停止应用。 |
ELB健康检查 |
弹性负载均衡ELB流量分发正常工作的后端。
|
在个别实例异常、节点异常或者整个AZ、整个集群故障时,能快速地隔离故障实例,保证业务访问成功率。 |
容器镜像 |
容器镜像体积最大应不超过1G。容器镜像标签使用具体的版本号。
|
体积小的镜像有利于分发、快速启动;镜像使用具体的版本号才能做版本控制。 |
资源配额 |
资源配额应为资源申请量的两倍数值 |
预防应用升级、应用扩容场景时,因资源配额不足导致失败的情况。 |
节点级容错配置建议
节点级容错是指当某个节点发生故障时,可以将Pod自动重新调度到其他健康节点上。
项目 |
描述 |
说明 |
节点故障自动驱逐 |
当节点出现异常,变为不可用状态时,容器将在该容忍时间后自动驱逐,默认为300s。默认对所有的容器生效,用户也可以为指定pod进行差异化容忍配置,此时将以Pod配置的容忍时长为准。 |
无特殊需求建议保持默认配置,容忍时间配置过小可能导致容器在网络抖动等一些短时故障场景下频繁迁移影响业务,容忍时间配置过大可能导致容器在节点故障时长时间无法迁移导致业务受损。 |
集群节点弹性 |
节点弹性伸缩,也就是资源层面的弹性伸缩。CA(Cluster AutoScaling)会检查所有Pending状态的Pod,根据用户配置的扩缩容策略,选择出一个最合适的节点池进行扩容。 |
当集群资源不够时需要CA扩容节点,使得集群有足够资源;而当HPA缩容后集群会有大量空余资源,这时需要CA缩容节点释放资源,才不至于造成浪费。自动扩容支撑单边资源量,CA的上限应设置为支撑所有流量所需资源量; |
通过kubectl命令恢复集群级/AZ级故障(可选)
集群关键系统组件出现故障或者集群升级策略不当、升级配置有误、操作人员执行有误等人为因素导致集群整体不可用或者出现AZ站点级别的故障时,UCS提供手动切流的能力。通过创建Remedy对象将MultiClusterIngress流量从故障集群上摘除。
通过Kubectl命令恢复故障步骤如下:
- 使用kubectl连接集群联邦,详细操作请参见通过kubectl连接集群。
- 集群故障后,在执行机上创建并编辑remedy.yaml文件,文件内容如下所示,参数定义请参见表1。
vi remedy.yaml
示例YAML定义了一个Remedy对象,触发条件为空,表示无条件触发,集群联邦控制器会立即将ucs01上的流量摘除。在集群故障恢复后,删除该Remedy对象,ucs01上的流量会自动恢复,由此保证单集群的故障不会影响服务的可用性。apiVersion: remedy.karmada.io/v1alpha1 kind: Remedy metadata: name: foo spec: clusterAffinity: clusterNames: - ucs01 actions: - TrafficControl
表1 Remedy参数说明 参数
描述
spec.clusterAffinity.clusterNames
策略关注的集群名列表。仅在该列表中的集群会执行指定动作,为空时不会执行任何动作。
spec.decisionMatches
触发条件列表。当上述集群列表中指定的集群满足任一触发条件时,即会执行指定动作。当列表为空时,表示无条件触发。
conditionType
触发条件的类型。当前仅支持ServiceDomainNameResolutionReady类型,即CPD上报的CoreDNS域名解析状态。
operator
判断逻辑,仅支持Equal和NotEqual两种值,即等于和不等于。
conditionStatus
触发条件的状态。
actions
策略要执行的动作,目前仅支持TrafficControl,即流量控制。
- 集群故障恢复后,删除该Remedy对象。
kubectl delete remedy foo
- 检查集群ucs01上的流量已自动恢复,手动切流成功。
使用kubectl命令实现UCS高可用部署操作步骤
前置条件:
- 使用kubectl连接集群联邦,详细操作请参见通过kubectl连接集群。
- 已创建可使用的独享性ELB实例,并绑定弹性公网,详情可参见购买独享型负载均衡器。
以下均为示例yaml,请根据实际情况修改参数内容。
实践操作操作步骤:
使用UCS高可用部署,需要进行指定资源下发规则,示例如下yaml:
apiVersion: policy.karmada.io/v1alpha1 kind: ClusterPropagationPolicy metadata: name: karmada-global-policy # 策略名 spec: resourceSelectors: # 分发策略关联的资源,支持同时分发多个资源对象 - apiVersion: apps/v1 # group/version kind: Deployment # 资源类型kind - apiVersion: apps/v1 kind: DaemonSet - apiVersion: v1 kind: Service - apiVersion: v1 kind: Secret - apiVersion: v1 kind: ConfigMap - apiVersion: v1 kind: ResourceQuota - apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler - apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler - apiVersion: autoscaling.cce.io/v2alpha1 kind: CronHorizontalPodAutoscaler priority: 0 # 数值越大,优先级越高 conflictResolution: Overwrite # ConflictResolution声明当正在传播的资源已存在于目标集群中时, 默认为“Abort”,这意味着停止传播以避免意外覆盖。Overwrite则表示强制覆盖。 placement: # 放置规则即把关联资源分发到哪些集群 clusterAffinity: # 配置集群亲和性 clusterNames: # 使用集群名选择集群 - ucs01 # 集群名:需要修改为环境中实际集群名 - ucs02 # 集群名:需要修改为环境中实际集群名 replicaScheduling: # 实例调度策略 replicaSchedulingType: Divided # 实例拆分 replicaDivisionPreference: Weighted # 根据权重拆分 weightPreference: # 权重选项 staticWeightList: # 静态权重表: ucs01的权重为1(分配到大约1/2的实例数),ucs02权重为1 (分配到大约1/2的实例数) - targetCluster: # 目标集群 clusterNames: - ucs01 # 集群名:需要修改为环境中实际集群名 weight: 1 # 权重为1 - targetCluster: # 目标集群 clusterNames: - ucs02 # 集群名:需要修改为环境中实际集群名 weight: 1 # 权重为1 clusterTolerations: # 集群容忍,当集群master不健康或不可达时,应用不作驱逐处理 - key: cluster.karmada.io/not-ready operator: Exists effect: NoExecute - key: cluster.karmada.io/unreachable operator: Exists effect: NoExecute
若创建了HPA,需要拆分hpa中的最小实例数,可使用如下示例yaml(可选):
以下yaml中clusterNum为集群数量,示例集群数量为2,请根据实际场景配置。
apiVersion: config.karmada.io/v1alpha1 kind: ResourceInterpreterCustomization metadata: name: hpa-min-replica-split-ric spec: customizations: replicaResource: luaScript: | function GetReplicas(obj) clusterNum = 2 replica = obj.spec.minReplicas if ( obj.spec.minReplicas == 1 ) then replica = clusterNum end return replica, nil end replicaRevision: luaScript: | function ReviseReplica(obj, desiredReplica) obj.spec.minReplicas = desiredReplica return obj end target: apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler
创建configmap实例,示例如下yaml:
apiVersion: v1 kind: ConfigMap metadata: name: demo-configmap namespace: default #命名空间,默认为default data: foo: bar
创建deployment实例,示例如下yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: demo namespace: default #命名空间,默认为default labels: app: demo spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% selector: matchLabels: app: demo template: metadata: labels: app: demo spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - demo topologyKey: kubernetes.io/hostname containers: - env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace - name: POD_IP valueFrom: fieldRef: apiVersion: v1 fieldPath: status.podIP envFrom: - configMapRef: name: demo-configmap name: demo image: nginx #若使用“开源镜像中心”的镜像,可直接填写镜像名称;若使用“我的镜像”中的镜像,请在SWR中获取具体镜像地址。 command: - /bin/bash args: - '-c' - 'sed -i "s/nginx/podname: $POD_NAME podIP: $POD_IP/g" /usr/share/nginx/html/index.html;nginx "-g" "daemon off;"' imagePullPolicy: IfNotPresent resources: requests: cpu: 100m memory: 100Mi limits: cpu: 100m memory: 100Mi
创建hpa实例,示例如下yaml:
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: demo-hpa namespace: default #命名空间,默认为default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo minReplicas: 2 maxReplicas: 4 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 30 behavior: scaleDown: policies: - type: Pods value: 2 periodSeconds: 100 - type: Percent value: 10 periodSeconds: 100 selectPolicy: Min stabilizationWindowSeconds: 300 scaleUp: policies: - type: Pods value: 2 periodSeconds: 15 - type: Percent value: 20 periodSeconds: 15 selectPolicy: Max stabilizationWindowSeconds: 0
创建service实例,示例如下yaml:
apiVersion: v1 kind: Service metadata: name: demo-svc namespace: default #命名空间,默认为default spec: type: ClusterIP selector: app: demo sessionAffinity: None ports: - name: http protocol: TCP port: 8080 targetPort: 8080
创建mci实例,示例如下yaml:
apiVersion: networking.karmada.io/v1alpha1 kind: MultiClusterIngress metadata: name: demo-mci # MCI的名字 namespace: default #命名空间,默认为default annotations: karmada.io/elb.id: xxx # TODO: ELB实例ID karmada.io/elb.projectid: xxx #TODO: ELB实例的项目ID karmada.io/elb.port: "8080" #TODO: ELB监听端口 karmada.io/elb.health-check-flag: "on" karmada.io/elb.health-check-option.demo-svc: '{"protocol":"TCP"}' spec: ingressClassName: public-elb # ELB类型,固定值 rules: - host: demo.localdev.me # 对外暴露的域名 TODO:修改实际地址 http: paths: - backend: service: name: demo-svc # 暴露的service名字 port: number: 8080 # 暴露service端口 path: / pathType: Prefix # 前缀匹配
验证双集群高可用业务
用户在执行机上执行如下命令,验证双集群高可用业务:
- 获取HOSTNAME与ELBIP
kubectl get mci demo-mci -oyaml
- 多次访问业务,回显不同PODNAME和PODID,表示实现双集群访问成功
curl -H "host:demo.localdev.me" http://[ELBIP]:8080/