[Retry, k8s] 28. 리소스 관리, 오토 스케일링 - 리소스 제안

Date:     Updated:

카테고리:

태그:


01. 리소스 제안



(1) CPU/메모리 리소스 제한

리소스 유형 단위
CPU 1 = 1000M = 1 vCPU
메모리 1G = 1000M = (1Gi = 1024Mi)
# 예시 1) sample-resources.yaml

apiVersion: apps/v1
kind: Deployment
metadata: 
    name: sample
spec:
    replicas: 3
    selector:
        matchLabels:
            app: sample-app
    template:
        metadata:
            labels:
                app: sample-app
        spec:
           containers:
            - name: nginx-container
              image: nginx:1.16 
              resources:
                # 사용할 리소스 최솟값 지정
                requests:   
                    memory: "1024Mi"
                    cpu: "500M"
                # 사용할 리소스 최댓값 지정
                limits:
                    memory: "2048Mi"
                    cpu: "1000M"

“Limits를 설정하지 않으면 호스트 측의 부하가 최대로 상승할 때까지 리소스를 소비하려 한다.”

# 예시 2) sample-resources-only-requests.yaml

apiVersion: v1
kind: Pod
metadata:
    name: sample-resources-only-requests
spec:
    containers:
    - name: nginx-container
      image: nginx:1.16
      resources:
        # requests만 설정
        requests:
            memory: 256Mi
            cpu: 200M
# 리소스 설정 확인

$ kubectl get pod sample-resources-only-requests -o json | jq ".spec.containers[].resources"
{
  "requests": {
    "cpu": "200M",
    "memory": "256Mi"
  }
}

“반대로 Limits만 설정한 경우 Limits와 같은 값이 Requests에 설정되게 되어 있다.”

# 예시 3) sample-resources-only-limits.yaml

apiVersion: v1
kind: Pod
metadata:
    name: sample-resources-only-limits
spec:
    containers:
    - name: nginx-container
      image: nginx:1.16
      resources:
        # Limits만 설정
        limits:
            memory: 256Mi
            cpu: 200M
# 리소스 설정 확인

$ kubectl get pod sample-resources-only-requests -o json | jq ".spec.containers[].resources"
{
  "limits": {
    "cpu": "200M",
    "memory": "256Mi"
  },
  "requests": {
    "cpu": "200M",
    "memory": "256Mi"
  }
}


(2) Ephemeral 스토리지 리소스 제어

쿠버네티스 노드에서 기동 중인 시스템 구성 요소의 kubelet이 디스크 사용 현황을 정기적으로 모니터링하고 초과한 경우 파드는 축출(Evict)되게 되어 있다. Ephemeral 스토리지 용량을 계산되는 것은 다음과 같다.

구분 설명
(1) 컨테이너가 출력하는 로그
(2) emptyDir에 기록된 데이터(medium: 메모리가 아닌 것)
(3) 컨테이너의 쓰기 가능한 레이어(영구 볼륨/hostPath/nfs 등이 마운트된 영역을 제외한 모든 영역)에 기록된 데이터
# 예시 4) sample-ephemeral-storage.yaml

apiVersion: v1
kind: Pod
metadata:
    name: sample-ephemeral-storage
spec:
    containers:
    - name: nginx-container
      image: nginx:1.16
      resources:
        # Ephemeral 스토리지도 마찬가지로 Requests, Limits에 의해 제한을 설정한다.
        # 2048Mi를 초과하는 데이터를 쓰면 파드는 자동으로 Evict 된다.
        requests:
            ephemeral-storage: "1024Mi"
        limits:
            ephemeral-storage: "2048Mi"

“기본적으로 Evict되지 않게 여분의 리소스가 확보되지만, ephemeral-storage 사용 제한을 하지 않으면 예상치 못한 데이터 쓰기로 인해 쿠버네티스 노드의 디스크 용량을 초과하여 그 노드의 모든 파드에 영향을 줄 가능성도 있으므로 주의해야 한다.”

# 컨테이너의 쓰기 가능한 레이어에 2048MB 데이터 쓰기
$ kubectl exec -it sample-ephemeral-storage -- dd if=/dev/zero of=/dummy bs=1M count=2048


# 데이터 기록 후 파드가 Evict된 것을 확인
$ kubectl describe pod sample-ephemeral-storage

...~(생략)~

Events:
  Type     Reason     Age    From               Message
  ----     ------     ----   ----               -------
  Normal   Scheduled  2m42s  default-scheduler  Successfully assigned default/sample-ephemeral-storage to k8s-worker01
  Normal   Pulled     2m41s  kubelet            Container image "nginx:1.16" already present on machine
  Normal   Created    2m41s  kubelet            Created container nginx-container
  Normal   Started    2m41s  kubelet            Started container nginx-container
  # Evict 된 것을 확인할 수 있다.
  Warning  Evicted    68s    kubelet            Pod ephemeral local storage usage exceeds the total limit of containers 2Gi.
  Normal   Killing    68s    kubelet            Stopping container nginx-container

아래 코드는 파드의 각 컨테이너의 /cache 디렉터리에 같은 emptyDir이 마운트되어 있고, 2GiB의 Ephemeral 스토리지 제한이 설정되어 있다.

# 예시 5) sample-ephemeral-storage-multi.yaml

apiVersion: v1
kind: Pod
metadata:
    name: sample-ephemeral-storage-multi
spec:
    containers:
    - name: container-a
      image: amsy810/tools:v2.0
      resources:
        requests:
            ephemeral-storage: "1024Mi"
        limits:
            ephemeral-storage: "2048Mi"
      volumeMounts:
      - mountPath: /cache
        name: cache-volume
    - name: container-b
      image: amsy810/tools:v2.0
      resources:
        requests:
            ephemeral-storage: "1024Mi"
        limits:
            ephemeral-storage: "2048Mi"
      volumeMounts:
      - mountPath: /cache
        name: cache-volume
    volumes:
    - name: cache-volume
      emptyDir: {}

첫 번째 컨테이너의 쓰기 가능한 레이어에 3GB 데이터를 쓰면 일정 시간 후 파드가 Evict된다.

# 컨테이너의 쓰기 가능한 레이어에 3GB 데이터 쓰기
$ kubectl exec -it sample-ephemeral-storage-multi -c container-a -- dd if=/dev/zero of=/dummy bs=1M count=3000


# 데이터 기록 후 파드가 Evict된 것을 확인
$ kubectl describe pod sample-ephemeral-storage-multi

...~(생략)~

Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  48s   default-scheduler  Successfully assigned default/sample-ephemeral-storage-multi to k8s-worker02
  Normal   Pulled     47s   kubelet            Container image "amsy810/tools:v2.0" already present on machine
  Normal   Created    47s   kubelet            Created container container-a
  Normal   Started    47s   kubelet            Started container container-a
  Normal   Pulled     47s   kubelet            Container image "amsy810/tools:v2.0" already present on machine
  Normal   Created    47s   kubelet            Created container container-b
  Normal   Started    47s   kubelet            Started container container-b
  Warning  Evicted    2s    kubelet            Container container-a exceeded its local ephemeral storage limit "2Gi".
  Normal   Killing    2s    kubelet            Stopping container container-a
  Normal   Killing    2s    kubelet            Stopping container container-b

이번에는 첫 번째 컨테이너의 emptyDir 영역에 3GB 데이터를 써도 두 개 파드의 Ephemeral 스토리지 제한인 4GiB에 도달하지 않아 파드는 Evict되지 않는다. (4GB 이상의 데이털를 쓸 때 Evict된다.)

# emptyDir(/cahce) 영역에 3GB 데이터 쓰기
$ kubectl exec -it sample-ephemeral-storage-multi -c container-a -- dd if=/dev/zero of=/cache/dummy bs=1M count=3000


# Evict 되지 않음을 확인
$ kubectl describe pod sample-ephemeral-storage-multi

...~(생략)~

Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  3m11s  default-scheduler  Successfully assigned default/sample-ephemeral-storage-multi to k8s-worker02
  Normal  Pulled     3m10s  kubelet            Container image "amsy810/tools:v2.0" already present on machine
  Normal  Created    3m10s  kubelet            Created container container-a
  Normal  Started    3m10s  kubelet            Started container container-a
  Normal  Pulled     3m10s  kubelet            Container image "amsy810/tools:v2.0" already present on machine
  Normal  Created    3m10s  kubelet            Created container container-b
  Normal  Started    3m10s  kubelet            Started container container-b


(3) 시스템에 할당된 리소스와 Eviction 매니저

리소스(CPU, 메모리, Ephemeral 스토리지)가 고갈되면 쿠버네티스 자체가 동작하지 않거나 그 노드 전체에 영향을 미칠 수 있기 때무에 각 노드에는 “kube-reserved”, “system-reserved”라는 두 가지 리소스가 시스템용으로 확보되어 있다.

구분 설명
kube-reserved 쿠버네티스 시스템 구성 요소나 컨테이너 런타임에 확보된 리소스
system-reserved OS에 깊이 관련된 데몬 등에 확보된 리소스

실제 파드에 할당 가능한 리소스(Allocatable)는 쿠버네티스 노드에 존재하는 리소스 총량에서 kube-reserved, system-reserved를 제외한 양이다.

$ kubectl get nodes -o custom-columns="
> NAME:.metadata.name, \
> CPU Capacity:.status.capacity.cpu, \
> CPU Allocatable:.status.allocatable.cpu, \
> Mem Capacity:.status.capacity.memory, \
> Mem Allocatable:.status.allocatable.memory"

NAME            CPU Capacity    CPU Allocatable    Mem Capacity    Mem Allocatable
k8s-master01   2               2                  3969444Ki       3867044Ki
k8s-worker01   2               2                  1974556Ki       1872156Ki
k8s-worker02   2               2                  1974540Ki       1872140Ki

Eviction 매니저는 시스템 전체가 과부하되지 않도록 관리하며 Allocatable, System-reserved, kube-reserved에서 실제로 사용되는 리소스 합계가 Eviction Threshold를 초과하지 않는지 정기적으로 확인하고, 초과한 경우 파드를 Evict한다.

Eviction Threshold는 soft와 hard가 있고, soft 제한에 걸리면 SIGTERM 신호를 보내 파드를 정지하려고 하며, hard 제한이 걸리면 SIGKILL이 보내져 파드가 정지된다.

Eviction 매니저가 Evict하는 파드는 다음 우선순에 따라 결정된다.

구분 설명
(1) Requests에 할당된 양보다 초과하여 리소스를 소비하고 있는 것
(2) PodPriority가 더 낮은 것
(3) Requests에 할당된 양보다 초과하여 소비하고 있는 리소스 양이 더 많은 것


(4) GPU 등의 리소스 제한

k8s 1.8 이후 Divice Plugins 기능이 지원되며 GPU(Nvidia/AMD)/FPGA/VPU(머신 비전 액셀러레이터)/QAT(암호 관련 워크로드 액셀러레이터)/TPU(기계 학습용 프로세서) 등의 장치도 제한을 설정할 수 있게 되었다.

# 엔비디아 GPU 제한
resources:
    requests:
        nvidia.com/gpu: 2
    limits:
        nvidia.com/gpu: 2

# 

클러스터 구축 후 쿠버네티스가 GPU를 사용할 수 있도록 엔비디아의 드라이버를 설치하는 데몬셋을 배포해야 한다.


(5) 오버커밋과 리소스 부족

위 예제의 “sample-resource” 디플로이먼트는 한 개의 파드당 ‘500m CPU/1024Mi 메모리’의 Requests가 설정되어 있으며, 오버커밋 동작을 테스트하기 위해 스케일 아웃을 한다.

# 디플로이먼트 스케일 아웃
$ kubectl scale deployment --replicas 24 sample
deployment.apps/sample scaled


# pending status 상태의 파드가 존재
$ kubectl get all
NAME                          READY   STATUS    RESTARTS   AGE
pod/sample-5bb75c5db9-2254h   0/1     Pending   0          2s
pod/sample-5bb75c5db9-4d54d   0/1     Pending   0          2s
pod/sample-5bb75c5db9-4vxf9   0/1     Pending   0          2s
pod/sample-5bb75c5db9-4zrcb   0/1     Pending   0          2s
pod/sample-5bb75c5db9-55msb   0/1     Pending   0          2s
pod/sample-5bb75c5db9-5xbjt   0/1     Pending   0          2s


# 디플로이먼트 상태 확인
$ kubectl get deployments.apps
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
sample   5/24    24           5           104s

노드에 할당된 리소스 상태를 확인(kubectl describe node ‘노드명’)해보면, CPU Requests(최소) 제한이 거의 꽉 찬 상태가 되었고 sample-resource 디플로이먼트가 생성하는 파드의 CPU Requests 값 500m를 허용하는 리소스가 없는 것을 알 수 있다.

KUBERNETES 카테고리 내 다른 글 보러가기

댓글 남기기