Pod建立流程代碼版本[kubelet篇]

在k8s的面試中Pod的建立流程是一個常問的問題,而kubelet則無疑重中之重,以前也寫過一篇Pod的運行,不過沒有涉及到具體的代碼,本文嘗試用代碼的方式,來複數整個核心的流程,同時爲了方便記憶,又將整個過程分爲:準備、配置、清理、構建運行四個階段,讓咱們一塊兒來看下吧, 文末有大圖總結​node

2. 準備階段

當獲取到Pod添加的事件的時候,首先會進行一些基礎的工做,我吧這個過程稱爲準備階段,準備階段主要作的事情有以下:1)加入PodManager 2)准入控制檢查 3)分發事件 4)根據Pod添加對應的探針, 讓咱們一塊兒來看下關鍵實現git

2.1 加入PodManager

PodManager中的功能除了存儲Pod的信息,還會進行對應Pod的configMap和secret的管理,小心加入Pod的時候,會檢查對應的Pod是否有對應的configMap和secret配置,若是有則就會建立對應的監聽器,監聽資源的變化,進行本地緩存github

除此以外,若是對應的Pod的BootstrapCheckpointAnnotationKey有設定,則還會建立對應的checkpoint,即將pod的配置數據寫入到本地磁盤面試

kl.podManager.AddPod(pod)

2.2 准入控制檢查

准入控制檢查主要是在運行Pod以前在kubelet上進行Pod運行條件的檢查,檢查當前節點在scheduler決策完成後到感知到Pod運行這段時間資源是否依舊知足,而且檢查Pod的一些特殊資源好比好比sysctl、security等檢查,這裏我感受比較重要的兩個分別是eviction和predicate, 若是不知足准入檢查,則會直接拒絕算法

2.2.1 eviction准入檢查

若是當前節點只存在內存壓力,則會根據對應的Pod的QOS等級來判斷,若是說不是BestEffort或者容忍內存壓力的污點,則會容許,不然則會拒絕運行docker

nodeOnlyHasMemoryPressureCondition := hasNodeCondition(m.nodeConditions, v1.NodeMemoryPressure) && len(m.nodeConditions) == 1
    if nodeOnlyHasMemoryPressureCondition {
        // 若是不是PodQOSBestEffort, 則都會嘗試運行
        notBestEffort := v1.PodQOSBestEffort != v1qos.GetPodQOS(attrs.Pod)
        if notBestEffort {
            return lifecycle.PodAdmitResult{Admit: true}
        }
        // 若是對應的Pod容忍內存壓力的污點,則就能夠繼續進行其餘准入控制器的檢查
        if v1helper.TolerationsTolerateTaint(attrs.Pod.Spec.Tolerations, &v1.Taint{
            Key:    v1.TaintNodeMemoryPressure,
            Effect: v1.TaintEffectNoSchedule,
        }) {
            return lifecycle.PodAdmitResult{Admit: true}
        }
    }

2.2.2 predicate准入檢查

predicate准入控制器中的邏輯主要是分爲兩個部分:
1)檢查對應的資源是否知足分配請求,同時會記錄缺乏的資源
2)若是是Critical類型的Pod則會按照QOS等級來進行資源的搶佔,知足這些高優先的Pod
這裏的Critical類型的Pod主要包含以下三類:靜態Pod、鏡像Pod、高優先Pod(優先級高於2000000000)json

func (w *predicateAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
    node, err := w.getNodeAnyWayFunc()
    // 踢出擴展資源,只進行內存和CPU資源的檢查
    podWithoutMissingExtendedResources := removeMissingExtendedResources(admitPod, nodeInfo)

    // 進行預選算法篩選, 篩選出那些資源不足的資源
    fit, reasons, err := predicates.GeneralPredicates(podWithoutMissingExtendedResources, nil, nodeInfo)

    if !fit {
        // 若是預選失敗,則嘗試進行搶佔
        fit, reasons, err = w.admissionFailureHandler.HandleAdmissionFailure(admitPod, reasons)
    }

}

2.3 探針管理

k8s裏面的探針主要分爲三類:startup、readiness、liveness,在Pod經過准入控制檢查後,會根據Pod的探針配置建立對應的探針,可是這裏的探針並不會真正的進行探測,由於當前還沒法感知到對應的pod的狀態api

kl.probeManager.AddPod(pod)

2.4 分發事件

在kubelet中會爲每一個Pod都建立一個對應的goroutine和事件管道,後續新的事件也都經過管道發送給對應的goroutine緩存

func (p *podWorkers) UpdatePod(options *UpdatePodOptions) {
    // 獲取pod信息
    pod := options.Pod
    uid := pod.UID
    var podUpdates chan UpdatePodOptions
    var exists bool

    p.podLock.Lock()
    defer p.podLock.Unlock()
    // kubelet會爲每一個pod建立一個goroutine, 而且經過管道來進行通訊
    if podUpdates, exists = p.podUpdates[uid]; !exists {
        podUpdates = make(chan UpdatePodOptions, 1)
        p.podUpdates[uid] = podUpdates

        // 爲當前pod啓動一個goroutine
        go func() {
            defer runtime.HandleCrash()
            p.managePodLoop(podUpdates)
        }()
    }
    if !p.isWorking[pod.UID] {
        p.isWorking[pod.UID] = true
        // 更新Pod的事件發送到管道
        podUpdates <- *options
    } 
}
至此一個Pod的啓動的準備階段就基本完成了,檢查運行環境、拉取對應的cofnigMap和secret資源、建立探針、啓動負責Pod狀態維護的線程,至此準備階段完成

3.配置階段

在kubelet最終的狀態同步都是由syncPod來完成,該函數會根據傳遞進來的目標狀態和Pod的當前狀態來進行決策,從而知足目標狀態,由於內部邏輯的複雜,會分爲:配置階段、清理階段、構建運行階段,這裏先看下配置階段微信

配置階段主要是獲取當前的Pod狀態、應用CGOUP配置、Pod數據目錄構建、等待VOlume掛載、獲取鏡像拉取的secret等

3.1 計算Pod的狀態

Pod的狀態數據主要包含當前階段、Conditions(容器Condition、初始化容器Condition、PodReadyCondition),而這些狀態則須要根據當前的PodStatus裏面的狀態計算,還有probeManager裏面探測的數據兩部分共同完成

func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.PodStatus) v1.PodStatus {
    allStatus := append(append([]v1.ContainerStatus{}, s.ContainerStatuses...), s.InitContainerStatuses...)
    // 根據Pod的容器狀態,設定當前的的階段
    s.Phase = getPhase(spec, allStatus)
    kl.probeManager.UpdatePodStatus(pod.UID, s)
    s.Conditions = append(s.Conditions, status.GeneratePodInitializedCondition(spec, s.InitContainerStatuses, s.Phase))
    s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(spec, s.Conditions, s.ContainerStatuses, s.Phase))
    s.Conditions = append(s.Conditions, status.GenerateContainersReadyCondition(spec, s.ContainerStatuses, s.Phase))
    return *s
}

3.2 運行環境准入檢查

該運行環境是指的一些軟件狀態的,這裏主要涉及到Appmor、特權模式、proc掛載,實現機制就是檢測對應的Pod是否須要對應的操做,而且SecurityContext中是否容許對應的操做,從而肯定Pod是否可以進行運行

func (kl *Kubelet) canRunPod(pod *v1.Pod) lifecycle.PodAdmitResult {
    // 准入控制插件
    for _, handler := range kl.softAdmitHandlers {
        if result := handler.Admit(attrs); !result.Admit {
            return result
        }
    }

    return lifecycle.PodAdmitResult{Admit: true}
}

3.3 更新狀態

更新狀態主要是爲了probeManager來進行狀態檢查的,若是probeManager沒法獲取到對應的狀態,就不會執行對應的健康探針的檢查,這裏的狀態就是根據以前的各類計算在kubelet上對應Pod的當前狀態

kl.statusManager.SetPodStatus(pod, apiPodStatus)

3.4 網絡運行時檢查

if err := kl.runtimeState.networkErrors(); err != nil && !kubecontainer.IsHostNetworkPod(pod) {
        kl.recorder.Eventf(pod, v1.EventTypeWarning, events.NetworkNotReady, "%s: %v", NetworkNotReadyErrorMsg, err)
        return fmt.Errorf("%s: %v", NetworkNotReadyErrorMsg, err)
    }

3.5 CGroup配置

Cgroup的配置主要是按照QOS等級來進行cgroup目錄的構建,而且更新當前Pod的配置

pcm := kl.containerManager.NewPodContainerManager()
    // cgroup應用cgroup
    if !kl.podIsTerminated(pod) {
        podKilled := false
        if !pcm.Exists(pod) && !firstSync {
            // 若是對於的cgroup不存在,而且也不是第一次運行,就先將以前的pod沙雕
            if err := kl.killPod(pod, nil, podStatus, nil); err == nil {
                podKilled = true
            }
        }
        if !(podKilled && pod.Spec.RestartPolicy == v1.RestartPolicyNever) {
            if !pcm.Exists(pod) {
                // 更新qoscgroup設置
                if err := kl.containerManager.UpdateQOSCgroups(); err != nil {
                }
                // 更新podde的cgroup配置
                if err := pcm.EnsureExists(pod); err != nil {
                }
            }
        }
    }

3.6 鏡像Pod的檢查

由於要經過鏡像Pod來向apiserver傳遞靜態Pod的狀態,因此該階段主要是爲靜態Pod建立對應的鏡像Pod

if kubetypes.IsStaticPod(pod) {
        // 靜態pod
        podFullName := kubecontainer.GetPodFullName(pod)
        deleted := false
        if mirrorPod != nil {
            if mirrorPod.DeletionTimestamp != nil || !kl.podManager.IsMirrorPodOf(mirrorPod, pod) {
                deleted, err = kl.podManager.DeleteMirrorPod(podFullName, &mirrorPod.ObjectMeta.UID)
            }
        }
        if mirrorPod == nil || deleted {
                if err := kl.podManager.CreateMirrorPod(pod); err != nil {
                }
            }
        }
    }

3.7 建立Pod的數據目錄

Pod的數據目錄主要是包含三個部分:Pod目錄、Volume目錄、Plugin目錄三個目錄

if err := kl.makePodDataDirs(pod); err != nil {
        return err
    }

3.8 等待volume的掛載

if !kl.podIsTerminated(pod) {
        if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil {
        }
    }

3.9 獲取鏡像拉取的secrets

pullSecrets := kl.getPullSecretsForPod(pod)

3.10 調用容器的運行時進行同步

着多是最複雜的一部分了,接下來就進入到下一個階段:清理階段

result := kl.containerRuntime.SyncPod(pod, podStatus, pullSecrets, kl.backOff)
    kl.reasonCache.Update(pod.UID, result)

4. 清理階段

在Pod運行前可能已經有部分容器已經在運行,則此時就須要根據當前的狀態,來進行一些容器的清理工做,爲接下來的構建運行階段提供一個相對乾淨的環境

4.1 計算Pod狀態變動

在k8s中Pod的狀態主要包含sandbox容器狀態、初始化容器狀態、臨時容器狀態、業務容器狀態等幾部分,咱們依次來看下關鍵的實現

podContainerChanges := m.computePodActions(pod, podStatus)
沙箱狀態計算:當且僅有一個Ready的沙箱而且沙箱的IP不爲空的狀況,沙箱的狀態纔不須要更改,其餘狀況下,都須要從新進行沙箱的構建,而且須要kill掉Pod關聯的全部容器
func (m *kubeGenericRuntimeManager) podSandboxChanged(pod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, uint32, string) {
    if len(podStatus.SandboxStatuses) == 0 {
        return true, 0, ""
    }
    readySandboxCount := 0
    for _, s := range podStatus.SandboxStatuses {
        if s.State == runtimeapi.PodSandboxState_SANDBOX_READY {
            readySandboxCount++
        }
    }

    sandboxStatus := podStatus.SandboxStatuses[0]
    if readySandboxCount > 1 {
        return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id
    }
    if sandboxStatus.State != runtimeapi.PodSandboxState_SANDBOX_READY {
        return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id
    }

    if sandboxStatus.GetLinux().GetNamespaces().GetOptions().GetNetwork() != networkNamespaceForPod(pod) {
        return true, sandboxStatus.Metadata.Attempt + 1, ""
    }

    if !kubecontainer.IsHostNetworkPod(pod) && sandboxStatus.Network.Ip == "" {
        return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id
    }

    return false, sandboxStatus.Metadata.Attempt, sandboxStatus.Id
}

計算Pod的容器狀態計算邏輯相對長一些,這裏我就不貼代碼了,其如要流程分爲兩個部分:

1.須要建立sandbox:

在該狀態下,若是存在初始化容器,則會先進行初始化容器的初始化,即當前步驟只建立第一個初始化容器,若是沒有初始化容器,則就將全部的業務容器加入到啓動的列表裏面

2.不須要建立sandbox:

該狀態下會檢查遍歷全部的臨時容器,初始化容器(若是存在失敗的初始化容器,則就先啓動初始化容器,不會進行業務容器的啓動),業務容器,最終會構建一個須要kill掉的容器列表,還有兩個啓動的容器列表

4.2 killPod所有清理

須要進行KillPod的狀態有兩種:

sanbbox狀態變動

即當sandbox狀態不知足要求,則此時須要將Pod的全部容器都殺掉,而後進行重建

無需進行保留的容器

若是Pod對應的容器的hash值變動、狀態爲失敗,則就須要重建

if podContainerChanges.KillPod {
        // 殺死當前全部的pod
        killResult := m.killPodWithSyncResult(pod, kubecontainer.ConvertPodStatusToRunningPod(m.runtimeName, podStatus), nil)
        if podContainerChanges.CreateSandbox {
            // 終止初始化運行
            m.purgeInitContainers(pod, podStatus)
        }
    }

4.3 部分清理

若是容器當前的狀態是正常的,而且hash沒有發生變化,則就不須要進行變動,此時就只須要將當前狀態不正常的容器進行清理重建便可

for containerID, containerInfo := range podContainerChanges.ContainersToKill {

            if err := m.killContainer(pod, containerID, containerInfo.name, containerInfo.message, nil); err != nil {
                return
            }
        }
清理初始化容器

在正式啓動容器以前,除了上面兩部分,還會進行初始化容器的清理工做

m.pruneInitContainersBeforeStart(pod, podStatus)

5.構建運行階段

構建運行階段,主要分爲兩個大的部分:建立並運行sandbox容器、運行用戶容器

5.1 運行sandbox

檢查須要建立sandbox,則會首先建立sandbox容器,並獲取狀態,而後填充當前的Pod的IP信息

// Step 4: Create a sandbox for the pod if necessary.
    // 建立沙箱環境
    podSandboxID := podContainerChanges.SandboxID
    if podContainerChanges.CreateSandbox {

        podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)
        
        podSandboxStatus, err := m.runtimeService.PodSandboxStatus(podSandboxID)
        
        if !kubecontainer.IsHostNetworkPod(pod) {
            podIPs = m.determinePodSandboxIPs(pod.Namespace, pod.Name, podSandboxStatus)
        }
    }

5.2 建立sandbox主流程

建立sandbox的主流程主要就三個步驟:建立配置信息、建立日誌目錄、調用cri運行sandbox
生成配置階段主要包含端口映射、主機名、DNS、Linux中的SecurityContext燈的配置

func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) {
    // 獲取沙箱配置
    podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt)

    // 建立目錄
    err = m.osInterface.MkdirAll(podSandboxConfig.LogDirectory, 0755)

    runtimeHandler := ""
    if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) && m.runtimeClassManager != nil {
        // 獲取當前的runtimeHandler
        runtimeHandler, err = m.runtimeClassManager.LookupRuntimeHandler(pod.Spec.RuntimeClassName)
    }
    // 運行Sandbox
    podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler)
    return podSandBoxID, "", nil
}

5.3 cri中的RunSandbox

sandbox的啓動主要包含下面幾部分:1) 拉取sanbox容器鏡像 2)建立sandbox容器 3)建立sandbox的checkpoint 4)啓動sandbox容器 5)爲sandbox啓動網絡(若是不是主機網絡)

func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
    config := r.GetConfig()

    // Step 1: Pull the image for the sandbox.
    // 拉取sandbox沙箱
    //  defaultPodSandboxImageName    = "k8s.gcr.io/pause"
    //  defaultPodSandboxImageVersion = "3.1"
    image := defaultSandboxImage
    podSandboxImage := ds.podSandboxImage
    if len(podSandboxImage) != 0 {
        image = podSandboxImage
    }

    // 拉取鏡像
    if err := ensureSandboxImageExists(ds.client, image); err != nil {
        return nil, err
    }
    // 2.建立sandbox容器
    if r.GetRuntimeHandler() != "" && r.GetRuntimeHandler() != runtimeName {
        return nil, fmt.Errorf("RuntimeHandler %q not supported", r.GetRuntimeHandler())
    }
    // 建立沙箱配置 
    createConfig, err := ds.makeSandboxDockerConfig(config, image)
    
    // 建立容器
    createResp, err := ds.client.CreateContainer(*createConfig)

    resp := &runtimeapi.RunPodSandboxResponse{PodSandboxId: createResp.ID}

    ds.setNetworkReady(createResp.ID, false)
    defer func(e *error) {
        // Set networking ready depending on the error return of
        // the parent function
        if *e == nil {
            ds.setNetworkReady(createResp.ID, true)
        }
    }(&err)

    // Step 3: 建立sandbox checkpoint
    if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
        return nil, err
    }

    // Step 4: Start the sandbox container.
    // // 4.啓動sandbox容器
    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)
    }
    //重寫docker生成的resolv.conf文件。
    if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
        containerInfo, err := ds.client.InspectContainer(createResp.ID)
        if err != nil {
            return nil, fmt.Errorf("failed to inspect sandbox container for pod %q: %v", config.Metadata.Name, err)
        }

        // 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)
        }
    }

    // 若是處於主機網絡模式,請不要調用網絡插件。
    if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE {
        return resp, nil
    }

    // Step 5: 設置sandbox容器的網絡
    //全部的pod網絡都是由啓動時發現的CNI插件設置的。
    //這個插件分配pod ip,在沙盒內設置路由,建立接口等。理論上,它的管轄權以pod沙盒網絡結束,
    // 但它也可能在主機上插入iptables規則或打開端口,以知足CNI標準還沒有識別的pod規範的部分要求。
    cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID)
    networkOptions := make(map[string]string)
    if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
        // Build DNS options.
        dnsOption, err := json.Marshal(dnsConfig)
        if err != nil {
            return nil, fmt.Errorf("failed to marshal dns config for pod %q: %v", config.Metadata.Name, err)
        }
        // 設置網絡dns
        networkOptions["dns"] = string(dnsOption)
    }
    // 網絡信息
    err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)

    return resp, nil
}

5.4 容器啓動函數

容器啓動函數中會經過閉包來保存上面建立的sandbox的信息,同時根據當前容器的配置,建立新的業務容器

start := func(typeName string, container *v1.Container) error {

        klog.V(4).Infof("Creating %v %+v in pod %v", typeName, container, format.Pod(pod))
        if msg, err := m.startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podIP, podIPs); err != nil {
            startContainerResult.Fail(err, msg)
        }

        return nil
    }

5.5 啓動容器

容器的啓動,主要包含四個流程:1.拉取鏡像 2.建立容器&PreStart鉤子回調 3) 啓動容器 4)postStart啓動容器

func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, container *v1.Container, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
    // 啓動容器
    // Step 1: pull the image.
    imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets, podSandboxConfig)
    
    // Step 2: create the container.
    ref, err := kubecontainer.GenerateContainerRef(pod, container)

    // 獲取容器配置, 裏面會進行各類文件目錄的掛載
    containerConfig, cleanupAction, err := m.generateContainerConfig(container, pod, restartCount, podIP, imageRef, podIPs)
    if cleanupAction != nil {
        defer cleanupAction()
    }
    if err != nil {
        s, _ := grpcstatus.FromError(err)
        m.recordContainerEvent(pod, container, "", v1.EventTypeWarning, events.FailedToCreateContainer, "Error: %v", s.Message())
        return s.Message(), ErrCreateContainerConfig
    }

    // 建立容器
    containerID, err := m.runtimeService.CreateContainer(podSandboxID, containerConfig, podSandboxConfig)
    // 啓動容器鉤子
    err = m.internalLifecycle.PreStartContainer(pod, container, containerID)
    m.recordContainerEvent(pod, container, containerID, v1.EventTypeNormal, events.CreatedContainer, fmt.Sprintf("Created container %s", container.Name))

    if ref != nil {
        m.containerRefManager.SetRef(kubecontainer.ContainerID{
            Type: m.runtimeName,
            ID:   containerID,
        }, ref)
    }

    // Step 3: 啓動容器
    err = m.runtimeService.StartContainer(containerID)
    if err != nil {
        s, _ := grpcstatus.FromError(err)
        m.recordContainerEvent(pod, container, containerID, v1.EventTypeWarning, events.FailedToStartContainer, "Error: %v", s.Message())
        return s.Message(), kubecontainer.ErrRunContainer
    }
    containerMeta := containerConfig.GetMetadata()
    sandboxMeta := podSandboxConfig.GetMetadata()
    legacySymlink := legacyLogSymlink(containerID, containerMeta.Name, sandboxMeta.Name,
        sandboxMeta.Namespace)
    // 容器日誌
    containerLog := filepath.Join(podSandboxConfig.LogDirectory, containerConfig.LogPath)
    if _, err := m.osInterface.Stat(containerLog); !os.IsNotExist(err) {
        if err := m.osInterface.Symlink(containerLog, legacySymlink); err != nil {
        }
    }

    // Step 4: 執行postStart鉤子
    if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
        msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)
        if handlerErr != nil {
            if err := m.killContainer(pod, kubeContainerID, container.Name, "FailedPostStartHook", nil); err != nil {
            }
        }
    }

    return "", nil
}

5.6 cri.CreateContainer

CreateContainer中會首先根據k8s裏面傳遞的配置信息,根據當前平臺和對應的參數來進行docker容器運行的配置,而後調用docker接口進行容器的配置

func (ds *dockerService) CreateContainer(_ context.Context, r *runtimeapi.CreateContainerRequest) (*runtimeapi.CreateContainerResponse, error) {
    podSandboxID := r.PodSandboxId
    config := r.GetConfig()
    sandboxConfig := r.GetSandboxConfig()
    containerName := makeContainerName(sandboxConfig, config)
    // 建立容器配置
    createConfig := dockertypes.ContainerCreateConfig{
        Name: containerName,
        Config: &dockercontainer.Config{
            // TODO: set User.
            Entrypoint: dockerstrslice.StrSlice(config.Command),
            Cmd:        dockerstrslice.StrSlice(config.Args),
            Env:        generateEnvList(config.GetEnvs()),
            Image:      image,
            WorkingDir: config.WorkingDir,
            Labels:     labels,
            // Interactive containers:
            OpenStdin: config.Stdin,
            StdinOnce: config.StdinOnce,
            Tty:       config.Tty,
            // Disable Docker's health check until we officially support it
            // (https://github.com/kubernetes/kubernetes/issues/25829).
            Healthcheck: &dockercontainer.HealthConfig{
                Test: []string{"NONE"},
            },
        },
        HostConfig: &dockercontainer.HostConfig{
            Binds: generateMountBindings(config.GetMounts()),
            RestartPolicy: dockercontainer.RestartPolicy{
                Name: "no",
            },
        },
    }

    hc := createConfig.HostConfig
    err = ds.updateCreateConfig(&createConfig, config, sandboxConfig, podSandboxID, securityOptSeparator, apiVersion)
    if err != nil {
        return nil, fmt.Errorf("failed to update container create config: %v", err)
    }
    // 設置容器devices
    devices := make([]dockercontainer.DeviceMapping, len(config.Devices))
    for i, device := range config.Devices {
        devices[i] = dockercontainer.DeviceMapping{
            PathOnHost:        device.HostPath,
            PathInContainer:   device.ContainerPath,
            CgroupPermissions: device.Permissions,
        }
    }
    hc.Resources.Devices = devices

    securityOpts, err := ds.getSecurityOpts(config.GetLinux().GetSecurityContext().GetSeccompProfilePath(), securityOptSeparator)
    if err != nil {
        return nil, fmt.Errorf("failed to generate security options for container %q: %v", config.Metadata.Name, err)
    }

    hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)

    cleanupInfo, err := ds.applyPlatformSpecificDockerConfig(r, &createConfig)
    if err != nil {
        return nil, err
    }

    createResp, createErr := ds.client.CreateContainer(createConfig)
    if createErr != nil {
        createResp, createErr = recoverFromCreationConflictIfNeeded(ds.client, createConfig, createErr)
    }

    if createResp != nil {
        containerID := createResp.ID

        if cleanupInfo != nil {
            // we don't perform the clean up just yet at that could destroy information
            // needed for the container to start (e.g. Windows credentials stored in
            // registry keys); instead, we'll clean up when the container gets removed
            ds.containerCleanupInfos[containerID] = cleanupInfo
        }
        return &runtimeapi.CreateContainerResponse{ContainerId: containerID}, nil
    }

    return nil, createErr
}
更新容器配置
func (ds *dockerService) updateCreateConfig(
    createConfig *dockertypes.ContainerCreateConfig,
    config *runtimeapi.ContainerConfig,
    sandboxConfig *runtimeapi.PodSandboxConfig,
    podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
    if lc := config.GetLinux(); lc != nil {
        rOpts := lc.GetResources()
        if rOpts != nil {
            // 更新資源配置信息
            createConfig.HostConfig.Resources = dockercontainer.Resources{
                Memory:     rOpts.MemoryLimitInBytes,
                MemorySwap: rOpts.MemoryLimitInBytes,
                CPUShares:  rOpts.CpuShares,
                CPUQuota:   rOpts.CpuQuota,
                CPUPeriod:  rOpts.CpuPeriod,
            }
            createConfig.HostConfig.OomScoreAdj = int(rOpts.OomScoreAdj)
        }
        // 應用SecurityContext
        if err := applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, createConfig.HostConfig, securityOptSep); err != nil {
            return fmt.Errorf("failed to apply container security context for container %q: %v", config.Metadata.Name, err)
        }
    }

    // 應用cgroup配置
    if lc := sandboxConfig.GetLinux(); lc != nil {
        // Apply Cgroup options.
        cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent)
        createConfig.HostConfig.CgroupParent = cgroupParent
    }

    return nil
}

5.7 cri.StartContainer

其實就直接掉Docker的接口啓動容器便可

func (ds *dockerService) StartContainer(_ context.Context, r *runtimeapi.StartContainerRequest) (*runtimeapi.StartContainerResponse, error) {
    err := ds.client.StartContainer(r.ContainerId)
    return &runtimeapi.StartContainerResponse{}, nil
}

6. 總結

image.png

Pod啓動的核心流程大概就這些,裏面會有一些筆認購具體參數數據的構建,沒有寫明,可是若是對代碼感興趣的,能夠順着這個核心流程基本能夠讀下來,若是對代碼不感興趣,則後面這張圖能夠算做一個精簡版的,面試可用的Pod建立流程圖

本文地址: https://www.yuque.com/baxiaoshi/tyado3/kw00f2

kubernetes學習筆記地址: https://www.yuque.com/baxiaos...

微信號:baxiaoshi2020
關注公告號閱讀更多源碼分析文章 圖解源碼
本文由博客一文多發平臺 OpenWrite 發佈!
相關文章
相關標籤/搜索