Home 6. kubernetes deploy Options
Post
Cancel

6. kubernetes deploy Options

kubernetes depoloy

Kubernetes 배포 시 설정사항

자원을 할당하는 방법, 활용률을 높이는 Overcommit, ResourceQuota, LimitRange

Pod 자원 사용량 제한 : Limits and Requests

스케일 아웃(Scale-out)만큼 중요한 작업이 컴퓨팅 자원 활용률(Utilization)을 늘리는 것! 서버 클러스터에서 자원을 얼마나 효율적으로, 빠짐없이 사용하고 있는지를 의미한다. spec.containers.resources.limits 항목은 해당 포드의 컨테이너가 최대로 사용할 수 있는 자원의 상한선을 의미한다.

Requests는 적어도 이정도의 자원은 컨테이너에게 보장되어야 한다는 것을 의미한다. -> 자원의 Overcommit을 가능하게 만든다. 이때의 Overcommit은 정해진 자원중에서, 할당받은 양보다 더 많은 자원을 사용하는 것을 의미한다. 따라서 requests 는 최소 요구량, limit은 최대 한계치라고 이해하면 될 것 같다. -> 결국 포드를 할당할때 사용되는 할당 기준은 최소 요구량인 requests 이다..!

CPU Requests and Limits

쿠버네티스에서는 CPU를 m(밀리코어) 단위로 제한한다. –cpu-shares(Request)는 할당 비율에 따라 자원 할당이 결정된다. –cpus(Limits) 과 함께 사용한다면 overcommit 을 적용할 수 있다. Requests 범위를 넘어 자원을 사용하게 되면 CPU throttle이 발생하지만, 일시적으로 내부 프로세스에 걸리는 현상이라 컨테이너 자체에는 큰 문제가 발생하지 않는다.

QoS 클래스와 메모리 자원 사용량 제한

메모리는 CPU와 다르게 압축 불가능한 자원이다. 따라서 메모리가 부족한 경우(MemoryPressure = true)

  • 1) 우선순위가 낮은 포드는 다른 노드로 퇴거(Evict), 또는 프로세스는 강제로 종료되게 된다.
  • 2) 급작스럽게 메모리 사용량이 많아지면 OOM(Out Of Memory) 값이 높은 순서대로 프로세스가 종료된다.

그렇다면, 메모리 자원이 부족해 지는 경우, 어떤 포드를 먼저 종료해야 하는가? 에 대한 의문이 발생할 수 있다. 이때 cpu와 동일하게 Limits, Request를 사용하지만, 이를 위해 내부적으로 우선순위를 구분하기 위한 세가지 QoS(Quality Of Service)클래스를 명시적으로 포드에 설정한다.

1
2
3
4
5
6
7
8
9
10
1) Guaranteed class
- 포드의 컨데이너에 설정된 Limits와 Requests값이 완전히 동일할 때 부여되는 클래스이다.
2) BestEffort
- Request와 Limits을 아에 설정하지 않은 포드에 설정되는 클래스. 유휴자원이 있다면 제한 없이 모든 자원을 사용할 수 있다.
3) Burstable 
- Guaranteed나 BestEffort에 속하지 않는 모든 포드가 분류되는 클래스를 의미한다. Requests 내에서 자원을 사용하면 문제가 없지만, 
Requests를 넘어 Limits를 사용하려고 시도한다면, 다른 포드와 자원경합이 발생할 수 있다.

기본적으로 우선순위는 Guaranteed > Burstable > BestEffort 순이다. 하지만 Burstable과 BestEffort는 현재 메모리가 얼마나 사용
되고 있는지에 따라 우선순위가 변결될 수 도 있다.

메모리 사용량이 갑자기 높아진다면 리눅스의 OOM Killer는 OOM 점수가 가장 낮은 컨테이너의 프로세스를 강제로 종료한다. 하지만 OOM Killer에 의해 포드의 컨테이너의 프로세스가 종료된다면 해당 컨테이너는 포드의 재시작 정책(restart Policy) 에 의해 다시 시작된다.

오버커밋이 반드시 좋은것만은 아니다. 어플리케이션의 안정성을 위해 단순히 모든 포드의 Limits와 Requests를 동일하게 설정함으로서 Guaranteed 클래스로 보장받을 수 있게 생성하는 것도 해답이 될 수 있다.

ResourceQuota 와 LimitRange

여러 개발팀이 k8s에서 개발, 테스트를 진행하기 위해서는 다음과 같은 방법이 있을 수 있다.

1
2
3
4
5
6
1) k8s cluster를 하나씩 제공한다. : 이 방법은 관리가 힘들고 비용이 높아진다.
2) namespace를 개발팀마다 생성한 뒤, 개발팀이 해당 namespace에서만 쿠버네티스를 사용할 수 있도록 RBAC(Role-Based Access Control)을 
설정한다. : 다른 네임스페이스에서 자원이 부족해 질 수 있고 이를 컨트롤 할 방법이 없다.
3) ResourceQuota 와 LimitRange라는 오브젝트를 사용한다.
    - ResourceQuota는 네임스페이스의 자원 사용량을 제한한다.
    - LimitRange는 자원 할당의 기본값이나 범위를 설정한다.

ResourceQuota

특정 네임스페이스에서 사용할 수 있는 자원 사용량의 합을 제한하는 쿠버네티스 오브젝트. - 네임스페이스에서 할당할 수 있는 자원(CPU,메모리,퍼시스턴스 볼륨 클레임의 크기,컨테이너 내부 스토리지)의 합을 제한 - 네임스페이스에서 생성할 수 있는 리소스(서비스, 디플로이먼트)의 개수를 제한 - 디플로이먼트, 포드, 시크릿, 컨피그맵, PVC 등의 개수 - NodePort 타입의 서비스 개수, LoadBalancer 타입의 서비스 개수 - Qos 클래스 중에서 BestEffort 클래스에 속하는 포드의 개수

LimitRange

특정 네임스페이스에서 할당되는 자원의 범위 또는 기본값을 지정할 수 있는 쿠버네티스 오브젝트. - 포드의 컨테이너에 CPU나 메모리 할당량이 설정돼 있지 않은 경우, 컨테이너에 자동으로 기본 Requests(:defaultRequest) 또는 Limits(:default) 값을 설정할 수 있다. - 포드 또는 컨테이너의 CPU, Memory, PVC의 최대/최소값을 설정할 수 있다.

Admission Controller

ResourceQuota와 LimitRange는 Admission Controller 에 의해 조작된다. kubectl 명령어를 통해 쿠버네티스 API 서버에 요청을 보낼 떄, 인증과 인가 외에 Admission Controller 라는 단계가 있었다. 간단하게 설명하자면, 사용자의 API 요청이 적절한지를 검증하고, 필요에 따라 API요청을 변형하는 단계라고 할 수 있다.

1
2
3
4
5
6
step 1) 사용자가 kubectl 명령어로 API 서버에 요청을 날린다.
step 2) x509 인증서, serviceAccount 등을 통해 인증 단계를 서친다.
step 3) role, cluster-role 등을 통해 인가 단계를 거친다.
step 4) ResourceQuota(Admission Controller) 는 해당 자원할당이 적절한지 검증(Validating)한다. 
step 5) 해당 API 요청에 포함된 포드 데이터에 자원할당이 설정되지 않은 경우, LimitRange(Admission Controller) 는 포드 데이터에 자원할당 기본값을 
        추가하고, 원래의 포드 생성 API의 데이터를 변형한다.

Kubernetes Scheduling

여기서의 Scheduling이란, 인스턴스를 새롭게 생성할 때, 그 인스턴스를 특성 목적에 따라 가장 합리적으로, 어느 서버에 생성할 것인지 결정하는 일을 의미한다.

스케줄링의 과정

스케줄링이 어떤 과정속에서 이루어지는 지를 보면 다음과 같다.

1
2
3
4
5
6
7
8
9
step 1) 사용자가 kubectl로 API서버로 포드 생성 요청을 날린다.
step 2) ServiecAccount,  RoleBinding 등을 이용해 포드 생성을 요청한 사용자의 인증, 인가 작업을 수행한다.
step 3) ResourceQuota, LimitRange 같은 Admission Contoller 가 해당 요청을 적절히 변형/검증 한다.
step 4) Admission Controller의 검증을 통과해 포드 생성이 승인되면, k8s는 해당 포드를 워커 노드 중 한곳에 생성한다.
    - 이때 k8s의 스케줄링이 수행된다. kube-system namespace의 컴포넌트(kube-scheduler & etcd) 가 담당한다. 
    - ectd(분산코디네이터) : 쿠버네티스 클러스터의 전반적인 상태 데이터(포드 목록정보, 클러스터 자체의 정보)를 저장하는 일종의 데이터베이스 역할을 담당
                        이떄 ectd에는 nodeName이 설정되지 않은 상태로 저장됨
    - kube-scheduler : 쿠버네티스 스케줄러에 해당. nodeName이 할당되지 않은 포드를 스케줄링 대상으로 판단하고 적절한 노드를 선택해 API 서버에 해당 노드
                        와 포드를 바인딩 할 것을 요청한다.

이때, 핵심은 scheduler가 적절한 노드를 어떻게? 선택할 것인가 에 대한 부분이다.

  • 노드 필터링: 포드를 할당할 수 있는 노드와 그렇지 않은 노드를 분리해 걸러내는 과정.
  • 노드 스코어링: 도커 이미지 존재여부, 가용자원 용량 등을 자체적인 알고리즘으로 점수화 해 반영한다. 내장된 로직의므로 사용자가 수정하는 경우는 많지 않다.

Schedule: nodeName, nodeSelector

가장 확실한 방법은 특정 워커노드의 이름(nodeName)을 포드에 명시하는 것이다. 하지만 이런 경우, 노드의 이름을 고정으로 설정했기 때문에 다른 환경에서 이 YAML파일을 보편적 으로 사용하기가 힘들다. 다른 방법으로는 노드의 라벨(Label)을 사용하는 방법이다. 그리고 이 라벨(: mylabel/disk=ssd)을 선택하는 nodeSelector를 선언해준다.

Default Label : node OS, CPU architecture, Hostname …

Schedule: Node Affinity

nodeSelector에서 좀더 발전된 방법으로, 반드시 만족해야 하는 조건(Hard: requiredDuringSchedulingIngnoredDuringExecution) 과 선호하는 조건 (Soft: preferredDuringSchedulingIngnoredDuringExecution) 을 설정할 수 있다. 포드가 할당될떄만 유요하다. 추후 라벨이 변경되어도 Eviction(퇴거)는 발생하지 않는다.

  • requiredDuringSchedulingIngnoredDuringExecution : operator 옵션이 In, NotIn, Gt(큼), Lt(작음) 등 다양하게 있으며, 이를 반드시 만족하는 조건중 하나를 선택한다.
  • preferredDuringSchedulingIngnoredDuringExecution : 반드시 만족해야 할 필요는 없으며, 해당 조건을 만족하는 노드가 있다면 좀더 선호하겠다는 뜻.

Schedule: Pode Affinity

Pod Affinity는 특정 조건을 만족하는 포드와 함께 실행되도록 스케줄링 하는 것이다. 토폴로지(: topologyKey)에 속한 포드 중 조건에 가장 부합하는 하나를 선택하게 하므로, 응답시간을 줄이기 위해 동일한 가용영역에 배포해야 하는 경우에 적합한 사용법이다.

Schedule: Pode Anti-affinity

Pode Anti-affinity는 Pode Affinity와 반대로 동작한다. matchExpression 조건을 만족하는 포드와 최대한 멀리 떨어진 토폴로지에 포드가 할당된다. 고가용성을 보장하기 위해, 포드를 여러 가용영역에 흩뿌릴 수 있는 전략이다! 더불어, topologyKey를 hostname으로 설정하는 경우, 하나의 노드는 하나의 topology로 간주된다. 따라서 각 포드가 서로 다른 hostname을 가지도록 할당 될 것이다(하나의 노드에 두개의 포드가 스케줄링될 일이 발생하지 않는다 == 마치 DaemonSet 과 비슷한 deployment).


Taints, Tolerations

특정 노드에 얼룩(Taints)을 남기고 해당 노드에 포드가 할당되는 것을 막늗다. 하지만 이를 용인(Tolerations) 할 수 있는 포드만 해당 노드에 할당한다. 에를 들어 master 노드에 taint를 남겨, worker 노드에 포드가 할당되도록 하는 방법등이 있다.

1
2
3
4
5
taint는 label과 다른점이 존재한다. key=value 뒤에 effect(Taint 효과)를 추가로 명시하는 것이다. 
taint의 effect는 아래와 같다.
    - 1) NoSchedule(포드를 스케줄링하지 않음) 
    - 2) NoExecute(포드의 실행 자체를 허용하지 않음)
    - 3) PreferNoSchedule(가능하면 스케줄링하지 않음)

NoSchedule, NoExecute, tolerationSeconds

NoSchedule은 노드에 설정하더라도 기존에 실행하던 포드는 정상적으로 동작한다. 하지만 NoExecute의 경우는 스케줄링도 하지 않을 뿐더러, 별도의 toleration이 설정되어 있지 않다면 해당 노드에서 실행 중인 포드를 아예 종료시킨다. 물론 포드가 deployment나 replicaset 같은 리소스에 생성되어 있다면, 포드는 다른 포드로 옮겨가는 Eviction 이 발생한다.

쿠버네티스는 문제가 발생한 노드에 대해서는 자동으로 Taint를 추가한다. 특히나 NotReady 혹은 Unreachable과 같이 노드 자체에 장애가 생겼을 경우를 대비해 아래의 Taint를 추가한다.

1
2
- node.kubernetes.io/not-ready:NoExecute
- node.kubernetes.io/unreachable:NoExcute

위의 경우, 이런 Taint가 발생하더라도 특정시간 동안은 해당 Taint를 용인하기 위해 (동작이 정상범위 안에 있더라도 강제로 처리되는 것을 막기 위해) tolerationSeconds 라는 시간을 부여한다. 해당 시간 동안은 Taint를 용인하겠다는 뜻이다.

Cordon, Drain, PodDistributionBudget

Cordon : cordon 명령어로 지정된 노드는 새로운 포드가 할당되지 않는다. Drain : drain은 노드에 스케줄링을 금지하는 것은 물론 기존에 실행중이던 포드를 다른 노드로 옮겨가도록 Eviction을 수행한다. PodDisruptionBudget(PDB) : drain 된 노드에서 실행 중인 포드가 졸료되어 다른 노드로 옮겨가는 사이에는 어플리케이션이 중지될 수 있다. 이때, 특정 개수 혹은 비율의 포드는 정상적인 상태로 유지시킨다. - maxUnavailable : 최대 몇개까지 동시에 종료될 수 있는가? - minAvailable : 최소 몇개가 정상상태를 유지해야 하는가?

Custom Scheduler

custom scheduler 구현하기 1) API 서버의 Watch API를 통해 새롭게 생성된 포드의 데이터를 받아온다. 2) 포드의 데이터 중에서 nodeName이 설정되어 있지 않고, schedulerName이 일치하는지 검사한다. 3) 스케줄링 알고리즘을 수행하고, 바인딩 API 요청을 통해 스케줄링된 노드의 이름을 nodeName에 설정한다.

This post is licensed under CC BY 4.0 by the author.