kubernetes pod內(nèi)容器狀態(tài)OOMKilled和退出碼137全流程解析

在kubernetes的實(shí)際生產(chǎn)實(shí)踐中,經(jīng)常會(huì)看到pod內(nèi)的容器因?yàn)閮?nèi)存使用超限被內(nèi)核kill掉劝篷,使用kubectl命令查看pod枫笛,可以看到容器的退出原因是OOMKilled法精,退出碼是137徒河。

文章導(dǎo)讀

cgroup簡介與使用
linux epoll原理分析
containerd代碼解析
kubelet代碼解析
使用event_control監(jiān)聽oom事件

經(jīng)過前面幾篇文章的鋪墊與遞進(jìn)系馆,本篇終于可以進(jìn)入正題,已以下兩條主線進(jìn)行分析顽照。

  1. 容器的退出原因OOMKilled是如何經(jīng)由containerd更新到kubelet它呀,并最終更新到Pod的status中。
  2. 退出碼為何是137棒厘。

本文的分析思路是由下至上,先分析內(nèi)核的oom是如何觸發(fā)的下隧,再分析進(jìn)程被kill掉后奢人,退出碼是何時(shí)賦值的,最后再分析containerd-shim淆院,containerd何乎,kubelet是如何處理進(jìn)程的oom狀態(tài)和退出碼。

場景再現(xiàn)

本次實(shí)驗(yàn)基于4.19內(nèi)核的centos的系統(tǒng)土辩,kubernetes版本為1.23.1支救,kubelet的運(yùn)行時(shí)配置為containerd,containerd版本為1.4拷淘,cgroup使用v1版本各墨。

首先,編輯一個(gè)Pod的yaml文件启涯,配置Pod的restartPolicy為Nerver贬堵,內(nèi)存限額設(shè)置為50M,容器鏡像為mem_alloc:v1结洼。該容器啟動(dòng)后會(huì)不斷申請并使用內(nèi)存黎做,直到內(nèi)存超限,被內(nèi)核kill掉松忍。

[root@localhost oom]# cat pod_oom.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: lugl-oom-test
spec:
  restartPolicy: Never
  containers:
  - name: oom-test
    image: docker.io/registry/mem_alloc:v1
    imagePullPolicy: IfNotPresent
    resources: 
      limits:
        memory: "50Mi"

使用kubectl create -f pod_oom.yaml創(chuàng)建該pod蒸殿,過一會(huì)兒使用kubectl查看pod的狀態(tài)(刪除掉不相關(guān)的字段信息)。可以看到容器的退出原因(reason)為OOMKilled宏所,退出碼(exitCode)為137酥艳。其中還有個(gè)字段lastState可以記錄上一次的容器的狀態(tài)信息,這里為了簡化分析楣铁,忽略該字段玖雁。

[root@localhost oom]# kubectl  get pods  lugl-oom-test  -o yaml
apiVersion: v1
kind: Pod
spec:
  containers:
  - image: docker.io/registry/mem_alloc:v1
    imagePullPolicy: IfNotPresent
    name: oom-test
    resources:
      limits:
        memory: 50Mi
      requests:
        memory: 50Mi
  restartPolicy: Never

status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-02-20T02:10:30Z"
    status: "True"
    type: Initialized
  containerStatuses:
  - containerID: containerd://388f01cfb6b8ba2817ff85ef8e72c654f866200d96123a623a73e0304e4934cf
    image: docker.io/registry/mem_alloc:v1
    imageID: sha256:3bd194272e76fa429b1c5df19d91bcf47eacc1ed07e3afe378ec3d7b49524ef0
    lastState: {}
    name: oom-test
    ready: false
    restartCount: 0
    started: false
    state:
      terminated:
        containerID: containerd://388f01cfb6b8ba2817ff85ef8e72c654f866200d96123a623a73e0304e4934cf
        exitCode: 137
        finishedAt: "2022-02-20T02:10:42Z"
        reason: OOMKilled
        startedAt: "2022-02-20T02:10:31Z"

oom機(jī)制和信號(hào)處理

(1) 本環(huán)境中的PAGESIZE為4K,mem_alloc程序在運(yùn)行中盖腕,會(huì)申請內(nèi)存并使用赫冬,所以會(huì)不斷觸發(fā)缺頁異常,內(nèi)核在每次申請內(nèi)存頁時(shí)都會(huì)去比較該cgroup進(jìn)程的內(nèi)存使用量 是否超過了該cgroup內(nèi)設(shè)置的閾值(memory.limit_in_bytes)溃列,如果超過閾值劲厌,并且在該cgroup內(nèi)也沒法通過內(nèi)存回收釋放足夠的內(nèi)存,則內(nèi)核會(huì)在該cgroup內(nèi)選擇一個(gè)進(jìn)程kill掉(oom_badness)听隐。

(2)當(dāng)觸發(fā)oom時(shí)补鼻,內(nèi)核會(huì)向進(jìn)程發(fā)送SIGKILL信號(hào),這里的發(fā)送信號(hào)是擬人化的寫法雅任,方便人去理解风范,但實(shí)際的內(nèi)核發(fā)送信號(hào)僅僅是對進(jìn)程數(shù)據(jù)結(jié)構(gòu)(task_struct)中的信號(hào)相關(guān)字段的修改,如將SIGKILL這個(gè)sig添加到該進(jìn)程的信號(hào)處理隊(duì)列中沪么,這樣就算內(nèi)核完成了信號(hào)的發(fā)送硼婿。

SIGKILL(對應(yīng)信號(hào)值9)和SIGSTOP(對應(yīng)信號(hào)值19)是兩個(gè)特權(quán)信號(hào),即用戶不可以注冊這兩個(gè)信號(hào)的信號(hào)處理函數(shù)禽车,由內(nèi)核的默認(rèn)函數(shù)進(jìn)行處理寇漫。 SIGTERM(對應(yīng)信號(hào)值15)是可以被注冊信號(hào)處理函數(shù)的。kill命令不加參數(shù)默認(rèn)就是向進(jìn)程發(fā)送SIGTERM信號(hào)殉摔。kubelet停止pod時(shí)州胳,也是先發(fā)送SIGTERM信號(hào),經(jīng)過terminationGracePeriodSeconds時(shí)間后逸月,如果pod沒有退出再發(fā)送SIGKILL信號(hào)栓撞。所以容器內(nèi)的進(jìn)程最好是注冊SIGTERM對應(yīng)的信號(hào)處理函數(shù),在進(jìn)程退出的時(shí)候做一些資源清理的操作碗硬,減少異常的發(fā)生腐缤,如關(guān)閉遠(yuǎn)端連接。容器內(nèi)的init進(jìn)程退出時(shí)肛响,內(nèi)核會(huì)向該init進(jìn)程命名空間下的其他進(jìn)程發(fā)送SIGKILL信號(hào)岭粤,更完善的做法是init進(jìn)程攔截SIGTERM信號(hào)后,將SIGTERM轉(zhuǎn)發(fā)給子進(jìn)程特笋,這樣init和init的子進(jìn)程都可以實(shí)現(xiàn)優(yōu)雅退出剃浇,如docker的 --init參數(shù)就是使用一個(gè)專用的init進(jìn)程接管用戶的程序巾兆。

下面就是mem_alloc進(jìn)程因?yàn)閛om被kil掉的堆棧信息。

# 設(shè)置以下關(guān)注函數(shù)
[root@node-135 tracing]# cat set_ftrace_filter 
__send_signal
do_send_sig_info
__oom_kill_process
out_of_memory
mem_cgroup_out_of_memory
try_charge
# cat trace_pipe
 => __send_signal  -- 向進(jìn)程發(fā)送SIGKILL信號(hào)
 => do_send_sig_info
 => __oom_kill_process    -- kill掉一些進(jìn)程
 => oom_kill_process
 => out_of_memory     -- 內(nèi)存不足
 => mem_cgroup_out_of_memory
 => try_charge            
 => mem_cgroup_try_charge  -- cgroup內(nèi)的內(nèi)存是否充足
 => mem_cgroup_try_charge_delay
 => __handle_mm_fault
 => handle_mm_fault         
 => __do_page_fault
 => do_page_fault
 => page_fault                     -- 缺頁異常

(3)內(nèi)核修改了進(jìn)程的task_struct虎囚,表明該進(jìn)程有信號(hào)需要處理角塑。用戶注冊的信號(hào)處理函數(shù)在用戶態(tài),此時(shí)還在內(nèi)核中執(zhí)行異常處理流程淘讥,那么信號(hào)處理函數(shù)的執(zhí)行時(shí)機(jī)是什么呢圃伶? 在返回到用戶態(tài)的時(shí)候是一個(gè)執(zhí)行時(shí)機(jī)。信號(hào)處理的實(shí)現(xiàn)比較復(fù)雜蒲列,具體細(xì)節(jié)分析可以參見極客時(shí)間《趣談linux操作系統(tǒng)》中的信號(hào)處理章節(jié)窒朋,詳細(xì)介紹了信號(hào)處理函數(shù)執(zhí)行的時(shí)機(jī),以及執(zhí)行完信號(hào)處理函數(shù)又是如何返回到正常的執(zhí)行流程中的整個(gè)過程蝗岖。

以下堆棧信息展示了mem_alloc程序的退出過程侥猩。

# 以下的函數(shù)堆棧是一個(gè)SIGKILL信號(hào)的處理
 => __send_signal  -- 向父進(jìn)程發(fā)送SIGCHILD信號(hào)
 => do_notify_parent
 => do_exit      -- 在這個(gè)函數(shù)里會(huì)設(shè)置進(jìn)程的退出碼
 => do_group_exit -- 這個(gè)函數(shù)的參數(shù)只有一個(gè)退出碼
 => get_signal    -- 獲取需要處理的信號(hào)
 => do_signal  
 => exit_to_usermode_loop   -- 返回到用戶態(tài)
 => prepare_exit_to_usermode
 => swapgs_restore_regs_and_return_to_usermode

(4)正常的程序在退出時(shí)是調(diào)用exit系統(tǒng)調(diào)用,exit是調(diào)用do_exit函數(shù)抵赢,在(3)的堆棧中可以看到處理SIGKILL也會(huì)調(diào)用do_exit欺劳,區(qū)別在于正常的退出,錯(cuò)誤碼是經(jīng)過了處理的铅鲤,而在處理SIGKILL信號(hào)時(shí)划提,是直接將信號(hào)的值賦值給了進(jìn)程的退出碼(task_struct.exit_code)。

SYSCALL_DEFINE1(exit, int, error_code)
{
    do_exit((error_code&0xff)<<8);
}

(5)使用$?查看程序的退出碼邢享,這也上面的分析不一致鹏往,錯(cuò)誤碼應(yīng)該是9,還不是137驼仪,問題在哪里呢?

[root@localhost test]# echo 104857600 > memory.limit_in_bytes                             
[root@localhost test]# echo $$ > cgroup.procs                             
[root@localhost test]# /root/training/memory/oom/mem-alloc/mem_alloc 40000
Allocating,set to 40000 Mbytes
Killed
[root@localhost test]# echo $?
137

(6)重復(fù)(5)中的步驟袜漩,使用ftrace查看函數(shù)do_exit的參數(shù)值绪爸。從下面的trace_pipe中確實(shí)看到mem_alloc的退出碼確實(shí)是9,而不是137宙攻。這里直接說結(jié)論奠货,在下面會(huì)再次驗(yàn)證,bash進(jìn)程是mem_alloc的父進(jìn)程座掘,在處理mem_alloc進(jìn)程的返回值時(shí)递惋,如果是9,說明是被SIGKILL掉的溢陪,所以將9加上一個(gè)偏移量128萍虽,結(jié)果就是137,這樣做可能是為了與linux系統(tǒng)標(biāo)準(zhǔn)的錯(cuò)誤碼做一個(gè)區(qū)分吧形真。

# 設(shè)置kprobe
[root@localhost tracing]# echo 'p:myprobe do_exit exit_code=%di' > kprobe_events
[root@localhost tracing]# echo 1 > events/kprobes/myprobe/enable 
# 設(shè)置過濾條件
[root@localhost tracing]# echo exit_code==9 > events/kprobes/myprobe/filter
[root@localhost tracing]# /root/training/memory/oom/mem-alloc/mem_alloc 40000
Allocating,set to 40000 Mbytes
Killed
#查看結(jié)果
[root@localhost tracing]# cat trace_pipe 
       mem_alloc-17077 [001] ....   875.147095: myprobe: (do_exit+0x0/0xc60) exit_code=0x9

containerd-shim 進(jìn)程的處理

containerd-shim監(jiān)聽cgroup內(nèi)進(jìn)程的oom事件與使用event_control監(jiān)聽oom事件中的一致杉编,這里不再贅述。

(1) 在Run函數(shù)中會(huì)監(jiān)聽eventfd,如果epoll_wait返回邓馒,則說明該cgroup內(nèi)有進(jìn)程觸發(fā)了oom嘶朱。

func (e *epoller) Run(ctx context.Context) {
    var events [128]unix.EpollEvent
    for {
        select {
        default:
            n, err := unix.EpollWait(e.fd, events[:], -1)
            for i := 0; i < n; i++ {
                e.process(ctx, uintptr(events[i].Fd))
            }
        }
    }
}

(2) process函數(shù)中會(huì)通過GRPC消息將TaskOOMEvent轉(zhuǎn)發(fā)到containerd中。

func (e *epoller) process(ctx context.Context, fd uintptr) {
    if err := e.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{
        ContainerID: i.id,
    });
}

(3)在進(jìn)程退出時(shí)光酣,內(nèi)核會(huì)向其父進(jìn)程(containerd-shim)發(fā)送SIGCHILD信號(hào)疏遏。handleSignals會(huì)處理SIGCHILD信號(hào),調(diào)用Reap救军,進(jìn)而使用系統(tǒng)調(diào)用wait4拿到進(jìn)程的退出碼财异。

// runtime/v2/shim/shim_unix.go
func handleSignals(ctx context.Context, logger *logrus.Entry, signals chan os.Signal) error {
    for {
        select {
        case s := <-signals:
            switch s {
            case unix.SIGCHLD:
                if err := reaper.Reap(); err != nil {
                    logger.WithError(err).Error("reap exit status")
                }
        }
    }
}

(4)在containerd-shim啟動(dòng)的時(shí)候,會(huì)啟動(dòng)goroutine監(jiān)聽容器進(jìn)程的退出缤言,checkProcess中會(huì)將容器id宝当,進(jìn)程id,退出碼胆萧,退出時(shí)間等信息通過GRPC消息轉(zhuǎn)發(fā)到containerd中庆揩。

func New(ctx context.Context, id string, publisher shim.Publisher, shutdown func()) (shim.Shim, error) {
    if cgroups.Mode() == cgroups.Unified {
        ep, err = oomv2.New(publisher)
    } else {
        ep, err = oomv1.New(publisher)
    }

    go ep.Run(ctx)

    go s.processExits()

    go s.forward(ctx, publisher)
}

func (s *service) checkProcesses(e runcC.Exit) {
    for _, container := range s.containers {
        for _, p := range container.All() {
            p.SetExited(e.Status)
            s.sendL(&eventstypes.TaskExit{
                ContainerID: container.ID,
                ID:          p.ID(),
                Pid:         uint32(e.Pid),
                ExitStatus:  uint32(e.Status),
                ExitedAt:    p.ExitedAt(),
            })
        }
    }
}

(5)回收子進(jìn)程會(huì)調(diào)用reap函數(shù),在exitStatus中跌穗,會(huì)判斷進(jìn)程是否是被信號(hào)中斷的订晌,如果是的話,則加上一個(gè)偏移量128蚌吸,這里就驗(yàn)證了上面說的結(jié)論锈拨,137 = 9 + 128。那么可以推斷bash中 $羹唠?也是同樣的處理奕枢。

func reap(wait bool) (exits []exit, err error) {
    for {
        pid, err := unix.Wait4(-1, &ws, flag, &rus)
        exits = append(exits, exit{
            Pid:    pid,
            Status: exitStatus(ws),
        })
    }
}

// sys/reaper/reaper_unix.go
const exitSignalOffset = 128
func exitStatus(status unix.WaitStatus) int {
    if status.Signaled() {
        return exitSignalOffset + int(status.Signal())
    }
    return status.ExitStatus()
}

// unix/syscall_linux.go
const (
    mask    = 0x7F
    core    = 0x80
    exited  = 0x00
    stopped = 0x7F
    shift   = 8
)
func (w WaitStatus) Signaled() bool { 
    return w&mask != stopped && w&mask != exited 
}

containerd的處理

(1)在handleEvent中會(huì)處理containerd-shim轉(zhuǎn)發(fā)過來的容器的事件信息,如果是TaskOOM佩微,則調(diào)用UpdateSync更新容器的退出原因缝彬。
(2)如果是進(jìn)程退出,則調(diào)用handleContainerExit哺眯,再調(diào)用UpdateSync更新容器的退出碼谷浅。

// handleEvent handles a containerd event.
func (em *eventMonitor) handleEvent(any interface{}) error {
    switch e := any.(type) {
    case *eventtypes.TaskExit:
        logrus.Infof("TaskExit event %+v", e)
        cntr, err := em.c.containerStore.Get(e.ID)
        handleContainerExit(ctx, e, cntr); 
    case *eventtypes.TaskOOM:
        logrus.Infof("TaskOOM event %+v", e)
        // For TaskOOM, we only care which container it belongs to.
        cntr, err := em.c.containerStore.Get(e.ContainerID)
        err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
            status.Reason = oomExitReason
            return status, nil
        })
    return nil
}

func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr containerstore.Container) error {
    err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
        if status.FinishedAt == 0 {
            status.Pid = 0
            status.FinishedAt = e.ExitedAt.UnixNano()
            status.ExitCode = int32(e.ExitStatus)
        }

        return status, nil
    })
}

(3)UpdateSync是containerd中用來更新容器存儲(chǔ)狀態(tài)的函數(shù)。

func (s *statusStorage) UpdateSync(u UpdateFunc) error {
    newStatus, err := u(s.status)
    data, err := newStatus.encode()
    continuity.AtomicWriteFile(s.path, data, 0600)
    s.status = newStatus
}

(4)containerd的處理就算完成了奶卓,接下來就等kubelet來獲取容器的狀態(tài)了一疯。

kubelet的處理

kubelet的細(xì)節(jié)分析參見 kubelet代碼解析
(1)relist函數(shù)會(huì)定期執(zhí)行,對比pod狀態(tài)的變化夺姑。
(2)通過computeEvent墩邀、updateEvents和generateEvents對比內(nèi)存中的pod的狀態(tài)和從runtime接口獲取的實(shí)時(shí)的pod的狀態(tài)差異,從而可以推斷出pod內(nèi)的容器發(fā)生了狀態(tài)變化盏浙。

func (g *GenericPLEG) Start() {
    go wait.Until(g.relist, g.relistPeriod, wait.NeverStop)
}
func (g *GenericPLEG) relist() {
    klog.V(5).InfoS("GenericPLEG: Relisting")
    for pid := range g.podRecords {
        oldPod := g.podRecords.getOld(pid)
        pod := g.podRecords.getCurrent(pid)
        // Get all containers in the old and the new pod.
        allContainers := getContainersFromPods(oldPod, pod)
        for _, container := range allContainers {
            events := computeEvents(oldPod, pod, &container.ID)
            for _, e := range events {
                updateEvents(eventsByPodID, e)
            }
        }
    }
}

(3)如因oom產(chǎn)生一個(gè)類型為ContainerDied的PodLifecycleEvent事件

func generateEvents(podID types.UID, cid string, oldState, newState plegContainerState) []*PodLifecycleEvent {
    klog.V(4).InfoS("GenericPLEG", "podUID", podID, "containerID", cid, "oldState", oldState, "newState", newState)
    switch newState {
    case plegContainerRunning:
        return []*PodLifecycleEvent{{ID: podID, Type: ContainerStarted, Data: cid}}
    case plegContainerExited:
        return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}}
    case plegContainerUnknown:
        return []*PodLifecycleEvent{{ID: podID, Type: ContainerChanged, Data: cid}}
}

(4)在syncLoopIteration中會(huì)處理該pleg事件磕蒲,繼而調(diào)用Podworker的sync方法留潦。

func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    select {
    case e := <-plegCh:
        handler.HandlePodSyncs([]*v1.Pod{pod})
    }

(5)generateAPIPodStatus會(huì)將容器的狀態(tài)轉(zhuǎn)換為 v1.PodStatus定義中的字段。再調(diào)用statusManager的SetPodStatus方法更新pod的狀態(tài)信息辣往。

func (kl *Kubelet) syncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) error {
    klog.V(4).InfoS("syncPod enter", "pod", klog.KObj(pod), "podUID", pod.UID)

    // Generate final API pod status with pod and status manager status
    apiPodStatus := kl.generateAPIPodStatus(pod, podStatus)

    kl.statusManager.SetPodStatus(pod, apiPodStatus)

    // Call the container runtime's SyncPod callback
    result := kl.containerRuntime.SyncPod(pod, podStatus, pullSecrets, kl.backOff)
}

(6)根據(jù)狀態(tài)做不同的賦值兔院,如ContainerStateExited,則要更新容器的退出碼站削,原因等坊萝。

func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecontainer.PodStatus, previousStatus []v1.ContainerStatus, containers []v1.Container, hasInitContainers, isInitContainer bool) []v1.ContainerStatus {
        switch {
        case cs.State == kubecontainer.ContainerStateRunning:
            status.State.Running = &v1.ContainerStateRunning{StartedAt: metav1.NewTime(cs.StartedAt)}
        case cs.State == kubecontainer.ContainerStateCreated:
            fallthrough
        case cs.State == kubecontainer.ContainerStateExited:
            status.State.Terminated = &v1.ContainerStateTerminated{
                ExitCode:    int32(cs.ExitCode),
                Reason:      cs.Reason,
                Message:     cs.Message,
                StartedAt:   metav1.NewTime(cs.StartedAt),
                FinishedAt:  metav1.NewTime(cs.FinishedAt),
                ContainerID: cid,
            }
}

(7)getPhase也是一個(gè)比較重要的函數(shù),這里會(huì)決定pod的狀態(tài)许起,PodSpec中的restartPolicy是在這里生效的十偶。

func getPhase(spec *v1.PodSpec, info []v1.ContainerStatus) v1.PodPhase {
    for _, container := range spec.Containers {
        containerStatus, ok := podutil.GetContainerStatus(info, container.Name)
        if !ok {
            unknown++
            continue
        }

        switch {
        case containerStatus.State.Running != nil:
            running++
        case containerStatus.State.Terminated != nil:
            stopped++
            if containerStatus.State.Terminated.ExitCode == 0 {
                succeeded++
            }
    }

    switch {
    case running > 0 && unknown == 0:
        return v1.PodRunning
    case running == 0 && stopped > 0 && unknown == 0:
        if spec.RestartPolicy == v1.RestartPolicyAlways {
            // All containers are in the process of restarting
            return v1.PodRunning
        }
        if stopped == succeeded {
            return v1.PodSucceeded
        }
        if spec.RestartPolicy == v1.RestartPolicyNever {
            return v1.PodFailed
        }
        return v1.PodRunning
    }
}

(8)status_manager中的syncPod方法會(huì)將新的pod狀態(tài)通過patch方法更新到apiserver。

func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
    pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(context.TODO(), status.podName, metav1.GetOptions{})
    newPod, patchBytes, unchanged, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status))
    klog.V(3).InfoS("Patch status for pod", "pod", klog.KObj(pod), "patch", string(patchBytes))
}

mem_alloc代碼

參考極客時(shí)間 《容器高手實(shí)戰(zhàn)課》

[root@localhost mem-alloc]# cat mem_alloc.c 
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define BLOCK_SIZE (10*1024*1024)

int main(int argc, char **argv)
{

        int thr, i;
        char *p1;

        if (argc != 2) {
                printf("Usage: mem_alloc <num (MB)>\n");
                exit(0);
        }

        thr = atoi(argv[1]);

        printf("Allocating," "set to %d Mbytes\n", thr);
        sleep(10);
        for (i = 0; i < thr; i++) {
                p1 = malloc(BLOCK_SIZE);
                memset(p1, 0x00, BLOCK_SIZE);
        }

        sleep(600);

        return 0;
}

[root@localhost oom]# cat Dockerfile 
FROM nginx:latest

COPY ./mem-alloc/mem_alloc /

CMD ["/mem_alloc", "2000"]
[root@localhost oom]# cat Makefile 
all: image

mem_alloc: mem-alloc/mem_alloc.c
        gcc -o mem-alloc/mem_alloc mem-alloc/mem_alloc.c
image: mem_alloc
        docker build -t registry/mem_alloc:v1 .
clean:
        rm mem-alloc/mem_alloc -f
        docker stop mem_alloc;docker rm mem_alloc;docker rmi registry/mem_alloc:v1

總結(jié)

本文研究的都是確定性的問題园细,好像沒有什么意義惦积,話說回來,如果沒有學(xué)習(xí)這些確定性問題的積累猛频,又如何去應(yīng)對不確定的問題呢狮崩?

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鹿寻,隨后出現(xiàn)的幾起案子睦柴,更是在濱河造成了極大的恐慌,老刑警劉巖毡熏,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坦敌,死亡現(xiàn)場離奇詭異,居然都是意外死亡痢法,警方通過查閱死者的電腦和手機(jī)狱窘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來财搁,“玉大人蘸炸,你說我怎么就攤上這事「菊” “怎么了幻馁?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵洗鸵,是天一觀的道長越锈。 經(jīng)常有香客問我,道長膘滨,這世上最難降的妖魔是什么甘凭? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮火邓,結(jié)果婚禮上丹弱,老公的妹妹穿的比我還像新娘德撬。我一直安慰自己,他們只是感情好躲胳,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布蜓洪。 她就那樣靜靜地躺著,像睡著了一般坯苹。 火紅的嫁衣襯著肌膚如雪隆檀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天粹湃,我揣著相機(jī)與錄音恐仑,去河邊找鬼。 笑死为鳄,一個(gè)胖子當(dāng)著我的面吹牛裳仆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播孤钦,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼歧斟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了司训?” 一聲冷哼從身側(cè)響起构捡,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壳猜,沒想到半個(gè)月后勾徽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡统扳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年喘帚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咒钟。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吹由,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朱嘴,到底是詐尸還是另有隱情倾鲫,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布萍嬉,位于F島的核電站乌昔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏壤追。R本人自食惡果不足惜磕道,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望行冰。 院中可真熱鬧溺蕉,春花似錦伶丐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漓雅,卻和暖如春啡彬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背故硅。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工庶灿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吃衅。 一個(gè)月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓往踢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徘层。 傳聞我的和親對象是個(gè)殘疾皇子峻呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

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