쿠버네티스 인 액션 '4장.레플리케이션과 그 밖의 컨트롤러: 관리되는 파드 배포'을 읽고 정리한 내용이다.
여기서 다루는 내용은 아래와 같다
우리는 k8s를 사용하면서, 파드를 수동적으로 개입하지 않아도 안정적인 상태를 스스로 유지하길 원한다.
-> 레플리케이션컨트롤러, 디플로이먼트가 이런 것들을 해준다.
사실 k8s 스스로도 기본적으로 해주는 것들이 있다...
컨테이너의 주 프로세스에 크래시가 발생하면 kubelet이 컨테이너를 다시 시작한다.
예를 들어, 만약 어플리케이션 버그로 인해 크래시 발생 시, k8s가 어플리케이션을 자동으로 재시작한다.
하지만 어플리케이션이 무한 루프나 교착 상태에 빠져 응답하지 않는 상황이라면?
-> 이 경우에는 어플리케이션을 자동으로 재시작하지 않는다
즉, 어플리케이션 내부 기능에 의존하지 말고 외부에서 상태를 체크해야한다. 외부에서 어플리케이션의 상태를 체크하고 있다가, 문제가 발생함을 인지하고 조치를 취해야 한다.
이 것을 통해 컨테이너가 살아있는지 확인할 수 있다.
파드의 specification에 각 컨테이너 마다 라이브니스 프로브를 지정할 수 있다.
k8s는 주기적으로 프로브를 실행 -> 문제있을 시 해당 프로브가 지정된 컨테이너 재시작
HTTP GET 프로브
TCP 소켓 프로브
Exec 프로브
apiVersion: v1
kind: pod
metadata:
name: kubia-liveness
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
여기서 정의한 라이브니스 프로브는 컨테이너가 문제가 있다고 판단하면 재시작 시킨다.
kubectl describe po kubia-liveness 명령을 내렸을 때 볼 것
Last State: Terminated
Exit Code: 137 이면 128+x (x: 9 == SIGKILL)
Liveness: .....
- delay : 컨테이너 시작 후 얼마나 지나서 프로브 시작할 것인지
- timeout : 얼마의 시간 안에 컨테이너가 응답해야하는지
- period : 프로브 실행 주기
- failure : 연속 실패 횟수 제한
...
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15 # 컨테이너 뜨고나서 첫 프로브까지 대기 시간
운영환경에서 실행 중인 파드는 반드시 라이브니스 프로브를 정의해야 한다.
정의하지 않으면 쿠버네티스가 어플리케이션이 살아 있는지를 알 수 있는 방법이 없다.
프로세스가 실행되는 한 쿠버네티스는 컨테이너가 정상적이라고 간주할 것이다.
더 나은 라이브니스 프로브를 위해 특정 URL 경로 (ex, /health)에 요청하도록 프로브를 구성해 어플리케이션 내에서 실행중인 모든 주요 구성요소가 살아 있는지 또는 응답이 없는지 확인하도록 구성할 수 있다.
웹서버의 경우, 웹서버 컨테이너를 재시작해서 해결할 수 있는 문제일때만 라이브니스 프로브가 실패하도록 해야한다.
예를 들어, DB 서버의 문제가 있다고 해서 웹서버 컨테이너의 프로브가 실패하게 구현하면 안된다.
또한 너무 많은 리소스를 사용하지 않고 1초내에 응답해야한다.
컨테이너에 크래시가 발생하거나 라이브니스 프로브가 실패한 경우,
K8S는 컨테이너를 재시작 한다.
이 작업은 kubelet이 한다.
하지만 노드 자체에 크래시가 발생한 경우, 중단된 모든 파드의 대체 파드를 생성하는 것은 컨트롤 플레인이 한다.
k8s 리소스 중 하나로 파드가 항상 실행되도록 보장한다.
파드가 하나 사라지면? 사라진 파드를 감지해 교체 파드를 생성한다.
지정한 숫자의 파드가 항상 클러스터 내에 동작하는 상태로 존재하도록 유지한다!
만약 노드가 고장나더라도 해당 노드안에 있던 관리되는 pod들은 레플리케이션 컨트롤러에 의해 다른 노드에 자동으로 생성된다.
일반적으로 파드의 여러 복제본을 관리하기 때문에 이런 이름을 가졌다.
질문) 한 파드가 여러 레이블을 가지고 있고, 동시에 2개 이상이 rc에서 관리받는다면 두 rc 각각의 스케일링에 따라 어떻게 동작하나?
실행중인 파드 목록 지속적으로 모니터링 -> 의도하는 수와 일치하는지 확인
레플리케이션 컨트롤러는 레이블 셀렉터 당 desired count 를 관리한다.
역할은 항상 정확한 수의 파드가 레이블 셀렉터의 desired count와 일치하는지를 확인하고 조치하는 것이다.
기존 파드가 사라지면 새파드를 시작해 항상 실행되도록 한다
노드에 장애 발생시, 노드에 속해있던 파드들 교체 복제본 생성
수동 또는 자동으로 파드를 쉽게 수평 확장할 수 있음
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
replicas: 3
selector:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
템플릿의 파드 레이블과 컨트롤러의 레이블 셀렉터는 완전히 일치해야 함.
그렇지 않으면 무한정 생산할 수 있음.
k8s API 서버가 이를 검증해주지만, 셀렉터를 지정하지 않는 것도 방법이다.
그러면 자동으로 템플릿의 레이블로 설정된다.
위 컨트롤러를 통해 생성된 파드 3개중, 하나를 제거하면 새로운 파드를 즉시 기동해 3개로 숫자를 맞춰준다.
레플리케이션 컨트롤러의 상태를 확인해보면 (kubectl describe rc kubia)
desired , current, ready 수를 확인할 수 있다.
ready 상태에 대해서는 추후 스 프로브에서 공부할 예정이다.
기존 파드를 삭제한 그 자체에 대한 대응으로 신규 파드를 만든게 아니다. 결과적인 상태(부족한 파드 수)에 대응하는 것이다.
한 노드의 네트워크 인터페이스를 종료시켜 노드 장애 시물레이션을 해본다.
1. 네트워크 인터페이스 종료
2. 노드의 상태가 NotReady 상태로 변경
이때 파드의 상태는???
k8s가 파드를 재 스케줄링 하기 전 대기하는 기간 : 일시적인 네트워크 결함이나 kubelet 재시작 등의 이유로 노드에 일시적으로 도달할 수 없는 경우를 위해
3. 몇 분 동안 노드의 상태가 NotReady 상태로 유지되면 파드의 상태를 Unknown 상태로 변경.
이때 레플리케이션 컨트롤러는 즉시 새 파드를 기동한다.
즉 위 과정을 거쳐, 노드에 장애가 발생한 경우 인간의 개입없이 시스템이 자동으로 자가치유를 진행한다.
만약 이 상태에서 노드를 되살리면, Ready 상태로 돌아온다.
레플리케이션 컨트롤러와 그가 생성한 파드는 독립적이다.
레플리케이션 컨트롤러는 레이블 셀렉터와 일치하는 파드만을 관리한다.
파드의 레이블을 변경하면 기존 컨트롤러의 범위에서 제거되거나 추거될 수 있다.
다른 컨트롤러의 관리 범위로 이동할 수 있다(이동할 컨트롤러의 레이블 셀렉터와 일치하도록 레이블을 변경하면!)
아무 컨트롤러도 레이블 셀렉터로 지정하지 않은 레이블로 수정을 한다면, no-managed 파드가 된다. 그럼 당연히 자동 스케줄링 혜택도 못받아요..ㅠ
특정 파드에 어떤 작업을 하려는 경우 해당 파드를 컨트롤러의 범위에서 제거하면 작업이 수월하다.
ex) 문제가 있는 파드를 컨트롤러 관리범위에서 빼내어 컨트롤러가 대체 파드를 생성하도록 한후 원인분석을 하면 좋다
질문) 레플리케이션 컨트롤러의 레이블 셀렉터를 수정하면 어떻게 되나?
기존에 셀렉터가 가리키던 레이블의 파드들은 그대로 있되 관리되지 않는 상태로 가고,
새 레이블에 속한 파드들을 새로 생성한다.
파드 템플릿 변경은 이미 생성이 되어있는 파드들에는 아무런 영향을 주지 않는다.
기존 파드 수정시 해당 파드를 삭제하고 컨트롤러가 새 템플릿을 기반으로 새 파드를 생성하도록 해야한다.
원하는 파드 복제본 수를 변경하여 '수평 스케일링'을 할 수 있다.
$ kubectl scale rc kubia --replicas=10
$ kubectl edit rc kubia
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
# 이 값을 10으로 수정후 편집기를 닫으면 rc가 업데이트 되고 즉시 파드 수가 10개로 확장된다
replicas: 3
selector:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
rc 를 삭제하면 그 rc가 관리하던 파드들은 어떻게 될까? 같이 삭제되나? 아니면 그냥 Not Managed 상태로 될까?
-> 이를 결정할 수 있는 옵션이 'cascade'
$ kubectl delete rc kubia --cascade=true
$ kubectl delete rc kubia --cascade=false
더 풍부한 표현식을 사용하는 파드 셀렉터를 갖고 있음
레플리카 셋의 레이블 셀렉터
# API group 'apps', 버전 'v1beta2' 에 속한다.
# rs는 v1 API의 일부가 아니다
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
# rs의 가장 간단한 셀렉터
selector:
matchLabels:
app: kubia
# 템플릿은 rc와 동일
template:
metadata:
labels:
app: kubia
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
위 YAML 파일을 kubectl create 명령과 함께 사용하여 rs을 생성한다.
# rs 정보 확인 : rc와 동일한 정보를 관리하며 이를 보여줌
$ kubectl get rs
$ kubectl describe rs
앞서 사용한 matchLabels 는 가장 단순한 레이블 셀렉터
'matchExpressions' 레이블 셀렉터 표현방법을 알아야 한다.
apiVersion: apps/v1beta2
kind: ReplicaSet
spec:
replicas: 3
selector:
matchExpressions:
- key: app
operator: In
values:
- kubia
template:
...
matchExpressions을 사용하면, 위와 같이 셀렉터에 표현식을 추가할 수 있다.
각 표현식은 키, 연산자, 가능한 값이 포함되어야 한다.
여러 표현식을 지정하는 경우, 셀렉터가 파드와 매칭되기 위해서는, 모든 표현식이 true 여야함.
matchLabels 와 matchExpressions 를 모두 지정하면, 셀렉터가 파드를 매칭하려면 1.모든 레이블이 일치 2.모든 표현식이 true로 평가 되어야함.
$ kubectl delete rs kubia
레플리카 셋을 삭제하면 모든 파드가 삭제된다.
질문) cascade 옵션 없음???
클러스터의 특정 레이블을 가진 노드들에 노드당 하나의 파드만 생성하도록 지정한다.
대상 노드들에 정확히 하나의 파드가 생성되며, 생성되지 않는 노드가 없도록 한다.
즉 노드 수 만큼 파드를 만들어 각 노드에 배치한다.
데몬셋은 타겟 노드들에 해당 파드가 실행되고 있는지를 모니터링 하며, 만약 실행되지 않고 있으면 새로 파드를 생성해서 실행시킨다.
마찬가지로 데몬셋에 지정된 파드 템플릿으로 새 파드 생성한다.
node-Selector 지정안하면 클러스터의 모든 노드 대상으로 파드를 배치한다.
rc, rs 에서 nodeSelector 를 사용하는 것과 차이점은?
보통 시스템 수준의 작업을 수행하는 인프라 관련 파드가 데몬 셋을 이용하여 배치된다.
k8s 미사용 환경에서는 일반적으로 노드 부팅시 '시스템 초기화 스크립트' 또는 'systemd' 데몬이 동작하며 노드가 시작된다. k8s 사용 환경에서도 그렇게 할 수 있지만, 그러면 k8s가 제공하는 모든 기능을 최대한 활용할 수 없다. (?)
노드 다운시 -> 해당 파드 새로 생성안함
새노드 추가시 -> 새 파드 인스턴스를 새 노드에 배포
대상 노드 하나에 실수로 누가 대상 파드를 삭제 -> 새 파드 인스턴스를 배포
SSD를 갖는 모든 노드에서 실행되어야하는 ssd-monitor 데몬을 데몬 셋을 이용해 배치해보자.
apiVersion: apps/v1beta2
kind: DemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector:
disk: ssd
containers:
- image: luksa/ssd-monitor
name: kubia
데몬셋의 파드 템플릿에서 파드의 label을 'app: ssd-monitor' 로 지정. 이 파드들은 'disk: ssd' 라는 label을 가진 node 들에만 배포됨.
데몬셋의 selector.matchLabels 설정을 통해 자신이 관리할 파드의 label을 'ssd-monitor' 로 지정.
$ kubectl create -f ssd-monitor-daemonset.yaml
$ kubectl get ds
만약 위 예제에서, 갑자기 파드의 label 을 다른 것으로 변경하면 어떻게 되나?
label 변경된 파드가 종료된다.
또한, 만약 ssd-monitor 데몬셋을 삭제하면 실행중인 데몬 파드도 삭제된다.
(--cascade 옵션 없나?*)
작업완료 후 종료되는 태스크만 실행하려는 경우가 있다.
완료 가능한 태스크 (completable task)에서는 프로세스가 종료되어도 자동으로 다시 시작되지 않는다.
Job 이라는 리소스가 이런 기능을 지원한다.
파드의 컨테이너 내부에서 실행중인 프로세스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는다. 파드는 완료된 것으로 간주된다.
Job이 실행되고 있던 노드에 장애가 발생한 경우, 잡이 관리하는 파드는 다른 노드로 다시 스케줄링된다. 실행이 완료될 때 까지는 스케줄링 일어남.
프로세스 자체에 장애가 발생한 경우에는 Job 설정에서 컨테이너를 다시 시작할지 여부를 설정할 수 있다.
Job은 작업이 제대로 완료되는 것이 중요한 임시 작업에 유용하다.
예를 들어 ETL 작업같은 배치성 작업에 유용하다.
배치 프로세스를 실행시키는데, 노드에 문제발생 or 노드에서 해당 파드 제거됨 or 프로세스에서 장애발생 등의 상황에 자동으로 대처해준다.
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
template:
metadata:
labels:
app: batch-job # 파드셀렉터 지정안하고 템플릿에 있는 label을 쓴다
spec:
restartPolicy: OnFailure # Job의 템플릿으로 사용되는 파드의 restartPolicy 는 기본 값(Always)을 사용할 수 없다
containers:
- image: luksa/batch-job
name: main
Job으로 관리되는 pod는 실행이 완료되어도 삭제되지 않는다.
kubectl logs 로 로그를 확인할 수 있다.
삭제하는 방법
Job은 2개 이상의 파드 인스턴스를 생성해 병렬 또는 순차적으로 실행하도록 구성할 수 있다.
정의 파일의 spec 하위에 지정할 수 있는 속성을 가진다.
kind: Job
..
spec:
completions: 5
parallelism: 2
template:
..
이 Job은 5개 파드를 성공적으로 완료해야 하며, 2개 까지 병렬로 실행할 수 있다.
잡은 파드가 완료될 때 까지 얼마나 기다려야 하나?
파드 스펙에 activeDeadlineSeconds 속성 설정.
Job이 미래 특정시간 또는 지정된 간격으로 반복 실행하고 싶을 때 'CronJob' 리소스 사용.
잡 리소스는 예정된 시간에 크론잡 리소스에서 생성된다. 그러면 잡은 파드를 생성한다.
잡이나 파드가 상대적으로 늦게 생성되고 실행될 수 있다. 예정된 시간을 너무 초과해 시작되면 안되는 경우도 있다. 이런 경우에는 크론잡 스펙의 startingDeadlineSeconds 필드를 지정해 데드라인을 실행할 수 있다.
ex) startingDeadlineSeconds: 15 -> 파드는 예정된 시간에서 늦어도 15초내에 시작해야함.
일반적인 상황에서 크론잡은 각 실행에 항상 하나의 잡만 생성하지만, 2개 이상의 잡이 동시 생성되거나 전혀 생성되지 않을 수 있다.
그렇기 때문에 잡은 멱등성을 가져야하며 다음 번 잡 실행이 이전의 누락된 실행에서 완료했어야 하는 작업을 수행하는지 확인해야 한다.