Kubernetes ContainerGC分析

為了回收系統(tǒng)上的資源kubelet有ImageGC和ContainerGC等功能對image和container進行回收,下面就根據(jù)kubelet代碼對ContainerGC部分進行分析擅编。
相關的參數(shù)主要有:

  • minimum-container-ttl-duration
  • maximum-dead-containers-per-container
  • minimum-container-ttl-duration

相對應的代碼是:

//pkg/kubelet/container_gc.go

type ContainerGCPolicy struct {
    // 已經(jīng)死掉的容器在機器上存留的時間
    MinAge time.Duration

    // 每個pod可以保留的死掉的容器
    MaxPerPodContainer int

    // 機器上最大的可以保留的死亡容器數(shù)量
    MaxContainers int
}
...
func NewContainerGC(runtime Runtime, policy ContainerGCPolicy, sourcesReadyProvider SourcesReadyProvider) (ContainerGC, error) {
    if policy.MinAge < 0 {
        return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge)
    }

    return &realContainerGC{
        runtime:              runtime,
        policy:               policy,
        sourcesReadyProvider: sourcesReadyProvider,
    }, nil
}

func (cgc *realContainerGC) GarbageCollect() error {
    return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), false)
}

func (cgc *realContainerGC) DeleteAllUnusedContainers() error {
    return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), true)
}

GarbageCollect方法里面的調(diào)用就是ContainerGC真正的邏輯所在, GarbageCollect函數(shù)是在pkg/kubelet/kubelet.go里面調(diào)用的茬故,每隔一分鐘會執(zhí)行一次。GarbageCollect里面所調(diào)用的runtime的GarbageCollect函數(shù)是在pkg/kubelet/kuberuntime/kuberuntime_gc.go里面熬的。

//pkg/kubelet/kuberuntime/kuberuntime_gc.go
func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error {
    // Remove evictable containers
    if err := cgc.evictContainers(gcPolicy, allSourcesReady, evictNonDeletedPods); err != nil {
        return err
    }

    // Remove sandboxes with zero containers
    if err := cgc.evictSandboxes(evictNonDeletedPods); err != nil {
        return err
    }

    // Remove pod sandbox log directory
    return cgc.evictPodLogsDirectories(allSourcesReady)
}

第一步是驅逐容器

func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error {
    // Separate containers by evict units.
    evictUnits, err := cgc.evictableContainers(gcPolicy.MinAge)
    if err != nil {
        return err
    }

    // Remove deleted pod containers if all sources are ready.
    if allSourcesReady {
        for key, unit := range evictUnits {
            if cgc.isPodDeleted(key.uid) || evictNonDeletedPods {
                cgc.removeOldestN(unit, len(unit)) // Remove all.
                delete(evictUnits, key)
            }
        }
    }

    // Enforce max containers per evict unit.
    if gcPolicy.MaxPerPodContainer >= 0 {
        cgc.enforceMaxContainersPerEvictUnit(evictUnits, gcPolicy.MaxPerPodContainer)
    }

    // Enforce max total number of containers.
    if gcPolicy.MaxContainers >= 0 && evictUnits.NumContainers() > gcPolicy.MaxContainers {
        // Leave an equal number of containers per evict unit (min: 1).
        numContainersPerEvictUnit := gcPolicy.MaxContainers / evictUnits.NumEvictUnits()
        if numContainersPerEvictUnit < 1 {
            numContainersPerEvictUnit = 1
        }
        cgc.enforceMaxContainersPerEvictUnit(evictUnits, numContainersPerEvictUnit)

        // If we still need to evict, evict oldest first.
        numContainers := evictUnits.NumContainers()
        if numContainers > gcPolicy.MaxContainers {
            flattened := make([]containerGCInfo, 0, numContainers)
            for key := range evictUnits {
                flattened = append(flattened, evictUnits[key]...)
            }
            sort.Sort(byCreated(flattened))

            cgc.removeOldestN(flattened, numContainers-gcPolicy.MaxContainers)
        }
    }
    return nil
}

1.首先獲取已經(jīng)死掉的并且創(chuàng)建時間大于minage的容器

2.如果pod已經(jīng)delete那么把屬于這個pod的容器全部刪除

3.如果設置了MaxPerPodContainer那么把MaxPerPodContainer之外數(shù)量的容器刪除,這個值默認是1

4.如果設置MaxContainers那么再次對容器進行清理咆槽,這個值默認是-1也就是不清理的派诬。首先會拿所有容器的數(shù)量除以pod的數(shù)量劳淆,這樣會得到一個平均值,然后按照這個值對3進行再次處理默赂。這個時候如果機器上的死亡容器的數(shù)量還大于MaxContainer那么直接按照時間對容器進行排序然后刪除大于MaxContainer數(shù)量之外的容器沛鸵。

下一步是清理機器上的sandbox

func (cgc *containerGC) evictSandboxes(evictNonDeletedPods bool) error {
    containers, err := cgc.manager.getKubeletContainers(true)
    if err != nil {
        return err
    }

    sandboxes, err := cgc.manager.getKubeletSandboxes(true)
    if err != nil {
        return err
    }

    sandboxesByPod := make(sandboxesByPodUID)
    for _, sandbox := range sandboxes {
        podUID := types.UID(sandbox.Metadata.Uid)
        sandboxInfo := sandboxGCInfo{
            id:         sandbox.Id,
            createTime: time.Unix(0, sandbox.CreatedAt),
        }

        // Set ready sandboxes to be active.
        if sandbox.State == runtimeapi.PodSandboxState_SANDBOX_READY {
            sandboxInfo.active = true
        }

        // Set sandboxes that still have containers to be active.
        hasContainers := false
        sandboxID := sandbox.Id
        for _, container := range containers {
            if container.PodSandboxId == sandboxID {
                hasContainers = true
                break
            }
        }
        if hasContainers {
            sandboxInfo.active = true
        }

        sandboxesByPod[podUID] = append(sandboxesByPod[podUID], sandboxInfo)
    }

    // Sort the sandboxes by age.
    for uid := range sandboxesByPod {
        sort.Sort(sandboxByCreated(sandboxesByPod[uid]))
    }

    for podUID, sandboxes := range sandboxesByPod {
        if cgc.isPodDeleted(podUID) || evictNonDeletedPods {
            // Remove all evictable sandboxes if the pod has been removed.
            // Note that the latest dead sandbox is also removed if there is
            // already an active one.
            cgc.removeOldestNSandboxes(sandboxes, len(sandboxes))
        } else {
            // Keep latest one if the pod still exists.
            cgc.removeOldestNSandboxes(sandboxes, len(sandboxes)-1)
        }
    }
    return nil
}

先獲取機器上所有的容器和sandbox,如果pod的狀態(tài)是0則致為active狀態(tài),如果此sandbox還有運行的container也認為是active狀態(tài)缆八,接著對sandbox進行排序曲掰,如果sandbox所屬的pod的已經(jīng)被刪除那么刪除所有的sandbox,如果pod還存在那么就留下最新的一個sandbox其他的都刪除.

最后一步是清除container和pod日志

// evictPodLogsDirectories evicts all evictable pod logs directories. Pod logs directories
// are evictable if there are no corresponding pods.
func (cgc *containerGC) evictPodLogsDirectories(allSourcesReady bool) error {
    osInterface := cgc.manager.osInterface
    if allSourcesReady {
        // Only remove pod logs directories when all sources are ready.
        dirs, err := osInterface.ReadDir(podLogsRootDirectory)
        if err != nil {
            return fmt.Errorf("failed to read podLogsRootDirectory %q: %v", podLogsRootDirectory, err)
        }
        for _, dir := range dirs {
            name := dir.Name()
            podUID := types.UID(name)
            if !cgc.isPodDeleted(podUID) {
                continue
            }
            err := osInterface.RemoveAll(filepath.Join(podLogsRootDirectory, name))
            if err != nil {
                glog.Errorf("Failed to remove pod logs directory %q: %v", name, err)
            }
        }
    }

    // Remove dead container log symlinks.
    // TODO(random-liu): Remove this after cluster logging supports CRI container log path.
    logSymlinks, _ := osInterface.Glob(filepath.Join(legacyContainerLogsDir, fmt.Sprintf("*.%s", legacyLogSuffix)))
    for _, logSymlink := range logSymlinks {
        if _, err := osInterface.Stat(logSymlink); os.IsNotExist(err) {
            err := osInterface.Remove(logSymlink)
            if err != nil {
                glog.Errorf("Failed to remove container log dead symlink %q: %v", logSymlink, err)
            }
        }
    }
    return nil
}

首先會讀取/var/log/pods目錄下面的子目錄奈辰,下面的目錄名稱都是pod的uid栏妖,如果pod已經(jīng)刪除那么直接把pod所屬的目錄刪除,然后刪除/var/log/containers目錄下的軟連接奖恰。
至此單次ContainerGC的流程結束吊趾。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宛裕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子论泛,更是在濱河造成了極大的恐慌揩尸,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁奏,死亡現(xiàn)場離奇詭異岩榆,居然都是意外死亡,警方通過查閱死者的電腦和手機了袁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門朗恳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人载绿,你說我怎么就攤上這事∮秃剑” “怎么了崭庸?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谊囚。 經(jīng)常有香客問我怕享,道長,這世上最難降的妖魔是什么镰踏? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任函筋,我火速辦了婚禮,結果婚禮上奠伪,老公的妹妹穿的比我還像新娘跌帐。我一直安慰自己,他們只是感情好绊率,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布谨敛。 她就那樣靜靜地躺著,像睡著了一般滤否。 火紅的嫁衣襯著肌膚如雪脸狸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天藐俺,我揣著相機與錄音炊甲,去河邊找鬼。 笑死欲芹,一個胖子當著我的面吹牛卿啡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耀石,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼牵囤,長吁一口氣:“原來是場噩夢啊……” “哼爸黄!你這毒婦竟也來了?” 一聲冷哼從身側響起揭鳞,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤炕贵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后野崇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體称开,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年乓梨,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳖轰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡扶镀,死狀恐怖蕴侣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭觉,我是刑警寧澤昆雀,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站蝠筑,受9級特大地震影響狞膘,放射性物質發(fā)生泄漏。R本人自食惡果不足惜什乙,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一挽封、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臣镣,春花似錦辅愿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至褒繁,卻和暖如春亦鳞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棒坏。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工燕差, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坝冕。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓徒探,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喂窟。 傳聞我的和親對象是個殘疾皇子测暗,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容