30. 与 StatefulSets 的多集群通信

Linkerd多集群(multi-cluster)扩展通过在集群之间“镜像”服务信息来工作。 目标集群中导出的服务将被镜像为 clusterIP 副本。 默认情况下,每个导出的服务都将镜像为 clusterIP。 当运行需要 headless 服务的工作负载时, 例如 StatefulSetsLinkerd 的多集群扩展可以配置为支持 headless 服务以保留服务类型。 导出的 headless 服务将在源集群中镜像为 headless, 保留 DNS 记录创建等功能和寻址单个 Pod 的能力。

本指南将引导您安装和配置 Linkerd 以及支持 headless 服务的多集群扩展, 并将举例说明如何在目标集群中部署 StatefulSet。 部署后,我们还将研究如何从源集群中 的客户端目标集群StatefulSet 中的任意 Pod 进行通信。 有关 headless 服务的多集群支持如何工作的更详细概述, 请查看多集群通信

前提条件

  • 两个 Kubernetes 集群。它们将被称为 eastwest, 其中 east 分别是“源”集群和“west”是目标集群。 这些可以在任何云或本地环境中,本指南将使用 k3d 配置两个本地集群。
  • smallstep/CLILinkerd 安装生成证书。
  • linkerd:stable-2.11.0 来安装 Linkerd

为了帮助创建和安装集群,有一个 demo 存储库可用。 在整个指南中,我们将使用存储库中的脚本,但您可以在不克隆或使用脚本的情况下继续操作。

安装具有 headless 支持的 Linkerd 多集群

为了开始我们的 demo 并查看实践中的所有内容,我们将经历一个多集群场景, 其中 east 集群中的 pod 将尝试与来自 west 集群的任意 pod 通信。

第一步是在本地机器上克隆 demo 存储库。

# clone example repository
$ git clone git@github.com:mateiidavid/l2d-k3d-statefulset.git
$ cd l2d-k3d-statefulset

第二步包括创建两个名为 eastwestk3d 集群,其中 east 集群是源,west 集群是目标。 创建集群时,我们需要一个共享的信任根。幸运的是,您刚刚克隆的存储库包含一些脚本,可以大大简化一切。

# create k3d clusters
$ ./create.sh

# list the clusters
$ k3d cluster list
NAME   SERVERS   AGENTS   LOADBALANCER
east   1/1       0/0      true
west   1/1       0/0      true

创建集群后,我们将安装 Linkerdmulti-cluster 扩展。 最后,一旦两者都安装完毕,我们需要将两个集群链接在一起,以便镜像它们的服务。 为了启用对 headless 服务的支持, 我们将额外的 --set "enableHeadlessServices=true flag 传递给 linkerd multicluster link。 和以前一样,这些步骤是通过提供的脚本自动执行的,可随时查看!

# Install Linkerd and multicluster, output to check should be a success
$ ./install.sh

# Next, link the two clusters together
$ ./link.sh

完美的!如果你已经做到了这一点,没有任何错误,那么这是一个好兆头。 在下一章中,我们将部署一些服务并了解通信是如何工作的。

Pod-to-Pod: 从 east, 到 west

完成安装步骤后,我们现在可以专注于 podpod 的通信。 首先,我们将部署我们的 pod服务

  • 我们将在 eastwest 中对默认命名空间进行 mesh 划分。
  • west,我们将部署一个 nginx StatefulSet 和它自己的 headless 服务 nginx-svc
  • east,我们的脚本将部署一个 curl pod,然后将用于 curl nginx service

    # deploy services and mesh namespaces
    $ ./deploy.sh
    
    # verify both clusters
    #
    # verify east
    $ kubectl --context=k3d-east get pods
    NAME                    READY   STATUS        RESTARTS   AGE
    curl-56dc7d945d-96r6p   2/2     Running       0          7s
    
    # verify west has headless service
    $ kubectl --context=k3d-west get services
    NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   10m
    nginx-svc    ClusterIP   None         <none>        80/TCP    8s
    
    # verify west has statefulset
    #
    # this may take a while to come up
    $ kubectl --context=k3d-west get pods
    NAME          READY   STATUS    RESTARTS   AGE
    nginx-set-0   2/2     Running   0          53s
    nginx-set-1   2/2     Running   0          43s
    nginx-set-2   2/2     Running   0          36s

在我们继续之前,让我们看一下 nginx-svc 的端点对象:

$ kubectl --context=k3d-west get endpoints nginx-svc -o yaml
...
subsets:
- addresses:
  - hostname: nginx-set-0
    ip: 10.42.0.31
    nodeName: k3d-west-server-0
    targetRef:
      kind: Pod
      name: nginx-set-0
      namespace: default
      resourceVersion: "114743"
      uid: 7049f1c1-55dc-4b7b-a598-27003409d274
  - hostname: nginx-set-1
    ip: 10.42.0.32
    nodeName: k3d-west-server-0
    targetRef:
      kind: Pod
      name: nginx-set-1
      namespace: default
      resourceVersion: "114775"
      uid: 60df15fd-9db0-4830-9c8f-e682f3000800
  - hostname: nginx-set-2
    ip: 10.42.0.33
    nodeName: k3d-west-server-0
    targetRef:
      kind: Pod
      name: nginx-set-2
      namespace: default
      resourceVersion: "114808"
      uid: 3873bc34-26c4-454d-bd3d-7c783de16304

我们可以看到,基于端点对象,该服务具有三个端点,每个端点都有一个地址(或 IP), 其主机名对应于一个 StatefulSet pod。 如果我们直接对这些端点中的任何一个进行 curl,我们将得到答复。 我们可以通过将 curl pod 应用于 west 集群来测试这一点:

$ kubectl --context=k3d-west apply -f east/curl.yml
$ kubectl --context=k3d-west get pods
NAME                    READY   STATUS            RESTARTS   AGE
nginx-set-0             2/2     Running           0          5m8s
nginx-set-1             2/2     Running           0          4m58s
nginx-set-2             2/2     Running           0          4m51s
curl-56dc7d945d-s4n8j   0/2     PodInitializing   0          4s

$ kubectl --context=k3d-west exec -it curl-56dc7d945d-s4n8j -c curl -- bin/sh
/$ # prompt for curl pod

如果我们现在 curl 这些实例之一,我们将得到一个 response

# exec'd on the pod
/ $ curl nginx-set-0.nginx-svc.default.svc.west.cluster.local
"<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>"

现在,让我们做同样的事情,但这次是从 east 集群。我们将首先导出服务。

$ kubectl --context=k3d-west label service nginx-svc mirror.linkerd.io/exported="true"
service/nginx-svc labeled

$ kubectl --context=k3d-east get services
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes         ClusterIP   10.43.0.1       <none>        443/TCP   20h
nginx-svc-west     ClusterIP   None            <none>        80/TCP    29s
nginx-set-0-west   ClusterIP   10.43.179.60    <none>        80/TCP    29s
nginx-set-1-west   ClusterIP   10.43.218.18    <none>        80/TCP    29s
nginx-set-2-west   ClusterIP   10.43.245.244   <none>        80/TCP    29s

如果我们查看 endpoints 对象,我们会注意到一些奇怪的东西, nginx-svc-west 的端点将具有相同的主机名,但每个主机名将指向我们上面看到的服务之一:

$ kubectl --context=k3d-east get endpoints nginx-svc-west -o yaml
subsets:
- addresses:
  - hostname: nginx-set-0
    ip: 10.43.179.60
  - hostname: nginx-set-1
    ip: 10.43.218.18
  - hostname: nginx-set-2
    ip: 10.43.245.244

这就是我们在教程开始时概述的内容。 来自目标集群(west)的每个 pod 都将被镜像为一个 clusterIP 服务。 我们稍后会看到为什么这很重要。

$ kubectl --context=k3d-east get pods
NAME                    READY   STATUS    RESTARTS   AGE
curl-56dc7d945d-96r6p   2/2     Running   0          23m

# exec and curl
$ kubectl --context=k3d-east exec pod curl-56dc7d945d-96r6p -it -c curl -- bin/sh
# we want to curl the same hostname we see in the endpoints object above.
# however, the service and cluster domain will now be different, since we
# are in a different cluster.
#
/ $ curl nginx-set-0.nginx-svc-west.default.svc.east.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

如您所见,我们得到了相同的回复! 但是,nginx 在不同的集群中。那么,幕后发生了什么?

  1. 当我们镜像 headless 服务时,我们为每个 pod 创建了一个 clusterIP 服务。 由于服务创建 DNS 记录, 因此使用来自目标的 hostname 命名每个端点为我们提供了这些 pod FQDN (nginx-set-0.(...).cluster.local)。
  2. Curlpod DNS 名称解析为 IP 地址。在我们的例子中,这个 IP10.43.179.60
  3. 一旦请求在进行中,linkerd2-proxy 就会拦截它。 它查看 IP 地址并将其与我们的 clusterIP 服务相关联。 服务本身指向网关,因此代理将请求转发到目标集群网关。这是通常的多集群场景。
  4. 目标集群中的网关查看请求并查找原始目标地址。 在我们的例子中,由于这是一个“端点镜像(endpoint mirror)”, 它知道它必须转到同一集群中的 nginx-set-0.nginx-svc
  5. 请求再次由网关转发到 pod,然后返回响应。

就是这样!您现在可以跨集群向 pod 发送请求。 查询 3StatefulSet pod 中的任何一个都应该有相同的结果。

清理

要进行清理,您可以使用 k3d CLI 完全删除两个集群:

$ k3d cluster delete east
cluster east deleted
$ k3d cluster delete west
cluster west deleted