[译] 如何实现无 Pod 的 Kubernetes 和 Istio 部署

探索如何将 Kubernetes 和 Istio 的完整功能嵌入到单一二进制文件中,实现无 Pod 的极简部署方案。

声明
此文为个人翻译,仅供参考,不代表我个人立场。翻译过程中可能有删改或遗漏,如需了解原文,请自行查阅。如有疏漏,欢迎指正。
查看本文大纲

Kubernetes 经常被批评(有些不公平)操作起来过于复杂,促使大多数人依赖托管服务。然而,k3s 某种程度上颠覆了这一点,将完整的 Kubernetes 发行版打包成一个二进制文件。这非常方便,特别是在物联网等小型环境中运行时;虽然隔离组件对非常大规模、先进的部署有好处,但对较小的环境来说,操作微服务可能只是一种负担——这正是 Istio 多年前选择重构为更单体架构的原因

然而,它还是没有那么“精简”。在一个空集群中运行 k3d cluster create test 后,我们会在集群中看到各种 pod:

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                      READY   STATUS     RESTARTS  AGE
kube-system   local-path-provisioner-6c86858495-gc9jq   1/1     Running    0         2m18s
kube-system   coredns-6799fbcd5-pdf4b                   1/1     Running    0         2m18s
kube-system   helm-install-traefik-crd-cp9s2            0/1     Completed  0         2m18s
kube-system   helm-install-traefik-pch7c                0/1     Completed  1         2m18s
kube-system   traefik-f4564c4f4-q4lkj                   1/1     Running    0         2m8s
kube-system   metrics-server-54fd9b65b-d69w6            1/1     Running    0         2m18s
kube-system   svclb-traefik-58c5bb65-sq54b              2/2     Running    0         2m8s

k3d 是一个方便的工具,可以在 Docker 内部部署 k3s,便于测试。

这是怎么回事?我们的“单二进制 Kubernetes”怎么变成了 6 个不同的容器?

虽然 k3s 将许多组件(kube-proxyflannelcontainerdkubelet 等)嵌入到一个二进制文件中,但其他组件则作为标准 pod 在集群中运行。

此外,一旦我们部署了我们最喜欢的 服务网格,我们将会有更多的 pod,使我们离没有 pod 的目标更远。

没有 pod 的 Kubernetes?

那么问题是——我们能否通过进一步推进 k3s 的理念,将完整的集群功能嵌入到一个二进制文件中,来获得一个功能齐全的 Kubernetes 和 Istio 部署?

警告:这些是实验性概念;绝不要在生产环境中尝试!

首先,我们可以直接去除一些不必要的组件,如 servicelb(负载均衡服务需要)、traefik(Ingress 需要)、local-storage(PVC 需要)和 metrics-serverkubectl top 需要)。

这就剩下 coredns 和 Istio。

如果我们追求极简,我们肯定会希望使用 Istio 的 ambient mode,它完全不需要 sidecar。幸运的是,它开箱即用并且有完整的 DNS 支持。这让我们可以去掉 coredns

这样一来,如果我们能运行 Istio ambient,就可以去掉 kube-system 中的所有内容。这相对简单;难点在于不为 Istio 添加更多的 pod。

嵌入 Istio

通过 k3s 的一个分支,我修改了它,使 Istio 本身嵌入到 k3s 中。k3s 可以作为服务器和/或代理运行。通常你会有 1 个服务器,每个其他节点作为代理运行。

server 上,我们希望运行 Istiod(Istio 的控制平面)。在代理上,我们希望运行 istio-cni(每个节点的控制平面)和 ztunnel(每个节点的数据平面)。

这三个组件都可以直接嵌入到 k3s 中,只需一些工作!

使用这个自定义构建,我们可以通过一些自定义配置启动一个新的 k3d 集群,禁用我们不再需要的组件:

apiVersion: k3d.io/v1alpha5
kind: Simple
metadata:
  name: podless
servers: 1
agents: 1
options:
  k3d:
    wait: true
    timeout: "60s"
    disableLoadbalancer: true
    disableRollback: true
  k3s:
    extraArgs:
      - arg: --disable-cloud-controller
        nodeFilters:
          - server:*
      - arg: --disable-kube-proxy
        nodeFilters:
          - server:*
      - arg: --disable-network-policy
        nodeFilters:
          - server:*
      - arg: --disable-helm-controller
        nodeFilters:
          - server:*
      - arg: --disable=coredns,servicelb,traefik,local-storage,metrics-server
        nodeFilters:
          - server:*

这里我们禁用了上面看到的所有 pod,包括一些额外的。

一个显著的例子是 kube-proxy。像其他一些项目一样(如 Cilium),Istio 的 ztunnel 可以有效地替代大多数用例中的 kube-proxy

无 pod 的服务网格

所有配置就绪后,我们的集群是什么样子?

$ kubectl get pods --all-namespaces
No resources found

到目前为止一切顺利….当然,什么都不运行很容易;真正的挑战是保持集群的功能。

让我们部署一些应用程序 pod。再次强调,这些是集群中的唯一 pod:

$ kubectl get pods --all-namespaces
NAMESPACE   NAME                     READY   STATUS    RESTARTS   AGE
default     shell-5fff89ccf5-98kgg   1/1     Running   0          19s
default     echo-66d88ff694-9qprp    1/1     Running   0          14s

然后我们可以发送流量:

$ kubectl exec deploy/shell -- curl -s echo
RequestHeader=Accept:*/*
RequestHeader=User-Agent:curl/8.5.0
Hostname=echo-66d88ff694-9qprp

流量完全正常,包括服务流量(以前由 kube-proxy 处理)和 DNS(以前由 coredns 处理)。现在这些全部由 ztunnel 处理,并且所有内容都通过安全的 mTLS 传输。

除了 mTLS 加密,我们还可以基于 mTLS 身份应用策略。同样,这些都由 ztunnel 执行。

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: allow-default
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: echo
  rules:
  - from:
    - source:
        namespace: ["cluster.local/ns/default/sa/shell"]

现在 default 命名空间的流量被允许,但其他流量不被允许。我们可以通过从 shell 发送流量以及我在 other 命名空间中部署的新测试工作负载来验证这一点:

$ kubectl exec deploy/shell -- curl -s echo
RequestHeader=Accept:*/*
RequestHeader=User-Agent:curl/8.5.0
Hostname=echo-66d88ff694-9qprp
$ kubectl exec deploy/shell -n other -- curl -s echo
command terminated with exit code 56

正如预期的那样,我们的其他应用程序被拒绝了!

此外,如果我们愿意,我们可以将流量升级通过完整的 HTTP 代理(“waypoint”):

$ istioctl x waypoint apply --enroll-namespace
waypoint default/waypoint applied

$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
echo-66d88ff694-czd65       1/1     Running  

 0          93m
shell-56bd5dbdbf-f4gh9      1/1     Running   0          93m
waypoint-7cd4dc789f-2s7z2   1/1     Running   0          41s

$ kubectl exec deploy/shell -- curl -s echo
RequestHeader=Accept:*/*
RequestHeader=User-Agent:curl/8.5.0
RequestHeader=X-Request-Id:18d72190-9caa-4162-8bc5-4c11518d7568
Hostname=echo-66d88ff694-czd65

现在我们的 waypoint 已经部署,所有到命名空间的流量会自动转发到它,在那里可以执行完整的 HTTP 策略。这里,我们可以看到 X-Request-Id 被添加到我们的请求中,但我们还可以获得 自动配置的其他功能,以及更多 我们可以配置的内容

总结

最终,我们能够部署一个完整的 Kubernetes 集群和服务网格,所有基础设施组件嵌入到一个隐藏的节点二进制文件中——集群功能不需要 pod。

这实际操作起来是否实用?不太实用。然而,这确实表明 Kubernetes/Istio 被认为过于臃肿和复杂的看法并不完全准确。

它真的比典型的集群更简单吗?某种程度上是的……我们确实替换了两个组件(kube-proxycoredns),但其余的我们基本上只是隐藏和打包。这显然不如完全替换有意义,但也不错。话虽如此,隐藏东西对 社交媒体参与度 有好处,而 k3s 通过有效地隐藏和打包取得了巨大成功,因此显然提供了一些实实在在的好处。

最后更新于 2024/09/18