쿠버네티스는 클러스터 내부에서 컴퓨팅 자원 활용률(Utilization)을 늘리는 것이 중요하다.
컨테이너와 포드의 자원 사용량 제한: Limit, Request
apiVersion: v1
kind: Pod
metadata:
name: resource-limit-with-request-pod
labels:
name: resource-limit-with-request-pod
spec:
containers:
- name: nginx
image: nginx:latest
resources:
limits:
memory: "256Mi"
cpu: "1000m"
requests:
memory: "128Mi"
cpu: "500m"
- 참고: cpu 1개를 뜻하는 1000m(밀리코어)
requests는 적어도 이만큼의 자원은 컨테이너에게 보장되어야 한다라는 의미이며 limits은 최대사용량
즉 위에서는 최소 128Mi 메모리 사용은 보장되지만 유휴 메모리 자원이 있다면 최대 256Mi까지 사용할 수 있다란 의미
QoS 클래스와 메모리 자원 사용량 제한 원리
CPU 사용량에 경합이 발생하면 throttle이 발생하지만 컨테이너 자체에는 문제가 발생하지 않는다.
하지만 메모리는 incompressible 자원으로 경합이 발생하면 우선순위가 낮은 포드 또는 프로세스가 종료된다.
강제 종료된 포드는 다른 노드로 옮겨가게 되는데, 이를 퇴거(Eviction)이라고 표현한다.
쿠버네티스는 포드의 우선순위를 구분하기 위해 3가지 종류의 QoS(Quality of Service) 클래스를 명시적으로 포드에 설정한다. 우선순위는 OOM(Out Of Memory) 값으로 설정되며 OOM 점수가 높을수록 우선순위가 낮아져 강제종료된다.
각 클래스는 kubectl describe 명령어로 확인 가능
- Guaranteed 클래스 - Limit과 Request의 값이 동일할 때 부여되는 클래스. Requests를 생략하면 Limit으로 동일하게 설정되며 이 경우 OOM 점수는 -998로 설정된다.
- Burstable 클래스 - Limit의 값이 Request보다 큰 포드를 의미함.
- BestEffort 클래스 - Limit과 Request를 아예 설정하지 않은 포드에 설정되는 클래스.
기본적으로 Guaranteed가 우선순위가 가장 높으며, 그 뒤로 Burstable와 BestEffort 순이다.
따라서 노드에 메모리가 부족하면 BestEffort 클래스 포드 먼저 종료된다.
하지만 메모리 사용량에 따라 우선순위가 역전될 수도 있는데, 메모리를 많이 사용하면 사용할수록 우선순위가 낮아진다.
ResourceQuota로 네임스페이스의 자원 사용량 제한
- 네임스페이스에서 할당할 수 있는 자원의 총합을 제한할 수 있음
- 네임스페이스에서 생성할 수 있는 리소스의 개수를 제한할 수 있음
ResourceQuota와 LimitRange는 namespace 에서 이미 살펴봤으므로 간단하게 넘어가자.
$ kubectl get quota
$ kubectl describe quota
리소스 개수를 제한하려면 count/<오브젝트이름>을 명시
scope로 BestEffort 클래스의 포드 개수 제한 가능
LimitRange로 자원 사용량 제한
- 포드의 컨테이너에 CPU나 메모리 할당량이 설정돼 있지 않은 경우, 컨테이너에 자동으로 기본 Request 또는 Limit 값을 설정할 수 있음
- 포드 또는 컨테이너의 CPU, 메모리, 퍼시스턴트 볼륨 클레임 스토리지 크기의 최소값/ 최대값 설정 가능
쿠버네티스 스케줄링
스케줄링에 관여하는 컴포넌트는 kube-scheduler와 쿠버네티스 클러스터의 전반적인 상태 데이터를 저장하는 일종의 데이터베이스인 etcd이다.
스케줄링 종류
- nodeName (노드명 지정, 그러나 노드가 삭제되면 이름이 바뀌기 때문에 유의)
- nodeSelector (키, 값이 있는 노드로 지정, 그러나 매칭이 되는 라벨이 없으면 어느 노드에도 매칭이 안됨)
- nodeAffinity (키만 설정해도 자원이 많은 노드에 설정됨. 매칭이 안 되어도 스케줄러가 자원이 많은 노드에 매칭가능)
- podAffinity (관련 파드가 같은 노드에 매칭이 되어야 하는 경우에 사용, ex. PV)
- podAntiAffinity (관련 파드가 다른 노드에 매칭이 되어야 하는 경우에 사용, ex. master-slave)
노드 라벨 생성 명령어
$ kubectl label nodes k8s-node1 kr=az-1
$ kubectl label nodes k8s-node2 us=az-1
Taints와 Tolerations를 이용한 포드 스케줄링
Taints는 특정 노드에 얼룩(Taint)을 지정함으로써 해당 노드에 포드가 할당되는 것을 막는 기능이며, 이에 대응하는 Tolerations를 포드에 설정하면 Taints가 설정된 노드에도 포드를 할당할 수 있다.
쿠버네티스는 기본적으로 다양한 Taints를 노드에 설정한다.
지금까지 포드를 생성하면 기본적으로 마스터 노드가 아닌 워커 노드에 할당됐는데, 이는 기본적으로 마스터 노드에 Taints NoSchedule이 설정되어 있기 때문이다. 장애가 발생한 경우엔 NoExecute로 다른 노드에 설정되게끔 한다.
Taints 효과에는 NoSchedule(포드를 처음 매칭할 때 스케줄링하지 않음), NoExecute(포드의 실행 자체를 허용하지 않음), PreferNoSchedule(가능하면 스케줄링하지 않음) 총 3가지가 있다.
$ kubectl taint node ip-10-43-0-30.ap-northeast-2.compute.internal alicek106/your-taint=your-taint-value:NoExecute
포드가 디플로이먼트나 레플리카셋 등과 같은 리소스로 생성됐다면 NoExecute에 의해 포드가 종료됐더라도 포드는 다른 노드로 옮겨가는 퇴거(Eviction)가 발생한다.
또한 쿠버네티스는 특정 문제가 발생한 노드에 대해서는 자동으로 Taint를 추가한다.
대표적으로 아직 노드가 준비되지 않은 상태(NotReady), 네트워크가 불안정한 상태(Unreachable), 메모리 부족(memory-pressure)이나 디스크 공간 부족(disk-pressure)에 대한 Taint 등이 있다.
cordon을 이용한 스케줄링 비활성화
cordon 명령어로 지정된 노드는 새로운 포드가 할당되지 않는다.
$ kubectl cordon ip-10-43-0-30.ap-northeast-2.compute.internal
kubectl get nodes 명령어로 STATUS 항목 확인 가능
drain 명령어로 노드 비활성화
drain은 cordon처럼 해당 노드에 스케줄링을 금지한다는 것은 같지만, 노드에서 기존에 실행중이던 포드를 다른 노드로 옮겨가도록 퇴거(Eviction)를 수행한다는 점이 다르다.
$ kubectl drain ip-10-43-0-30.ap-northeast-2.compute.internal
PodDisruptionBudget으로 포드 개수 유지
kubectl drain 명령어 등으로 포드에 퇴거가 발생할 때, 특정 개수 또는 비율만큼의 포드는 반드시 정상적인 상태를 유지하기 위해 사용된다.
$ kubectl get pdb
<simple-pdb-example.yaml>
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: simple-pdb-example
spec:
maxUnavailable: 1 # 비활성화될 수 있는 포드의 최대 개수 또는 비율
# minAvailable: 2
selector:
matchLabels:
app: webserve
커스텀 스케줄러 및 스케줄러 확장
포드를 생성하면 기본적으로 기본 스케줄러(kube-scheduler)를 사용하게 된다.
추가로 본인이 스케줄러를 구현해서 사용할 수도 있다.
쿠버네티스 애플리케이션 상태와 배포
간단히 사용하기에는 kubectl apply -f 명령어도 나쁜 방법은 아니지만 좀 더 고도화된 배포방식을 원한다면 Spinnaker, Helm, Kustomize 또는 ArgoCD나 Jenkins와 같은 CD 도구를 사용할 수 있다.
디플로이먼트를 이용한 레플리카셋의 버전 관리
--record 옵션을 추가해 디플로이먼트의 변경사항을 적용하면 이전에 사용하던 레플리카 셋의 정보는 디플로이먼트 히스토리에 기록된다.
$ kubectl apply -f deployment-v1.yaml --record
$ kubectl apply -f deployment-v2.yaml --record
$ kubectl rollout history deployment nginx-deployment
배포중에 애플리케이션이 중단돼도 괜찮은 경우는 ReCreate 방법을 사용할 수 있다.
<deployment-recreate.yaml>
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-recreate
spec:
replicas: 3
strategy:
type: Recreate
하지만 이를 허용하지 않았을때는 ReCreate 방식은 적절하지 않으며, 쿠버네티스에서는 포드를 조금씩 삭제하고 생성하는 롤링 업데이트 기능을 제공한다. 별도의 설정하지 않아도 롤링 업데이트를 사용하도록 설정돼있음
...
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 2
...
- maxUnavailable: 롤링 업데이트 도중 사용 불가능한 상태가 되는 포드의 최대 개수 설정
- maxSurge: 롤링 업데이트 도중 전체 포드의 개수가 디플로이먼트의 replicas 값보다 얼마나 더 많이 존재할 수 있는지 설정
블루그린 배포
기존 버전의 포드를 그대로 놔둔 상태에서 새로운 버전의 포드를 미리 생성해 둔 뒤 서비스의 라우팅만 변경하는 배포 방식
특정 순간에는 디플로이먼트에 설정된 replicas 개수의 두 배에 해당하는 포드가 실행되기 때문에 자원을 많이 소모할 수 있다.
참고)
포드의 생애 주기(Lifecycle)
- Pending: 포드를 생성하는 요청이 API 서버에 의해 승인됐지만, 문제가 발생해서 생성되지 않은 상태
- Running: 정상적으로 실행
- Completed: 포드가 정상적으로 실행돼 종료됐음을 의미함. 포드 컨테이너의 init 프로세스가 종료 코드로 0을 반환
- Error: 포드가 정상적으로 실행되지 않은 상태로 종료됐음을 의미. init 프로세스가 0이 아닌 종료 코드 반환
- Terminating: 포드가 삭제 또는 퇴거 되기 위해 삭제 상태에 머물러있는 경우에 해당
포드의 재시작 정책을 설정하는 restartPolicy는 기본적으로 Always로 설정되어 있고 포드의 컨테이너가 종료되면 자동으로 재시작한다. restartPolicy 값을 Never로 설정하면 재시작하지 않는다.
Running 상태가 되기 위한 조건
포드가 Running 상태에 머물러 있다고 해서 컨테이너 내부의 애플리케이션이 제대로 동작하고 있을 것이란 보장은 없다.
쿠버네티스는 다음과 같은 기능을 제공함
- InitContainer
- postStart
- livenessProbe, readinessProbe
apiVersion: v1
kind: Pod
metadata:
name: init-container-example
spec:
initContainers: # 초기화 컨테이너를 항목에 정의합니다.
- name: my-init-container
image: busybox
command: [" sh" , "-e" , "echo Hello World!"]
containers: # 애플리케이션 컨테이너를 항목에 정의합니다.
- name: nginx
image: nginx
포드의 컨테이너가 실행되거나 삭제될 때, 특정 작업을 수행하도록 훅을 정의할 수 있다.
컨테이너가 시작될 때 실행되는 postStart와 종료될 때 실행되는 preStop 이다.
apiVersion: v1
kind: Pod
metadata:
name: poststart-hook
spec:
containers:
- name: poststart-hook
image: nginx
lifecycle:
postStart:
exec:
command: ["sh", "-c", "touch /myfile"]
postStart 명령이나 HTTP 요청이 제대로 실행되지 않으면 컨테이너는 Running 상태로 전환되지 않으며, Init 컨테이너와 마찬가지로 restartPolicy에 의해 해당 컨테이너가 재시작된다. 또한 시간이 오래 걸리면 그만큼 Running 상태까지 도달하는 시간이 길어질 수 있다.
애플리케이션의 상태 검사, livenessProbe, readinessProbe
Init 컨테이너나 postStart 훅이 정상적으로 실행됐다고 해서 애플리케이션이 제대로 동작하고 있다고 보장할 수 없다.
- livenessProbe: 컨테이너 내부의 애플리케이션이 살아있는지 검사. 실패인 경우 restartPolicy에 의해 재시작
- readinessProbe: 컨테이너 내부의 애플리케이션이 사용자 요청을 처리할 준비가 됐는지 검사. 실패할 경우 컨테이너는 서비스의 라우팅 대상에서 제외됨
apiVersion: v1
kind: Pod
metadata:
name: livenessprobe-pod
spec:
containers:
- name: livenessprobe-pod
image: nginx
livenessProbe: # 컨테이너에 대해 livenessProbe 정의합니다.
httpGet: # HTTP 요청을 통해 애플리케이션의 상태를 검사합니다.
port: 80 # <포드으 IP):801 경로를 통해 헬스 체크 요청을 보냄니다.
path: /
3가지 방식이 있으며 httpGet, exec, tcpSocket 등이 있다.
위에서 파드를 생성한 후, 일부러 index.html 파일을 삭제해서 livenessProbe가 실패하도록 유도하면
시간이 지난 뒤 포드의 RESTARTS 횟수가 증가하고 컨테이너가 재시작함으로써 Nginx 서버의 index.html 파일이 원래대로 돌아오기 때문에 livenessProbe는 다시 성공하게 됨 (describe 명령어로 확인 가능)
<readinessprobe-pod-svc.yaml>
apiVersion: v1
kind: Pod
metadata:
name: readinessprobe-pod
labels:
my-readinessprobe: test
spec:
containers:
- name: readinessprobe-pod
image: nginx # Nginx 서버 컨테이너를 생성합니다.
readinessProbe: # <포드의 IP):80/ 상태 검사 요청을 전송합니다.
httpGet:
port: 80
path: /
---
apiVersion: v1
kind: Service
metadata:
name: readinessprobe-svc
spec:
ports:
- name: nginx
port: 80
targetPort: 80
selector:
my-readinessprobe: test
type: ClusterIP
파드를 생성한 후 엔드포인트 리소스 목록을 확인해보면 서비스로 접근하는 요청이 포드의 IP로 라우팅되고 있는것을 볼 수 있음
$ kubectl get endpoints
마찬가지로 고의적으로 index.html 파일을 삭제하면 livenessProbe와 달리 RESTARTS 횟수가 증가하지 않고 단순히 READY 상태인 컨테이너가 하나 줄어든다.
readinessProbe가 실패했기 때문에 컨테이너가 준비되지 않았다고 간주하며 서비스 리소스는 사용자 요청을 이 포드로 전달하지 않는다.
엔드포인트 리소스 목록을 확인해보면 라우팅 대상에서 포드의 IP가 제거되어 있다.
세부옵션
- periodSeconds :상태 검사를 진행할 주기를 설정합니다. 기본값은 10초
- initialDelaySeconds: 포드가 생성된 상태 검사를 시작할 때까지의 대기 시간을 설정합니다. 기본적으로는 설정돼 있지 않음
- timeoutSeconds :요청에 대한 타임이웃 시간을 설정합니다. 기본값은 1초
- successThreshold: 상태 검사에 성공했다고 간주할 검사 성공 횟수를 설정합니다. 기본값은 1
- failureThreshold: 상태 검사가 실패했다고 간주할 검사 실패 횟수를 설정합니다. 기본값은 3
명령어 Tip)
$ kubectl get events -w | grep [pod 이름] # 파드 이벤트 확인
$ kubectl describe pod [pod 이름] | grep -A5 Conditions # 파드 컨디션 확인
Terminating 상태와 애플리케이션의 종료
apiVersion: v1
kind: Pod
metadata:
name: prestop-hook
spec:
containers:
- name: prestop-hook
image: nginx
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx", "-s", "quit"]
preStop 훅이 실행되고나면 포드의 컨테이너들에게 SIGTERM 시그널을 보냄으로써 포드가 곧 종료될 것이라고 알린다.
이때 애플리케이션의 소스코드에서는 SIGTERM 시그널을 수신했을 때 어떤 행동을 취할 것인지 별도로 구현해야 한다.
만약 포드 내부의 애플리케이션이 SIGTERM을 수신했는데도 특정 유예 시간 이내에 종료되지 않으면 쿠버네티스는 SIGKILL 시그널을 전송해 강제로 프로세스를 종료하게 된다. (기본 30초이며 terminationGracePeriodSeconds 값을 통해 조정 가능)
References:
https://kubetm.github.io/k8s/04-beginner-controller/deployment/
'DevOps > Kubernetes' 카테고리의 다른 글
Kubernetes Architecture (0) | 2022.07.10 |
---|---|
SonarQube on Kubernetes (0) | 2022.06.25 |
쿠버네티스 워커노드 초기화 (0) | 2022.06.12 |
쿠버네티스(Kubernetes) PV, PVC (0) | 2022.05.20 |
[troubleshooting] [ERROR CRI]: container runtime is not running (0) | 2022.05.16 |