前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kubernetes 上容器的启动顺序如何把控?

Kubernetes 上容器的启动顺序如何把控?

作者头像
CNCF
发布2021-05-07 16:44:21
2K0
发布2021-05-07 16:44:21
举报
文章被收录于专栏:CNCF

去年写过一篇博客:控制 Pod 内容器的启动顺序,分析了 TektonCD[1] 的容器启动控制的原理。

为什么要做容器启动顺序控制?我们都知道 Pod 中除了 init-container 之外,是允许添加多个容器的。类似 TektonCD 中 taskstep 的概念就分别与 podcontainer 对应,而 step 是按照顺序执行的。此外还有服务网格的场景,sidecar 容器需要在服务容器启动之前完成配置的加载,也需要对容器的启动顺序加以控制。否则,服务容器先启动,而 sidecar 还无法提供网络上的支持。

现实

期望

到了这里肯定有同学会问,spec.containers[] 是一个数组,数组是有顺序的。Kubernetes 也确实是按照顺序来创建和启动容器,但是 容器启动成功,并不表示容器可以对外提供服务

在 Kubernetes 1.18 非正式版中曾在 Lifecycle 层面提供了对 sidecar 类型容器的 支持,但是最终该功能并没有落地[2]。

那到底该怎么做?

TL;DR

笔者准备了一个简单的 go 项目[3],用于模拟 sidecar 的启动及配置加载。

克隆代码后可以通过 make build 构建出镜像,假如你是用的 minikube 进行的实验,可以通过命令 make load-2-minikube 将镜像加载到 minikube 节点中。

使用 Deployment 的方式进行部署,直接用 Pod 也可以。

代码语言:javascript
复制
apiVersion: apps/v1kind: Deploymentmetadata:  creationTimestamp: null  labels:    app: sample  name: samplespec:  replicas: 1  selector:    matchLabels:      app: sample  strategy: {}  template:    metadata:      creationTimestamp: null      labels:        app: sample    spec:      containers:      - image: addozhang/k8s-container-sequence-sidecar:latest        name: sidecar        imagePullPolicy: IfNotPresent        lifecycle:          postStart:            exec:              command:                - /entrypoint                - wait      - image: busybox:latest        name: app        imagePullPolicy: IfNotPresent        command: ["/bin/sh","-c"]        args: ["date; echo 'app container started'; tail -f /dev/null"]

下面的截图中,演示了在 sample 命名空间中,pod 内两个容器的执行顺序。

Kubernetes 源码

在 kubelet 的源码 pkg/kubelet/kuberuntime/kuberuntime_manager.go 中,#SyncPod 方法用于创建 Pod,步骤比较繁琐,直接看第 7 步:创建普通容器。

代码语言:javascript
复制
// SyncPod syncs the running pod into the desired pod by executing following steps:////  1. Compute sandbox and container changes.//  2. Kill pod sandbox if necessary.//  3. Kill any containers that should not be running.//  4. Create sandbox if necessary.//  5. Create ephemeral containers.//  6. Create init containers.//  7. Create normal containers.func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {    ...    // Step 7: start containers in podContainerChanges.ContainersToStart.    for _, idx := range podContainerChanges.ContainersToStart {        start("container", containerStartSpec(&pod.Spec.Containers[idx]))    }    return}

#start 方法中调用了 #startContainer 方法,该方法会启动容器,并返回容器启动的结果。注意,这里的结果还 包含了容器的 Lifecycle hooks 调用

也就是说,假如容器的 PostStart hook 没有正确的返回,kubelet 便不会去创建下一个容器。

代码语言:javascript
复制
// startContainer starts a container and returns a message indicates why it is failed on error.// It starts the container through the following steps:// * pull the image// * create the container// * start the container// * run the post start lifecycle hooks (if applicable)func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, spec *startSpec, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {     ...    // Step 4: execute the post start hook.    if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {        kubeContainerID := kubecontainer.ContainerID{            Type: m.runtimeName,            ID:   containerID,        }        msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)        if handlerErr != nil {            m.recordContainerEvent(pod, container, kubeContainerID.ID, v1.EventTypeWarning, events.FailedPostStartHook, msg)            if err := m.killContainer(pod, kubeContainerID, container.Name, "FailedPostStartHook", reasonFailedPostStartHook, nil); err != nil {                klog.ErrorS(fmt.Errorf("%s: %v", ErrPostStartHook, handlerErr), "Failed to kill container", "pod", klog.KObj(pod),                    "podUID", pod.UID, "containerName", container.Name, "containerID", kubeContainerID.String())            }            return msg, fmt.Errorf("%s: %v", ErrPostStartHook, handlerErr)        }    }    return "", nil}

实现方案

cmd/entrypoint/wait.go#L26[4] (这里参考了 Istio 的 pilot-agent 实现 )

PostStart 中持续的去检查 /ready 断点,可以 hold 住当前容器的创建流程。保证 /ready 返回 200 后,kubelet 才会去创建下一个容器。

这样就达到了前面截图中演示的效果。

代码语言:javascript
复制
for time.Now().Before(timeoutAt) {    err = checkIfReady(client, url)    if err == nil {        log.Println("sidecar is ready")        return nil    }    log.Println("sidecar is not ready")    time.Sleep(time.Duration(periodMillis) * time.Millisecond)}return fmt.Errorf("sidecar is not ready in %d second(s)", timeoutSeconds)

参考

•Sidecar container lifecycle changes in Kubernetes 1.18[5]•Delaying application start until sidecar is ready[6]

引用链接

[1] TektonCD: https://github.com/tektoncd [2] 没有落地: https://github.com/kubernetes/enhancements/issues/753#issuecomment-713471597 [3] go 项目: https://github.com/addozhang/k8s-container-sequence-sample [4] cmd/entrypoint/wait.go#L26: https://github.com/addozhang/k8s-container-sequence-sample/blob/main/cmd/entrypoint/wait.go#L26 [5] Sidecar container lifecycle changes in Kubernetes 1.18: https://banzaicloud.com/blog/k8s-sidecars/ [6] Delaying application start until sidecar is ready: https://medium.com/@marko.luksa/delaying-application-start-until-sidecar-is-ready-2ec2d21a7b74

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CNCF 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 现实
  • 期望
  • TL;DR
  • Kubernetes 源码
  • 实现方案
  • 参考
    • 引用链接
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档