--- title: 运行于多区环境 weight: 10 content_type: concept --- 本页描述如何在多个区(Zone)中运行集群。 ## 介绍 Kubernetes 1.2 添加了跨多个失效区(Failure Zone)运行同一集群的能力 (GCE 把它们称作“区(Zones)”,AWS 把它们称作“可用区(Availability Zones)”, 这里我们用“区(Zones)”指代它们)。 此能力是更广泛的集群联邦(Cluster Federation)特性的一个轻量级版本。 集群联邦之前有一个昵称 ["Ubernetes"](https://github.com/kubernetes/community/blob/{{< param "githubbranch" >}}/contributors/design-proposals/multicluster/federation.md))。 完全的集群联邦可以将运行在多个区域(Region)或云供应商(或本地数据中心)的多个 Kubernetes 集群组合起来。 不过,很多用户仅仅是希望在同一云厂商平台的多个区域运行一个可用性更好的集群, 而这恰恰是 1.2 引入的多区支持所带来的特性 (此特性之前有一个昵称 “Ubernetes Lite”)。 多区支持有意实现的有局限性:可以在跨多个区域运行同一 Kubernetes 集群,但只能 在同一区域(Region)和云厂商平台。目前仅自动支持 GCE 和 AWS,尽管为其他云平台 或裸金属平台添加支持页相对容易,只需要确保节点和卷上添加合适的标签即可。 ## 功能 节点启动时,`kubelet` 自动向其上添加区信息标签。 在单区(Single-Zone)集群中, Kubernetes 会自动将副本控制器或服务中的 Pod 分布到不同节点,以降低节点失效的影响。 在多区集群中,这一分布负载的行为被扩展到跨区分布,以降低区失效的影响, 跨区分布的能力是通过 `SelectorSpreadPriority` 实现的。此放置策略亦仅仅是 尽力而为,所以如果你的集群所跨区是异质的(例如,节点个数不同、节点类型 不同或者 Pod 资源需求不同),放置策略都可能无法完美地跨区完成 Pod 的 均衡分布。如果需要,你可以使用同质区(节点个数和类型相同)以降低不均衡 分布的可能性。 持久卷被创建时,`PersistentVolumeLabel` 准入控制器会自动为其添加区标签。 调度器使用 `VolumeZonePredicate` 断言确保申领某给定卷的 Pod 只会被放到 该卷所在的区。这是因为卷不可以跨区挂载。 ## 局限性 多区支持有一些很重要的局限性: * 我们假定不同的区之间在网络上彼此距离很近,所以我们不执行可感知区的路由。 尤其是,即使某些负责提供该服务的 Pod 与客户端位于同一区,通过服务末端 进入的流量可能会跨区,因而会导致一些额外的延迟和开销。 * 卷与区之间的亲和性仅适用于 PV 持久卷。例如,如果你直接在 Pod 规约中指定某 EBS 卷,这种亲和性支持就无法工作。 * 集群无法跨多个云平台或者地理区域运行。这类功能需要完整的联邦特性支持。 * 尽管你的节点位于多个区中,`kube-up` 脚本目前默认只能构造一个主控节点。 尽管服务是高可用的,能够忍受失去某个区的问题,控制面位于某一个区中。 希望运行高可用控制面的用户应该遵照 [高可用性](/zh/docs/setup/production-environment/tools/kubeadm/high-availability/) 中的指令构建。 ### 卷局限性 以下局限性通过 [拓扑感知的卷绑定](/zh/docs/concepts/storage/storage-classes/#volume-binding-mode)解决: * 使用动态卷供应时,StatefulSet 卷的跨区分布目前与 Pod 亲和性和反亲和性策略不兼容。 * 如果 StatefulSet 的名字中包含连字符("-"),卷的跨区分布可能无法实现存储的 跨区同一分布。 * 当在一个 Deployment 或 Pod 规约中指定多个 PVC 申领时,则需要为某特定区域 配置 StorageClass,或者在某一特定区域中需要静态供应 PV 卷。 另一种解决方案是使用 StatefulSet,确保给定副本的所有卷都从同一区中供应。 ## 演练 我们现在准备对在 GCE 和 AWS 上配置和使用多区集群进行演练。为了完成此演练, 你需要设置 `MULTIZONE=true` 来启动一个完整的集群,之后指定 `KUBE_USE_EXISTING_MASTER=true` 并再次运行 `kube-up` 添加其他区中的节点。 ### 建立集群 和往常一样创建集群,不过需要设置 MULTIZONE,以便告诉集群需要管理多个区。 这里我们在 `us-central1-a` 创建节点。 GCE: ```shell curl -sS https://get.k8s.io | MULTIZONE=true KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=us-central1-a NUM_NODES=3 bash ``` AWS: ```shell curl -sS https://get.k8s.io | MULTIZONE=true KUBERNETES_PROVIDER=aws KUBE_AWS_ZONE=us-west-2a NUM_NODES=3 bash ``` 这一步骤和往常一样启动一个集群,不过尽管 `MULTIZONE=true` 标志已经启用了多区功能特性支持,集群仍然运行在一个区内。 ### 节点已被打标签 查看节点,你会看到节点上已经有了区信息标签。 目前这些节点都在 `us-central1-a` (GCE) 或 `us-west-2a` (AWS)。 对于区域(Region),标签为 `topology.kubernetes.io/region`, 对于区(Zone),标签为 `topology.kubernetes.io/zone`: ```shell kubectl get nodes --show-labels ``` 输出类似于: ``` NAME STATUS ROLES AGE VERSION LABELS kubernetes-master Ready,SchedulingDisabled 6m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-1,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-master kubernetes-minion-87j9 Ready 6m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-87j9 kubernetes-minion-9vlv Ready 6m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-9vlv kubernetes-minion-a12q Ready 6m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-a12q ``` ### 添加第二个区中的节点 让我们向现有集群中添加另外一组节点,复用现有的主控节点,但运行在不同的区 (`us-central1-b` 或 `us-west-2b`)。 我们再次运行 `kube-up`,不过设置 `KUBE_USE_EXISTING_MASTER=true`。 `kube-up` 不会创建新的主控节点,而会复用之前创建的主控节点。 GCE: ```shell KUBE_USE_EXISTING_MASTER=true MULTIZONE=true KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=us-central1-b NUM_NODES=3 kubernetes/cluster/kube-up.sh ``` 在 AWS 上,我们还需要为额外的子网指定网络 CIDR,以及主控节点的内部 IP 地址: ```shell KUBE_USE_EXISTING_MASTER=true MULTIZONE=true KUBERNETES_PROVIDER=aws KUBE_AWS_ZONE=us-west-2b NUM_NODES=3 KUBE_SUBNET_CIDR=172.20.1.0/24 MASTER_INTERNAL_IP=172.20.0.9 kubernetes/cluster/kube-up.sh ``` 再次查看节点,你会看到新启动了三个节点并且其标签表明运行在 `us-central1-b` 区: ```shell kubectl get nodes --show-labels ``` 输出类似于: ``` NAME STATUS ROLES AGE VERSION LABELS kubernetes-master Ready,SchedulingDisabled 16m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-1,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-master kubernetes-minion-281d Ready 2m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-b,kubernetes.io/hostname=kubernetes-minion-281d kubernetes-minion-87j9 Ready 16m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-87j9 kubernetes-minion-9vlv Ready 16m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-9vlv kubernetes-minion-a12q Ready 17m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-a12q kubernetes-minion-pp2f Ready 2m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-b,kubernetes.io/hostname=kubernetes-minion-pp2f kubernetes-minion-wf8i Ready 2m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-b,kubernetes.io/hostname=kubernetes-minion-wf8i ``` ### 卷亲和性 通过动态卷供应创建一个卷(只有 PV 持久卷支持区亲和性): ```bash kubectl apply -f - < {{< note >}} Kubernetes 1.3 及以上版本会将动态 PV 申领散布到所配置的各个区。 在 1.2 版本中,动态持久卷总是在集群主控节点所在的区 (这里的 `us-central1-a` 或 `us-west-2a`), 对应的 Issue ([#23330](https://github.com/kubernetes/kubernetes/issues/23330)) 在 1.3 及以上版本中已经解决。 {{< /note >}} 现在我们来验证 Kubernetes 自动为 PV 打上了所在区或区域的标签: ```shell kubectl get pv --show-labels ``` 输出类似于: ``` NAME CAPACITY ACCESSMODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE LABELS pv-gce-mj4gm 5Gi RWO Retain Bound default/claim1 manual 46s topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a ``` 现在我们将创建一个使用 PVC 申领的 Pod。 由于 GCE PD 或 AWS EBS 卷都不能跨区挂载,这意味着 Pod 只能创建在卷所在的区: ```yaml kubectl apply -f - < 注意 Pod 自动创建在卷所在的区,因为云平台提供商一般不允许跨区挂接存储卷。 ```shell kubectl describe pod mypod | grep Node ``` ``` Node: kubernetes-minion-9vlv/10.240.0.5 ``` 检查节点标签: ```shell kubectl get node kubernetes-minion-9vlv --show-labels ``` ``` NAME STATUS AGE VERSION LABELS kubernetes-minion-9vlv Ready 22m v1.6.0+fff5156 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-9vlv ``` ### Pod 跨区分布 同一副本控制器或服务的多个 Pod 会自动完成跨区分布。 首先,我们现在第三个区启动一些节点: GCE: ```shell KUBE_USE_EXISTING_MASTER=true MULTIZONE=true KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=us-central1-f NUM_NODES=3 kubernetes/cluster/kube-up.sh ``` AWS: ```shell KUBE_USE_EXISTING_MASTER=true MULTIZONE=true KUBERNETES_PROVIDER=aws KUBE_AWS_ZONE=us-west-2c NUM_NODES=3 KUBE_SUBNET_CIDR=172.20.2.0/24 MASTER_INTERNAL_IP=172.20.0.9 kubernetes/cluster/kube-up.sh ``` 验证你现在有来自三个区的节点: ```shell kubectl get nodes --show-labels ``` 创建 `guestbook-go` 示例,其中包含副本个数为 3 的 RC,运行一个简单的 Web 应用: ```shell find kubernetes/examples/guestbook-go/ -name '*.json' | xargs -I {} kubectl apply -f {} ``` Pod 应该跨三个区分布: ```shell kubectl describe pod -l app=guestbook | grep Node ``` ``` Node: kubernetes-minion-9vlv/10.240.0.5 Node: kubernetes-minion-281d/10.240.0.8 Node: kubernetes-minion-olsh/10.240.0.11 ``` ```shell kubectl get node kubernetes-minion-9vlv kubernetes-minion-281d kubernetes-minion-olsh --show-labels ``` ``` NAME STATUS ROLES AGE VERSION LABELS kubernetes-minion-9vlv Ready 34m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-a,kubernetes.io/hostname=kubernetes-minion-9vlv kubernetes-minion-281d Ready 20m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-b,kubernetes.io/hostname=kubernetes-minion-281d kubernetes-minion-olsh Ready 3m v1.13.0 beta.kubernetes.io/instance-type=n1-standard-2,topology.kubernetes.io/region=us-central1,topology.kubernetes.io/zone=us-central1-f,kubernetes.io/hostname=kubernetes-minion-olsh ``` 负载均衡器也会跨集群中的所有区;`guestbook-go` 示例中包含了一个负载均衡 服务的例子: ```shell kubectl describe service guestbook | grep LoadBalancer.Ingress ``` 输出类似于: ``` LoadBalancer Ingress: 130.211.126.21 ``` 设置上面的 IP 地址: ```shell export IP=130.211.126.21 ``` 使用 curl 访问该 IP: ```shell curl -s http://${IP}:3000/env | grep HOSTNAME ``` 输出类似于: ``` "HOSTNAME": "guestbook-44sep", ``` 如果多次尝试该命令: ```shell (for i in `seq 20`; do curl -s http://${IP}:3000/env | grep HOSTNAME; done) | sort | uniq ``` 输出类似于: ```shell "HOSTNAME": "guestbook-44sep", "HOSTNAME": "guestbook-hum5n", "HOSTNAME": "guestbook-ppm40", ``` 负载均衡器正确地选择不同的 Pod,即使它们跨了多个区。 ### 停止集群 当完成以上工作之后,清理任务现场: GCE: ```shell KUBERNETES_PROVIDER=gce KUBE_USE_EXISTING_MASTER=true KUBE_GCE_ZONE=us-central1-f kubernetes/cluster/kube-down.sh KUBERNETES_PROVIDER=gce KUBE_USE_EXISTING_MASTER=true KUBE_GCE_ZONE=us-central1-b kubernetes/cluster/kube-down.sh KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=us-central1-a kubernetes/cluster/kube-down.sh ``` AWS: ```shell KUBERNETES_PROVIDER=aws KUBE_USE_EXISTING_MASTER=true KUBE_AWS_ZONE=us-west-2c kubernetes/cluster/kube-down.sh KUBERNETES_PROVIDER=aws KUBE_USE_EXISTING_MASTER=true KUBE_AWS_ZONE=us-west-2b kubernetes/cluster/kube-down.sh KUBERNETES_PROVIDER=aws KUBE_AWS_ZONE=us-west-2a kubernetes/cluster/kube-down.sh ```