Análise do código-fonte do mecanismo de expulsão de kubelet

1. Visão Geral:

1.1 Ambiente de código-fonte

As informações da versão são as seguintes:
a. Cluster kubernetes: v1.15.0

1.2 Duas linhas de defesa para manter a estabilidade do nó

Para o sistema operacional Linux, fatia de tempo da CPU, memória, capacidade do disco, inode, PID, etc. são todos recursos do sistema.

Uma classificação dos recursos do sistema:
a, recursos compressíveis (CPU)
b, recursos incompressíveis (memória, capacidade do disco e inode, PID)

Como um agente de nó, o kubelet naturalmente precisa de um certo mecanismo para garantir que os recursos do servidor não se esgotem. Quando os recursos compactáveis ​​não são suficientes, o pod (contêiner ou processo) ficará sem energia, mas não será forçado a sair pelo sistema operacional e pelo kubelet. Quando recursos incompressíveis (como memória) não são suficientes, a camada kubelet pode recuperar alguns recursos do servidor com antecedência (excluir imagens não utilizadas, limpar contêineres saídos e pods com falha, eliminar pods em execução) e o OOM do sistema operacional Linux como o última linha de defesa, KILLER também tem a oportunidade de matar diretamente o processo do modo de usuário.

O kubelet mata o Pod em execução. Esse processo é chamado de despejo. O Kubelet exclui imagens não utilizadas, limpa contêineres desativados e pods com falha. Esse processo é chamado de reciclagem de recursos no nível do nó.

Portanto, existem duas linhas de defesa para manter a estabilidade do nó: a primeira linha de defesa é o kubelet de processo do usuário e a segunda linha de defesa é o Linux OOM KILLER.


1.3 mecanismo do kubelet para manter a estabilidade do nó

Quando recursos incompressíveis (memória, capacidade de disco e inode, PID) não são usados ​​o suficiente, isso afetará seriamente a estabilidade do nó. Por esse motivo, o kubelet, como o agente do nó do processo do modo de usuário do Linux e do cluster k8s, deve ter um certo mecanismo para manter a estabilidade do nó. A essência é atingir o objetivo excluindo a imagem e interrompendo o processo do contêiner.


1.3.1 Limite de recursos, os usuários têm a palavra final

Esse kubelet precisa obter as informações gerais dos recursos do nó regularmente, o que é inevitável.
Quantos recursos restam no servidor para serem considerados recursos incompressíveis "não é suficiente"? Essa base "insuficiente" é especificada pelo usuário no arquivo de configuração kubelet.
Contanto que o kubelet obtenha a capacidade dos recursos do nó, o uso real (taxa) e o limite configurado pelo usuário, ele sente que as "condições foram atendidas" podem começar a excluir a imagem e interromper o processo do contêiner.


1.3.2 Remoção leve e pesada

"As condições foram atendidas", aguarde zero segundos para iniciar a exclusão da imagem e interromper o processo do contêiner, este é um despejo forçado.
"As condições são atendidas", aguarde N (N> 0) segundos para iniciar a operação de exclusão da imagem e interromper o processo do contêiner, que é o despejo suave. O número N pode ser definido pelo parâmetro -eviction-soft-grace-period do kubelet. Além disso, para o despejo suave, o parâmetro --eviction-max-pod-grace-period afeta o tempo que o gerente de despejo espera que o kubelet limpe o Pod (o tempo real de espera é eviction-max-pod-grace-period + ( eviction-max-pod- grace-period / 2)).


1.3.3 Criação de pod de bloco

Quando os recursos incompressíveis do nó "não bastam", o kubelet também deve ter um mecanismo para bloquear a criação do Pod, que está relacionado ao método Admitir (...) do gerenciador de expulsões.

1.3.4 Afeta o Linux OOM KILLER

Kubelet é a primeira linha de defesa para manter a estabilidade do servidor. Se você não conseguir descobrir, a segunda linha de defesa, Linux OOM KILLER, inevitavelmente chegará ao fim. A correlação entre a primeira linha de defesa e a segunda linha de defesa é oom_score_adj.
O Qos do pod é diferente, o kubelet configura pontuações diferentes para oom_score_adj e o oom_score_adj do processo é um fator que afeta a eliminação dos processos de modo de usuário do Linux OOM KILLER.

Insira a descrição da imagem aqui

Outras instruções:
1) Modelo de comando para visualizar a pontuação oom_score_adj do processo do contêiner no Pod:
kubectl exec demo-qos-pod cat / proc / 1 / oom_score_adj


1.4 Visão geral do Linux OOM KILLER

1) O Linux responde "sim" à maioria dos pedidos de aplicação de memória, de forma que mais e maiores programas possam ser executados. Porque depois de solicitar a memória, a memória não será usada imediatamente. Essa técnica é chamada de Overcommit.

2) Quando o Linux descobrir que a memória é insuficiente, o OOM killer (OOM = out-of-memory) ocorrerá. Neste momento, ele escolherá matar alguns processos (processos de modo de usuário, não threads de kernel) para liberar memória .

3) Quando ocorre o oom-killer, quais processos o Linux escolherá para matar? O Linux calculará o número de pontos (0 ~ 1000) para cada processo. Quanto maior o número de pontos, maior a probabilidade de esse processo ser eliminado.
A fórmula dos pontos oom:

进程OOM点数 = oom_score + oom_score_adj 

#oom_score está relacionado à memória consumida pelo processo.
#oom_score_adj é configurável e o intervalo de valores é -1000 é o mais baixo e 1000 é o mais alto.

4) O log oom está no arquivo / var / log / messages, que pertence ao log do kernel.Você pode ver o log do kernel através do comando grep kernel / var / log / messages.
Insira a descrição da imagem aqui


Análise de código-fonte de expulsão de 2 kubelet:

2.1 Principais métodos

																				|-> kl.cadvisor.Start()  // 启动cadvisor,cadvisor能获取节点信息和容器信息
																				|
		  //启动							//初始化额外模块,只会运行一次		        |
Kubelet -> Run() -> updateRuntimeUp()  ->  initializeRuntimeDependentModules -->|-> kl.containerManager.Start()  //containerManager需要cAdvisor提供的文件系统信息
																				|
																				|
																				|	//evictionManager需要通过cadvisor得知容器运行时是否具备一个独立专用的imagefs
																				|
																				|-> kl.evictionManager.Start() -> 定时任务 ->| -> m.synchronize() -> | -> m.reclaimNodeLevelResources(...)  //清理节点级别的资源(镜像、已退出的容器)
																															|					    |
																															| 	                    | -> m.evictPod(...)  //清理pod对象
																															|
																															| -> m.waitForPodsCleanup(...)   //如果m.synchronize()驱逐了pod,执行本方法进行等待pod被清理.

2.2 Sinais suportados que podem desencadear o despejo do Pod

Quando a memória, a capacidade do disco, o número do inode do disco e o número do pid são insuficientes, o kubelet pode ser acionado para limpar imagens e contêineres para atingir o objetivo de recuperar os recursos do nó.

// Signal defines a signal that can trigger eviction of pods on a node.
type Signal string

/*
分类:
1)内存
2)磁盘容量
3)磁盘inode数量
4)pid数量
*/
const (
	// SignalMemoryAvailable is memory available (i.e. capacity - workingSet), in bytes.
	SignalMemoryAvailable Signal = "memory.available"
	
	// SignalNodeFsAvailable is amount of storage available on filesystem that kubelet uses for volumes, daemon logs, etc.
	// nodefs是kubelet使用的卷、进程日志所在的文件系统
	SignalNodeFsAvailable Signal = "nodefs.available"
	
	// SignalNodeFsInodesFree is amount of inodes available on filesystem that kubelet uses for volumes, daemon logs, etc.
	SignalNodeFsInodesFree Signal = "nodefs.inodesFree"
	
	// SignalImageFsAvailable is amount of storage available on filesystem that container runtime uses for storing images and container writable layers.
	// imagefs是容器运行时(例如docker)的镜像层、读写层所在的文件系统
	SignalImageFsAvailable Signal = "imagefs.available"
	
	// SignalImageFsInodesFree is amount of inodes available on filesystem that container runtime uses for storing images and container writable layers.
	SignalImageFsInodesFree Signal = "imagefs.inodesFree"
	
	// SignalAllocatableMemoryAvailable is amount of memory available for pod allocation (i.e. allocatable - workingSet (of pods), in bytes.
	SignalAllocatableMemoryAvailable Signal = "allocatableMemory.available"
	
	// SignalPIDAvailable is amount of PID available for pod allocation
	SignalPIDAvailable Signal = "pid.available"
)

2.3 Código geral

//kubelet的启动入口
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
	/*
		其他代码
	*/
	
	go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)
	
}


// 当容器运行时第一次运行,初始化一些它依赖的模块
func (kl *Kubelet) updateRuntimeUp() {
	/*
		其他代码
	*/
	
	kl.oneTimeInitializer.Do(kl.initializeRuntimeDependentModules)

}

//启动cadvisor、containerManager和本文的核心研究对象evictionManager
func (kl *Kubelet) initializeRuntimeDependentModules() {

	// 启动cadvisor
	if err := kl.cadvisor.Start(); err != nil {		
	}
	
	/*
		其他代码
	*/	
	
	// containerManager必须在cAdvisor之后启动,因为它需要cAdvisor提供的文件系统信息
	if err := kl.containerManager.Start(node, kl.GetActivePods, kl.sourcesReady, kl.statusManager, kl.runtimeService); err != nil {		
	}
	
	//evictionManager是本文的核心
	// evictionManager必须在cAdvisor之后启动,因为它需要知道容器运行时是否具备一个独立专用的imagefs
	kl.evictionManager.Start(kl.StatsProvider, kl.GetActivePods, kl.podResourcesAreReclaimed, evictionMonitoringPeriod)
	
	/*
		其他代码
	*/	
}

Os principais atributos da estrutura do administrador de despejo

// managerImpl implements Manager
type managerImpl struct {
	
	//kubelet的配置文件中和驱逐相关的字段会赋值到本属性
	config Config
	
	//杀死Pod的方法
	//--eviction-max-pod-grace-period参数和此方法相关
	killPodFunc KillPodFunc
	
	
	// the interface that knows how to do image gc
	imageGC ImageGC
	// the interface that knows how to do container gc
	containerGC ContainerGC
	// protects access to internal state
	
	// 达到阈值的不可压缩资源,就会出现在本属性
	thresholdsMet []evictionapi.Threshold
	
	// 对Pod进行排序的方法保存在此属性
	signalToRankFunc map[evictionapi.Signal]rankFunc
	// signalToNodeReclaimFuncs maps a resource to an ordered list of functions that know how to reclaim that resource.
	
	thresholdNotifiers []ThresholdNotifier

}	

2.4 Como iniciar o gerenciador de despejo

func (m *managerImpl) Start(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc, podCleanedUpFunc PodCleanedUpFunc, monitoringInterval time.Duration) {
	/*
	其他代码
	*/
	
	//通过for循环和睡眠来定时执行m.synchronize()方法
	go func() {
		for {
		    //m.synchronize()方法内部有清理 节点资源 和 Pod 的逻辑
			if evictedPods := m.synchronize(diskInfoProvider, podFunc); evictedPods != nil {
				m.waitForPodsCleanup(podCleanedUpFunc, evictedPods)
			} else {
			    //睡眠
				time.Sleep(monitoringInterval)
			}
		}
	}()
}

2.4 A lógica principal do gerente de expulsão para recuperar recursos


func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc) []*v1.Pod {
		
	/*
		填充m.signalToRankFunc的值,也就是pod排序的排序函数,整个过程只执行一次;
	*/
	
	// 获取节点上运行中的pod
	activePods := podFunc()
	// 从cadvisor获取节点的详细信息,并进行统计
	summary, err := m.summaryProvider.Get(true)
	//再从统计数据中获得节点资源的使用情况observations
	observations, statsFunc := makeSignalObservations(summary)
	
	// 将资源实际使用量和资源容量进行比较,最终得到阈值结构体对象的列表
	thresholds = thresholdsMet(thresholds, observations, false)	
	thresholdsFirstObservedAt := thresholdsFirstObservedAt(thresholds, m.thresholdsFirstObservedAt, now)
	//此时的thresholds是即将触发回收资源的thresholds,因为这些thresholds已经过了平滑时间
	thresholds = thresholdsMetGracePeriod(thresholdsFirstObservedAt, now)

		
	/*
		获取nodeConditions;
		nodeConditions是一个字符串切片;
		打印一些和nodeConditions相关的日志信息;	
	*/
	
	/*
		更新驱逐管理器(即本方法的接收者)的一些成员变量的值
		
		m.thresholdsMet = thresholds
		
		//m.nodeConditions过了--eviction-pressure-transition-period指定的时间,也会更新到kubelet本地的node对象中,而node对象最终也会被kubelet同步到kube-apiserver中
		//m.nodeConditions也和m.Admit(...)方法相关,m.Admit(...)方法决定是否允许在当前节点上创建或运行目标pod
		m.nodeConditions = nodeConditions
		
	*/
	
	// 本地临时存储导致的驱逐(一定是驱逐pod),如果发生,后续的回收资源(清理节点资源和运行中的pod)的操作不会执行,直接返回
	if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
		if evictedPods := m.localStorageEviction(summary, activePods); len(evictedPods) > 0 {
			return evictedPods
		}
	}
	
	// 节点资源使用量没有到达用户配置的阈值,不需要回收节点资源,因此直接返回
	if len(thresholds) == 0 {
		klog.V(3).Infof("eviction manager: no resources are starved")
		return nil
	}
	
	// 回收节点级的资源,如果回收的资源足够的话,直接返回,不需要驱逐正在运行中的pod
	if m.reclaimNodeLevelResources(thresholdToReclaim.Signal, resourceToReclaim) {
		klog.Infof("eviction manager: able to reduce %v pressure without evicting pods.", resourceToReclaim)
		return nil
	}

	// 根据资源类型,从一个map中拿到一个排序方法,使用该排序方法对运行中的pod进行排序
	rank, ok := m.signalToRankFunc[thresholdToReclaim.Signal]	
	rank(activePods, statsFunc)
	// 此时activePods已经按照一定规则进行排序
	klog.Infof("eviction manager: pods ranked for eviction: %s", format.Pods(activePods))
	
	// 开始对运行中的pod实施驱逐操作
	for i := range activePods {	
		//如果驱逐了一个pod,则立马返回,因为一个周期最多驱逐一个pod
		if m.evictPod(pod, gracePeriodOverride, message, annotations) {
			return []*v1.Pod{pod}
		}
	}
	
	// 到达此处,说明本周期试图驱逐pod,但连一个pod都没驱逐成功
	klog.Infof("eviction manager: unable to evict any pods from the node")
	return nil
}

2.4 A propriedade signalToRankFunc do gerenciador de despejo

Ele contém um conjunto de métodos para classificar pods.

func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc) []*v1.Pod {
	/*
	其他代码
	*/
	
	if m.dedicatedImageFs == nil {
		hasImageFs, ok := diskInfoProvider.HasDedicatedImageFs()
		if ok != nil {
			return nil
		}
		//dedicatedImageFs被赋值后,不会再进入当前整个if语句
		m.dedicatedImageFs = &hasImageFs
		//把pod的排序方法作为map传给驱逐管理器的signalToRankFunc属性
		m.signalToRankFunc = buildSignalToRankFunc(hasImageFs)   
	}
	
	/*
	其他代码
	*/
}
func buildSignalToRankFunc(withImageFs bool) map[evictionapi.Signal]rankFunc {

	//不管入参是true还是false,内存、pid这两种资源的排序方法是不变的
	signalToRankFunc := map[evictionapi.Signal]rankFunc{
		evictionapi.SignalMemoryAvailable:            rankMemoryPressure,
		evictionapi.SignalAllocatableMemoryAvailable: rankMemoryPressure,
		evictionapi.SignalPIDAvailable:               rankPIDPressure,
	}
	
	//虽然都是rankDiskPressureFunc,但方法的入参不同。
	if withImageFs {
		
		// 使用独立的imagefs,那么nodefs的排序方法只包含日志和本地卷
		signalToRankFunc[evictionapi.SignalNodeFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
		signalToRankFunc[evictionapi.SignalNodeFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
			
		// 使用独立的imagefs,那么imagefs的排序方法只包含rootfs
		signalToRankFunc[evictionapi.SignalImageFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot}, v1.ResourceEphemeralStorage)
		signalToRankFunc[evictionapi.SignalImageFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot}, resourceInodes)
	} else {	
		// 不使用独立的imagefs,nodefs的排序方法是包含所有文件系统,即fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource
		// 此时imagefs和nodefs在使用同一个块设备,因此它们的排序方法都是一样的。
		signalToRankFunc[evictionapi.SignalNodeFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
		signalToRankFunc[evictionapi.SignalNodeFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
		signalToRankFunc[evictionapi.SignalImageFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
		signalToRankFunc[evictionapi.SignalImageFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
	}
	return signalToRankFunc
}

2.4 Método de classificação de pod

Quando o gerenciador de expulsão expulsa o pod, ele classifica os pods em execução, e o pod no topo da fila é limpo primeiro.

O pod cujo Qos é garantido é o último da fila, porque durante a operação normal, a diferença entre o uso e a solicitação deve ser um número negativo e, claro, está no final da fila. Quando o uso de recursos deste tipo de Pod atinge a solicitação, ou seja, quando o limite é atingido, o linux detecta e compara o valor definido pelo limite (no arquivo cgroup), e envia um sinal kill para o processo no Pod . Não é um pod em execução no momento.

E o pod com Qos como Best-Effort é o topo, porque a solicitação não está definida, então a diferença entre o uso e a solicitação deve ser um número positivo, portanto, deve ser a parte superior da fila.


2.4.1 Como classificar pods quando a memória está apertada

/*
当内存使用量达到阈值时,对入参pods进行排序。
首先看使用量是否超过pod的request字段
接着看pod的QOS类型
最后看使用量和request之间的差值
*/

func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {

	orderedBy(
		exceedMemoryRequests(stats),  //使用量是否超过pod的request字段
		priority,  //pod的QOS类型
		memory(stats)  //使用量和request之间的差值
	).Sort(pods) 
	
}

2.4.2 Como classificar pods quando Pid está nervoso

/*
当pid用量达到阈值时,根据pod的QOS类型,对入参pods进行排序。
*/
func rankPIDPressure(pods []*v1.Pod, stats statsFunc) {
	orderedBy(priority).Sort(pods)
}

2.4.3 Como classificar pods quando o disco está apertado

/*
当磁盘使用量达到阈值时,对入参pods进行排序。
首先看使用量是否超过pod的request字段
接着看pod的QOS类型
最后看使用量和request之间的差值
*/
func rankDiskPressureFunc(fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) rankFunc {
	return func(pods []*v1.Pod, stats statsFunc) {
	
		orderedBy(
			exceedDiskRequests(stats, fsStatsToMeasure, diskResource),	//使用量是否超过pod的request字段
			priority,  //pod的QOS类型
			disk(stats, fsStatsToMeasure, diskResource)   //使用量和request之间的差值
		).Sort(pods) 
		
	}
}

2.4 Outros métodos

2.4.1 func (m * managerImpl) Admit (...)

O método Admit (...) do gerenciador de despejo afetará se o Pod pode ser criado.

/*
1)入参attrs对象中包含一个pod对象
2)m.nodeConditions字段通过本方法和kubelet的其他流程有关联:
  2.1)HandlePodAdditions(...)方法中会间接调用本方法,从而达到不创建目标pod的效果。
  2.2)syncPod(...)方法中会间接调用本方法,从而到达目标pod(如果存在的话)会被删除的效果。
*/

func (m *managerImpl) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
	m.RLock()
	defer m.RUnlock()
	
	//节点无资源压力,则允许创建任意pod
	if len(m.nodeConditions) == 0 {
		return lifecycle.PodAdmitResult{Admit: true}
	}
	
	//Critical pods也能被允许创建
	if kubelettypes.IsCriticalPod(attrs.Pod) {
		return lifecycle.PodAdmitResult{Admit: true}
	}
	
	if hasNodeCondition(m.nodeConditions, v1.NodeMemoryPressure) {
		notBestEffort := v1.PodQOSBestEffort != v1qos.GetPodQOS(attrs.Pod)
		
		// 节点处于内存压力状态,best-effort类型的pod不能被允许创建,其余类型的pod可以
		if notBestEffort {
			return lifecycle.PodAdmitResult{Admit: true}
		}

		// 节点处于内存压力状态并且开启TaintNodesByCondition特性,pod能容忍内存压力污点的话,它也能被允许创建
		if utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition) &&
			v1helper.TolerationsTolerateTaint(attrs.Pod.Spec.Tolerations, &v1.Taint{
				Key:    schedulerapi.TaintNodeMemoryPressure,
				Effect: v1.TaintEffectNoSchedule,
			}) {
			return lifecycle.PodAdmitResult{Admit: true}
		}
	}

	// 来到此处,说明节点是处于磁盘压力状态,或者处于内存压力状态但入参的pod是bestEffort类型,此时不允许创建pod	
	klog.Warningf("Failed to admit pod %s - node has conditions: %v", format.Pod(attrs.Pod), m.nodeConditions)
	return lifecycle.PodAdmitResult{
		Admit:   false,
		Reason:  Reason,
		Message: fmt.Sprintf(nodeConditionMessageFmt, m.nodeConditions),
	}
}

3 A experiência do Pod sendo expulso

3.1 Depois que os pods dos controladores ds e sts forem expulsos, o controlador não criará novos pods.

O pod expulso ainda ocupa um registro, e a lógica do loop principal do controlador sts e controlador ds não criará um novo pod.
Portanto, é melhor executar uma tarefa crontab (kubectl delete pod --all-namespaces --field-selector = 'status.phase == Failed') no nó mestre e excluir periodicamente os pods com falha (incluindo os pods expulsos )

Depois que os dois pods ds na captura de tela foram expulsos, porque o controlador não criou um novo pod, o problema de comunicação do contêiner do nó foi causado, então os serviços ds e sts expulsos não serão reparados.
O pod coredns na captura de tela é um pod gerenciado pelo controlador de implantação. Depois que o pod antigo for removido, o controlador criará um novo pod, para que o serviço dns dentro do cluster seja autocorrigido.
Insira a descrição da imagem aqui


4 resumo

É reconhecido que os recursos do nó podem ser divididos em recursos compressíveis e recursos incompressíveis.Quando os recursos incompressíveis estão em estado de escassez, este tipo de escassez levará à instabilidade do nó.
Como um agente de nó, o processo do usuário kubelet é a primeira linha de defesa para manter a estabilidade do nó. Ele verifica periodicamente o status do recurso do nó, o status do recurso real usado pelo pod e o limite de recurso configurado pelo usuário, e por fim realizar a operação de limpeza da imagem e do contêiner para manter a estabilidade do nó, afinal, a estabilidade do nó é maior que a estabilidade do contêiner.
A segunda linha de defesa é o Linux OOM KILLER, que também é a linha de fundo da defesa.
Haverá uma espécie de colaboração entre a primeira linha de defesa e a segunda linha de defesa, que é oom_score_adj. Quando o kubelet cria um contêiner, oom_score_adj pode ser definido para afetar o processo de eliminação do Linux OOM KILLER.

Acho que você gosta

Origin blog.csdn.net/nangonghen/article/details/109696462
Recomendado
Clasificación