본문 바로가기

데브옵스/K8S

K8S Node 캐시된 컨테이너 이미지 정리 (kubelet)

반응형
반응형

Intro

저희는 컨테이너 오케스트레이션인 k8s를 통해 컨테이너 기반의 애플리케이션을 배포할 수 있습니다. k8s cluster 속에 pod라고 불리는 자원들은 노드 위에 격리되어 생성되고, container image를 사용해 생성되는데요. k8s에서는 image를 노드에 캐시 저장하여, 빠르게 이미지를 이용해 애플리케이션을 생성하도록 도와줍니다.
 
하지만 이렇게 저장되는 container image가 과다해지면, 노드의 스토리지 사용량에 영향을 줄 수 있습니다.
K8S에서는 k8s node의 cached 되는 container image에 대해, kubelet을 통해 garbage collection을 제공합니다.
 
 

kubelet garbage-collection

kubelet의 config 변경을 통해 cached container image를 정리할 수 있는데요.
 
아래 두가지 limit을 통해 cached 되는 image percentage를 조정합니다.
 
imageGCHighThresholdPercent : 해당 Limit을 초과하면, kubelet은 node의 cached 된 이미지를 정리한다.
imageGCLowThresholdPercent : kubelet은 해당 Limit 까지 이미지를 정리한다.
 
ex) HighThresholdPercent : 70 / LowThresholdPercent: 50 일 경우, 70%가 넘었을 때 이미지를 정리하여 50%가 되면 이미지 정리를 멈춘다.
 
단, 여기서 유의할 점이 있습니다.
* imageGCHighThresholdPercent 는 imageGCLowThresholdPercent 보다 커야 합니다.
동일하게 설정할 경우 아래와 같은 에러를 받아볼 수 있습니다.

"Failed to validate kubelet configuration" err="invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 50 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 50" path="&TypeMeta{Kind:,APIVersion:,}"

 

 적용 명령어

sed 명령어를 통해 node에 접속하여 직접 적용합니다. 이후 kubelet은 재기동 해줍니다.

### EKS Node image cached percentage

### when cached percent High than imageGCHighThresholdPercent, kubelet clean up image
sed -i '/"apiVersion*/a \ \ "imageGCHighThresholdPercent": 50,' /etc/kubernetes/kubelet/kubelet-config.json

### kubelet clean up image by imageGCHighThresholdPercent til imageGCLowThresholdPercent
sed -i '/"imageGCHigh*/a \ \ "imageGCLowThresholdPercent": 50,' /etc/kubernetes/kubelet/kubelet-config.json

 

 

검증

node에 대해 curl 명령어로 조회할 수 있습니다.

## 로컬 PC에서 node를 호출할 수 있도록 설정
kubectl proxy

## node의 kubelet config를 조회

curl -sSL "http://localhost:8001/api/v1/nodes/${node 명}/proxy/configz" | python3 -m json.tool

## 결과값

{
    "kubeletconfig": {
        "enableServer": true,
        "staticPodPath": "/etc/kubernetes/manifests",
        "syncFrequency": "1m0s",
        "fileCheckFrequency": "20s",
        "httpCheckFrequency": "20s",
        "address": "0.0.0.0",
        "port": 10250,
        "tlsCertFile": "/var/lib/kubelet/pki/kubelet.crt",
        "tlsPrivateKeyFile": "/var/lib/kubelet/pki/kubelet.key",
        "rotateCertificates": true,
        "authentication": {
            "x509": {
                "clientCAFile": "/etc/kubernetes/ssl/ca.crt"
            },
            "webhook": {
                "enabled": true,
                "cacheTTL": "2m0s"
            },
            "anonymous": {
                "enabled": false
            }
        },
        "authorization": {
            "mode": "Webhook",
            "webhook": {
                "cacheAuthorizedTTL": "5m0s",
                "cacheUnauthorizedTTL": "30s"
            }
        },
        "registryPullQPS": 5,
        "registryBurst": 10,
        "eventRecordQPS": 5,
        "eventBurst": 10,
        "enableDebuggingHandlers": true,
        "healthzPort": 10248,
        "healthzBindAddress": "127.0.0.1",
        "oomScoreAdj": -999,
        "clusterDomain": "cluster.local",
        "clusterDNS": [
            "169.254.25.10"
        ],
        "streamingConnectionIdleTimeout": "4h0m0s",
        "nodeStatusUpdateFrequency": "10s",
        "nodeStatusReportFrequency": "10s",
        "nodeLeaseDurationSeconds": 40,
        "imageMinimumGCAge": "2m0s",
        "imageGCHighThresholdPercent": 85,
        "imageGCLowThresholdPercent": 80,
        "volumeStatsAggPeriod": "1m0s",
        "kubeletCgroups": "/systemd/system.slice",
        "cgroupsPerQOS": true,
        "cgroupDriver": "systemd",
        "cpuManagerPolicy": "none",
        "cpuManagerReconcilePeriod": "10s",
        "memoryManagerPolicy": "None",
        "topologyManagerPolicy": "none",
        "topologyManagerScope": "container",
        "runtimeRequestTimeout": "2m0s",
        "hairpinMode": "promiscuous-bridge",
        "maxPods": 110,
        "podPidsLimit": -1,
        "resolvConf": "/etc/resolv.conf",
        "cpuCFSQuota": true,
        "cpuCFSQuotaPeriod": "100ms",
        "nodeStatusMaxImages": 50,
        "maxOpenFiles": 1000000,
        "contentType": "application/vnd.kubernetes.protobuf",
        "kubeAPIQPS": 5,
        "kubeAPIBurst": 10,
        "serializeImagePulls": true,
        "evictionHard": {
            "imagefs.available": "15%",
            "memory.available": "100Mi",
            "nodefs.available": "10%",
            "nodefs.inodesFree": "5%"
        },
        "evictionPressureTransitionPeriod": "5m0s",
        "enableControllerAttachDetach": true,
        "protectKernelDefaults": true,
        "makeIPTablesUtilChains": true,
        "iptablesMasqueradeBit": 14,
        "iptablesDropBit": 15,
        "featureGates": {
            "IndexedJob": true
        },
        "failSwapOn": true,
        "containerLogMaxSize": "10Mi",
        "containerLogMaxFiles": 5,
        "configMapAndSecretChangeDetectionStrategy": "Watch",
        "systemReserved": {
            "cpu": "500m",
            "memory": "512Mi"
        },
        "kubeReserved": {
            "cpu": "100m",
            "memory": "256Mi"
        },
        "enforceNodeAllocatable": [
            "pods"
        ],
        "allowedUnsafeSysctls": [
            "net.ipv4.ip_unprivileged_port_start"
        ],
        "volumePluginDir": "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/",
        "logging": {
            "format": "text"
        },
        "enableSystemLogHandler": true,
        "shutdownGracePeriod": "1m0s",
        "shutdownGracePeriodCriticalPods": "20s",
        "enableProfilingHandler": true,
        "enableDebugFlagsHandler": true
    }
}

 
 
 

Reference

https://repost.aws/knowledge-center/eks-worker-nodes-image-cache
https://kubernetes.io/docs/concepts/architecture/garbage-collection/#containers-images

반응형