1️⃣ 포드의 자원 사용량 제한
쿠버네티스와 같은 컨테이너 오케스트레이션 툴에서 중요한 것 중 하나는 바로 클러스터 내부에서 컴퓨팅 자원 활용륭(utilization)을 늘리는 것이다.
🪜 Limits와 Requests
- Limits: 해당 포드의 컨테이너가 최대로 사용할 수 있는 자원의 상한선
- Requests: 적어도 이 만큼의 자원은 컨테이너에게 보장돼야 한다는 것 의미
- 오버커밋(Overcommit)
- Requests보다 많은 자원을 사용하는 것
- 한정된 컴퓨팅 자원을 효율적으로 사용하기 위한 방법으로, 사용할 수 있는 자원보다 더 많은 양을 가상 머신이나 컨테이너에 할당함으로써 전체 자원의 사용률을 눂이는 방법이다.
- 오버커밋을 통해 실제 물리 자원보다 더 많은 양의 자원을 할당하는 기능을 제공한다.
- 최소한 Requests만큼의 자원 사용이 보장되지만, 유휴 자원이 있다면 Limits만큼 사용할 수 있다.
- 포드를 할당할 때 사용되는 자원 할당 기준은 Limits가 아닌 Requests이다.
- 컨테이너 A가 requests보다 많은 자원을 사용하고 있는데, 컨테이너 B가 requests만큼 자원을 사용하려고 시도하면 컨테이너 A에 스로틀(throttle)이 발생한다. → 컨테이너 B가 requests만큼 자원 사용이 가능해진다.
🐳 메모리 자원 사용량 제한 원리
- 쿠버네티스는 가용 메모리를 확보하기 위해 우선순위가 낮은 포드 또는 프로세스를 강제로 종료하도록 설계되어 있다.
- (중요!) 노드에 메모리 자원이 부족해지면 어떤 포드나 프로세스가 먼저 종료되어야 하는가?!
- Limits와 Requests 값에 따라ㅏ 내부적으로 우선순위 계산
- 우선순위를 구분하기 위해 3가지 종류의 QoS(Quality Of Service) 클래스를 명시적으로 포드에 설정
- 노드의 이상 상태 정보를 의미하는 Conditions에는 MemoryPressure, DiskPressure 등이 있다.
- MemoryPressure 값이 True가 되면 가용 메모리가 부족하다는 의미로, 쿠버네티스가 해당 노드에서 실행 중이던 모든 포드에 대해 순위를 매긴 다음 가장 우선순위가 낮은 포드를 다른 노드로 퇴거(Evict)시킨다.
- 이때 포드의 우선순위는 QoS 클래스 및 메모리 사용량에 따라 정렬되어 매겨진다.
- MemoryPressure 상태를 감지하기 전에 급작스럽게 메모리 사용량이 많아질 경우, OOM(Out Of Memory) Killer라는 기능이 우선순위 점수가 낮은 컨테이너의 프로세스를 강제로 종료해 가능한 메모리를 확보할 수도 있다.
- 프로세스가 메모리를 얼마나 더 많이 사용하고 있는지에 따라 프로세스의 최종 OOM 점수가 갱신된다.
- OOM 킬러에 의해 포드 컨테이너의 프로세스가 종료되면 해당 컨테이너는 포드의 재시작 정책에 의해 다시 시작된다.
👑 QoS 클래스
- Guaranteed 클래스
- 포드의 컨테이너에 설정된 Limits와 Requests 값이 완전히 동일할 때 부여되는 클래스
- 자원의 오버커밋이 허용되지 않기 때문에 할당 받은 자원의 사용을 안정적으로 보장 받을 수 있다.
- BestEffort 클래스
- Requests와 Limits를 아예 설정하지 않은 포드에 설정되는 클래스
- 노드에 유휴 자원이 있다면 제한 없이 모든 자원을 사용할 수 있다.
- 그러나 보장 받을 수 있는 자원이 존재하지 않는다.
- 즉, 모든 자원을 사용할 수도 있지만 자원을 전혀 사용하지 못할 수도 있다.
- Burstable 클래스
- Limits의 값이 Requests보다 큰 포드를 의미한다.
- Requests에 지정된 자원만큼 사용을 보장받을 수 있지만, 상황에 따라서는 Limits까지 자원을 사용할 수도 있다.
- 필요에 따라 자원의 한계를 확장해 사용할 수 있는 포드
- 다른 포드와 자원 경합이 발생할 수 있는데, 이때 requests보다 더 많은 자원을 사용하고 있는 포드나 프로세스의 우선순위가 더 낮게 설정된다.
→ 포드의 우선순위는 Guaranteed → Burstable → BestEffort
이다.
→ 포드가 메모리를 많이 사용할수록 우선순위가 낮아진다.
🌙 ResourceQuota와 LimitRange
- 쿠버네티스를 여러 사람 또는 개발팀이 함께 사용하고 있다면 각 네임스페이스에서 할당할 수 있는 자원의 최대 한도 또는 범위를 설정할 필요가 있다.
- 이를 위해 ResourceQuota와 LimitRange라는 오브젝트를 이용해 자원 사용량을 관리할 수 있는 기능을 제공한다.
- ResourceQuota
- 특정 네임스페이스에서 사용할 수 있는 자원 사용량의 합을 제한하는 쿠버네티스 오브젝트
- 네임스페이스에서 할당할 수 있는 자원의 총합 또는 네임스페이스에서 생성할 수 있는 리소스의 개수를 제한
- LimitRange
- 특정 네임스페이스에서 할당되는 자원의 범위 또는 기본값을 지정할 수 있는 쿠버네티스 오브젝트
- 포드의 컨테이너에 CPU나 메모리 할당량이 설정돼 있지 않은 경우, 컨테이너에 자동으로 기본 Requests 또는 Limits 값을 설정할 수 있다.
- 포드 또는 컨테이너의 CPU, 메모리, 퍼시스턴트 볼륨 클레임 스토리지 크기의 최솟값/최댓값을 설정할 수 있다.
- Admission Controller
- 사용자의 API 요청이 적절한지 검증하고, 필요에 따라 API 요청을 변형하는 단계
- ResourceQuota와 LimitRange는 Admission Controller의 한 종류
- ResourceQuota와 LimitRange의 원리
- 사용자가 API 서버에 kubectl 명령으로 요청을 전송
- x509, 서비스 어카운트 등을 통해 인증 단계를 거친다.
- 롤, 클러스터 롤 등을 통해 인가 단계를 거친다.
- 어드미션 컨트롤러인 ResourceQuota는 해당 포드의 자원 할당 요청이 적절한지 검증한다.
- 해당 API 요청에 포함된 포드 데이터에 자원 할당이 설정되지 않은 경우 LimitRange는 포드 데이터에 CPU 및 메모리 할당의 기본값을 추가함으로써 원래의 포드 생성 API의 데이터를 변형한다.
2️⃣ 쿠버네티스 스케줄링
- 컨테이너나 가상 머신과 같은 인스턴스를 새롭게 생성할 때, 그 인스턴스를 어느 서버에 생성할 것일지 결정하는 일
- 최종적으로 포드 생성이 승인되면 쿠버네티스는 해당 포드를 워커 노드 중 한 곳에 생성하는데, 이 단계에서 스케줄링이 수행된다.
- etcd
- 분산 코디네이터라고 불리는 도구의 일종으로, 클라우드 플랫폼 등의 환경에서 여러 컴포넌트가 정상적으로 상호작용할 수 있도록 데이터를 조정하는 역할을 담당한다.
- 쿠버네티스는 클러스터 운용에 필요한 정보를 분산 코디네이터인 etcd에 저장한다.
- etcd에 저장된 포드의 데이터에는 해당 포드가 어느 워커 노드에서 실행되는지 나타내는 nodeName 항목이 존재한다.
- 스케줄러는 nodeName이 설정되지 않은 해당 포드를 스케줄링 대상으로 판단하고, 포드를 할당할 적절한 노드를 선택한 다음 API 서버에게 해당 노드와 포드를 바인딩할 것을 요청한다.
- 그러고 나면 포드의 nodeName 값에는 선택된 노드의 이름이 설정된다.
- nodeName이 설정되면 해당 nodeName에 해당하는 노드의 kubelet이 컨테이너 런타임을 통해 포드를 생성한다.
- (중요!) 스케줄러가 적절한 노드를 어떻게 선택하느냐
- 노드 필터링: 포드를 할당할 수 있는 노드와 그렇지 않은 노드를 분리해 걸러내는 단계
- 노드 스코어링: 쿠버네티스의 소스코드에 미리 정의된 알고리즘의 가중치에 따라서 노드의 점수를 계산
📅 NodeSelector와 Node Affinity, Pod Affinity
- NodeSelector
- 포드의 YAML 파일에 노드의 이름을 직접 명시
- Node Affinity
- 반드시 충족해야 하는 조건과 선호하는 조건을 별도로 정의할 수 있다.
**required**DuringSchedulingIgnoredDuringExecution
**preferred**DuringSchedulingIgnoredDuringExecution
- Pod Affinity
- 특정 조건을 만족하는 포드와 함께 실행되도록 스케줄링한다.
- Pod Anti-affinity
- 특정 포드와 같은 토폴로지의 노드를 선택하지 않는 방법
🎨 Taints와 Toleration
- Taints
- 특정 노드에 얼룩을 지정함으로써 해당 노드에 포득 ㅏ할당되는 것을 막는 기능
- NoSchedule, PreferNoSchedule, NoExecute의 효과가 있다.
- Toleration
- Taints가 설정된 노드에도 포드를 할당할 수 있다.
- tolerationSeconds 옵션을 추가할 수 있다. 특정 시간 동안 해당 Taint를 용인하겠다는 뜻이다.
💧 Cordon, Drain, PodDistributionBudget
- cordon
- 해당 명령어로 지정된 노드는 새로운 포드가 할당되지 않는다.
- drain
- 해당 노드에 스케줄링을 금지하며, 노드에서 기존에 실행 중이던 포드를 다른 노드로 옮겨가도록 퇴거를 수행한다.
- PodDistributionBudget
- drain 명령어 등으로 인해 포드에 퇴거가 발생할 때, 특정 개수 또는 비율만큼의 포드는 반드시 정상적인 상태를 유지하기 위해서 사용된다.
3️⃣ 쿠버네티스 애플리케이션 상태와 배포
🥐 디플로이먼트를 통해 롤링 업데이트
- 레플리카셋의 변경 사항을 저장하는 리비전을 디플로이먼트에서 관리함으로써 애플리케이션의 배포를 쉽게 할 수 있다.
- 쿠버네티스는 포드를 조금씩 삭제하고 생성하는 롤링 업데이트 기능을 제공한다.
- 롤링 업데이트를 사용하면 디플로이먼트를 업데이트하는 도중에도 사용자의 요청을 처리할 수 있는 포드가 계속 존재하기 때문에 애플리케이션의 중단이 발생하지 않는다.
- 블루 그린 배포: 기존 버전의 포드를 그대로 놔둔 상테에서 새로운 버전의 포드를 미리 생성해 둔 뒤 서비스의 라우팅만 변경하는 배포 방식. 롤링 업데이트와 달리 특정 순간에 두 버전의 애플리케이션이 공존하지 않는다.
🍼 포드의 생애 주기
Pending
: 포드를 생성하는 요청이 API 서버에 의해 승인됐지만, 어떠한 이유로 인해 아직 실제로 생성되지 않은 상태
Running
: 포드에 포함된 컨테이너들이 모두 생성돼 포드가 정상적으로 실행된 상태
Completed
: 포드가 정상적으로 실행돼 종료됨
Error
: 포드가 정상적으로 실행되지 않은 상태로 종료됨
Terminating
: 포드가 삭제 또는 퇴거되기 위해 삭제 상태에 머물러 있는 경우
- restartPolicy: Always, Never, OnFailure로 설정 가능
🏃♀️ Running 상태가 되기 위한 조건
- Init Container
- 포드의 컨테이너 내부에서 애플리케이션이 실행되기 전에 초기화를 수행하는 컨테이너
- postStart
- 포드의 컨테이너가 실행될 때 특정 작업을 수행하도록 라이프사이클 훅을 정의할 수 있다.
- 두 가지 방식으로 사용 가능
- HTTP → 특정 주소로 HTTP 요청을 전송
- Exec → 컨테이너 내부에서 특정 명령어 실행
- 애플리케이션 상태 검사
- livenessProbe: 컨테이너 내부의 애플리케이션이 살아있는지 검사. 검사에 실패할 경우 해당 컨테이너는 restartPolicy에 따라서 재시작된다.
- readinessProbe: 컨테이너내부의 애플리케이션이 사용자 요청을 처리할 준비가 됐는지 검사. 검사에 실패할 경우 컨테이너는 서비스의 라우팅 대상에서 제외된다.
➕ HPA를 활용한 오토스케일링
- 리소스 사용량에 따라 디플로이먼트의 포드 개수를 자동으로 조절하는 HPA(Horizontal Pod Autoscaler)라는 기능을 제공한다.
[출처] 시작하세요! 도커/쿠버네티스 (용찬호 지음)