云原生场景下高可用架构的最佳实践
作者:刘佳旭(花名:佳旭),阿里云容器服务技术专家
引言
Cloud Native
随着云原生技术的快速发展以及在企业 IT 领域的深入应用,云原生场景下的高可用架构,对于企业服务的可用性、稳定性、安全性越发重要。通过合理的架构设计和云平台的技术支持,云原生高可用架构可以提供高可用性、弹性扩展性、简化运维管理、提升可靠性和安全性等方面的优势,为企业提供了更加可靠和高效的应用运行环境。
应用高可用架构设计
Cloud Native
云原生应用的高可用架构设计,是应用高可用开发、部署和治理的重要前提,可以从如下方面考虑:
K8s 高可用技术和在 ACK 的运用
Cloud Native
Kubernetes 提供了多种高可用技术和机制,以确保集群和应用的高可用性。包括拓扑分布约束、PodAntiAffinity、容器健康检查和自动重启、存储冗余和持久化、服务发现和负载均衡等。这些技术和机制可以帮助构建高可用的 Kubernetes 集群和应用,提供稳定、可靠的服务,下面会展开介绍。
多可用区的选择 为集群选择云厂商支持的多个可用区,以减少单点故障的风险。
部署控制面节点/组件 将控制面节点和组件(如 etcd、kube-apiserver、kube-controller-manager、kube-scheduler)部署在不同的可用区中,使用云厂商的负载均衡器或 DNS 解析来将流量分发到后端。使用多个 etcd 节点配置一个高可用的 etcd 集群,将其分布在不同的可用区内。这样可以确保即使其中一个 etcd 节点失效,集群仍然可以继续工作。
部署数据面节点 将数据面节点分布在多个可用区,以确保 Pod 可以调度到不同可用区实现跨可用区高可用。
监控和自动恢复 配置监控系统来监测集群的健康状态,并设置自动恢复机制,以便在节点或可用区故障时自动进行故障转移和恢复。
通过按多可用区部署控制面节点/组件和数据面节点,可以提高 Kubernetes 集群的可用性和容错能力,确保即使在单个可用区或节点发生故障时,集群仍然能够继续提供服务。
3.1.1 拓扑分布约束 Topology Spread Constraints
MaxSkew 指定每个拓扑域内 Pod 的最大偏差值。拓扑域可以是节点、可用区或其他自定义拓扑域。最大偏差值是一个整数,表示 Pod 在集群的任意两个拓扑域内 Pod 的最大差值。例如,如果 MaxSkew 设置为 2,则在任意两个拓扑域内的 Pod 数量差值不得超过 2 个。
TopologyKey 指定用于标识拓扑域的标签或注释的键。可以使用节点标签(例如 node.kubernetes.io/zone),或者自定义的标签或注释。
WhenUnsatisfiable 指定当无法满足拓扑分布约束时采取的操作。可以选择的操作有 DoNotSchedule(不调度 Pod)、ScheduleAnyway(强制调度 Pod)和RequireDuringSchedulingIgnoredDuringExecution(要求拓扑分布约束,但不强制执行)。
通过使用这些配置,可以创建拓扑分布约束的策略,以确保 Pod 在集群中按照期望的拓扑分布进行部署。这对于需要在不同的可用区或节点之间均匀分布负载的应用程序非常有用,以提高可靠性和可用性。
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-run-per-zone
spec:
replicas: 3
selector:
matchLabels:
app: app-run-per-zone
template:
metadata:
labels:
app: app-run-per-zone
spec:
containers:
- name: app-container
image: app-image
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule
在上述示例中,创建了一个拓扑分布约束,其中 maxSkew 设置为 1,topologyKey 设置为 "topology.kubernetes.io/zone",whenUnsatisfiable 设置为 DoNotSchedule。这意味着 Pod 按由节点的 label "topology.kubernetes.io/zone"(对应到云厂商的可用区)定义的拓扑域进行按可用区强制打散,且拓扑域之间 Pod 数量最大差值为 1。通过这种方式,可以将 Workload 的 Pod 尽量按可用区打散调度。
3.1.2 拓扑分布约束与 NodeAffinity/NodeSelector 的关系
3.1.3 拓扑分布约束的不足
更多详细内容参考 [3]。
3.1.4 多可用区实现同时快速弹性扩容
以下图两个可用区为例,介绍利用现有架构上能满足多可用区同时扩容的方法。
3.1.5 可用区容量受损后的自动恢复
1. RequiredDuringSchedulingIgnoredDuringExecution
2. PreferredDuringSchedulingIgnoredDuringExecution
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-run-per-node
spec:
replicas: 3
selector:
matchLabels:
app: app-run-per-node
template:
metadata:
labels:
app: app-run-per-node
spec:
containers:
- name: app-container
image: app-image
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- app-run-per-node
topologyKey: "kubernetes.io/hostname"
在上述示例中,对 Pod 进行了反亲和设置,要求调度器在调度时确保这些 Pod 不会被调度到同一个节点上。拓扑键设置为 "kubernetes.io/hostname",使得调度器能够在不同的节点之间进行打散。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: my-app-image
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: DoNotSchedule
在上述示例中,创建了一个拓扑分布约束,其中 maxSkew 设置为 1,topologyKey 设置为 "kubernetes.io/hostname",whenUnsatisfiable 设置为 DoNotSchedule。这意味着在每个节点上最多只能运行 1 个 Pod 实现强制 Pod 按节点高可用打散。
3.3.1 应用多活高可用
3.3.2 应用主备高可用
3.3.3 PDB 提升应用高可用
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-pdb
spec:
replicas: 3
selector:
matchLabels:
app: app-with-pdb
template:
metadata:
labels:
app: app-with-pdb
spec:
containers:
- name: app-container
image: app-container-image
ports:
- containerPort: 80
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: pdb-for-app
spec:
minAvailable: 2
selector:
matchLabels:
app: app-with-pdb
在上述示例中,Deployment 配置定义了 3 个副本,PDB 配置指定了至少要保持 2 个副本可用。这意味着,即使在节点维护或故障时,Kubernetes 也会确保至少有 2 个副本一直在运行,提高应用程序在 Kubernetes 集群中的可用性,并减少可能的服务中断。
存活探针(Liveness Probes) 存活探针用于监测容器是否仍然运行正常。如果存活探针失败(返回非 200 状态码),Kubernetes 将会重启容器。通过配置存活探针,可以确保容器在出现问题时能够自动重启。存活探针可以使用 HTTP 请求、TCP 套接字或命令执行来进行检测。 就绪探针(Readiness Probes) 就绪探针用于监测容器是否已经准备好接收流量。只有在就绪探针成功(返回 200 状态码)时,Kubernetes 才会将流量转发给容器。通过配置就绪探针,可以确保只有当容器完全启动并准备好接收请求时,才将它们加入服务的负载均衡。 启动探针(Startup Probes) 启动探针用于监测容器是否正在启动过程中。与存活探针和就绪探针不同,启动探针在容器启动期间执行,并且只有在探针成功后,才会将容器标记为就绪状态。如果启动探针失败,Kubernetes 会将容器标记为失败状态,并重新启动容器。 重启策略(Restart Policy) 重启策略定义了在容器退出时应采取的操作。Kubernetes 支持以下三种重启策略:
Always:无论容器以任何方式退出,Kubernetes 都会自动重启容器。 OnFailure:只有当容器以非零状态退出时,Kubernetes 才会自动重启容器。 Never:无论容器以任何方式退出,Kubernetes 都不会自动重启容器。
可以通过在 Pod 或 Deployment 的配置中添加相应的探针和重启策略来进行配置,例如:
apiVersion: v1
kind: Pod
metadata:
name: app-with-probe
spec:
containers:
- name: app-container
image: app-image
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
startupProbe:
exec:
command:
- cat
- /tmp/ready
initialDelaySeconds: 20
periodSeconds: 15
restartPolicy: Always
在上述示例中,配置了一个存活探针(通过 HTTP GET 请求检测路径为 /health 的端口 80)、一个就绪探针(通过 TCP 套接字检测端口 8080)、一个启动探针(通过执行命令 cat /tmp/ready 来检测容器是否启动完成)和重启策略为 Always。根据实际需求,可以根据容器的特性和健康检查的要求进行适当的配置。
存储类型 根据应用程序的需求选择合适的存储类型。Kubernetes 支持多种存储类型,包括本地存储、网络存储(如 NFS、iSCSI 等)、云提供商的持久化存储(如阿里云 OSS 等)以及外部存储插件(如 Ceph、GlusterFS 等)。根据应用程序的读写性能、数据保护和可用性要求选择适当的存储类型。
存储容量 根据应用程序的存储需求选择合适的存储容量。在创建 Persistent Volume 时,可以指定存储容量的大小范围。在创建 Persistent Volume Claim 时,可以指定所需的存储容量。确保为应用程序提供足够的存储空间,以满足其数据存储需求。
访问模式 根据应用程序的访问模式选择适当的访问模式。Kubernetes 支持多种访问模式,包括读写一致(ReadWriteOnce)、读写多次(ReadWriteMany)和只读(ReadOnlyMany)。根据应用程序的多节点访问需求选择适当的访问模式。
选择合适的后端数据服务,如 RDS 等,需要考虑以下因素:
数据库类型和功能 根据应用程序的需求选择合适的数据库类型。不同的数据库类型如关系型数据库(如 MySQL、PostgreSQL)、NoSQ L数据库(如 MongoDB、Cassandra)等提供不同的功能和适应性。根据应用程序的数据模型和查询需求选择适当的数据库类型。
性能和扩展性 根据应用程序的性能要求选择后端数据服务。考虑数据库的性能指标(如吞吐量、延迟)以及其扩展性能。
可用性和可靠性 选择后端数据服务时要考虑其可用性和可靠性。云提供商的托管数据库服务通常提供高可用性和自动备份功能。确保选择一个能够满足应用程序的可用性和数据保护需求的后端数据服务。
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-master-zoneid: "cn-hangzhou-a"
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-slave-zoneid: "cn-hangzhou-b"
name: nginx
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: nginx
type: LoadBalancer
为了减少跨可用区网络流量,提升网络性能。我们可以使用在 Kubernetes 1.23 中引入拓扑感知提示 (Topology Aware Hints) 实现了拓扑感知的就近路由功能。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-topology-essd
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd
fstype: ext4
diskTags: "a:b,b:c"
zoneId: “cn-hangzhou-a,cn-hangzhou-b,cn-hangzhou-c” #指定可用区范围来生成云盘
encrypted: "false"
performanceLevel: PL1 #指定性能类型
volumeExpandAutoSnapshot: "forced" #指定扩容编配的自动备份开关,该设置仅在type为"cloud_essd"时生效。
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: topology-disk-pvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 100Gi
storageClassName: alicloud-disk-topology-essd
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: "mysql"
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "mysql"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumes:
- name: data
persistentVolumeClaim:
claimName: topology-disk-pvc
ECI 会把创建 Pod 的请求分散到所有的 vSwitch 中,从而达到分散压力的效果。 如果创建 Pod 请求在某一个 vSwitch 中遇到没有库存的情况,会自动切换到下一个 vSwitch 继续尝试创建。
根据实际需求修改 kube-system/eci-profile configmap 中的 vswitch 字段,修改后即时生效。
kubectl -n kube-system edit cm eci-profile
apiVersion: v1
data:
kube-proxy: "true"
privatezone: "true"
quota-cpu: "192000"
quota-memory: 640Ti
quota-pods: "4000"
regionId: cn-hangzhou
resourcegroup: ""
securitygroupId: sg-xxx
vpcId: vpc-xxx
vswitchIds: vsw-xxx,vsw-yyy,vsw-zzz
kind: ConfigMap
相关内容可以参考 [8]。
3.9.1 应用负载副本不可用的监控告警
# kube-system或者monitoring中的Deployment存在不可用副本,持续1m,则触发告警,告警serverity配置为L1
- alert: SystemPodReplicasUnavailable
expr: kube_deployment_status_replicas_unavailable{namespace=~"kube-system|monitoring",deployment!~"ack-stub|kubernetes-kdm"} > 0
labels:
severity: L1
annotations:
summary: "namespace={{$labels.namespace}}, deployment={{$labels.deployment}}: Deployment存在不可用Replica"
for: 1m
# kube-system或者monitoring中的Deployment副本总数>0,且全部副本不可用,持续1m,则触发告警,告警serverity配置为L1
- alert: SystemAllPodReplicasUnavailable
expr: kube_deployment_status_replicas_unavailable{namespace=~"kube-system|monitoring"} == kube_deployment_status_replicas{namespace=~"kube-system|monitoring"} and kube_deployment_status_replicas{namespace=~"kube-system|monitoring"} > 0
labels:
severity: L1
annotations:
summary: "namespace={{$labels.namespace}}, deployment={{$labels.deployment}}: Deployment全部Replicas不可用"
for: 1m
3.9.2 集群可用区内不健康节点百分比的监控告警
# HELP node_collector_unhealthy_nodes_in_zone [ALPHA] Gauge measuring number of not Ready Nodes per zones.
# TYPE node_collector_unhealthy_nodes_in_zone gauge
node_collector_unhealthy_nodes_in_zone{zone="cn-shanghai::cn-shanghai-e"} 0
node_collector_unhealthy_nodes_in_zone{zone="cn-shanghai::cn-shanghai-g"} 0
node_collector_unhealthy_nodes_in_zone{zone="cn-shanghai::cn-shanghai-l"} 0
node_collector_unhealthy_nodes_in_zone{zone="cn-shanghai::cn-shanghai-m"} 0
node_collector_unhealthy_nodes_in_zone{zone="cn-shanghai::cn-shanghai-n"} 0
# HELP node_collector_zone_health [ALPHA] Gauge measuring percentage of healthy nodes per zone.
# TYPE node_collector_zone_health gauge
node_collector_zone_health{zone="cn-shanghai::cn-shanghai-e"} 100
node_collector_zone_health{zone="cn-shanghai::cn-shanghai-g"} 100
node_collector_zone_health{zone="cn-shanghai::cn-shanghai-l"} 100
node_collector_zone_health{zone="cn-shanghai::cn-shanghai-m"} 100
node_collector_zone_health{zone="cn-shanghai::cn-shanghai-n"} 100
# HELP node_collector_zone_size [ALPHA] Gauge measuring number of registered Nodes per zones.
# TYPE node_collector_zone_size gauge
node_collector_zone_size{zone="cn-shanghai::cn-shanghai-e"} 21
node_collector_zone_size{zone="cn-shanghai::cn-shanghai-g"} 21
node_collector_zone_size{zone="cn-shanghai::cn-shanghai-l"} 21
node_collector_zone_size{zone="cn-shanghai::cn-shanghai-m"} 21
node_collector_zone_size{zone="cn-shanghai::cn-shanghai-n"} 21
AlertManager/Thanos Ruler 的告警示例如下:
# node_collector_zone_health <= 80 如果可用区内健康节点比例小于80%,就触发告警。
alert: HealthyNodePercentagePerZoneLessThan80
expr: node_collector_zone_health <= 80
labels:
severity: L1
annotations:
summary: "zone={{$labels.zone}}: 可用区内健康节点与节点总数百分比 <= 80%"
for: 5m
应用的单/多集群高可用架构
Cloud Native
基于如上第三章介绍的高可用技术和阿里云产品能力提供的产品能力,可以全面实现单个集群范围内的高可用架构。多集群高可用架构是在单集群高可用架构上进步升级的高可用架构,提供了跨集群/地域的高可用服务能力。
如果希望对多地域多集群进行统一管理,比如可观测、安全策略,以及实现应用的跨集群统一交付。我们可以利用 ACK One 进行实现。分布式云容器平台 ACK One(Distributed Cloud Container Platform for Kubernetes)是阿里云面向混合云、多集群、分布式计算、容灾等场景推出的企业级云原生平台。ACK One 可以连接并管理用户在任何地域、任何基础设施上的 Kubernetes 集群,并提供一致的管理和社区兼容的 API,支持对计算、网络、存储、安全、监控、日志、作业、应用、流量等进行统一运维管控。如您想要了解更多关于 ACK One 的信息 ,欢迎钉钉搜索群号:35688562 进群交流。
链接总结
Cloud Native
云原生场景下的应用高可用架构和设计,对于企业服务的可用性、稳定性、安全性至关重要,可以有效提高应用可用性和用户体验,提供故障隔离和容错能力等。本文介绍了云原生应用高可用架构设计的关键原则、K8s 高可用技术以及在 ACK 场景下的使用和实现、单集群和多集群高可用架构的使用,希望为有相关需求的企业提供参考和帮助,ACK 会继续为客户提供安全、稳定、性能、成本持续优化升级的云原生产品和服务!
本文参考了阿里云容器服务负责人易立对于 ACK 高可用架构分析的精彩分享,在此表示诚挚感谢!
相关链接:
[1] https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#interaction-with-node-affinity-and-node-selectors
[2] https://kubernetes.io/blog/2020/05/introducing-podtopologyspread/
[3] https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#known-limitations
[4] https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/configure-auto-scaling-for-cross-zone-deployment
[5] https://help.aliyun.com/document_detail/184995.html
[6] https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/dynamically-provision-a-disk-volume-by-using-the-cli-1#section-dh5-bd8-x0q
[7] https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/use-dynamically-provisioned-disk-volumes
[8] https://help.aliyun.com/zh/ack/serverless-kubernetes/user-guide/create-ecis-across-zones
END
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦
微信扫码关注该文公众号作者