CKA를 향하여 (Scheduling)
1. 수동 스케줄링 (Manual Scheduling)
스케줄러 없이 파드 배치하기
클러스터에 스케줄러가 없거나 내장 스케줄러를 사용하지 않고 직접 파드를 노드에 배치해야 하는 상황이 있다. 이 경우 파드는 스케줄러 없이 계속 Pending
상태에 머물게 된다.
스케줄러의 작동 방식
스케줄러는 백엔드에서 다음과 같이 작동한다:
- 모든 파드는
nodeName
이라는 필드를 가지고 있으며, 이 필드는 기본적으로 설정되어 있지 않다. - 스케줄러는 모든 파드를 검사하고
nodeName
속성이 설정되지 않은 파드를 찾는다. - 이러한 파드들이 스케줄링 대상이 된다.
- 스케줄링 알고리즘을 실행하여 파드에 적합한 노드를 식별한다.
- 바인딩 객체를 생성하여 파드의
nodeName
속성을 선택된 노드 이름으로 설정한다.
nodeName 필드 직접 설정
스케줄러 없이 파드를 스케줄링하는 가장 쉬운 방법은 파드 생성 시 사양 파일에 nodeName
필드를 직접 설정하는 것이다.
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: node01 # 이 파드를 node01에 직접 할당
이렇게 하면 파드가 지정된 노드에 직접 할당된다. 단, nodeName
은 파드 생성 시에만 지정할 수 있다는 점에 유의해야 한다.
기존 파드에 노드 할당하기
이미 생성된 파드에는 nodeName
속성을 수정할 수 없다. 이 경우 다른 방법으로 바인딩 객체를 생성하고 파드의 바인딩 API에 POST 요청을 보내는 방법이 있다.
바인딩 객체 생성
1
2
3
4
5
6
7
8
apiVersion: v1
kind: Binding
metadata:
name: nginx
target:
apiVersion: v1
kind: Node
name: node02
POST 요청 보내기
YAML을 JSON으로 변환하고 파드의 바인딩 API에 POST 요청을 보낸다:
1
2
3
4
curl --header "Content-Type: application/json" \
--request POST \
--data '{"apiVersion":"v1", "kind":"Binding", "metadata":{"name":"nginx"}, "target":{"apiVersion":"v1", "kind":"Node", "name":"node02"}}' \
http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
이 방법으로 기존에 생성된 파드를 특정 노드에 할당할 수 있다.
2. 레이블과 셀렉터 (Labels and Selectors)
레이블과 셀렉터의 개념
레이블과 셀렉터는 구성요소를 그룹화하는 방법이다. 구성요소들을 다양한 기준으로 필터링하고 싶을 때 사용할 수 있다.
이러한 분류는 각 항목에 속성(레이블)을 추가하고, 그 속성을 기준으로 필터링(셀렉터)하여 구현할 수 있다. 예를 들어:
- 클래스가 포유류인 모든 항목
- 색상이 녹색인 모든 항목
- 색상이 녹색이고 조류인 모든 항목
이러한 레이블과 셀렉터는 YouTube 동영상의 키워드 태그, 블로그의 카테고리, 온라인 쇼핑몰의 상품 필터 등 일상에서도 흔히 볼 수 있다.
쿠버네티스에서의 레이블과 셀렉터
쿠버네티스에서는 파드, 서비스, 레플리카셋, 디플로이먼트 등 많은 다양한 유형의 객체가 있다. 시간이 지남에 따라 클러스터에 수백 또는 수천 개의 객체가 생길 수 있으며, 이를 효율적으로 관리하기 위해 레이블과 셀렉터를 사용한다.
레이블을 사용하면 다음과 같은 방식으로 객체를 분류할 수 있다:
- 객체 유형별 그룹화
- 애플리케이션별 객체 보기
- 기능별 객체 분류
레이블 지정 방법
쿠버네티스에서 레이블은 다음과 같이 정의 파일의 metadata
섹션 아래에 지정한다:
1
2
3
4
5
6
7
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
labels:
app: app1
function: frontend
원하는 만큼 많은 레이블을 추가할 수 있다.
셀렉터 사용 방법
레이블이 지정된 객체를 선택하려면 kubectl
명령에 --selector
또는 -l
옵션을 사용한다.
1
kubectl get pods --selector app=app1
이 명령은 app=app1
레이블이 있는 모든 파드를 표시한다.
레이블과 셀렉터의 내부 사용
쿠버네티스 객체는 내부적으로 레이블과 셀렉터를 사용하여 서로 다른 객체를 연결한다. 예를 들어, 레플리카셋은 셀렉터를 사용하여 관리할 파드를 식별한다.
레플리카셋 정의 파일에서는 두 곳에 레이블이 정의된다:
- 상단의
metadata
섹션: 레플리카셋 자체에 대한 레이블 template
섹션 아래: 레플리카셋이 생성할 파드에 적용될 레이블
레플리카셋이 파드를 찾을 수 있도록 레플리카셋의 selector
필드는 파드의 레이블과 일치해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: my-app-replicaset
labels:
app: app1 # 레플리카셋 자체의 레이블
spec:
replicas: 3
selector:
matchLabels:
app: app1 # 파드를 선택하기 위한 셀렉터
template:
metadata:
labels:
app: app1 # 생성될 파드의 레이블
spec:
containers:
- name: nginx
image: nginx
애노테이션(Annotations)
레이블과 셀렉터가 객체를 그룹화하고 선택하는 데 사용되지만 애노테이션은 다른 세부 정보를 기록하는 데 사용된다.
어노테이션의 예시
- 도구 세부 정보 (이름, 버전, 빌드 정보 등)
- 연락처 세부 정보 (전화번호, 이메일 등)
- 통합 목적의 기타 정보
애노테이션은 다음과 같이 정의 파일에 추가할 수 있다.
1
2
3
4
5
6
7
8
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
annotations:
buildVersion: "1.3.4"
gitCommit: "a1b2c3d4"
team: "platform-engineering"
3. 테인트와 톨러레이션 (Taints and Tolerations)
테인트와 톨러레이션의 개념
테인트(Taints)와 톨러레이션(Tolerations)의 개념을 벌레와 사람의 비유하자면 아래와 같다.
사람에게 벌레가 앉지 못하게 하려면, 방충제(테인트)를 뿌린다. 벌레는 이 냄새에 견디지 못하고 사람에게 접근하려다 튕겨나간다.
그러나 일부 벌레는 이 냄새에 내성(톨러레이션)이 있어 사람에게 앉을 수 있다.
쿠버네티스에서도 마찬가지로
- 노드는 사람에 해당하고, 파드는 벌레에 해당한다.
- 테인트는 노드에 설정되며, 톨러레이션은 파드에 설정된다.
- 테인트와 톨러레이션은 보안이나 침입과는 관련이 없으며, 단지 어떤 파드가 어떤 노드에 스케줄링될 수 있는지 제한하는 데 사용된다.
테인트와 톨러레이션의 작동 방식
단순한 클러스터 예시를 통해 작동 방식을 살펴보자
- 3개의 워커노드 node1, node2, node3가 있다.
- A, B, C, D라는 4개의 파드가 있다
기본적으로 스케줄러는 파드를 모든 노드에 골고루 배치한다. 하지만 특정 애플리케이션을 위해 node1을 전용으로 사용하고 싶다면 어떻게 해야 할까?
- node1에 테인트를 설정한다(예:
app=blue
) - 기본적으로 파드는 톨러레이션이 없으므로, 테인트가 설정된 노드에 스케줄링될 수 없다
- 특정 파드(예: 파드 D)에만 이 테인트에 대한 톨러레이션을 추가한다
결과적으로
- 파드 A, B, C는 node1에 스케줄링될 수 없다.
- 파드 D만 node1에 스케줄링될 수 있다.
테인트 설정 방법
노드에 테인트를 설정하려면 다음 명령을 사용한다.
1
kubectl taint nodes node1 app=blue:NoSchedule
이 명령은 node1에 app=blue
라는 테인트를 설정하고, 테인트 효과로 NoSchedule
을 지정한다.
테인트 효과(Taint Effect)
테인트 효과는 톨러레이션이 없는 파드가 테인트가 설정된 노드에 어떻게 반응할지 정의한다.
- NoSchedule: 톨러레이션이 없는 파드는 노드에 스케줄링되지 않는다.
- PreferNoSchedule: 시스템은 톨러레이션이 없는 파드를 노드에 배치하지 않으려고 하지만, 반드시 그렇게 하는 것은 아니다
- NoExecute: 톨러레이션이 없는 파드는 노드에 스케줄링되지 않으며, 이미 실행 중인 파드도 노드에서 퇴출(삭제)된다
톨러레이션 설정 방법
파드에 톨러레이션을 추가하려면 파드 정의 파일의 spec
섹션에 다음과 같이 설정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: pod-d
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "app"
operator: "Equal"
value: "blue"
effect: "NoSchedule"
모든 값은 반드시 따옴표로 묶어야 한다.
테인트와 톨러레이션의 제한사항
테인트와 톨러레이션은 노드가 특정 파드만 수용하도록 제한하지만, 특정 파드가 특정 노드에만 배치되도록 보장하지는 않는다. 예를 들어, 파드 D가 node1에만 배치되어야 한다는 보장은 없으며, 다른 노드에도 배치될 수 있다.
파드를 특정 노드에 제한하려면 노드 어피니티(Node Affinity)라는 다른 개념을 사용해야 한다.
마스터 노드와 테인트
쿠버네티스 클러스터가 처음 설정될 때, 마스터 노드에는 자동으로 테인트가 설정되어 일반 파드가 마스터 노드에 스케줄링되지 않도록 한다. 이는 마스터 노드에서 애플리케이션 워크로드를 실행하지 않는 모범 사례를 따른 것이다.
마스터 노드의 테인트를 확인하려면 다음 명령을 실행한다:
1
kubectl describe node kube-master
출력 결과에서 Taints
섹션을 찾으면 마스터 노드에 설정된 테인트를 볼 수 있다.
4. 노드 셀렉터 (Node Selectors)
노드 셀렉터의 필요성
특정 파드를 특정 노드에서만 실행시키고 싶은 상황이 있다. 예를 들어, 3개의 노드로 구성된 클러스터가 있을때
- 2개는 하드웨어 리소스가 적은 작은 노드
- 1개는 하드웨어 리소스가 많은 큰 노드
이러한 클러스터에서 높은 리소스가 필요한 데이터 처리 워크로드는 리소스가 충분한 큰 노드에서만 실행되도록 하고 싶다.
하지만 기본 설정에서는 모든 파드가 모든 노드에 배치될 수 있어, 데이터 처리 파드가 작은 노드에 배치되는 상황이 발생할 수 있다.
이 문제를 해결하기 위한 방법으로 노드 셀렉터(Node Selector)를 사용하는 것이다.
노드 셀렉터 설정 방법
노드 셀렉터를 사용하려면 파드 정의 파일의 spec
섹션에 nodeSelector
필드를 추가하고, 원하는 노드의 레이블을 키-값 쌍으로 지정한다.
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: data-processor
spec:
containers:
- name: data-processor
image: data-processor
nodeSelector:
size: Large # 노드에 설정된, 레이블과 일치해야 함
이 설정은 파드가 size=Large
레이블이 있는 노드에서만 스케줄링되도록 한다.
노드 레이블 설정
노드 셀렉터를 사용하기 전에, 먼저 노드에 적절한 레이블을 지정해야 한다. 노드에 레이블을 추가하는 명령은 다음과 같다.
1
kubectl label nodes node1 size=Large
이 명령은 node1
이라는 노드에 size=Large
라는 레이블을 추가한다.
노드 셀렉터의 한계
노드 셀렉터는 간단한 노드 선택 요구사항을 충족하는 데 유용하지만, 다음과 같은 복잡한 요구사항을 처리하는 데는 한계가 있다.
- “Large 또는 Medium 노드에 파드 배치”
- “Small이 아닌 모든 노드에 파드 배치”
이러한 복잡한 요구사항을 충족하기 위해서는 노드 어피니티(Node Affinity)와 안티-어피니티(Anti-Affinity) 기능이 필요하다.
5. 노드 어피니티 (Node Affinity)
노드 어피니티의 목적
노드 어피니티는 특정 파드가 특정 노드에서 호스팅되도록 보장하는 기능이다.
이전에 배운 노드 셀렉터로도 이 작업을 수행할 수 있지만, 노드 어피니티는 더 복잡한 표현식(“or” 또는 “not” 같은)을 사용할 수 있는 고급 기능을 제공한다.
노드 어피니티 구문
노드 어피니티를 사용한 파드 정의 파일은 다음과 같이 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: data-processor
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- Large
단순히 노드 셀렉터를 사용했을 때와 동일한 기능을 수행하지만, 훨씬 더 복잡한 표현식을 가능하게 한다.
노드 어피니티 연산자
노드 어피니티는 다양한 연산자를 지원한다.
- In: 지정된 값 중 하나와 일치하는 레이블이 있는 노드 선택
1 2 3 4
operator: In values: - Large - Medium
- NotIn: 지정된 값과 일치하지 않는 레이블이 있는 노드 선택
1 2 3
operator: NotIn values: - Small
- Exists: 지정된 키를 가진 레이블이 있는 노드 선택 (값 무관)
1
operator: Exists
- DoesNotExist: 지정된 키를 가진 레이블이 없는 노드 선택
이 외에도 더 많은 연산자가 있다. 자세한 내용은 쿠버네티스 공식 문서를 참조하자.
노드 어피니티 유형
노드 어피니티의 유형은 스케줄러가 노드 어피니티 규칙을 처리하는 방법과 파드 생명주기의 여러 단계에서의 동작을 정의한다.
- requiredDuringSchedulingIgnoredDuringExecution
- During Scheduling(스케줄링 중): 파드가 반드시 일치하는 노드에 배치되어야 함. 일치하는 노드가 없으면 파드는 스케줄링되지 않음
- During Execution(실행 중): 실행 중인 파드는 나중에 노드 레이블이 변경되더라도 영향을 받지 않음
- preferredDuringSchedulingIgnoredDuringExecution
- During Scheduling: 일치하는 노드가 선호되지만, 일치하는 노드가 없으면 파드는 다른 노드에 스케줄링될 수 있음
- During Execution: 실행 중인 파드는 노드 레이블 변경에 영향을 받지 않음
향후 추가될 예정인 유형
- requiredDuringSchedulingRequiredDuringExecution: 노드 레이블이 변경되어 더 이상 어피니티 규칙과 일치하지 않을 경우, 실행 중인 파드도 퇴출(종료)됨
노드 어피니티 선택 기준
어떤 유형의 노드 어피니티를 선택할지는 워크로드의 중요성에 따라 달라진다.
- required: 파드 배치가 매우 중요한 경우 사용. 일치하는 노드가 없으면 파드는 실행되지 않음
- preferred: 파드 배치보다 워크로드 실행이 더 중요한 경우 사용. 일치하는 노드가 없더라도 파드는 다른 노드에서 실행됨
이를 통해 워크로드의 요구사항에 따라 적절한 노드 배치 전략을 선택할 수 있다.
6. 테인트와 노드 어피니티 결합하기
복합 문제 해결하기
테인트와 톨러레이션, 그리고 노드 어피니티를 모두 배웠으니 이제 이 두 개념을 결합하여 더 복잡한 문제를 해결해보자.
다음과 같은 시나리오를 생각해보자:
- 3개의 노드가 있고, 각각 파란색, 빨간색, 녹색으로 지정
- 3개의 파드가 있고, 각각 파란색, 빨간색, 녹색으로 지정
- 목표: 파란색 파드는 파란색 노드에, 빨간색 파드는 빨간색 노드에, 녹색 파드는 녹색 노드에 배치
- 추가 제약: 다른 팀의 파드가 우리 노드에 배치되지 않아야 하고, 우리 파드도 다른 팀의 노드에 배치되지 않아야 함
테인트와 톨러레이션 접근법
먼저 테인트와 톨러레이션만으로 이 문제를 해결해보자
- 각 노드에 색상 테인트 적용:
1 2 3
kubectl taint nodes blue-node color=blue:NoSchedule kubectl taint nodes red-node color=red:NoSchedule kubectl taint nodes green-node color=green:NoSchedule
- 각 파드에 해당 색상의 톨러레이션 설정: ```yaml
파란색 파드 예시
tolerations:
- key: “color” operator: “Equal” value: “blue” effect: “NoSchedule” ```
이 접근법의 결과:
- 다른 팀의 파드는 우리 노드에 배치되지 않음 (톨러레이션이 없으므로)
- 하지만, 우리 파드가 특정 노드를 선호한다는 보장이 없음
- 예: 빨간색 파드가 테인트가 없는 다른 팀의 노드에 배치될 수 있음
노드 어피니티 접근법
- 각 노드에 색상 레이블 지정
1 2 3
kubectl label nodes blue-node color=blue kubectl label nodes red-node color=red kubectl label nodes green-node color=green
- 각 파드에 해당 색상의 노드 어피니티 설정: ```yaml
파란색 파드 예시
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms:
- matchExpressions:
- key: color operator: In values:
- blue ```
- key: color operator: In values:
- matchExpressions:
이 접근법의 결과:
- 우리 파드는 지정된 색상 노드에만 배치됨
- 하지만, 다른 팀의 파드가 우리 노드에 배치되는 것을 막지 못함
두 접근법 결합하기
테인트와 톨러레이션, 노드 어피니티를 결합하여 해결
- 테인트와 톨러레이션으로 다른 팀의 파드가 우리 노드에 배치되는 것을 방지
- 노드 어피니티로 우리 파드가 다른 팀의 노드에 배치되는 것을 방지
구현 방법
- 각 노드에 색상 테인트와 레이블 적용
1 2 3
# 파란색 노드 예시 kubectl taint nodes blue-node color=blue:NoSchedule kubectl label nodes blue-node color=blue
- 각 파드에 해당 색상의 톨러레이션과 노드 어피니티 설정 ```yaml # 파란색 파드 예시 tolerations:
- key: “color” operator: “Equal” value: “blue” effect: “NoSchedule”
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: color operator: In values: - blue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
이 결합된 접근법을 통해
- 테인트와 톨러레이션은 다른 팀의 파드가 우리 노드에 배치되는 것을 방지
- 노드 어피니티는 우리 파드가 다른 팀의 노드에 배치되는 것을 방지
테인트/톨러레이션과 노드 어피니티는 서로 다른 목적을 가지며, 함께 사용할 때 더 강력한 노드 할당 전략을 구현할 수 있다.
---
## 7. 리소스 요구사항과 제한 (Resource Requirements and Limits)
### 리소스 요구사항의 개념
쿠버네티스 클러스터의 각 노드는 CPU와 메모리 리소스를 제공한다. 모든 파드는 실행하기 위해 이러한 리소스가 필요하며, 스케줄러는 파드에 필요한 리소스와 노드에서 사용 가능한 리소스를 고려하여 배치 결정을 내린다.
파드가 노드에 배치되면, 해당 노드의 사용 가능한 리소스를 점유한다. 노드에 충분한 리소스가 없으면 스케줄러는 파드를 다른 노드에 배치하거나, 모든 노드에 충분한 자원이 없을 경우 파드는 `Pending` 상태로 남게 된다.
### 리소스 요청 (Resource Requests)
파드를 생성할 때 각 컨테이너가 필요로 하는 CPU와 메모리 양을 지정할 수 있다. 이를 리소스 요청(Resource Requests)이라고 한다. 스케줄러는 이 정보를 사용하여 파드를 배치할 노드를 결정한다.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: resource-demo-container
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
이 예시에서 컨테이너는 64 MiB의 메모리와 0.25 CPU(250 밀리코어)를 요청한다.
CPU 단위
CPU 리소스는 다음과 같이 표현할 수 있다.
0.1
또는100m
: 0.1 CPU, 즉 100 밀리코어1
: 1 CPU 코어 (AWS에서는 1 vCPU, GCP/Azure에서는 1 코어, 기타 시스템에서는 1 하이퍼스레드)- 최소 단위:
1m
(밀리코어)
메모리 단위
메모리 리소스는 다음과 같이 표현할 수 있다.
256Mi
: 256 메비바이트 (MiB)1Gi
: 1 기비바이트 (GiB)512M
: 512 메가바이트 (MB)1G
: 1 기가바이트 (GB)
주의할 점은 G
와 Gi
의 차이다:
G
(기가바이트): 1,000 메가바이트 = 1,000,000,000 바이트Gi
(기가바이트): 1,024 메가바이트 = 1,073,741,824 바이트
리소스 제한 (Resource Limits)
기본적으로 컨테이너는 노드의 리소스를 무제한으로 소비할 수 있다. 이로 인해 다른 컨테이너나 노드의 기본 프로세스가 리소스 부족을 겪을 수 있다. 이를 방지하기 위해 리소스 제한(Resource Limits)을 설정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: resource-demo-container
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
이 예시에서 컨테이너는 최대 128 MiB의 메모리와 0.5 CPU를 사용할 수 있다.
리소스 제한 초과 시 동작
CPU와 메모리 제한 초과 시 동작이 다르다.
- CPU 제한 초과: 시스템은 CPU를 스로틀링하여 컨테이너가 설정된 제한을 초과하지 못하게 한다.
- 메모리 제한 초과: 컨테이너는 일시적으로 제한을 초과할 수 있지만, 지속적으로 초과하면 OOM(Out Of Memory) 에러와 함께 종료된다.
리소스 구성 시나리오
1. 요청과 제한 모두 설정하지 않은 경우
- 컨테이너는 노드의 모든 리소스를 소비할 수 있다.
- 다른 파드나 프로세스가 리소스 부족을 겪을 수 있다.
- 기본적으로 권장하지 않는 방식이다.
2. 제한만 설정하고 요청은 설정하지 않은 경우
- 쿠버네티스는 자동으로 요청을 제한과 동일하게 설정한다.
- 각 파드는 지정된 양의 리소스를 보장받지만, 그 이상은 사용할 수 없다.
3. 요청과 제한 모두 설정한 경우
- 각 파드는 요청한 리소스를 보장받고, 제한까지 사용할 수 있다.
- 일반적으로 가장 균형 잡힌 접근법이다.
4. 요청만 설정하고 제한은 설정하지 않은 경우
- 각 파드는 요청한 리소스를 보장받는다.
- 필요할 경우 노드에서 사용 가능한 추가 리소스를 소비할 수 있다.
- CPU 집약적인 워크로드에 적합한 방식이다.
LimitRange로 기본값 설정하기
기본적으로 쿠버네티스는 파드에 리소스 요청이나 제한을 설정하지 않는다. LimitRange 객체를 사용하면 네임스페이스 수준에서 기본값을 설정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default:
cpu: "500m"
defaultRequest:
cpu: "500m"
max:
cpu: "1"
min:
cpu: "100m"
type: Container
이 LimitRange는 다음을 설정한다:
- 기본 CPU 제한: 500m (0.5 CPU)
- 기본 CPU 요청: 500m (0.5 CPU)
- 최대 CPU 제한: 1 (1 CPU)
- 최소 CPU 요청: 100m (0.1 CPU)
메모리에 대한 LimitRange도 유사하게 설정할 수 있다.
주의할 점: LimitRange는 생성된 후에 새로 생성되는 파드에만 적용되며, 기존 파드에는 영향을 주지 않는다.
ResourceQuota로 네임스페이스 리소스 제한하기
ResourceQuota를 사용하면 네임스페이스 수준에서 총 리소스 사용량을 제한할 수 있다.
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "10"
limits.memory: 10Gi
이 ResourceQuota는 네임스페이스에서 다음을 제한한다.
- 모든 파드의 총 CPU 요청: 4 CPU
- 모든 파드의 총 메모리 요청: 4 GiB
- 모든 파드의 총 CPU 제한: 10 CPU
- 모든 파드의 총 메모리 제한: 10 GiB
8. 파드와 디플로이먼트 편집 방법
쿠버네티스에서 기존 리소스를 수정하는 것은 흔한 작업이지만, 모든 필드가 수정 가능한 것은 아니다. 특히 파드와 디플로이먼트의 편집에는 몇 가지 중요한 제약과 방법이 있다.
파드 편집 제약 사항
기존 파드의 경우 아래 명시된 필드만 수정할 수 있다:
spec.containers[*].image
spec.initContainers[*].image
spec.activeDeadlineSeconds
spec.tolerations
환경 변수, 서비스 계정, 리소스 제한 등 다른 필드는 실행 중인 파드에서 직접 수정할 수 없다.
파드 편집 방법
실행 중인 파드의 다른 필드를 수정해야 할 경우, 다음 두 가지 방법을 사용할 수 있다.
방법 1: kubectl edit 명령 사용
kubectl edit pod <pod 이름>
명령을 실행한다.1
kubectl edit pod webapp
이 명령어는 vi 에디터에서 파드 정의를 열어준다.
필요한 속성을 편집하고 저장하려고 하면, 편집 불가능한 필드를 수정했기 때문에 거부된다.
변경 사항이 포함된 파일 사본이 임시 위치에 저장된다.
- 기존 파드를 삭제한다.
1
kubectl delete pod webapp
- 임시 파일을 사용하여 변경 사항이 적용된 새 파드를 생성한다.
1
kubectl create -f /tmp/kubectl-edit-ccvrq.yaml
방법 2: YAML 파일로 추출 후 편집
- 파드 정의를 YAML 형식으로 파일에 추출한다.
1
kubectl get pod webapp -o yaml > my-new-pod.yaml
- 추출된 파일을 에디터로 편집한다.
1
vi my-new-pod.yaml
- 기존 파드를 삭제한다.
1
kubectl delete pod webapp
- 편집된 파일로 새 파드를 생성한다.
1
kubectl create -f my-new-pod.yaml
디플로이먼트 편집
디플로이먼트의 경우 파드 템플릿의 모든 필드/속성을 쉽게 편집할 수 있다. 파드 템플릿은 디플로이먼트 사양의 하위 항목이므로, 변경할 때마다 디플로이먼트는 자동으로 기존 파드를 삭제하고 변경 사항이 적용된 새 파드를 생성한다.
디플로이먼트의 일부인 파드 속성을 수정하려면 다음 명령만 실행하면 된다.
1
kubectl edit deployment my-deployment
이는 파드를 직접 편집하는 것보다 훨씬 간단하며, 변경 사항이 자동으로 롤아웃된다.
9. 데몬셋 (DaemonSets)
데몬셋의 개념
데몬셋(DaemonSet)은 레플리카셋과 유사하게 여러 파드 인스턴스를 배포하는 데 도움이 되는 객체다.
레플리카 셋과의 차이점은 아래와 같다.
- 레플리카셋은 클러스터 전체에 지정된 수의 파드 복제본을 배포한다.
- 데몬셋은 클러스터의 각 노드에 파드의 복제본을 하나씩만 배포한다.
데몬셋의 특징으로는
- 새 노드가 클러스터에 추가되면 해당 노드에 파드가 자동으로 추가된다.
- 노드가 클러스터에서 제거되면 해당 파드도 자동으로 제거된다.
- 데몬셋은 클러스터의 모든 노드에 항상 파드의 복제본이 하나씩 존재하도록 보장한다.
데몬셋의 사용 사례
데몬셋은 다음과 같은 상황에서 유용하다.
- 모니터링 에이전트: 각 노드에 모니터링 에이전트를 배포하여 클러스터를 더 효과적으로 모니터링할 수 있다.
- 로그 수집기: 클러스터의 모든 노드에서 로그를 수집하기 위해 로그 수집기를 배포해야 할 때 사용한다.
- kube-proxy: 쿠버네티스 아키텍처에서 각 노드에 필요한 kube-proxy 컴포넌트는 데몬셋으로 배포될 수 있다.
- 네트워킹 솔루션: Weave Net 같은 많은 네트워킹 솔루션은 클러스터의 각 노드에 에이전트를 배포해야 한다.
이러한 경우 클러스터에 변경이 있을 때 에이전트를 수동으로 추가하거나 제거할 필요가 없고, 데몬셋이 이를 자동으로 처리해준다.
데몬셋 정의 파일
데몬셋 정의 파일의 구조는 레플리카셋과 유사하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-daemon
spec:
selector:
matchLabels:
app: monitoring-agent
template:
metadata:
labels:
app: monitoring-agent
spec:
containers:
- name: monitoring-agent
image: monitoring-agent
주요 구성요소:
apiVersion
:apps/v1
kind
:DaemonSet
spec.selector
: 데몬셋이 관리할 파드를 식별하는 레이블 셀렉터spec.template
: 배포할 파드의 템플릿
데몬셋 관리 명령어
데몬셋을 관리하기 위한 기본 명령어
1
2
3
4
5
6
7
8
# 데몬셋 생성
kubectl create -f daemon-set-definition.yaml
# 데몬셋 조회
kubectl get daemonsets
# 데몬셋 상세 정보 확인
kubectl describe daemonset monitoring-daemon
데몬셋의 작동 방식
데몬셋이 각 노드에 파드를 스케줄링하는 방식은 쿠버네티스 버전에 따라 다르다
- 쿠버네티스 1.12 이전:
- 데몬셋은 파드의
nodeName
속성을 설정하여 스케줄러를 우회하고 직접 노드에 파드를 배치했다. - 각 파드가 생성되기 전에 사양에
nodeName
속성을 설정하여 특정 노드에 자동으로 배치되게 했다.
- 데몬셋은 파드의
- 쿠버네티스 1.12 이후:
- 데몬셋은 기본 스케줄러와 노드 어피니티 규칙을 사용하여 파드를 노드에 배치한다.
- 이 방식은 더 표준화되어 있고 스케줄러의 기능을 활용할 수 있다.
11. 정적 파드 (Static Pods)
정적 파드의 개념
정적 파드(Static Pods)는 API 서버의 개입 없이 kubelet에 의해 직접 관리되는 파드다.
쿠버네티스 클러스터의 다른 구성 요소(kube-apiserver, kube-scheduler, etcd 등)가 없어도 kubelet이 독립적으로 노드를 관리할 수 있게 해준다.
정적 파드의 특징은 아래와 같다.
- API 서버의 개입 없이 특정 노드의 kubelet에 의해 직접 생성 및 관리된다.
- 노드에서 도커와 kubelet만 실행 중이면 된다.
- 파드 정의 파일을 특정 디렉토리에 배치하면 kubelet이 해당 파드를 생성한다.
정적 파드의 작동 방식
정적 파드의 작동 과정은 다음과 같다.
- kubelet이 특정 디렉토리에서 파드 정의 파일을 찾도록 구성한다.
- 해당 디렉토리에 파드 정의 파일을 배치한다.
- kubelet은 주기적으로 이 디렉토리를 확인하고, 파일을 읽어 파드를 생성한다.
- kubelet은 파드가 실행 중인 상태를 유지한다.
- 애플리케이션이 충돌하면 kubelet이 재시작을 시도한다.
- 파일이 변경되면 kubelet이 파드를 재생성한다.
- 파일이 디렉토리에서 제거되면 파드도 자동으로 삭제된다.
정적 파드 구성 방법
kubelet이 정적 파드 정의 파일을 찾는 디렉토리는 두 가지 방법으로 구성할 수 있다.
- 직접 경로 지정 kubelet 서비스 파일의
--pod-manifest-path
옵션을 사용한다.1
--pod-manifest-path=/etc/kubernetes/manifests
- 구성 파일 사용 kubelet 서비스 파일의
--config
옵션을 통해 구성 파일 경로를 지정하고, 해당 파일 내에staticPodPath
옵션을 설정한다.1
--config=<구성파일경로>
구성 파일 내부:
1
staticPodPath: /etc/kubernetes/manifests
kubeadm으로 설정된 클러스터는 주로 두 번째 방법을 사용한다.
정적 파드 확인 방법
정적 파드는 다음과 같은 방법으로 확인할 수 있다.
kubelet만 실행 중인 환경: API 서버가 없는 경우
docker ps
명령을 사용하여 컨테이너 상태를 확인한다.클러스터 환경:
- kubelet은 정적 파드를 생성할 때 API 서버에 미러 객체를 생성한다.
kubectl get pods
명령을 통해 정적 파드도 볼 수 있다.- 정적 파드 이름에는 자동으로 노드 이름이 추가된다 (예:
pod-name-node01
) - API 서버를 통해 볼 수 있는 미러 객체는 읽기 전용으로, 편집하거나 삭제할 수 없다.
- 정적 파드를 삭제하려면 노드의 매니페스트 디렉토리에서 해당 파일을 제거해야 한다.
정적 파드의 활용 사례
정적 파드의 가장 중요한 활용 사례는 쿠버네티스 컨트롤 플레인 구성 요소 자체를 배포하는 것이다.
- 마스터 노드에 kubelet 설치
- API 서버, 컨트롤러, etcd 등의 컨트롤 플레인 구성 요소를 위한 파드 정의 파일 생성
- 이 파일들을 지정된 매니페스트 디렉토리에 배치
- kubelet이 컨트롤 플레인 구성 요소를 파드로 배포하고 관리
이 방식의 장점:
- 바이너리를 직접 다운로드하거나 서비스를 구성할 필요가 없다.
- 서비스가 충돌하면 kubelet이 자동으로 재시작한다.
- kubeadm 도구가 클러스터를 설정하는 방식이 바로 이것이다.
정적 파드 vs 데몬셋
정적 파드와 데몬셋의 차이점:
정적 파드 | 데몬셋 |
---|---|
kubelet에 의해 직접 생성 | DaemonSet 컨트롤러에 의해 생성 |
API 서버, 컨트롤러 매니저 등의 개입 없음 | kube-apiserver를 통해 관리됨 |
단일 노드에 국한됨 | 모든 (또는 선택된) 노드에 배포 |
노드의 매니페스트 디렉토리를 통해 생성 | API를 통해 생성 |
컨트롤 플레인 구성 요소로 주로 사용됨 | 클러스터 전체 데몬으로 주로 사용됨 |
두 가지 모두 kube-scheduler에 의해 무시된다.
12. 다중 스케줄러 (Multiple Schedulers)
다중 스케줄러의 필요성
쿠버네티스는 기본 스케줄러를 통해 파드를 노드에 배치하는 알고리즘을 제공한다. 이 알고리즘은 테인트와 톨러레이션, 노드 어피니티 등 여러 조건을 고려한다.
다음과 같은 상황에서는 커스텀 스케줄러가 필요할 수 있다.
- 특정 애플리케이션이 추가적인 검사나 특별한 알고리즘을 통해 노드에 배치되어야 할 때
- 기본 스케줄러의 알고리즘이 특정 워크로드의 요구사항을 충족시키지 못할 때
- 특정 파드만 다른 방식으로 스케줄링하고 싶을 때
쿠버네티스는 자체 스케줄러 프로그램을 작성하고 배포할 수 있다. 다중 스케줄러를 사용하면 일반 애플리케이션은 기본 스케줄러를 통해, 특정 애플리케이션은 커스텀 스케줄러를 통해 배치할 수 있다.
다중 스케줄러 구성 방법
다중 스케줄러를 구성하기 위해서는 먼저 스케줄러마다 고유한 이름이 필요하다.
스케줄러 구성 파일
스케줄러 이름은 kube-scheduler 구성 파일에서 설정한다.
1
2
3
4
5
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
metadata:
name: my-custom-scheduler
schedulerName: my-custom-scheduler
기본 스케줄러는 이름을 별도로 지정하지 않아도 ‘default-scheduler’라는 이름이 자동으로 설정된다.
커스텀 스케줄러 배포 방법
커스텀 스케줄러는 여러 방법으로 배포할 수 있다.
1. 바이너리 실행
가장 단순한 방법으로, 기존 kube-scheduler 바이너리나 직접 개발한 바이너리를 사용하여 서비스로 실행한다.
1
kube-scheduler --config=/path/to/custom-scheduler-config.yaml
2. 파드로 배포
스케줄러를 파드로 배포하는 방법
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: my-custom-scheduler
namespace: kube-system
spec:
containers:
- name: my-custom-scheduler
image: k8s.gcr.io/kube-scheduler:v1.19.0
command:
- kube-scheduler
- --config=/etc/kubernetes/my-scheduler-config.yaml
3. 디플로이먼트로 배포
스케줄러를 디플로이먼트로 배포하면 관리가 더 용이하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-custom-scheduler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: my-custom-scheduler
template:
metadata:
labels:
app: my-custom-scheduler
spec:
containers:
- name: my-custom-scheduler
image: k8s.gcr.io/kube-scheduler:v1.19.0
command:
- kube-scheduler
- --config=/etc/kubernetes/my-scheduler-config.yaml
leader-elect 옵션
고가용성 설정에서 여러 마스터 노드에 동일한 스케줄러의 여러 인스턴스가 실행될 경우, leader-elect
옵션이 중요하다.
--leader-elect=true
: 여러 인스턴스 중 하나만 활성화되어 리더 역할을 한다 (HA 환경에서 사용)--leader-elect=false
: 단일 인스턴스만 실행될 때 사용
구성 파일 전달 방법
스케줄러에 구성 파일을 전달하는 방법에는 두 가지가 있다.
볼륨 마운트: 로컬에 구성 파일을 만들고 볼륨으로 마운트
ConfigMap 사용: 구성을 ConfigMap으로 만들고 볼륨으로 마운트
1 2 3 4
volumes: - name: scheduler-config configMap: name: my-scheduler-config
커스텀 스케줄러 사용하기
파드나 디플로이먼트가 커스텀 스케줄러를 사용하도록 설정하려면, 정의 파일에 schedulerName
필드를 추가한다.
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: custom-scheduled-pod
spec:
schedulerName: my-custom-scheduler
containers:
- name: container
image: nginx
이 설정을 통해 해당 파드는 지정된 커스텀 스케줄러에 의해 스케줄링된다.
스케줄러 작동 확인
커스텀 스케줄러가 올바르게 작동하는지 확인하는 방법
- 파드 상태 확인: 파드가 ‘Running’ 상태가 되었는지 확인
1
kubectl get pods
- 이벤트 확인: 어떤 스케줄러가 파드를 스케줄링했는지 확인
1
kubectl get events -o wide
이벤트 목록에서 ‘Successfully assigned’ 메시지와 스케줄러 이름을 확인할 수 있다.
- 스케줄러 로그 확인: 문제가 발생했을 경우 스케줄러 로그 확인
1
kubectl logs -n kube-system my-custom-scheduler
다중 스케줄러를 사용하면 서로 다른 워크로드에 대해 다른 스케줄링 전략을 적용할 수 있어 클러스터 리소스를 더 효율적으로 활용할 수 있다.
13. 스케줄러 프로파일 (Scheduler Profiles)
스케줄러 프레임워크
쿠버네티스 스케줄러는 여러 단계를 거쳐 파드를 노드에 배치한다.
- 스케줄링 큐(Scheduling Queue): 파드가 처음 생성되면 스케줄링 큐에 들어간다. 이 단계에서 파드는 우선순위에 따라 정렬된다.
- 필터링 단계(Filtering): 파드를 실행할 수 없는 노드들을 필터링한다. 예를 들어 리소스 요구사항을 충족하지 못하는 노드는 제외된다.
- 점수 부여 단계(Scoring): 필터링 후 남은 노드들에 점수를 부여한다. 여러 요소를 고려하여 가장 적합한 노드를 선택한다.
- 바인딩 단계(Binding): 가장 높은 점수를 받은 노드에 파드를 바인딩한다.
스케줄러 플러그인 시스템
각 단계에서는 다양한 플러그인이 동작한다.
- 스케줄링 큐 단계:
- PrioritySort 플러그인: 파드를 우선순위에 따라 정렬한다.
- 필터링 단계:
- NodeResourcesFit 플러그인: 파드의 리소스 요구사항을 충족하는 노드만 선택한다.
- NodeName 플러그인: 파드에
nodeName
이 지정된 경우 해당 노드만 선택한다. - NodeUnschedulable 플러그인:
unschedulable
플래그가 설정된 노드를 필터링한다.
- 점수 부여 단계:
- NodeResourcesFit 플러그인: 노드의 가용 리소스에 따라 점수를 부여한다.
- ImageLocality 플러그인: 파드가 사용하는 컨테이너 이미지가 이미 있는 노드에 높은 점수를 부여한다.
- 바인딩 단계:
- DefaultBinder 플러그인: 선택된 노드에 파드를 바인딩한다.
확장 포인트(Extension Points)
스케줄러는 다양한 확장 포인트를 제공하여 사용자 정의 플러그인을 연결할 수 있다.
- QueueSort: 스케줄링 큐에서 파드 정렬 방식 결정
- PreFilter/Filter/PostFilter: 필터링 단계 전/중/후
- PreScore/Score: 점수 부여 단계 전/중
- Reserve: 리소스 예약 단계
- Permit: 스케줄링 허용 여부 결정
- PreBind/Bind/PostBind: 바인딩 단계 전/중/후
이러한 확장 포인트에 플러그인을 연결하여 스케줄링 동작을 사용자 정의할 수 있다.
다중 스케줄러 프로파일
쿠버네티스 1.18부터는 단일 스케줄러 내에서 여러 스케줄러 프로파일을 지원한다. 이전에는 여러 스케줄러를 구성하려면 각각 별도의 바이너리를 실행해야 했다.
이전 방식의 문제점
- 여러 개의 독립적인 프로세스 관리 필요
- 스케줄러 간 경쟁 조건(race condition) 발생 가능
- 한 스케줄러가 다른 스케줄러의 결정을 알지 못함
스케줄러 프로파일 접근 방식
스케줄러 프로파일을 사용하면 단일 스케줄러 바이너리에서 여러 스케줄러를 구성할 수 있다:
1
2
3
4
5
6
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
- schedulerName: my-scheduler
- schedulerName: my-scheduler-2
각 프로파일은 별도의 스케줄러처럼 작동하지만 하나의 프로세스에서 실행되므로 경쟁 조건을 방지할 수 있다.
프로파일별 플러그인 구성
각 프로파일에 대해 서로 다른 플러그인 세트를 구성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
- schedulerName: my-scheduler-2
plugins:
filter:
disabled:
- name: TaintToleration
score:
enabled:
- name: MyCustomPlugin
weight: 5
- schedulerName: my-scheduler-3
plugins:
preScore:
disabled:
- name: '*'
score:
disabled:
- name: '*'
위 예시에서
my-scheduler-2
는TaintToleration
플러그인을 비활성화하고 사용자 정의 플러그인을 추가한다.my-scheduler-3
는 모든 preScore 및 score 플러그인을 비활성화한다.
파드에 스케줄러 프로파일 지정
파드 정의에서 schedulerName
필드를 통해 특정 스케줄러 프로파일을 지정할 수 있다.
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: custom-scheduled-pod
spec:
schedulerName: my-scheduler-2
containers:
- name: container
image: nginx
이렇게 하면 해당 파드는 my-scheduler-2
프로파일의 규칙에 따라 스케줄링된다.
스케줄러 프로파일을 사용하면 다양한 워크로드에 맞게 서로 다른 스케줄링 전략을 적용하면서도 단일 스케줄러 인스턴스만 관리하면 되는 장점이 있다.