개요

이번글에서는 쿠버네티스에서 Pod 배포시 CPU, MEM을 제한하는 방법을 알아본다. POD을 정의할 때 사용자는 CPU 와 MEM 를 각 컨테이너가 얼마나 필요한지 명시할 수 있다. 그러면 스케쥴러는 POD을 배치할 때 이부분을 고려하여 결정한다.

1. CPU/MEM 리소스 관리

1-1. Resource 요청/제한 관련 명세

아래 종류의 명세를 POD 정의 때 기입할 수 있다.

  • spec.containers[].resources.limits.cpu
  • spec.containers[].resources.limits.memory
  • spec.containers[].resources.requests.cpu
  • spec.containers[].resources.requests.memory

1-2. CPU 단위

CPU 리소스와 관련된 요청 및 제한은 cpu unit으로 표현된다. 쿠버네티스에서 하나의 CPU는 아래와 같을 수 있다.

  • 1 AWS vCPU
  • 1 GCP Core
  • 1 Azure vCore
  • 1 IBM vCPU
  • 1 Hyperthread on a bare-metal Intel processor with Hyperthreading

소수점은 허용되외 spec.containers[].resources.requests.cpu0.5와 같이 지정할 수 있다. 0.1100m과 같은 표현이다. 1 또는 1000m이 1개의 core를 의미한다.

여기서 알아둬야 할 점은 CPU 리소스는 항상 절대적인 수치라는 점이다. 이말의 뜻은 CPU 리소스 0.1이라는 수치는 single core, dual core, 48 core 머신에서 모두 동등한 양의 리소스를 의미한다.

1-3. Memory 단위

메모리 관련 요청/제한은 모두 바이트단위를 사용한다. 정수를 사용할수도 있고 용량 단위에 따라 Ei, Pi, Ti, Gi, Mi, Ki를 사용할 수 있다. 또는 줄여서 E, P, T, G, M, K로도 사용할 수 있다.

1-4. CPU 및 Memory 관련 Pod 예제

아래 예제는 각 컨테이너가 0.25 cpu64MB의 메모리로 요청하고 최대 0.5 cpu128MB 메모리를 가질 수 있다는 의미이다. 실제로는 POD 단위로 배포가 일어나므로 0.5 cpu128MB로 요청을 하고 최대 1 cpu256MB의 메모리로 제한된다고 이해하면 된다.

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

1-5. 어떻게 Pod 의 리소스 요청은 스케쥴 되는가?

사용자가 Pod을 생성할 때 쿠버네티스 스케쥴러는 어떤 노드에서 Pod이 실행되어야 될지 결정한다. 각 노드는 리소스 타입마다 max capacity를 가진다. 조금 당영한 얘기지만 스케쥴러는 각 리소스 타입마다 스케쥴된 컨테이너의 리소스 요청의 합이 노드의 capacity보다 낮게 유지되는것을 보장한다. 만약 노드의 실제 CPU나 메모리 리소스 사용이 낮더라도 스케쥴러는 capacity를 초과하는 Pod의 배치를 허용하지 않는다. 이것은 차후 리소스 사용이 늘어나 일어나게될 노드의 리소스 부족을 방지한다.

1-6. 어떻게 Pod의 리소스 제한은 동작하는가?

kubelet이 pod의 컨테이너를 시작할 때 CPU와 메모리 제한을 컨테이너 런타임으로 전달한다.

Docker를 사용한다면 아래와 같이 셋팅된다.

  • spec.containers[].resources.requests.cpu * 1024 또는 2 중 큰값이 docker run --cpu-shares의 값으로 설정된다.
  • spec.containers[].resources.limits.cpu * 100이 컨테이너가 100ms 이내에 최대로 사용할 수 있는 총 CPU time으로 이를 넘어서는 안된다.

interval 100ms가 기본이고 최소값으로 1ms까지 줄일 수 있다.

  • spec.containers[].resources.limits.memory값은 정수로 변환되어 docker run --memory값으로 사용된다.

만약 컨테이너가 메모리 limit을 넘으면 종료될 수 있다. 컨테이너가 재시작 가능하면 kubelet이 재시작 한다.

만약 컨테이너가 메모리 request를 넘으면 노드의 메모리가 부족할 시 Pod은 노드에서 쫓겨날 수 있다.

컨테이너는 일정시간 동안은 CPU limit을 넘어도 살아남도록 허용할수도있고 아닐수도 있다. 하지만 CPU 사용초과로 kill되지는 않는다.

컨테이너가 리소스 제한때문에 스케쥴되지 못하거나 kill되는 되는 확인하기 위해서는 Troubleshooting 섹션을 참고한다.

2. Local ephemeral storage

쿠버네티스 1.8 부터 ephemeral storage를 사용할 수 있는데 로컬 임시 스토리지라고 생각하면 된다. 각 쿠버네티스 노드에서는 kubelet의 root directory(/var/lib/kubelet)과 로그 디렉토리(/var/log)가 노드의 루트 파티션에 저장된다. 이 파티션은 또한 Pod과 emptyDir 볼륨을 공유하는데 여기에 컨테이너의 로그, image layer, writable image가 쓰여진다.
이 파티션이 ephemeral 하고 어플리케이션들은 성능에 대한 어떤 SLA도 보장받지 못한다. local ephemeral storage관리는 오직 루트 파티션(/)에만 적용가능하다.

2-1. local ephemeral storage의 요청/제한

Pod의 각 컨테이너에 아래와 같은 명세를 줄 수 있다.

  • spec.containers[].resources.limits.ephemeral-storage
  • spec.containers[].resources.requests.ephemeral-storage

    단위는 바이트를 사용한다. 정수를 사용해도 되고 용량 단위에 따라 Ei, Pi, Ti, Gi, Mi, Ki를 사용할 수 있다. 또는 줄여서 E, P, T, G, M, K로도 사용할 수 있다.

2-2. Local ephemeral storage Pod 예제

아래 예제에서 Pod 기준에서는 4GB의 스토리지 요청과 8GB의 스토리지 제한이 있다.

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
  - name: wp
    image: wordpress
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"

2-3. 어떻게 Pod의 ephemeral storage 요청은 스케쥴 되는가?

Pod을 생성할 때 쿠버네티스 스케쥴러는 Pod을 어떤 노드에 배치할 지 결정한다. 스케쥴러는 컨테이너의 스토리지 요청의 합이 노드의 max capacity보다 낮게 배치되는것을 보장한다.

2-4. 어떻게 Pod의 ephemeral storage 제한이 동작하는가?

  • 컨테이너 레벨 isolation에서는 만약 컨테이너의 writable layer 와 log 사용이 스토리지 제한을 초과하면 Pod은 쫓겨난다. Pod 레벨 isolation에서는 만약 로컬 ephemeral storage의 사용의 합과 Pod의 emptyDir 볼륨이 제한을 초과하면 Pod이 쫓겨난다.

3. 실제로 적용해보기

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sampleapp
  labels:
    env: test
spec:
  replicas: 2
  template:
    metadata:
      name: sampleapp
      labels:
        env: test
    spec:
      containers:
      - name: nginx
        image: idock.daumkakao.io/rectech/starport-v2:0.7.8
        env:
        - name: ENV_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
            ephemeral-storage: "2Gi"
          limits:
            memory: "128Mi"
            cpu: "500m"
            ephemeral-storage: "4Gi"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: sampleapp
                operator: In
                values:
                - large

CPU Request, CPU Limit항목을 보면 정상적으로 리소스가 할당된 것을 확인 할 수 있다.

$ kubectl describe node search-so-dev52
Name:               search-so-dev52
Roles:              node
Labels:             sampleapp=large
                    beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/hostname=search-so-dev52
                    node-role/worker=true
                    node-role.kubernetes.io/node=true
                    stargate=large
Annotations:        io.cilium.network.ipv4-cilium-host=10.230.5.1
                    io.cilium.network.ipv4-health-ip=10.230.5.77
                    io.cilium.network.ipv4-pod-cidr=10.230.5.0/24
                    io.cilium.network.ipv6-health-ip=f00d::ae6:500:0:4341
                    io.cilium.network.ipv6-pod-cidr=f00d::ae6:500:0:0/96
                    node.alpha.kubernetes.io/ttl=0
                    volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:             <none>
CreationTimestamp:  Fri, 25 Jan 2019 18:20:55 +0900
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  OutOfDisk        False   Thu, 14 Feb 2019 20:47:09 +0900   Fri, 25 Jan 2019 18:20:55 +0900   KubeletHasSufficientDisk     kubelet has sufficient disk space available
  MemoryPressure   False   Thu, 14 Feb 2019 20:47:09 +0900   Fri, 25 Jan 2019 18:20:55 +0900   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Thu, 14 Feb 2019 20:47:09 +0900   Fri, 25 Jan 2019 18:20:55 +0900   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Thu, 14 Feb 2019 20:47:09 +0900   Fri, 25 Jan 2019 18:20:55 +0900   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            True    Thu, 14 Feb 2019 20:47:09 +0900   Fri, 25 Jan 2019 18:21:15 +0900   KubeletReady                 kubelet is posting ready status. AppArmor enabled
Addresses:
  InternalIP:  10.61.43.102
  Hostname:    search-so-dev52
Capacity:
 cpu:                32
 ephemeral-storage:  911988260Ki
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             65838004Ki
 pods:               110
Allocatable:
 cpu:                31900m
 ephemeral-storage:  840488379025
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             65485604Ki
 pods:               110
System Info:
 Machine ID:                 80403aab3ecd42139c55821f4807962f
 System UUID:                32353537-3835-4753-4837-333153545342
 Boot ID:                    fedfed3d-b8c1-45d7-84a1-6f70119eaa2c
 Kernel Version:             4.13.0-45-generic
 OS Image:                   Ubuntu 16.04.5 LTS
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://17.3.2
 Kubelet Version:            v1.11.5
 Kube-Proxy Version:         v1.11.5
PodCIDR:                     10.230.5.0/24
ExternalID:                  search-so-dev52
Non-terminated Pods:         (7 in total)
  Namespace                  Name                                CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                                ------------  ----------  ---------------  -------------
  default                    sampleapp-547946678b-svdzd         250m (0%)     500m (1%)   64Mi (0%)        128Mi (0%)
  default                    stargate-6464cc9647-6w525           250m (0%)     500m (1%)   64Mi (0%)        128Mi (0%)
  ingress-nginx              default-backend-55d45476bb-tkg82    10m (0%)      10m (0%)    20Mi (0%)        20Mi (0%)
  kube-system                cilium-ld2kd                        100m (0%)     500m (1%)   64M (0%)         500M (0%)
  kube-system                fluentd-2l7zw                       100m (0%)     0 (0%)      200Mi (0%)       200Mi (0%)
  kube-system                kube-proxy-search-so-dev52          150m (0%)     500m (1%)   64M (0%)         2G (2%)
  kube-system                nginx-proxy-search-so-dev52         25m (0%)      300m (0%)   32M (0%)         512M (0%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ------------  ----------  ---------------  -------------
  885m (2%)     2310m (7%)  512602Ki (0%)    3511122176 (5%)
Events:         <none>

4. Extended resources

다 읽어보지 않았지만 Device Plugin을 이용하면 GPU나 NIC에 대한 제한도 고려해 볼수 있을듯 하다.

5. 참고자료