当前,Kubernetes 中的 Sidecar 概念正越来越受欢迎,并在社区内被广泛采用。

这种现象有其缘由:在容器世界中,容器一般只解决单个问题,但 Sidecar 模式通过将功能从应用本身剥离出来作为单独进程的方式,以额外的容器扩展、增强主容器。

在 Kubernetes 中,容器是共享存储和网络的一个或多个容器,而 Sidecar 则是 Pod 中与主应用松散耦合的实用程序容器。Sidecar 最著名的用例是充当服务网格中的代理,它也可作为日志传送器、监视代理和数据加载器。

然而,虽然 Sidecar 容器可以称得上是 Pod 中的常规容器,但目前 Kubernetes 还未把它添加作为内置功能。随着 Sidecar 用户数不断增长,据可靠消息,在 Kubernetes v1.18 中,用户将可以把容器标记为 Sidecar,并让它在普通容器之前启动,并在其他容器终止后关闭。

本文将对 Kubernetes 新版本中 Sidecar 容器生命周期变化做简单介绍。

存在的问题

Sidecar 容器的问题都与容器生命周期有关。由于 Pod 中容器之间没有区别,当前我们是无法控制哪个容器先启动、哪个容器最后终止的。但 Sidecar 容器必须要在主应用程序容器运行之前先启动,因此这就成了 Kubernetes v1.18 着重解决的一个问题。

Pod 启动

这里以 Istio 服务网格为例。

Envoy Sidecar 将所有传入和传出流量代理到应用程序容器。所以在 Envoy Sidecar 和应用程序容器全部启动之前,该应用程序是无法发送和接收流量请求的。如果应用程序尝试发送 outbound 流量,这时 Readiness 探针也发挥不了作用。

这个问题说大不大,说小不小,因为容器可以恢复,但是当应用程序容器无法启动时,我们可能会在日志里看到 CrashLoopBackoffs 报错。在某些情况下,如果应用程序不够弹性,或者某些启动脚本不是幂等的,那么容器可能根本无法启动。

为了让 Sidecar 容器可以提前启动,Istio 提出了一种简单粗暴的解决方案,即在应用程序容器的启动脚本中添加几秒钟延迟。但这种方法治标不治本。

在 1.18 版本中,Kubernetes Sidecar 这个新特性更改了 Pod 的启动生命周期:Sidecar 容器将在 init 容器完成后启动,并且只有当 Sidecar 就绪后,普通容器才能被启动。这个设计确保了 Sidecar 的提前运行。

Job 完成

如果 Kubernetes Job 具有 Sidecar 容器,那么目前在主容器完成后它还是会持续运行任务,永远达不到完成状态。

相对上一个问题,这个问题更棘手,因为它需要在主进程结束后以某种方式把信号发送给 Sidecar 容器,然后结束运行。但这就意味着我们需要用自定义逻辑扩展所有 Job,并通过共享暂存卷或某些临时解决方案(如 Envoy 的退出服务 /quitquitquit)实现容器间的同步。

如果是第三方容器,这就更复杂了,我们甚至需要把某种 wrapper 脚本作为 entrypoint 发送给容器,而且对于没有 Shell 的最小容器,这种方法并不总是可行。

好在新版本对这个问题也给出了解决方案。从 Kubernetes v1.18 开始,如果所有普通容器都已到达终止状态:

  • restartPolicy=OnFailure:Succeeded
  • restartPolicy=Never:Succeeded/Failed

它会向所有 Sidecar 容器发送 SIGTERM

Pod 终止

终止 Pod 的情况和启动 Pod 有些类似。如果 Sidecar 容器在主进程停止前先行终止,那么主应用程序容器在正常终止的过程中会大量报错。

这是因为根据 Kubernetes 的 Pod 关闭机制,应用程序容器会执行某种清除逻辑,比如关闭长期存在的连接、回滚事务和把状态保存到外部存储(如 s3)。如果事先杀死了 Sidecar,这个清除逻辑会无法正常运行。

一个很好的例子是 Argo 项目中报告的一个问题。用户尝试用 Argo 把容器日志存储在 s3 中,但是由于他先杀死了 istio-proxy,最后根本连接不到 s3,因为所有流量都流经那个被终止的容器。

Kubernetes v1.18 推出的解决方案是首先向所有普通容器发送一个 SIGTERM,普通容器都终止后,再向 Sidecar 容器发送 SIGTERM

若普通容器没有在 TerminationGracePeriod 之前退出,它们会接收到 SIGKILL 信号,但是 SIGTERM 仍然只会在那之后被发送到 Sidecars。preStop hook 也会被发给 Sidecars。

示例

在新版本中,把容器标记为 Sidecar 很方便。容器类型可以是选普通,也可以设置成 Sidecar,如果没有手动设置,默认情况下容器为普通容器。

apiVersion: v1
kind: Pod
metadata:
name: bookings-v1-b54bc7c9c-v42f6
labels:
app: demoapp
spec:
containers:
- name: bookings
image: banzaicloud/allspark:0.1.1
...
- name: istio-proxy
image: docker.io/istio/proxyv2:1.4.3
lifecycle:
type: Sidecar
...

在 Kubernetes v1.18 中,这个新特性需要用命令行参数来打开或关闭。

总结

其实在 Kubernetes v1.17 发布之前,项目开发组就已经透露了这个功能的存在。如果这次它能顺利在 v1.18 中被发布出来,相信它可以帮助开发者克服目前 Sidecar 存在的很多问题,会非常实用。

关于这个功能的更多进展,大家可以关注 GitHub:

https://github.com/kubernetes/enhancements/issues/753