[Kubernetes] 리소스 스케쥴링(Scheduling) - Resource Request / Limits
Cluster내에서는 여러 Worker Node들이 연결되어 있고, 각 Node에는 여러 Pod들이 배치될 수 있다.
배치된 Pod들은 CPU를 많이 잡아먹는 Workload, 혹은 메모리를 많이 잡아먹는 Workload 등 다양한 요구사항을 가질 수 있다. 그리고 Worker Node 또한 각각 사양이 다를 수 있기에, 이러한 요구사항과 cluster 내의 Node 상황에 맞춰 Pod를 효율적으로 Scheduling 하는 것이 중요하다.
효율적인 Pod의 Scheduling을 위해 Kubernetes에서는 Resource Requests와 Limits를 사용한다.
이번에는 이 두 요소를 알아보도록 한다.
Resource Requests
Container가 생성될 때, 특정 용량을 요청하는 것, 해당 pod가 정상 동작하기 위해 각 Container에 제공될 최소 리소스 용량이다.
Scheduler는 Pod내의 각 Container에 설정된 이 Requests 값을 기준으로 해당 Pod를 적절한 Node에 배치하게된다.
아래는 Requests를 설정한 Pod의 Manifest 파일이다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: test-container
image: my-workload
resources:
requests:
memory: "64Mi"
cpu: "250m"
`spec.containers[].resources.requests`를 통해 컨테이너에 Request할 CPU와 메모리 용량을 지정할 수 있다.
위 예시는 Pod의 컨테이너에 대해 '64Mi'의 메모리, '250m'의 CPU를 요청한 것을 볼 수 있다.
CPU / Memory 단위
CPU는 정/소수 혹은 'm'(milli) 단위를 가질 수 있다. 정/소수 값을 가지는 경우 vCPU의 갯수로 표현되고, 1 vCPU는 '1000m'과 동일한 값을 가진다고 한다.
- 250m → 0.25 vCPU
메모리는 흔히 사용하는 kilo, mega, giga 단위를 사용한다. 다만, 세부단위로 2가지가 혼용되어 사용된다.
- 1G (1 Gigabytes) → 1000^3 = 1,000,000,000 bytes
- 1Gi (1 Gibibytes) → 1024^3 = 1,073,741,824 bytes
Resource Limits
Pod내의 컨테이너가 사용할 수 있는 최대 자원량을 제한하는 것, 해당 Pod가 배치된 Node에서 과도한 자원을 사용하지 않도록 제한할 수 있다.
Limits은 Requests와 동일한 값 이상으로 지정할 수 있다.
아래는 Requests와 함께 Limits를 설정한 Pod의 Manifest 파일이다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: test-container
image: my-workload
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
`spec.containers[].resources.limits`를 통해 컨테이너에 할당할 리소스의 Limit을 설정할 수 있다.
Limits가 설정된 상황에서 해당 Container가 CPU Limits을 넘는 경우, 쓰로틀링이 발생하고, 메모리 Limits을 넘는 경우 Pod가 죽게된다 (`Out-Of-Memory`)
Requests와 Limits의 관계
Requests와 Limits는 위와 같이 명시적으로 지정하지 않는 경우, Pod 내의 컨테이너가 실행되는 만큼 배치된 위치의 Node의 자원을 계속 잡아먹는다.
이는 결과적으로 그 Node에서 동작중인 다른 Pod들에 영향을 줄 수 있다. 즉, 특정 Pod가 리소스를 다 잡아먹게되면, 그 Node가 죽어버리게 될 경우도 있다는 것이다.
그렇다면 Requests와 Limits를 어떻게 설정해줘야 Node의 리소스를 효율적으로 사용할 수 있을까?
크게 4가지 경우를 생각해 볼 수 있다.
- Requests만 설정된 경우
- Limits만 설정된 경우
- Requests와 Limits가 모두 설정된 경우
- 그리고 둘다 설정되지 않은 경우
예시로 CPU 리소스에 대해 Requests와 Limits를 고려를 해보도록 한다.
먼저 Requests, Limits 모두 설정되지 않은 기본 상황에서는 앞서 이야기한 바와 같이 Container가 실행되며 사용한 만큼 Node 리소스를 사용한다.
다음으로 Limits만 설정된 경우에는 명시적으로 Requests가 설정되지 않아도, 그 값이 Limits와 같은 값으로 설정된다. 이 상황에서는 설정된 Limits 이상으로 리소스를 주지 않는다. 따라서 Limits를 넘어선 CPU 사용은 쓰로틀링의 발생으로 성능 저하가 발생하게 될 것이다.
그리고 Requests와 Limits가 모두 설정된 상황을 보자. 어떻게 보면 리소스 최소치와 최대치가 설정된 가장 이상적인 모습이라고 생각될 수 있다.
그러나 만약 이 Node에 2개의 Pod가 있고, 1번 Pod가 CPU작업이 평소보다 더 필요한 상황이 있고, 2번 Pod는 비교적 CPU 리소스 여유가 있는 상황이 발생한 경우는 어떨까?
이 경우 1번 Pod가 2번 Pod의 CPU 리소스를 조금 가져와 사용하면 가장 효율적으로 리소스를 사용할 수 있다. 하지만 Limits가 설정되어 그 이상의 값을 Pod에 할당할 수 없다.
그래서 이러한 상황들도 고려하면 Requests만 설정된 모습이 가장 이상적인 상황으로 여겨질 수 있다.
물론 이들은 어디까지나 이론적인 상황일 뿐, 실제 운영되는 환경에서는 이들에 부합하지 않는 예외적인 상황도 많이 발생할 것이다. Cluster에 배치될 Pod의 성격과 Node의 상태 등을 충분히 고려해가며 Requests와 Limits를 설정할 필요가 있을 것이다.
LimitRange
Namespace 내에서 생성되는 Container와 Pod들의 리소스 Request와 Limit을 정의한 것
Resource Limits와는 적용 범위, 목적, 관리 방식의 차이점이 존재한다.
Resource Limits | LimitRange | |
적용 범위 | 개별 Pod, Container | Namespace 내의 모든 Pod와 Container |
목적 | 특정 Pod,Container의 최대 자원 사용량의 제한 | Namespace 수준에서의 Request/Limit의 기본값과 범위 지정 |
관리 방식 | 각 Pod의 Container Manifest파일의 field로 설정 | Namespace 수준에서의 하나의 Object (kind: LimitRange) |
아래는 Namespace에 속하는 모든 컨테이너에 대한 LimitRange를 생성하는 Manifest 파일이다.
apiVersion: v1
kind: LimitRange
metadata:
name: limits
namespace: my-namespace
spec:
limits:
- max: # 최대 자원
cpu: "1"
memory: "512Mi"
min: # 최소 자원
cpu: "100m"
memory: "64Mi"
default: # 기본 Resource Limits
cpu: "500m"
memory: "256Mi"
defaultRequest: # 기본 Resource Request
cpu: "200m"
memory: "128Mi"
type: Container
LimitRange는 LimitRange object를 생성하기 이전의 리소스들에는 적용되지 않고, object를 생성한 이후의 리소스들 부터 적용된다.
ResourceQuota
Namespace 별로 사용가능한 총 리소스 양을 지정하는 것, 팀이나 리소스 그룹별로 Namespace를 지정해주고, 그들 별로 제한을 설정해주어 다른 그룹에 피해를 주지 않도록 사용할 수 있다.
아래는 ResourceQuota를 생성하는 Manifest 파일이다.
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace
spec:
hard:
requests.cpu: "100m"
requests.memory: "256Mi"
limits.cpu: "2"
limits.memory: "1Gi"
이렇게 배치할 object의 리소스 할당을 기반으로 스케쥴링하는 방법을 알아보았다.
Pod들의 특성을 미리 파악하고, 적절한 할당을 지정해주어 스케쥴링함을 통해 더욱 효율적인 리소스 사용을 할 수 있을 것이다.