延迟应用启动,直到Sidecar准备就绪

Kubernetes在Pod中启动容器的方式出乎意料。在检查了源代码以确认我所看到的内容之后,我意识到我刚刚找到了Istio Service Mesh中一个长期存在的问题的解决方案。

我相信大多数Kubernetes用户都假定Pod的初始化容器完成后,将并行启动Pod的常规容器。事实并非如此。

如果检查启动容器的Kubelet代码,则会注意到它是按顺序执行的。该代码在一个线程中执行,并按照容器在pod的spec.containers数组中列出的顺序启动容器。

    // Step 7: start containers in podContainerChanges.ContainersToStart.
    for _, idx := range podContainerChanges.ContainersToStart {
        start("container", containerStartSpec(&pod.Spec.Containers[idx]))
    }

但是,假设容器镜像已经存储在本地,则在Kubelet启动第一个容器之后,Kubelet启动第二个容器所花费的时间可以忽略不计。实际上,它们都是同时启动的。

当一个容器依赖于另一个容器并要求它完全启动才能运行时,这是不理想的。 Istio Proxy sidecar容器就是一个例子。由于应用程序的传出通信是通过代理路由的,因此在启动应用程序本身之前,代理必须已启动并正在运行。

您可以在应用程序容器中添加一个Shell脚本,以等待代理启动,然后运行该应用程序的可执行文件。但这需要更改应用程序本身。理想情况下,我们希望在不对应用程序或其容器镜像进行任何更改的情况下将代理注入Pod。

事实证明,这可以通过利用Kubernetes中的同步容器启动来完成。

首先,我们需要将代理指定为spec.containers中的第一个容器,但这只是解决方案的一部分,因为它只能确保首先启动代理容器,而不会等待其准备就绪。其他容器立即启动,从而导致容器之间的竞争状态。我们需要防止Kubelet在代理准备好之前启动其他容器。

这是启动后生命周期钩子出现的地方。事实证明,启动容器的Kubelet代码会阻止下一个容器的启动,直到启动后处理程序终止为止。

我们可以利用这种行为。不仅在Istio中,而且在必须启动Sidecar容器并准备就绪的每个Pod中,应用程序容器才能启动。

简而言之,Sidecar启动问题的解决方案如下:

如果sidecar容器提供了一个等待该sidecar就绪的可执行文件,则可以在容器的启动后挂钩中调用该文件,以阻止pod中其余容器的启动。

下图应该可以帮助您直观地看到容器中发生的情况。

延迟应用启动,直到Sidecar准备就绪_第1张图片

具体yaml如下:

apiVersion: v1
kind: Pod
metadata:
  name: sidecar-starts-first
spec:
  containers:
  - name: sidecar
    image: my-sidecar
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/wait-until-ready.sh
  - name: application
    image: my-application

但是,这种方法并不完美。如果在启动后挂钩中调用的命令或容器的主进程失败,则其他容器将立即启动。尽管如此,在Kubernetes引入对sidecar container的适当支持之前,这应该是一个好的解决方法。或者直到有人决定更改Kubelet的行为,并使其在单独的goroutine中启动容器。

尽管此技术可以解决容器启动顺序的问题,但是当您删除容器时,容器的容器停止顺序却无济于事。Pod的容器实际上是并行终止的。当它关闭一个Pod时,Kubelet在goroutine中执行容器终止代码-每个容器一个。与启动后挂钩不同,停止前挂钩因此并行运行。

如果仅在主应用程序容器终止后才需要停靠sidecar,则必须以其他方式处理。

PS: 本文属于翻译,原文

你可能感兴趣的:(k8s,kubernetes)