1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        源碼級解讀為何 kubernetes 棄用 Docker 容器運(yùn)行時

        共 19662字,需瀏覽 40分鐘

         ·

        2021-08-15 05:26

        本文轉(zhuǎn)載自【源碼解讀】從代碼實(shí)現(xiàn)層面思考 Kubernetes 為什么會棄用對 Docker 的支持?[1]

        作者: ?Colstuwjx[2]

        2020年底,在 Kubernetes v1.20 正式發(fā)布的同時,k8s 官方還搞了一個大動作:他們宣布將會逐步棄用對 Docker 容器運(yùn)行時的支持。為了不讓用戶驚慌失措,官方還貼心地寫了一篇博客文章[3],對此事進(jìn)行了一番詳細(xì)說明。

        **K8s 為什么會棄用對 Docker 的支持呢?**除了官方的這篇文章以外,很多科技媒體也做了相應(yīng)的解讀,比如 infoq 的這篇文章[4]。但是,為什么一定要棄用 docker 呢?這方面的維護(hù)成本究竟有多高?為了得到一個明確的答案,筆者決定展開一次 k8s 源碼的探索之旅,一探究竟。


        前世

        在官方發(fā)布的博客文章里鏈接了一份棄用 Dockershim 的常見問題解答[5]。在這份 FAQ 里,官方也提到了棄用 Dockershim 的根本原因:

        Docker?itself?doesn't?currently?implement?CRI,?thus?the?problem.?Dockershim?was?always?intended?to?be?a?temporary?solution?(hence?the?name:?shim).

        翻譯一下就是: Dockershim 是當(dāng)初 k8s 引入 CRI 容器運(yùn)行時標(biāo)準(zhǔn)接口的時候?yàn)榱思嫒?Docker,k8s 官方自行維護(hù)的一套臨時解決方案,他們現(xiàn)在不想再維護(hù)了。

        dockershim 的起點(diǎn)

        那么,dockershim 是什么時候加進(jìn)去的呢?當(dāng)時的背景又是怎樣的?

        筆者找到了當(dāng)初開發(fā)人員提的第一個 PR #29553 [6],PR title 里面有這么一句話:

        yujuhong:?...?Add?a?new?docker?integration?with?kubelet?using?the?new?runtime?API?...

        根據(jù) PR 里給出的信息,順藤摸瓜,筆者又找到了相關(guān)的 umbrella issue [7],主要是用來跟蹤 CRI 對接的進(jìn)展。也就是說,為了讓 Docker 支持 CRI 標(biāo)準(zhǔn),核心開發(fā) yujuhong 貢獻(xiàn)了集成 docker 操作并且支持新版 runtime API 的一個組件(也即是 dockershim ),具體可以查閱這個 issue ,這是當(dāng)時用來跟蹤 dockershim 實(shí)現(xiàn) CRI 接口專門開的一個 issue。

        注:dockershim 的代碼位于 k8s 倉庫的這里[8],我們可以很方便地通過追溯 commit history [9]來找到首次提交,最終便找到了這個 PR 。

        前 CRI 時代

        在翻閱這塊代碼的時候,筆者內(nèi)心還有一個疑問: 在 CRI 標(biāo)準(zhǔn)提出之前,k8s 是怎么和容器運(yùn)行時交互的呢?

        帶著這個問題,筆者通過搜索找到了宣布引入 CRI 標(biāo)準(zhǔn)的官方文章[10]。

        文章里介紹到,自 k8s 1.5 起,CRI 功能作為一個 alpha 特性被引入到 k8s,而早在 1.3 版本開始,k8s 就集成了 rkt 容器運(yùn)行時的支持,作為替代 docker 的可選方案。

        然而,這些代碼都是托管在 k8s kubelet 的核心代碼里,后續(xù)維護(hù)和增加更多容器運(yùn)行時支持都會變得越來越困難。

        那么,我們不妨來看看當(dāng)時版本的 k8s 具體是怎么和 docker 及 rkt 引擎做交互的吧。定位代碼的方式也很簡單,直接選擇 1.5 之前的版本,比如 v1.4.0 的 tag 版本。然后,既然官方說代碼嵌在了 kubelet 代碼里,我們可以直接切到 kubelet 代碼的目錄,不難找到下面這兩個子目錄:

        • rkt[11]
        • dockertools[12]

        注:這里還有一個小彩蛋,我們在該目錄下還找到了一個 rktshim [13]的目錄,說明當(dāng)初各個容器運(yùn)行時尚未普及對 CRI 的支持時,在 k8s 代碼里嵌 xxxshim 服務(wù)用作臨時支持是一個常規(guī)操作。

        那么,它們到底是咋交互的呢?

        我們不難在 dockertools 目錄下的 kube_docker_client.go[14] 里面找到一個 kubeDockerClient [15]的實(shí)現(xiàn):

        //?kubeDockerClient?is?a?wrapped?layer?of?docker?client?for?kubelet?internal?use.?This?layer?is?added?to:
        //?1)?Redirect?stream?for?exec?and?attach?operations.
        //?2)?Wrap?the?context?in?this?layer?to?make?the?DockerInterface?cleaner.
        //?3)?Stabilize?the?DockerInterface.?The?engine-api?is?still?under?active?development,?the?interface
        //?is?not?stabilized?yet.?However,?the?DockerInterface?is?used?in?many?files?in?Kubernetes,?we?may
        //?not?want?to?change?the?interface?frequently.?With?this?layer,?we?can?port?the?engine?api?to?the
        //?DockerInterface?to?avoid?changing?DockerInterface?as?much?as?possible.
        //?(See
        //???*?https://github.com/docker/engine-api/issues/89
        //???*?https://github.com/docker/engine-api/issues/137
        //???*?https://github.com/docker/engine-api/pull/140)
        //?TODO(random-liu):?Swith?to?new?docker?interface?by?refactoring?the?functions?in?the?old?DockerInterface
        //?one?by?one.
        type?kubeDockerClient?struct?{
        ?//?timeout?is?the?timeout?of?short?running?docker?operations.
        ?timeout?time.Duration
        ?client??*dockerapi.Client
        }

        上面的注釋也寫的挺詳細(xì),大致意思就是它是一個和 Docker 交互的 client,并封裝了一些 k8s 操作 Docker 需要的一些接口方法,這套接口方法具體定義在同一目錄的 docker.go[16] 里:

        //?DockerInterface?is?an?abstract?interface?for?testability.??It?abstracts?the?interface?of?docker?client.
        type?DockerInterface?interface?{
        ?ListContainers(options?dockertypes.ContainerListOptions)?([]dockertypes.Container,?error)
        ?InspectContainer(id?string)?(*dockertypes.ContainerJSON,?error)
        ?CreateContainer(dockertypes.ContainerCreateConfig)?(*dockertypes.ContainerCreateResponse,?error)
        ?StartContainer(id?string)?error
        ?StopContainer(id?string,?timeout?int)?error
        ?RemoveContainer(id?string,?opts?dockertypes.ContainerRemoveOptions)?error
        ?InspectImage(image?string)?(*dockertypes.ImageInspect,?error)
        ?ListImages(opts?dockertypes.ImageListOptions)?([]dockertypes.Image,?error)
        ?PullImage(image?string,?auth?dockertypes.AuthConfig,?opts?dockertypes.ImagePullOptions)?error
        ?RemoveImage(image?string,?opts?dockertypes.ImageRemoveOptions)?([]dockertypes.ImageDelete,?error)
        ?ImageHistory(id?string)?([]dockertypes.ImageHistory,?error)
        ?Logs(string,?dockertypes.ContainerLogsOptions,?StreamOptions)?error
        ?Version()?(*dockertypes.Version,?error)
        ?Info()?(*dockertypes.Info,?error)
        ?CreateExec(string,?dockertypes.ExecConfig)?(*dockertypes.ContainerExecCreateResponse,?error)
        ?StartExec(string,?dockertypes.ExecStartCheck,?StreamOptions)?error
        ?InspectExec(id?string)?(*dockertypes.ContainerExecInspect,?error)
        ?AttachToContainer(string,?dockertypes.ContainerAttachOptions,?StreamOptions)?error
        ?ResizeContainerTTY(id?string,?height,?width?int)?error
        ?ResizeExecTTY(id?string,?height,?width?int)?error
        }

        可以看到,k8s 在 Pod 的生命周期里需要用到的一些操作函數(shù)都已經(jīng)包含在內(nèi)。

        到這里,大致概括一下 k8s 在引入 CRI 階段的一個迭代過程吧:

        1、在 CRI 標(biāo)準(zhǔn)落地之前,k8s 等于是為每一個容器運(yùn)行時都實(shí)現(xiàn)了一個具體的對接,rkt 和 dockertools 目錄下即對應(yīng)的代碼實(shí)現(xiàn);

        2、官方于 1.5 版本開始正式引入 CRI 標(biāo)準(zhǔn),并實(shí)現(xiàn)了對應(yīng)的 shim 代碼,如 dockershim 和 rktshim,在各個容器運(yùn)行時尚未支持 CRI 標(biāo)準(zhǔn)的接口之前,充當(dāng)一個膠水服務(wù)。

        今生

        通過追溯之前的版本歷史,筆者終于了解了 k8s 在支持容器運(yùn)行時這塊的”坎坷經(jīng)歷”。

        然而,最開始的問題始終未能得到解答:為什么非得要棄用 dockershim ? 繼續(xù)維護(hù)下去的話究竟會有哪些具體的痛點(diǎn)呢?

        畢竟,如果棄用 dockershim 的話,這意味著原本使用 docker 作為容器引擎的用戶需要為此計劃實(shí)施遷移到 containerd 或者其他支持 CRI 的容器運(yùn)行時,這會是一個不小的時間和人力成本。

        想要解答這個問題,恐怕還得先看看 dockershim 目前的使用場景以及 CRI 的發(fā)展現(xiàn)狀。

        啟動前還要運(yùn)行 dockershim 服務(wù)?

        時至今日,kubelet 要去啟動一個 docker 容器的話,究竟是怎么和 dockershim 配合工作的呢?不妨再來看看 kubelet 這層的代碼實(shí)現(xiàn)。

        這次筆者選的是剛發(fā)布不久的 v1.22[17] 版本的代碼。

        kubelet 的啟動入口位于 cmd/kubelet/kubelet.go[18],熟悉 cobra 的朋友應(yīng)該知道,它最終是會調(diào)用具體 Command 的 Run 方法。對于 kubelet 來說,調(diào)用的即是它實(shí)現(xiàn)的 Run[19] 方法。

        在經(jīng)過一系列的處理后,kubelet 會走到核心的用來啟動服務(wù)的 run 方法。直接看和 Dockershim 相關(guān)的部分!劃到靠近函數(shù)末尾的部分,可以看到在真正啟動前,kubelet 執(zhí)行了一個 kubelet.PreInitRuntimeService[20] 的操作。

        這個 PreInitRuntimeService 方法做了什么事情呢?

        不妨繼續(xù)深入一下,看看它的具體內(nèi)容:

        //?PreInitRuntimeService?will?init?runtime?service?before?RunKubelet.
        func?PreInitRuntimeService(kubeCfg?*kubeletconfiginternal.KubeletConfiguration,
        ?kubeDeps?*Dependencies,
        ?crOptions?*config.ContainerRuntimeOptions,
        ?containerRuntime?string,
        ?runtimeCgroups?string,
        ?remoteRuntimeEndpoint?string,
        ?remoteImageEndpoint?string,
        ?nonMasqueradeCIDR?string)?error?{
        ?if?remoteRuntimeEndpoint?!=?""?{
        ??//?remoteImageEndpoint?is?same?as?remoteRuntimeEndpoint?if?not?explicitly?specified
        ??if?remoteImageEndpoint?==?""?{
        ???remoteImageEndpoint?=?remoteRuntimeEndpoint
        ??}
        ?}

        ?switch?containerRuntime?{
        ?case?kubetypes.DockerContainerRuntime:
        ??klog.InfoS("Using?dockershim?is?deprecated,?please?consider?using?a?full-fledged?CRI?implementation")
        ??if?err?:=?runDockershim(
        ???kubeCfg,
        ???kubeDeps,
        ???crOptions,
        ???runtimeCgroups,
        ???remoteRuntimeEndpoint,
        ???remoteImageEndpoint,
        ???nonMasqueradeCIDR,
        ??);?err?!=?nil?{
        ???return?err
        ??}
        ?case?kubetypes.RemoteContainerRuntime:
        ??//?No-op.
        ??break
        ?default:
        ??return?fmt.Errorf("unsupported?CRI?runtime:?%q",?containerRuntime)
        ?}

        ????...
        }

        可以看到,當(dāng) containerRuntime 參數(shù)是 kubetypes.DockerContainerRuntime 時,kubelet 需要執(zhí)行額外的 runDockershim 方法去啟動一個 dockershim 服務(wù)(可以看到,上面有一行警告 dockershim 已棄用的提醒),而如果是 kubetypes.RemoteContainerRuntime 類型的話,則什么事情也不用干。

        筆者還在 kubelet 目錄下找到了 kubelet_dockershim.go,該文件里即實(shí)現(xiàn)了這個 runDockershim 方法,它會去調(diào)用 dockershim 的相關(guān)服務(wù)代碼并啟動一個 dockerServer。

        很顯然, kubelet 是通過這個 dockershim 服務(wù)包裝的一層 CRI 接口調(diào)用 docker 啟動 Pod 容器的。我們不妨看下 kubelet 實(shí)際是怎么去起 Pod 的,然后再來看看它是如何調(diào)用的容器運(yùn)行時。

        kubeGenericRuntimeManager 的用途

        回到 cmd/kubelet/app/server.go,在執(zhí)行了 PreInitRuntimeService 之后,不難發(fā)現(xiàn) kubelet 會去執(zhí)行 RunKubelet,并最終通過 kubelet.NewMainKubelet 來初始化 kubelet 服務(wù)實(shí)例。

        注:關(guān)于 kubelet 完整的啟動邏輯,有位網(wǎng)易的同學(xué)寫了一個系列文章[21],有興趣的朋友可以看看。

        這里面有關(guān) runtime 部分最重要的就是這一段[22]了:

        runtime,?err?:=?kuberuntime.NewKubeGenericRuntimeManager(
        ????...
        )

        這里初始化了一個 kubeGenericRuntimeManager 的對象,它可以做哪些事情呢?我們暫且按下不表,先從 kubelet 這一層找找入口?;剡^頭來,我們再來看看 kubelet 啟動入口 NewMainKubelet 這塊??梢钥吹?,在初始化 kubeGenericRuntimeManager 之前,kubelet 初始化了一個 workQueue,并且初始化了一批 podWorker:

        klet.podWorkers?=?newPodWorkers(
        ????klet.syncPod,
        ????klet.syncTerminatingPod,
        ????klet.syncTerminatedPod,

        ????kubeDeps.Recorder,
        ????klet.workQueue,
        ????klet.resyncInterval,
        ????backOffPeriod,
        ????klet.podCache,
        )

        熟悉 k8s 異步調(diào)諧這套控制器邏輯的朋友,應(yīng)該能猜到。沒錯,這個 podWorker 就是監(jiān)聽 kubelet 關(guān)注的 Pod 資源的變化,并執(zhí)行相應(yīng)的調(diào)諧邏輯。這里先看一下 syncPod 這塊的實(shí)現(xiàn)。

        注:有興趣的朋友可以看看 syncPod 方法的注釋部分[23],里面描述了 syncPod 的整體流程。

        syncPod 方法里的其他細(xì)節(jié)部分忽略,我們直接關(guān)注最終調(diào)用容器運(yùn)行時服務(wù)同步 Pod 的操作部分[24]:

        result?:=?kl.containerRuntime.SyncPod(pod,?podStatus,?pullSecrets,?kl.backOff)

        可以看到,這里 kubelet 實(shí)例調(diào)用的 containerRuntime[25] 毫無疑問便是之前 kubelet 在 NewMainKubelet 初始化 kubeGenericRuntimeManager 時創(chuàng)建出來的 runtime 實(shí)例:

        runtime,?err?:=?kuberuntime.NewKubeGenericRuntimeManager(
        ????...
        )
        klet.containerRuntime?=?runtime

        那么,這個 runtime manager 具體又是怎么調(diào)用容器運(yùn)行時服務(wù)來 SyncPod 的呢?

        調(diào)用 runtime service 來 SyncPod

        我們不妨先來看看 SyncPod 方法的注釋部分:

        //?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)?{
        ?...
        }

        可以看到,這就是一次經(jīng)典的調(diào)諧邏輯。

        按照它的說法,它會計算 Pod 當(dāng)前的狀態(tài),然后按需清理環(huán)境,并嘗試保證 Pod Sandbox 及相關(guān)容器(依次是 ephemeral container、init container 以及應(yīng)用容器)處于運(yùn)行狀態(tài)。

        快速瀏覽了一下 SyncPod 具體實(shí)現(xiàn)之后,不難發(fā)現(xiàn),它將一些具體的實(shí)現(xiàn)部分放到了幾個單獨(dú)的方法里,如:createSandbox、startContainer。

        這里,以 createSandbox 為例,看看 kubelet 在創(chuàng)建 Pod Sandbox 這塊,調(diào)用 dockershim 和其他支持 CRI 的容器運(yùn)行時有什么不同。略過生成 Pod 配置等步驟,直接看最核心的這一段:

        podSandBoxID,?err?:=?m.runtimeService.RunPodSandbox(podSandboxConfig,?runtimeHandler)

        隱約可以猜到,這個 runtimeService 應(yīng)該就是一個統(tǒng)一實(shí)現(xiàn)調(diào)用 CRI 的入口,不妨回過頭來再看看 kuberuntime.NewKubeGenericRuntimeManager 這一步是怎么初始化這個 runtimeService 的:

        runtime,?err?:=?kuberuntime.NewKubeGenericRuntimeManager(
        ?...
        ?kubeDeps.RemoteRuntimeService,
        ?...
        )

        咦?這個 kubeDeps 又是何方神圣呢?順著源頭找,可以看到它是 NewMainKubelet 就傳入進(jìn)來的一個參數(shù)項(xiàng)。再順著調(diào)用鏈的源頭,筆者找到了 cmd/kubelet/app/server.go 里的 RunKubelet

        if?err?:=?RunKubelet(s,?kubeDeps,?s.RunOnce);?err?!=?nil?{
        ?return?err
        }

        再往上走便可以發(fā)現(xiàn),這個 kubeDeps 早在 kubelet NewKubeletCommand 時候就已經(jīng)做了初始化:

        //?use?kubeletServer?to?construct?the?default?KubeletDeps
        kubeletDeps,?err?:=?UnsecuredDependencies(kubeletServer,?utilfeature.DefaultFeatureGate)
        if?err?!=?nil?{
        ?klog.ErrorS(err,?"Failed?to?construct?kubelet?dependencies")
        ?os.Exit(1)
        }

        但是仔細(xì)一看,里面并沒有初始化 RemoteRuntimeService 啊,那什么時候做的呢?

        ?。∏拔奶岬竭^,在執(zhí)行 RunKubelet 前,kubelet 事先執(zhí)行了 PreInitRuntimeService,它在里面是這樣初始化 kubeDeps 的相關(guān)運(yùn)行時依賴的:

        if?kubeDeps.RemoteRuntimeService,?err?=?remote.NewRemoteRuntimeService(remoteRuntimeEndpoint,?kubeCfg.RuntimeRequestTimeout.Duration);?err?!=?nil?{
        ?return?err
        }

        想必這個 pkg/kubelet/cri/remote/remote_runtime.go[26] 便是統(tǒng)一實(shí)現(xiàn)了調(diào)用 CRI 的 client 接口!

        至此,kubelet 調(diào)用容器運(yùn)行時的流程基本浮出了水面:

        1、kubelet 在 NewKubeletCommand 命令入口便初始化了 kubeDeps 對象,用來存放一些 kubelet 需要的依賴;

        2、在 Kubelet 執(zhí)行 RunKubelet 之前它會先執(zhí)行 PreInitRuntimeService 根據(jù) containerRuntime 參數(shù)初始化 runtimeService 句柄并存放到 kubeDeps 便于后面部分調(diào)用;

        3、在上一步驟中,如果是 docker 的話,會額外執(zhí)行 runDockershim 啟動 dockershim 服務(wù);

        4、執(zhí)行 RunKubelet 方法時,它會進(jìn)一步去執(zhí)行 NewMainKubelet 并最終啟動 kubelet 服務(wù);

        5、在 NewMainKubelet 這一步 kubelet 會初始化 Pod Worker 去執(zhí)行 Pod 調(diào)諧,具體執(zhí)行方法為 syncPod、syncTerminatingPod 等;

        6、此外,NewMainKubelet 這一步還在初始化 KubeGenericRuntimeManager 的時候傳入了 kubeDeps.RemoteRuntimeService,然后將 runtime manager 該實(shí)例賦給了 kubelet.containerRuntime;

        7、當(dāng) kubelet 的 pod worker 進(jìn)入主要的 syncPod 調(diào)諧周期時,它會調(diào)用 runtime manager 的 SyncPod 方法去做同步;

        8、runtime manager 的 SyncPod 方法會做一系列判斷,并執(zhí)行相應(yīng)的必要操作,比如 createSandbox,它會通過之前傳入的 runtimeService 的 RunPodSandbox 方法調(diào)用具體的容器運(yùn)行時服務(wù)做對應(yīng)的事情。

        dockershim 的 CRI 實(shí)現(xiàn)

        嗯 ,大致了解了 kubelet 調(diào)用容器運(yùn)行時做 syncPod 調(diào)諧的這個過程了。那 dockershim 又是怎樣具體實(shí)現(xiàn)這一套運(yùn)行時接口的呢?

        RunSandbox 這個接口為例,可以看到 dockershim 的實(shí)現(xiàn)里做了大量手動操作的事情:

        //?RunPodSandbox?creates?and?starts?a?pod-level?sandbox.?Runtimes?should?ensure
        //?the?sandbox?is?in?ready?state.
        //?For?docker,?PodSandbox?is?implemented?by?a?container?holding?the?network
        //?namespace?for?the?pod.
        //?Note:?docker?doesn't?use?LogDirectory?(yet).
        func?(ds?*dockerService)?RunPodSandbox(ctx?context.Context,?r?*runtimeapi.RunPodSandboxRequest)?(*runtimeapi.RunPodSandboxResponse,?error)?{
        ?...
        ?//?dockershim?會先保證?sandbox?鏡像的存在,按需執(zhí)行?docker?pull
        ?if?err?:=?ensureSandboxImageExists(ds.client,?image);?err?!=?nil?{
        ??return?nil,?err
        ?}
        ?...
        ?//?dockershim?還會根據(jù)配置手動創(chuàng)建?infra?容器
        ?createConfig,?err?:=?ds.makeSandboxDockerConfig(config,?image)
        ?if?err?!=?nil?{
        ??return?nil,?fmt.Errorf("failed?to?make?sandbox?docker?config?for?pod?%q:?%v",?config.Metadata.Name,?err)
        ?}
        ?createResp,?err?:=?ds.client.CreateContainer(*createConfig)
        ?if?err?!=?nil?{
        ??createResp,?err?=?recoverFromCreationConflictIfNeeded(ds.client,?*createConfig,?err)
        ?}
        ?...
        ?//?dockershim?手動創(chuàng)建?checkpoint
        ?if?err?=?ds.checkpointManager.CreateCheckpoint(createResp.ID,?constructPodSandboxCheckpoint(config));?err?!=?nil?{
        ??return?nil,?err
        ?}
        ?...
        ?//?dockershim?調(diào)用?docker?client?去啟動容器
        ?//?注意,這個時候?infra?容器的網(wǎng)絡(luò)棧還沒設(shè)置
        ?err?=?ds.client.StartContainer(createResp.ID)
        ?if?err?!=?nil?{
        ??return?nil,?fmt.Errorf("failed?to?start?sandbox?container?for?pod?%q:?%v",?config.Metadata.Name,?err)
        ?}
        ?...
        ?//?如果?dns?配置需要定制,dockershim?還會去手動重寫該容器的?dns?配置
        ?//?這塊是真的沒想到,`rewriteResolvFile`?里就是一些調(diào)用操作系統(tǒng)接口去重寫文件
        ?// docker client 難道沒有提供設(shè)置 dns 的方式嗎?
        ?...
        ??if?err?:=?rewriteResolvFile(containerInfo.ResolvConfPath,?dnsConfig.Servers,?dnsConfig.Searches,?dnsConfig.Options);?err?!=?nil?{
        ???return?nil,?fmt.Errorf("rewrite?resolv.conf?failed?for?pod?%q:?%v",?config.Metadata.Name,?err)
        ??}
        ?...
        ?//?為了能夠調(diào)用?CNI?插件設(shè)置?infra?容器的網(wǎng)絡(luò)棧
        ?//?dockershim?還專門實(shí)現(xiàn)了一個?network?部分,它會給?CNI?插件傳入相應(yīng)的參數(shù),設(shè)置?infra?容器的網(wǎng)絡(luò)棧
        ?err?=?ds.network.SetUpPod(config.GetMetadata().Namespace,?config.GetMetadata().Name,?cID,?config.Annotations,?networkOptions)
        ?...
        }

        筆者在上述代碼里添加了一些自己的注釋??梢钥吹?,k8s 的 kubelet 為了兼容支持 docker 容器運(yùn)行時,做了大量膠水性質(zhì)的粘合操作,比如設(shè)置 DNS Server 這種甚至是直接調(diào)用操作系統(tǒng)接口,以重寫 resolv.conf 文件形式實(shí)現(xiàn)的!

        注1:dns 配置這塊為什么是直接重寫文件呢?為了解答這個問題,筆者找到了最初實(shí)現(xiàn)版本[27],這里面是沒有做任何重寫操作。繼續(xù)回溯歷史,可以找到這個 PR #43368[28],似乎 dockertool 時代就已經(jīng)是這種方式設(shè)置 DNS 了,為了支持 k8s 的一些 DNS 設(shè)置方面的功能,社區(qū)沿用了之前 dockertool 的方案,在 dockershim 處理 Pod Sandbox 的時候也加入了重寫 resolv.conf 的邏輯。那么,為什么 dockertool 會重寫 resolv.conf 呢,繼續(xù)回溯版本后,筆者發(fā)現(xiàn)了關(guān)于 dns 設(shè)置這塊的一段注釋[29],它的出處是 PR 10266[30]。終于破案了,由于當(dāng)時 docker 還不支持 ndots 選項(xiàng),k8s 選擇的是 hack 掉 infra 容器的 resolv.conf 來解決這個問題。

        注2:接著上面一個注解,PR #10266 的確是通過魔改的方式給 k8s 加上了 ndots 選項(xiàng)的支持,但是,k8s 官方的核心開發(fā)人員 thockin[31] 在同一年( 2015 年)的九月份就給 docker 提了 PR(見 PR #16031[32] )加上了該功能。其實(shí)從這個事情也可以看出來,兩個社區(qū)之間信息是不同步的,繼續(xù)維護(hù) dockershim 的話這樣的問題還會不少。最好的解決辦法恐怕還是將這些運(yùn)行時方面的功能通過 CRI 標(biāo)準(zhǔn)接口定義好,然后容器運(yùn)行時各自去實(shí)現(xiàn)。

        containerd beyond 1.0

        了解了 kubelet 調(diào)用 dockershim 這塊的情況以后,筆者又想到了它的表兄弟 containerd,按道理它應(yīng)該是 k8s 更為親和的方案。那么,它在這個過程中扮演什么樣的角色呢,現(xiàn)狀又如何呢?

        帶著這個疑問,筆者克隆了 containerd[33] 的倉庫代碼。通過 git log 很快便翻到了 commit 樹的起點(diǎn):

        commit?15a96783ca2ac8c0eb2c400701e8eb335059c63b?(HEAD)
        Author:?Michael?Crosby?<[email protected]>
        Date:???Thu?Nov?5?15:29:53?2015?-0800

        ????Initial?commit

        可以看到,containerd 作為一個單獨(dú)項(xiàng)目開發(fā)已經(jīng)是 2015 年底了。有興趣的朋友還可以翻閱一下這個起點(diǎn) commit 的內(nèi)容,其實(shí)等于就是從頭開始寫了…

        那么,docker 什么時候開始集成 containerd 作為它的容器運(yùn)行時呢?

        其實(shí)也很簡單,查一下 docker 倉庫的 PR 歷史就知道了。最終,筆者找到了 PR #20662[34]。在這個 PR 變更內(nèi)容里,很容易就找到了集成的 containerd 的版本:

        ENV?CONTAINERD_COMMIT?7146b01a3d7aaa146414cdfb0a6c96cfba5d9091

        對比 commit 提交時間,大致是 v0.1.0 版本發(fā)布的時間。

        在 containerd 單獨(dú)立項(xiàng)開發(fā)的兩年以后,2017 年 12 月份,containerd 1.0 GA 了,containerd 的核心開發(fā)人員 Michel Crosby 也撰文講述了 containerd 抵達(dá) 1.0 的這個旅程,其中包括像從 Graphdriver 切換到 Snapshot 這樣的架構(gòu)層面的重新設(shè)計。

        而在此之前的 11 月份,k8s 1.8 加入了對 containerd 運(yùn)行時的支持,見 1.8 changelog[35]。

        注1:有趣的是,containerd 自己也引入了一個 containerd-shim,這個 shim 是為了讓出自 containerd 的容器進(jìn)程能夠和 containerd 解耦,具體見 containerd v0.5 的 PR #98 title[36]。

        注2:此外,值得一提的是,引入 containerd 后的 docker 自身也不是太穩(wěn)定(當(dāng)然,剝離 containerd 之前筆者在生產(chǎn)環(huán)境使用 docker daemon 也遇到過不少問題),筆者自己就經(jīng)歷過一個詭異問題,具體可以參考筆者 17 年時候?qū)懙?span style="color:#1e6bb8;font-weight:bold;">這篇博客[37],現(xiàn)在回過頭來看,可能和 containerd-shim 的這個玩法有關(guān)系。順便說一句,那會兒的 containerd 盡管已經(jīng) 1.0 了,UX 交互卻還是相當(dāng)簡陋,這也是很多用戶在 containerd 可以單獨(dú)作為容器運(yùn)行時選項(xiàng)時仍然堅持選擇 docker 的重要原因之一。有興趣的朋友可以看下筆者在 18 年初試玩 containerd 的經(jīng)歷[38]。

        從 docker 到 containerd 的遷徙

        時至今日,CRI 已然在各個主流的容器運(yùn)行時得到支持和普及,containerd 的一些周邊支持也逐漸完善起來,比如命令行工具這塊,crictl 沿用了之前 docker 留下來的操作習(xí)慣,相關(guān)命令均可以接近無縫地切換到 crictl

        業(yè)內(nèi)也出現(xiàn)一些從 docker 引擎遷移到 containerd 的案例,如 eBay 早在 2019 年就將運(yùn)行時從 docker 切換到了 containerd[39],各大公有云提供的 Kubernetes 服務(wù)也在 k8s 官方宣布棄用 dockershim 支持后不久便宣布使用 containerd 替換 docker[40]。

        結(jié)語

        呼,花了點(diǎn)時間,終于摸清了 dockershim 的身世背景。整體看下來,似乎和 k8s 官方博客里說的差不多。筆者也感受到,在迭代過程中社區(qū)的開發(fā)人員為了彌補(bǔ) k8s 和 docker 之間的 gap 做出的一些妥協(xié):比如前面提到的實(shí)現(xiàn) dockershim 讓 docker 支持 CRI 標(biāo)準(zhǔn),以及重寫 resolv.conf 來支持 k8s 的一些 dns 功能等等。

        出于開發(fā)和運(yùn)維方面的復(fù)雜性考慮,無論是 k8s 官方棄用 dockershim 還是社區(qū)用戶將運(yùn)行時切換到 containerd 其實(shí)都是非常理性的做法。

        只是,似乎 docker 的那個時代已經(jīng)落幕了。

        參考資料

        [1]

        [源碼解讀]從代碼實(shí)現(xiàn)層面思考 Kubernetes 為什么會棄用對 Docker 的支持?: https://colstuwjx.github.io/dive-into-sourcecode-why-k8s-deprecated-dockershim/

        [2]

        Colstuwjx's site: https://colstuwjx.github.io/

        [3]

        dont panic kubernetes and docker: https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/

        [4]

        Kubernetes 棄用 Docker 后怎么辦?: https://www.infoq.cn/article/47hcixefry1cetbzugwd

        [5]

        dockershim-faq: https://kubernetes.io/blog/2020/12/02/dockershim-faq/

        [6]

        PR #29553: https://github.com/kubernetes/kubernetes/pull/29553

        [7]

        umbrella issue: https://github.com/kubernetes/kubernetes/issues/28789

        [8]

        dockershim: https://github.com/kubernetes/kubernetes/tree/v1.22.0/pkg/kubelet/dockershim

        [9]

        commit history: https://github.com/kubernetes/kubernetes/commits/master?after=5a732dcfe1d4ec0e8ee2871b106605b7f8a69b98+104&branch=master&path[]=pkg&path[]=kubelet&path[]=dockershim&path[]=docker_service.go

        [10]

        cri in kubernetes: https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/

        [11]

        rkt: https://github.com/kubernetes/kubernetes/tree/v1.4.0/pkg/kubelet/rkt

        [12]

        dockertools: https://github.com/kubernetes/kubernetes/tree/v1.4.0/pkg/kubelet/dockertools

        [13]

        kubelet rktshim: https://github.com/kubernetes/kubernetes/tree/v1.4.0/pkg/kubelet/rktshim

        [14]

        kube docker client: https://github.com/kubernetes/kubernetes/blob/v1.4.0/pkg/kubelet/dockertools/kube_docker_client.go

        [15]

        L38 kube docker client: https://github.com/kubernetes/kubernetes/blob/v1.4.0/pkg/kubelet/dockertools/kube_docker_client.go#L38

        [16]

        docker.go#L64: https://github.com/kubernetes/kubernetes/blob/v1.4.0/pkg/kubelet/dockertools/docker.go#L64

        [17]

        k8s v1.22.0: https://github.com/kubernetes/kubernetes/tree/v1.22.0

        [18]

        kubelet #L36: https://github.com/kubernetes/kubernetes/blob/v1.22.0/cmd/kubelet/kubelet.go#L36

        [19]

        kubelet server.go#L155: https://github.com/kubernetes/kubernetes/blob/v1.22.0/cmd/kubelet/app/server.go#L155

        [20]

        kubelet server.go#L796: https://github.com/kubernetes/kubernetes/blob/v1.22.0/cmd/kubelet/app/server.go#L796

        [21]

        系列文章: https://mp.weixin.qq.com/s/g3C0alyd21fNhbj4OqPprQ

        [22]

        kubelet #L662: https://github.com/kubernetes/kubernetes/blob/v1.22.0/pkg/kubelet/kubelet.go#L662

        [23]

        kubelet #L1498: https://github.com/kubernetes/kubernetes/blob/v1.22.0/pkg/kubelet/kubelet.go#L1498

        [24]

        kubelet #L1729: https://github.com/kubernetes/kubernetes/blob/v1.22.0/pkg/kubelet/kubelet.go#L1729

        [25]

        kubelet #L695: https://github.com/kubernetes/kubernetes/blob/v1.22.0/pkg/kubelet/kubelet.go#L695

        [26]

        kubelet remote_runtime.go: https://github.com/kubernetes/kubernetes/blob/v1.22.0/pkg/kubelet/cri/remote/remote_runtime.go

        [27]

        最初版本: https://github.com/kubernetes/kubernetes/commit/5960d87d2142055cd29ebbce0243652c4adc5742#diff-40b456472817aeb853ac82dfc7cdf7632243c09bd40a085b74c5748580f6e104R237

        [28]

        PR #43368: https://github.com/kubernetes/kubernetes/pull/43368

        [29]

        dockertools/manager.go #L1235: https://github.com/kubernetes/kubernetes/blob/v0.21.4/pkg/kubelet/dockertools/manager.go#L1235

        [30]

        PR #10266: https://github.com/kubernetes/kubernetes/pull/10266

        [31]

        thockin: https://github.com/thockin

        [32]

        moby PR #16031: https://github.com/moby/moby/pull/16031

        [33]

        containerd github: https://github.com/containerd/containerd

        [34]

        moby PR #20662: https://github.com/moby/moby/pull/20662

        [35]

        k8s 1.8 changelog: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.8.md#container-runtime-interface-cri

        [36]

        containerd PR#98: https://github.com/containerd/containerd/pull/98#issue-58078723

        [37]

        docker 排障經(jīng)歷: https://colstuwjx.github.io/2017/06/記一次失敗的docker排障經(jīng)歷/

        [38]

        初試 containerd: https://colstuwjx.github.io/2018/02/原創(chuàng)-小嘗containerd一/

        [39]

        ebay 從 docker 切換到 containerd: https://www.infoq.cn/article/odslclsjvo8bnxmbrbk*

        [40]

        azure-kubernetes-service-replaces-docker-with-containerd: https://thenewstack.io/azure-kubernetes-service-replaces-docker-with-containerd/


        瀏覽 88
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美二三四区 | 欧美网站成人 | 女优爱爱视频 | 五月激情四射丁香婷婷激情四射 | 极品国模叶桐啪啪大尺度 | 超碰在线97人人 | 国产片婬乱18一级毛片小说 | 欧美性猛交ⅩXXX乱大交麻豆 | 国产c区 蜜桃av秘 无码一区二区三欧 | 国产精品8888 |