삽질특기생

다음에 삽질 덜하려고 만든 블로그

0%

Kubernetes의 resource 관리(1) - requests와 limits

Kubernetes의 Resource Management

Kubernetes에서 resource management하는 것에 대해 정리해보고자 한다. 꽤나 복잡해서 하나의 포스팅으로는 끝나지 않을 것 같아 내용을 나눈다.
이번 포스팅은 pod의 spec 중 container별로 설정할 수 있는 resource requests와 limits에 대한 내용이다.

Resource Requests & Limits

Kubernetes에서 pod의 spec을 정의할 때, container별로 CPU와 Memory에 대해서 requests와 limits를 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: simple-pod
spec:
containers:
- name: simple-pod-ctr
image: busybox
stdin: true
tty: true
resources:
requests:
cpu: 0.5
memory: 300Mi
limits:
cpu: 1
memory: 500Mi

CPU의 경우 단위는 core이고, 0.1과 100m, 100 millicore는 모두 같은 값을 의미한다.
Memory의 경우 단위는 byte이다.

Requests

Requests는 pod을 scheduling할 때 참고하며, 실제 container process의 사용량이 이것을 넘는 것은 중요하지 않다.
사용자가 생각할 때 ‘이 container는 이 정도의 자원을 필요로 할 것이다’라는 예상치로 정하는 값이다. Soft limit정도로 생각하면 된다.

  • cgroup에 설정되는 값

    CPU의 경우 cpu.shares 값이 설정된다. 만약 container1은 CPU requests로 0.5를, container2는 CPU requests로 1을 지정했다면, A와 B는 1:2의 비율로 스케줄링된다.
    Memory는 별도로 설정하는 값이 없다.

    • 테스트한 conainer yaml
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      apiVersion: v1
      kind: Pod
      metadata:
      name: request-test
      spec:
      containers:
      - name: request-test-ctr-1
      image: busybox
      stdin: true
      tty: true
      resources:
      requests:
      cpu: 0.5
      memory: 300Mi
      - name: request-test-ctr-2
      image: busybox
      stdin: true
      tty: true
      resources:
      requests:
      cpu: 1
      memory: 300Mi
    • cgroup 확인
      • pod uuid와 docker container uuid 확인

        docker ps

        다른 방법들이 많겠지만 나는 그냥 worker node로 접속해서 docker ps를 쳐서 알아내는 편이다.
        Docker container name의 형식은 k8s_{container name}{pod name}{namespace}_{pod uuid}이다.

      • 해당 pod의 cgroup 접근

        pod cgroup

        • 참고) kubernetes cgroup layout (v1.17 기준)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          kubepods
          \_ besteffort
          \_ pod{uuid}
          \_ {container uuid}
          \_ {pause container uuid}
          \_ ...
          \_ burstable
          \_ pod{uuid}
          \_ {container uuid}
          \_ {pause container uuid}
          \_ ...
          \_ pod{uuid} # guaranteed pods
          \_ {container uuid}
          \_ {pause container uuid}
          \_ ...
          \_ ...
          /sys/fs/cgroup/{resource type}/kubepods/{QoS class}/pod{pod uuid}이다.
          QoS class가 BestEffort, Burstable, Guaranteed가 있으며, Guaranteed의 경우 추가 cgroup 없이 바로 pod cgroup이 위치한다.
          kubepods의 cpu.shares는 {node의 core 수} * 1024, besteffort의 cpu.shares는 2, burstable의 cpu.shares는 256이다. guaranteed cgroup이 따로 없고 바로 pod cgroup이 있는 건 kubernetes가 (같은 core에 대해 경쟁해야하는 상황인 경우)guaranteed pod에게 최대한 자원을 보장하겠다는 의지가 반영된 것이 아닐까 싶다. kubepod의 cpu.shares 수치로 보아서 해당 node는 온전히 kubernetes만을 위한 것이라고 생각하는 것이 아닐까. 뭐 이건 다 추측이다.
      • container별 cpu.shares 확인

        cpu.shares

        Pod cgroup의 cpu.shares는 1536, container1 cgroup의 것은 512, container2 cgroup의 것은 1024, pause container의 것은 2가 설정되어 있다.
        Kubernetes는 container별로 cpu.shares를 {CPU requests} * 1024 값으로 설정하고, pod의 cpu.shares는 이렇게 request가 지정된 container의 cpu.shares의 합으로 설정한다. (1536 = 512 + 1024)

        만약 requests를 설정하지 않으면 2이며, 이 때는 pod cgroup에 합산하지 않는다.
        만약 container1의 cpu request를 0.5, container2는 request를 설정하지 않았으면, pod의 cpu.shares는 512이다.
        만약 container 1과 2 둘 다 request를 설정하지 않았으면, pod의 cpu.shares는 2이다.
        (이러한 계산은 실험공학으로 알아낸 것이므로, 정확한 알고리즘은 나중에 코드를 확인해봐야할 것 같다.)

Limits

Limits는 container의 cgroup을 설정할 때 참고하는 값이므로, 실제 container process의 사용량은 이것을 넘지 못한다. Hard limit정도로 생각하면 된다.
Requests를 설정했다면 limits는 requests보다 크거나 같은 값이어야 한다. (Requests를 더 큰 값으로 설정할 경우 deploy 시 에러가 발생한다.)
Requests를 설정하지 않은 채 limits를 설정한다면, requests는 limits와 동일한 값으로 설정된다.
참고로 requests는 설정했지만 limits를 설정하지 않을 수도 있다. 물론 둘 다 설정하지 않아도 된다. CPU와 Memory 둘 중에 하나만 설정할 수도 있다.
(어떻게 설정하느냐에 따라서 아까 언급했던 QoS class가 나뉘는데, 이 역시 다른 포스팅에서…)

  • cgroup에 설정되는 값

    CPU의 경우 CPU bandwidth quota인 cpu.cfs_quota_us 값이 설정된다.
    Memory의 경우 memory 사용률 제한인 memory.limit_in_bytes 값이 설정된다.

    • 테스트한 conainer yaml
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      apiVersion: v1
      kind: Pod
      metadata:
      name: request-test
      spec:
      containers:
      - name: request-test-ctr-1
      image: busybox
      stdin: true
      tty: true
      resources:
      limits:
      cpu: 0.5
      memory: 300Mi
    • cgroup 확인
      • container별 cpu.cfs_quota_us 확인

        cpu.cfs_quota_us

        Pod의 quota는 50000, container1의 quota도 50000, pause container의 quota는 설정하지 않는다(-1).
        Kubernetes는 cfs_quota_us를 {CPU limit} * {cfs_period_us}로 설정하고, cfs_period_us는 kernel default값인 100000을 그대로 사용한다.

        참고로 cfs_quota_us는 -1일 때는 bandwidth 제한을 하지 않는다.
        만약 CPU limit을 주지 않을 경우 cfs_quota_us는 -1로 설정된다.

      • container별 memory.limit_in_bytes 확인

        memory.limit_in_bytes

        Pod의 limit은 314572800, container1의 limit도 314572800, pause container의 경우 설정하지 않는다.
        Kubernetes는 memory.limit_in_bytes를 Memory limit값을 byte로 변환하여 설정한다.

Memory stress test

  • 시나리오

    1. request & limit 설정하지 않았는데 out-of-memory가 발생하는 경우
    2. request 값 < 실 사용량
    3. limit 값 < 실 사용량
  • 테스트 과정

    1. request & limit 설정하지 않았는데 out-of-memory가 발생하는 경우

      • test한 yaml
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        apiVersion: v1
        kind: Pod
        metadata:
        name: memory-demo
        spec:
        restartPolicy: Never
        containers:
        - name: memory-demo-ctr
        image: polinux/stress
        command: ["stress"]
        args: ["--vm", "1", "--vm-bytes", "1700M", "--vm-hang", "1"]
      • 테스트 결과
        • Pod describe 결과

          mem stress test1

          해당 pod을 describe해보면 State가 Terminated로 바뀌었고, Reason이 Error라고 명시되어있다.

        • OOM killer 동작 여부 확인

          dmesg 확인

    2. request만 설정하는 경우

      • test한 yaml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        apiVersion: v1
        kind: Pod
        metadata:
        name: memory-demo
        spec:
        restartPolicy: Never
        containers:
        - name: memory-demo-ctr
        image: polinux/stress
        resources:
        requests:
        memory: 300Mi
        command: ["stress"]
        args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]

        mem stress test2

        해당 pod을 describe해보면 State는 Running으로, 아무런 일도 일어나지 않았다.

    3. limit 설정하는 경우

      • test한 yaml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        apiVersion: v1
        kind: Pod
        metadata:
        name: memory-demo
        spec:
        restartPolicy: Never
        containers:
        - name: memory-demo-ctr
        image: polinux/stress
        resources:
        limits:
        memory: 300Mi
        command: ["stress"]
        args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]
      • 테스트 결과

        mem stress test3

        해당 pod을 describe해보면 State가 Terminated로 바뀌었고, Reason이 OOMkilled라고 명시되어있다.
        OOM killer 역시 동작했다.

        mem stress test3 dmesg