将 Spring Cloud Data Flow 部署到 GKE 区域集群

在将应用程序部署到环境时,通常最好制定一个策略来处理基础设施中断。中断的范围可能从硬件故障到整个数据中心离线,导致应用程序不可用。与其让这些工作负载在中断解决之前可能停止运行,另一种选择是让这些工作负载迁移到基础设施的另一部分以继续运行。指定特定应用程序必须驻留在哪里或与其他应用程序位于同一位置也可能很有用。

使用 Kubernetes,节点故障和在其上运行的工作负载会通过将这些工作负载重新安排到其他可用节点来自动处理。在一个简单的用例中,通常默认实现一个由单个控制平面和位于同一位置的多个工作节点组成的 Kubernetes 集群。这种类型的配置在发生故障时不提供任何高可用性。

由于本方案侧重于 Google Kubernetes Engine (GKE) 作为云提供商,因此位置是指特定“区域”内的“地区”。“区域”是指特定的地理位置,例如 us-east1。“区域”内有多个“地区”(例如 us-east1-bus-east1-c 等),它们与其他地区隔离。您应该根据您的特定需求选择区域和地区,例如 CPU 和 GPU 类型、磁盘类型、计算能力等。您可以在 Google 的 区域和地区 文档中找到您应该查看的更深入的信息。

除了单地区集群之外,GKE 还支持其他两种类型的集群

  • 多区域集群:控制平面的单个副本在单个区域中运行,而工作节点在给定区域的多个区域中运行。
  • 区域集群:控制平面的多个副本在给定区域内的多个区域中运行,节点在每个控制平面所在的同一区域中运行。

本指南重点介绍“区域集群”,因为它比“单区域”或“多区域”集群提供更高级别的可用性。

先决条件

需要一个 Google Cloud (GCP) 帐户,该帐户具有创建新的 Google Kubernetes Engine (GKE) 集群的权限。提供了一个 Web 界面来与 GCP 和 GKE 进行交互。还有一个命令行界面,您可以从 Google Cloud SDK 获取。需要安装标准工具(例如 Kubectl)才能与 Kubernetes 集群进行交互。

创建集群

在本指南中,我们使用 GKE Web 控制台来创建集群。在大多数情况下,我们使用默认值,但位置类型和实例大小除外。创建新集群时,要选择的第一个信息是“集群基础知识”。以下屏幕截图显示了为此演示选择的选项

Regional Cluster Basics

本节的重要部分是

  • 选择 区域 作为 位置类型
  • 选择了 区域 - 在本例中为 us-east1。此值应根据您的具体要求确定,如前所述。
  • 指定默认节点位置 已选择三个区域。自动选择了三个区域。但是,当超过三个时,本节允许明确选择区域。

您可以根据需要自定义本节中的其他值(例如 名称主版本 设置)。

接下来,在 节点池 -> default-pool节点 子部分中选择机器类型,如下图所示

Regional Cluster Nodes

本节的主要变化是选择 n1-standard-4(4 个 vCPU,15GB 内存) 的机器类型。这为我们提供了比默认值更大的工作空间。您可以根据需要自定义设置。

节点数量和机器类型根据您对基本需求以及故障转移容差的具体要求而异。例如,您可以根据预期分布在这些节点上的应用程序数量来确定所选配置的大小。尽管如此,如果出现节点甚至一个或多个区域故障,则集群的大小应能够支持这种额外负载。否则,在容量可用之前,工作负载将无法调度。可以实施各种策略 - 例如,预先确定大小、使用 集群自动缩放器 等。

完成自定义后,您可以通过单击 **创建** 按钮来创建集群,如下图所示

Create Regional Cluster

虽然使用 GKE UI 来自定义集群配置很方便,但也可以从 Google Cloud CLI 完成相同的操作。生成此命令的一种便捷方法是单击 命令行 链接。这样做会创建相应的 gcloud CLI 命令,该命令可用于创建相同的集群配置。

创建集群后,会在三个区域中的每个区域部署三个工作节点,总共九个工作节点。请注意,GKE 不提供访问控制平面节点的功能。

最后,需要通过 gcloud CLI 获取凭据,以便 kubectl 可以与集群交互。为此,请运行以下命令

gcloud container clusters get-credentials regional-demo --zone us-east1 --project PROJECT_ID

PROJECT_ID 替换为您的 GKE 项目 ID。此外,为了更容易识别上下文名称,请运行以下命令

kubectl config rename-context gke_PROJECT_ID_us-east1_regional-demo regional-demo

使用 kubectl 验证是否设置了正确的当前上下文(由 * 指示)

kubectl config get-contexts
CURRENT   NAME            CLUSTER                                                   AUTHINFO                                                  NAMESPACE
*         regional-demo   gke_PROJECT_ID_us-east1_regional-demo   gke_PROJECT_ID_us-east1_regional-demo

验证集群创建

验证工作节点是否可用

kubectl get nodes
NAME                                           STATUS   ROLES    AGE   VERSION
gke-regional-demo-default-pool-e121c001-k667   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-e121c001-zhrt   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-e121c001-zpv4   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-ea10f422-5f72   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-ea10f422-ntdk   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-ea10f422-vw3c   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-fb3e6608-0lx2   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-fb3e6608-0rcc   Ready    <none>   13m   v1.16.9-gke.2
gke-regional-demo-default-pool-fb3e6608-2qsk   Ready    <none>   13m   v1.16.9-gke.2

如图所示,共有九个节点,每个池中三个。

每个节点都有一个由 failure-domain.beta.kubernetes.io/zone 键应用的标签,以及它所在区域的值。要确定哪些节点位于哪些区域,我们可以选择标签 - 例如

kubectl get nodes -l failure-domain.beta.kubernetes.io/zone=us-east1-b
NAME                                           STATUS   ROLES    AGE   VERSION
gke-regional-demo-default-pool-ea10f422-5f72   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-ea10f422-ntdk   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-ea10f422-vw3c   Ready    <none>   29m   v1.16.9-gke.2
kubectl get nodes -l failure-domain.beta.kubernetes.io/zone=us-east1-c
NAME                                           STATUS   ROLES    AGE   VERSION
gke-regional-demo-default-pool-e121c001-k667   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-e121c001-zhrt   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-e121c001-zpv4   Ready    <none>   29m   v1.16.9-gke.2
kubectl get nodes -l failure-domain.beta.kubernetes.io/zone=us-east1-d
NAME                                           STATUS   ROLES    AGE   VERSION
gke-regional-demo-default-pool-fb3e6608-0lx2   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-fb3e6608-0rcc   Ready    <none>   29m   v1.16.9-gke.2
gke-regional-demo-default-pool-fb3e6608-2qsk   Ready    <none>   29m   v1.16.9-gke.2

部署 Spring Cloud Data Flow

至此,我们有了一个功能齐全的多节点集群,分布在一个区域的三个区域中,每个区域有三个工作节点。

Spring Cloud Data Flow 提供了用于部署 Data Flow、Skipper 和服务依赖项(例如数据库和消息中间件)的清单文件。这些文件位于 Spring Cloud Data Flow Git 存储库的 src/kubernetes 目录中。

您可以按原样应用相关文件,让 Kubernetes 处理它们应该安排的位置,但您也可以使用标准 Kubernetes 结构修改要部署这些应用程序的位置、实例数量等。

在本方案中,我们为部署实现了以下用例

  • 应该部署三个 Skipper 副本,每个区域一个副本。
  • 应该部署三个数据流副本,每个区域一个副本,与 Skipper 位于同一节点上。
  • MySQL 用作数据库,并放置在特定区域中。
  • RabbitMQ 用作消息中间件,并放置在特定区域中。

部署 MySQL

在本方案中,MySQL 的实例将部署到单个区域。有关更多详细信息,请参阅 MySQL 产品的相关高可用性文档。如果区域发生故障,MySQL 可能不可用。为 HA 设置 MySQL 超出了 SCDF 和本方案的范围。

MySQL 的部署包含一个副本。MySQL 使用持久存储,并且对该存储的创建和访问受 区域集群中的持久存储 中概述的规则的约束。除非定义了引用应在其中创建它的特定区域的 StorageClass,否则 GKE 会选择一个随机区域。然后,将在 MySQL 的 PVC 配置中引用自定义 StorageClass

GKE 会自动创建一个默认的 StorageClass,为简单起见,我们将使用它。任何引用已配置磁盘的 Pod 都会自动安排在配置磁盘的同一区域中。由于我们在每个区域中有三个节点,因此如果一个节点发生故障,MySQL Pod 将被重新安排到该区域中的另一个节点。

部署清单

kubectl create -f mysql/
deployment.apps/mysql created
persistentvolumeclaim/mysql created
secret/mysql created
service/mysql created

获取卷名

kubectl get pvc/mysql
NAME    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql   Bound    pvc-24f2acb5-17cd-45e1-8064-34bf602e408f   8Gi        RWO            standard       3m1s

检查卷所在的区域

kubectl get pv/pvc-24f2acb5-17cd-45e1-8064-34bf602e408f -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-d

检查 MySQL Pod 以验证它是否正在运行以及它被分配到的节点

kubectl get pod/mysql-b94654bd4-9zpt2 -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
NAME                    STATUS    NODE
mysql-b94654bd4-9zpt2   Running   gke-regional-demo-default-pool-fb3e6608-0rcc

最后,验证节点所在的区域

kubectl get node gke-regional-demo-default-pool-fb3e6608-0rcc -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-d

部署 RabbitMQ

在本方案中,RabbitMQ 的实例将部署到单个区域。有关更多详细信息,请参阅 RabbitMQ 产品的相关高可用性文档。如果区域发生故障,RabbitMQ 可能不可用。为 HA 设置 RabbitMQ 超出了 SCDF 和本方案的范围。

RabbitMQ 的部署包含一个副本。与 MySQL 一样,提供的清单不配置任何持久存储。要将 RabbitMQ 放置在特定区域中,我们可以使用简单的 nodeSelector

Pod 被安排到的节点可能不是问题所在,这仅仅意味着 Pod 驻留在特定区域中。集群中的所有节点都会自动分配标签。其中一个标签表示节点所在的区域,我们将使用它。

要将 RabbitMQ 放置在 us-east1-b 区域的节点上,请对 rabbitmq/rabbitmq-deployment.yaml 文件进行以下修改

    spec:
      nodeSelector:
        failure-domain.beta.kubernetes.io/zone: us-east1-b

部署清单

kubectl create -f rabbitmq/
deployment.apps/rabbitmq created
service/rabbitmq created

获取 Pod 部署到的节点

kubectl get pod/rabbitmq-6d65f675d9-4vksj -o jsonpath='{.spec.nodeName}'
gke-regional-demo-default-pool-ea10f422-5f72

获取节点所在的区域

kubectl get node gke-regional-demo-default-pool-ea10f422-5f72 -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-b

部署 Skipper

Skipper 的部署包含三个副本。不需要持久存储,我们希望每个区域都运行一个副本。多个副本不应驻留在同一区域。一种方法是使用 Pod 反亲和性

skipper/skipper-deployment.yaml 文件进行以下修改,增加副本数量并添加 Pod 反亲和性

spec:
  replicas: 3
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - skipper
            topologyKey: failure-domain.beta.kubernetes.io/zone

部署清单

kubectl create -f server/server-roles.yaml
kubectl create -f server/server-rolebinding.yaml
kubectl create -f server/service-account.yaml
kubectl create -f skipper/skipper-config-rabbit.yaml
kubectl create -f skipper/skipper-deployment.yaml
kubectl create -f skipper/skipper-svc.yaml

获取 Pod 所部署到的节点

kubectl get pods -l app=skipper -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
NAME                       STATUS    NODE
skipper-6fd7bb796c-flm44   Running   gke-regional-demo-default-pool-e121c001-zhrt
skipper-6fd7bb796c-l99dj   Running   gke-regional-demo-default-pool-ea10f422-5f72
skipper-6fd7bb796c-vrf9m   Running   gke-regional-demo-default-pool-fb3e6608-0lx2

获取节点所在的区域

kubectl get node gke-regional-demo-default-pool-e121c001-zhrt -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-c
kubectl get node gke-regional-demo-default-pool-ea10f422-5f72 -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-b
kubectl get node gke-regional-demo-default-pool-fb3e6608-0lx2 -o jsonpath='{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}'
us-east1-d

部署数据流

数据流的部署包含三个副本。不需要持久存储,我们希望每个区域都运行一个副本。多个副本不应驻留在同一区域。此外,由于数据流经常调用 Skipper,我们希望将其共置在每个区域的同一节点上。一种方法是使用 Pod 亲和性

server/server-deployment.yaml 文件进行以下修改,增加副本数量并添加 Pod 亲和性

spec:
  replicas: 3
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - skipper
        topologyKey: kubernetes.io/hostname

部署清单

kubectl create -f server/server-config.yaml
kubectl create -f server/server-svc.yaml
kubectl create -f server/server-deployment.yaml

获取 Pod 所部署到的节点

kubectl get pods -l app=scdf-server -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
NAME                           STATUS    NODE
scdf-server-5ddf7bbd4f-dpnmm   Running   gke-regional-demo-default-pool-fb3e6608-0lx2
scdf-server-5ddf7bbd4f-hlf9h   Running   gke-regional-demo-default-pool-e121c001-zhrt
scdf-server-5ddf7bbd4f-vnjh6   Running   gke-regional-demo-default-pool-ea10f422-5f72

验证 Pod 是否部署到与 Skipper 相同的节点

kubectl get pods -l app=skipper -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
NAME                       STATUS    NODE
skipper-6fd7bb796c-flm44   Running   gke-regional-demo-default-pool-e121c001-zhrt
skipper-6fd7bb796c-l99dj   Running   gke-regional-demo-default-pool-ea10f422-5f72
skipper-6fd7bb796c-vrf9m   Running   gke-regional-demo-default-pool-fb3e6608-0lx2

验证与数据流的连接

SCDF_IP=$(kubectl get svc/scdf-server -o jsonpath='{.status.loadBalancer.ingress[*].ip}')
curl -s http://$SCDF_IP/about | jq
{
  "featureInfo": {
    "analyticsEnabled": true,
    "streamsEnabled": true,
    "tasksEnabled": true,
    "schedulesEnabled": true,
    "grafanaEnabled": true
  },
  "versionInfo": {
    "implementation": {
      "name": "spring-cloud-dataflow-server",
      "version": "2.7.0-SNAPSHOT"
    },
...
...
...

部署流和任务

与服务器组件一样,通过数据流部署的流和任务也可以从各种放置选项中受益。在某些情况下,某些流或任务可能只能部署到特定区域,甚至是节点本身。部署的流和任务与任何其他应用程序一样,因此它们也可以受益于 Kubernetes 在发生故障时将它们重新安排到其他节点上。

由于流和任务是由 Data Flow 和 Skipper 部署到 Kubernetes 的,而不是将这些功能应用于 YAML 清单,因此它们是通过部署程序属性设置的。有关可用部署程序属性的完整列表,请参阅 Spring Cloud Data Flow 参考手册的部署程序属性部分。

感兴趣的部署程序属性如下

节点选择器

  • deployment.nodeSelector

容忍度

  • tolerations.key
  • tolerations.effect
  • tolerations.operator
  • tolerations.tolerationSeconds
  • tolerations.value

节点亲和性

  • affinity.nodeAffinity

Pod 亲和性

  • affinity.podAffinity

Pod 反亲和性

  • affinity.podAntiAffinity

有关如何在应用程序或服务器级别设置部署属性的具体示例,请参阅参考手册的容忍度部分。相同的模式也适用于其他属性,但属性名称和值不同。

结论

在本教程中,我们在 GKE 中设置了一个区域集群,提供了跨区域多个可用区提供高可用性控制平面和工作器的基础架构。我们使用节点选择器、Pod 亲和性和 Pod 反亲和性等结构探索了应用程序放置的不同选项。每个应用程序和环境都有其自身的特殊需求,但本教程应该为如何将标准 Kubernetes 结构与 Data Flow 结合使用提供一个起点。