kubectl 等待 AWS EKS 上的服务公开 .status.loadBalancer.ingress 字段中报告的弹性负载均衡器 (ELB) 地址

kubectl wait for Service on AWS EKS to expose Elastic Load Balancer (ELB) address reported in .status.loadBalancer.ingress field

作为the kubernetes.io docs state about a Service of type LoadBalancer:

On cloud providers which support external load balancers, setting the type field to LoadBalancer provisions a load balancer for your Service. The actual creation of the load balancer happens asynchronously, and information about the provisioned balancer is published in the Service's .status.loadBalancer field.

在 AWS Elastic Kubernetes Service (EKS) 上提供了一个 AWS 负载均衡器来平衡网络流量 (see AWS docs & the example project on GitHub provisioning a EKS cluster with Pulumi). Assuming we have a Deployment ready with the selector app=tekton-dashboard (it's the default Tekton dashboard you can deploy as stated in the docs),Service 类型 LoadBalancer 定义在 tekton-dashboard-service.yml 可能看起来像这样:

apiVersion: v1
kind: Service
metadata:
  name: tekton-dashboard-external-svc-manual
spec:
  selector:
    app: tekton-dashboard
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9097
  type: LoadBalancer

如果我们使用 kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines 在我们的集群中创建服务,AWS ELB 会自动创建:

只有一个问题:.status.loadBalancer 字段由 ingress[0].hostname 字段异步填充,因此无法立即使用。我们可以检查这一点,如果我们 运行 以下命令一起使用:

kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines && \
kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}'

输出将为空字段:

{}%

因此,如果我们想 运行 在 CI 管道中进行此设置(例如 GitHub Actions, see the example project's workflow provision.yml), 我们需要以某种方式等待 .status.loadBalancer 字段填充了 AWS ELB 的主机名。 我们如何使用 kubectl wait 实现此目的?

TLDR;

Prior to Kubernetes v1.23 使用 kubectl wait 是不可能的,但是像这样将 untilgrep 一起使用:

until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep "ingress"; do : ; done

甚至增强命令 using timeout (brew install coreutils on a Mac) 以防止命令无限地 运行:

timeout 10s bash -c 'until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep "ingress"; do : ; done'

kubectl wait的问题&详细解释的解决方案

and the kubernetes issues kubectl wait unable to not wait for service ready #80828 & kubectl wait on arbitrary jsonpath #83094 所述,目前的 Kubernetes 版本无法使用 kubectl wait

主要原因是,kubectl wait 假设使用 kubectl get service/xyz --output=yaml 查询的 Kubernetes 资源的 status 字段包含一个 conditions 列表。 Service 没有。在这里使用 jsonpath 将是一个解决方案,并且可以从 Kubernetes v1.23 开始(参见 this merged PR)。但在这个版本在像 EKS 这样的托管 Kubernetes 集群中广泛可用之前,我们需要另一种解决方案。它也应该像 kubectl wait 一样作为“单行”提供。

这个超级用户关于 "watching" the output of a command until a particular string is observed and then exit:

的回答可能是一个很好的起点
until my_cmd | grep "String Im Looking For"; do : ; done

如果我们将这种方法与 kubectl get 一起使用,我们可以制作一个命令,该命令将等待字段 ingress 填充到 [=26= 中的 status.loadBalancer 字段]:

until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep "ingress"; do : ; done

这将等到 ingress 字段被填充,然后打印出 AWS ELB 地址(例如,之后通过使用 kubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'):

$ until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep "ingress"; do : ; done
{"ingress":[{"hostname":"a74b078064c7d4ba1b89bf4e92586af0-18561896.eu-central-1.elb.amazonaws.com"}]}

现在我们有一个单行命令,它的行为就像 kubectl wait 一样,我们的 Service 可以通过 AWS 负载均衡器使用。我们可以仔细检查这是否与以下命令结合使用(请务必在执行之前使用 kubectl delete service/tekton-dashboard-external-svc-manual -n tekton-pipelines 删除服务,否则包括 AWS LoadBalancer 在内的服务已经存在):

kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines && \
until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep "ingress"; do : ; done && \
kubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'

Here's also a full GitHub Actions pipeline run如果你有兴趣。