파드를 수동으로 생성, 감독, 관리하는 방법을 배웠지만 실환경에서는 배포한 애플리케이션이 자동으로 실행되고 수동적인 개입 없이도 안정적인 상태로 유지되길 원할 것이다.
이렇게 하기위해 파드를 직접 생성하는 일은 거의 없을 것이다. 대신 레플리케이션컨트롤러 또는 디플로이먼트와 같은 유형의 리소스를 생성해 실제 파드를 생성하고 관리한다.
쿠버네티스를 사용하면 얻을 수 있는 주요 이점은 쿠버네티스에 컨테이너 목록을 제공하면 해당 컨테이너를 클러스터 어딘가에서 게속 실행되도록 할 수 있다는 것이다.
컨테이너 주 프로세스에 크래시가 발생하면 kubelet이 컨테이너를 다시 시작한다. 쿠버네티스가 애플리케이션을 자동으로 다시 시작하므로, 애플리케이션에서 특별한 작업을 하지 않아도 쿠버네티스에서 애플리케이션을 실행하는 것만으로도 자동으로 치유할 수 있는 능력이 주어진다.
그러나 때때로 애플리케이션은 프로세스의 크래시 없이도 작동이 중단되는 경우가 있다. 일례로 자바 애플리케이션이 메모리 누수가 있어서 OutofMemoryError를 발생시키기 시작하더라도 JVM 프로세스는 계속 실행될 것이다.
제대로 동작하지 않는다는 신호를 쿠버네티스에게 신호를 보내서, 쿠버네티스가 다시 시작하도록 하는 방법이 있으면 좋을 것이다.
예를 들어 애플리케이션이 무한 루프나 교착 상태에 빠져서 응답을 하지 않는 상황이라면, 이런 경우 애플리케이션이 다시 시작되도록 하려면 애플리케이션 내부의 기능에 의존하지 말고 외부에서 애플리케이션의 상태를 체크해야한다.
쿠버네티스는 라이브니스 프로브를 통해 컨테이너가 살아 있는지 확인할 수 있다. 파드의 스펙에 각 컨테이너 라이브니스 프로브를 지정할 수 있다.
쿠버네티스는 주기적으로 프로브를 실행하고 프로브가 실패할 경우 컨테이너를 다시 시작한다.
쿠버네티스는 세 가지 메커니즘을 사용해 컨테이너 프로브를 실행한다.
HTTP GET 프로브는 지정한 IP 주소, 포트, 경로에 요청을 수행한다. 프로브가 응답을 수신하고 응답 코드가 오류를 나타내지 않으면 프로브가 성공했다고 간주된다.
서버가 오류 응답코드를 반환하거나 응답하지 않으면 프로브가 실패한 것으로 간주돼 컨테이너를 다시 시작한다.
TCP 소켓 프로브는 컨테이너의 지정된 포트에 TCP 연결을 시도한다. 연결에 성공하면 프로브가 성공한 것이고, 그렇지 않으면 컨테이너가 다시 시작된다.
Exec 프로브는 컨테이너 내의 임의의 명령을 실행하고 명령의 종료 상태 코드를 확인한다. 상태코드가 0이면 프로브가 성공한 것이다. 모든 다른 코드는 실패로 간주된다.
이전에 만든 node.js 를 약간 수정하여 다섯 번째 요청 이후 500 http error 상태 코드를 반환하도록 만든다.
컨테이너 이미지를 도커 허브에 푸시해놨기 때문에 직접 이미지를 빌드할 필요없다.
새 파드에 라이브니스 프로브 추가한다.
kubia-liveness-probe.yaml
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveness
spec:
containers:
- image: luksa/kubia-unhealthy # 약간 문제가 있는 애플리케이션을 포함한 이미지
name: kubia
livenessProbe: # HTTP GET을 수행하는 라이브니스 프로브
httpGet:
path: / # HTTP 요청 경로
port: 8080 # 프로브가 연결해야 하는 네트워크 포트
이 파드 디스크립터는 쿠버네티스가 주기적으로 "/" 경로와 8080포트에 HTTP GET을 요청해서 컨테이너가 정상 동작하는지 확인하도록 httpGet 라이브니스 프로브를 정의한다.
이런 요청은 컨테이너가 실행되는 즉시 시작된다.
다섯 번째 요청 후에 애플리케이션은 HTTP 상태 코드 500을 반환하기 시작하고 쿠버네티스가 프로브를 실패한 것으로 간주해 컨테이너를 다시 시작한다.
해당 기능을 보기 위해 파드를 만들어보자.
1분 30초 후 컨테이너가 시작된다 RESTARTS 열에는 파드의 컨테이너가 한 번 다시 시작했음을 보여준다.
$ kubectl get po kubia-liveness
NAME READY STATUS RESTARTS AGE
kubia-liveness 1/1 Running 1 (12s ago) 2m22s
kubectl describe로 출력되는 내용을 보면, 컨테이너가 다시 시작된 이유를 확인할 수 있다.
$ kubectl describe po kubia-liveness
Name: kubia-liveness
Namespace: default
Priority: 0
Node: gke-kubia-default-pool-c2f41b01-37p5/10.138.0.3
Start Time: Mon, 05 Sep 2022 13:59:24 +0000
Labels: <none>
Annotations: <none>
Status: Running # 컨테이너가 현재 실행 중이다.
IP: 10.32.1.17
IPs:
IP: 10.32.1.17
Containers:
kubia:
Container ID: containerd://05cd9ee39d8dd20b9103e6de01332c89e9bc923debfdd141144c907303cd2a98
Image: luksa/kubia-unhealthy
Image ID: docker.io/luksa/kubia-unhealthy@sha256:5c746a42612be61209417d913030d97555cff0b8225092908c57634ad7c235f7
Port: <none>
Host Port: <none>
State: Running
Started: Mon, 05 Sep 2022 14:01:35 +0000
Last State: Terminated
Reason: Error
Exit Code: 137 # 이전 컨테이너가 에러로 인해 코드 137을 반환하고 중지했다.
Started: Mon, 05 Sep 2022 13:59:44 +0000
Finished: Mon, 05 Sep 2022 14:01:34 +0000
Ready: True
Restart Count: 1 # 컨테이너가 한 번 다시 시작했다
Liveness: http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bgr9h (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-bgr9h:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m50s default-scheduler Successfully assigned default/kubia-liveness to gke-kubia-default-pool-c2f41b01-37p5
Normal Pulled 2m30s kubelet Successfully pulled image "luksa/kubia-unhealthy" in 19.303961211s
Warning Unhealthy 70s (x3 over 90s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing 70s kubelet Container kubia failed liveness probe, will be restarted
Normal Pulling 40s (x2 over 2m50s) kubelet Pulling image "luksa/kubia-unhealthy"
Normal Created 39s (x2 over 2m30s) kubelet Created container kubia
Normal Started 39s (x2 over 2m30s) kubelet Started container kubia
Normal Pulled 39s kubelet Successfully pulled image "luksa/kubia-unhealthy" in 842.277933ms
kubectl describe 에서 프로브에 관한 추가적인 정보도 표시되는것을 알 수 있다.
초기 지연을 설정하지 않으면 컨테이너가 시작된 후 바로 프로브가 시작한다. 이 경우 대부분 애플리케이션이 요청을 받을 준비가 돼 있지 않기 때문에 프로브가 실패한다.
초기 지연을 설정하려면 다음과 같이 initialDelaySeconds 속성을 라이브니스 프로브에 추가한다.
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15 # 쿠버네티스는 첫 번째 프로브 실행까지 15초를 대기한다.
만약 사용자가 kubectl describe를 사용했더라면 컨테이너가 종료 코드 137 또는 143으로 종료됐으며 외부에서 파드를 종료했음을 알 수 있었을 것이다.
파드 시작 시 이 문제가 발생한다면, initialDelaySeconds를 적절하게 설정해야 한다.
레플리케이션컨트롤러는 쿠버네티스 리소스로서 파드가 항상 실행되도록 보장한다.
일반적으로 레플리케이션컨트롤러는 파드의 여러 복제본을 작성하고 관리하기 위한 것이다.
레플리케이션컨트롤러를 생성하는 방법을 살펴본 다음, 이 컨트롤러가 파드의 실행을 유지하는 방법을 살펴보자.
kubia-rc.yaml
apiVersion: v1
kind: ReplicationController # 레플리케이션컨트롤러의 매니페스트 정의
metadata:
name: kubia # 레플리케이션컨트롤러 이름
spec:
replicas: 3 # 의도하는 파드 인스턴스 수
selector: # 파드 셀렉터로 레플리케이션컨트롤러가 관리하는 파드 선택
app: kubia
template: # 새 파드에 사용할 파드 탬플릿
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
ports:
- containerPort: 8080
파일을 API 서버에 게시하면, 쿠버네티스는 레이블 셀렉터 app=kubia라는 이름의 새로운 레플리케이션컨트롤러를 생성한다.
파드가 충분하지 않으면 제공된 파드 템플릿에서 새 파드가 만들어질 것이다.
레플리케이션컨트롤러를 생성한다.
$ kubectl create -f kubia-rc.yaml
replicationcontroller/kubia created
파드를 조회한다.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubia-85kwf 1/1 Running 0 14s
kubia-h8mrk 1/1 Running 0 14s
kubia-z5bc6 1/1 Running 0 14s
파드를 삭제한다
$ kubectl delete pod kubia-85kwf
pod "kubia-85kwf" deleted
삭제된 파드에 관한 레플리케이션컨트롤러 반응을 확인해보자
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubia-h8mrk 1/1 Running 0 3m18s
kubia-sh7x7 1/1 Running 0 42s
kubia-z5bc6 1/1 Running 0 3m18s
삭제한 파드는 종료 중이고, 새 파드는 생성되는걸 확인할 수 있다.
레플리케이션컨트롤러 정보를 살펴보자
$ kubectl get rc
NAME DESIRED CURRENT READY AGE
kubia 3 3 3 3m48s
decribe 명령을 통해 추가 정보를 볼 수 있다.
$ kubectl describe rc kubia
Name: kubia
Namespace: default
Selector: app=kubia
Labels: app=kubia
Annotations: <none>
Replicas: 3 current / 3 desired # 실제 의도하는 파드 인스턴스 수
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed # 파드의 상태별 파드 인스턴스 수
Pod Template:
Labels: app=kubia
Containers:
kubia:
Image: luksa/kubia
Port: 8080/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Events: # 레플리케이션컨트롤러와 관련된 이벤트
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 4m10s replication-controller Created pod: kubia-h8mrk
Normal SuccessfulCreate 4m10s replication-controller Created pod: kubia-z5bc6
Normal SuccessfulCreate 4m10s replication-controller Created pod: kubia-85kwf
Normal SuccessfulCreate 94s replication-controller Created pod: kubia-sh7x7
레플리케이션컨트롤러는 노드의 파드가 다운됐음을 감지하자마자 파드를 대체하기 위해 새 파드를 기동한다.
실습을 통해 살펴보자.
다음과 같이 gcloud compute ssh 명령을 사용해 노드 중 하나에 ssh로 접속한 다음 sudo ifconfig eth0 down을 사용해 네트워크 인터페이스를 종료한다.
$ gcloud compute ssh gke-kubia-default-pool-c2f41b01-37p5
// 노드 접속후 다음 명령어
$ sudo ifconfig eth0 down
네트워크 인터페이스를 종료하면 ssh 세션의 응답이 중단되므로, 다른 터미널을 열거나 ssh 세션을 강제 종료해야 한다. 새로운 터미널에서 노드를 조회하면 노드가 다운된 것을 쿠버네티스가 감지 했는지 확인 할 수 있다.
$ kubectl get node
# 첫번째 노드가 네트워크와 연결돼 있지 않기 때문에 준비돼 있지 않다.
NAME STATUS ROLES AGE VERSION
gke-kubia-default-pool-c2f41b01-37p5 NotReady <none> 32h v1.22.11-gke.400
gke-kubia-default-pool-c2f41b01-3gd0 Ready <none> 32h v1.22.11-gke.400
gke-kubia-default-pool-c2f41b01-wgt5 Ready <none> 32h v1.22.11-gke.400
노드가 몇 분 동안 접속할 수 없는 상태로 유지될 경우 해당 노드에 스케줄된 파드는 상태가 Terminating으로 변경된다. 이 때 레플리케이션 컨트롤러는 즉시 새 파드를 기동할 것이다.
$ kubectl get pods -o wide
# 노드에 연결할 수 없기 때문에 이 파드의 상태는 Terminating 이다.
# kubia-flrrv 파드가 생성되었다.
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kubia-flrrv 1/1 Running 0 3m9s 10.32.2.12 gke-kubia-default-pool-c2f41b01-3gd0 <none> <none>
kubia-h8mrk 1/1 Terminating 0 16m 10.32.1.18 gke-kubia-default-pool-c2f41b01-37p5 <none> <none>
kubia-sh7x7 1/1 Running 0 14m 10.32.0.11 gke-kubia-default-pool-c2f41b01-wgt5 <none> <none>
kubia-z5bc6 1/1 Running 0 16m 10.32.2.8 gke-kubia-default-pool-c2f41b01-3gd0 <none> <none>
노드를 되돌리려면 다음 명령으로 노드를 재설정해야 한다.
$ gcloud compute instances reset gke-kubia-default-pool-c2f41b01-37p5
노드가 다시 부팅되면 노드가 Ready 상태로 돌아오고, Terminating 상태 파드는 삭제된다.
레플리케이션컨트롤러는 레이블 셀렉터와 일치하는 파드만을 관리한다. 파드의 레이블을 변경하면 레플리케이션컨트롤러의 범위에서 제거되거나 추가될 수 있다. 한 레플리케이션컨트롤러에서 다른 레플리케이션컨트롤러로 이동할 수도 있다.
관리되는 파드에 레이블을 추가하더라도 레플리케이션컨트롤러가 정말로 상관하지 않는지 확인해보자.
$ kubectl label pod kubia-flrrv type=special
pod/kubia-flrrv labeled
파드 중 하나에 type=special 레이블을 추가했다 아래와 같다.
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
kubia-flrrv 1/1 Running 0 15m app=kubia,type=special
kubia-sh7x7 1/1 Running 0 26m app=kubia
kubia-z5bc6 1/1 Running 0 28m app=kubia
이제 레이블 app=kubia를 다른 것으로 변경해 보자. 이렇게하면 파드가 더 이상 레플리케이션 컨트롤러의 레이블 셀렉터와 일치하지 않게 돼 두 개의 파드만 일치하게 된다.
따라서 레플리케이션컨트롤러는 파드의 수를 세 개로 되돌리기 위해 새로운 파드를 시작해야 한다.
$ kubectl label pod kubia-flrrv app=foo --overwrite
pod/kubia-flrrv labeled
전체 파드를 다시 조회하면 네 개의 파드가 표시된다.
$ kubectl get pods -L app
NAME READY STATUS RESTARTS AGE APP
kubia-5bjl2 1/1 Running 0 24s kubia # 새롭게 생성된 파드
kubia-flrrv 1/1 Running 0 18m foo # 더 이상 레플리케이션 컨트롤러가 관리하지않는 파드
kubia-sh7x7 1/1 Running 0 29m kubia
kubia-z5bc6 1/1 Running 0 32m kubia
레플리케이션컨트롤러의 파드 템플릿은 언제든지 수정할 수 있다. 기존 파드를 수정하려면 해당 파드를 삭제하고 레플리케이션컨트롤러가 새 템플릿을 기반으로 새 파드로 교체하도록 해야 한다.
다음 명령을 사용해 레플리케이션컨트롤러를 편집할 수 있다.
$ kubectl edit rc kubia
그러면 기본 텍스트 편집기에서 레플리케이션컨트롤러 YAML 정의가 열린다. 파드 템플릿 섹션을 찾아 메타데이터에 레이블을 추가한다. 변경 사항을 저장하고 편집기를 종료하면 업데이트하고 다음 메시지를 출력한다.
replicationcontroller/kubia edited
이와 같이 레플리케이션컨트롤러의 파드 템플릿을 편집해 컨테이너 이미지를 변경하고 기존 파드를 삭제함으로써 새로운 템플릿 레이블을 사용해 기존 파드를 새로운 파드로 업그레이드 하는 데 사용할 수 있다.
레플리케이션컨트롤러의 스케일 업하는 방법으로 파드 인스턴스를 최대 10개로 수정한다고 해보자.
수정 방법은 두가지이다.
$ kubectl scale rc kubia --replicas=10
또는
$ kubectl edit rc kubia
편집기가 열리면 replias: 3을 10으로 바꾼다.
$ kubectl get rc
NAME DESIRED CURRENT READY AGE
kubia 10 10 10 49m
확인했으니, 다시 축소해보자
$ kubectl scale rc kubia --replicas=3
kubectl delet를 통해 레플리케이션컨트롤러를 삭제하면 파드도 삭제된다.
레플리케이션컨트롤러를 삭제할 때, 명령에 --cascade=false 옵션을 추가하면 레플리케이션컨트롤러만 삭제하고 해당 파드를 계속 실행 시킬 수 있다.
$ kubectl delete rc kubia --cascade=false
replicationcontroller "kubia" deleted
파드가 어디에도 속하지 않는 상태가 된다. 그러나 언제든 적절한 레이블 셀렉터를 사용하는 새 레플리케이션컨트롤러를 작성해 다시 관리할 수 있다.
레플리케이션컨트롤러를 완전히 대체할 차세대 레플리케이션컨트롤이다.
추후에 배울 디플로이먼트 리소스를 생성할 때 자동으로 생성되게 한다.
어쨋든 레플리카셋을 이해해야 하므로 레플리케이션컨트롤과 어떻게 다른지 확인해보자.
레플리케이션컨트롤러의 레이블 셀렉터는 특정 레이블이 있는 파드만을 매칭시킬수 있는 반면, 레플리카셋의 셀렉터는 특정 레이블이 없는 파드나 레이블의 값과 상관없이 특정 레이블의 키를 갖는 파드를 매칭시킬 수 있다.
또한 레플리카셋은 하나의 레플리카셋으로 두개의 파드 키값이 같은경우 두 세트를 모두 매칭시켜 하나의 그룹으로 취급할 수 있다.
kubia-replicaset.yaml
apiVersion: apps/v1 # 현재 레플리카셋은 vi api 일부이다.
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels: # 레플리케이션컨트롤러와 유사한 간단한 matchLabels 셀렉터를 사용한다.
app: kubia
template: # 템플릿은 레플리케이션컨트롤러와 동일하다.
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
kubectl create 명령을 사용해 생성후, 레플리카셋을 조회한다.
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
kubia 3 3 3 52s
kubia-replicaset-matchexpressions.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchExpressions: # 이 셀렉터는 파드의 키가 app인 레이블을 포함해야한다.
- key: app
operator: In
values: # 레이블의 값은 kubia 여야 한다
- kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
다음과 같이 셀렉터에 표현식을 추가할 수 있다.
여러 표현식을 지정하는 경우 셀렉터가 파드와 매칭되기 위해서는 모든 표현식이 true여야 한다. matchLabels와 matchExpression를 모두 지정하면, 셀렉터가 파드를 매칭하기 위해서는, 모든 레이블이 일치하고, 모든 표현식이 true로 평가돼야 한다.
레플리케이션컨트롤러를 삭제 하는 것 같은 방법으로 레플리카셋을 삭제할 수 있다.
$ kubectl delete rs kubia
replicaset.apps "kubia" deleted
클러스터의 모든 노드에, 노드당 하나의 파드만 실행되길 원하는 경우가 있을 수 있다.
예를 들면 모든 노드에서 로그 수집기와 리소스 모니터를 실행하는 경우, 또는 쿠버네티스의 kube-proxy 프로세스를 예를 들수 있다.
모든 클러스터 노드마다 파드를 하나만 실행하려면 데몬셋 오브젝트를 생성해야 한다. 데몬셋에 의해 생성되는 파드는 타깃 노드가 이미 지정돼 있고 쿠버네티스 스케줄러를 건너뛰는 것을 제외하면 이 오브젝트는 레플리케이션컨트롤러 또는 레플리카셋과 매우 유사하다. 파드가 클러스터 내에 무작위로 흩어져 배포되지 않는다.
예제를 진행해보자.
SSD를 갖는 모든 노드에서 실행돼야 하는 ssd-monitor라는 데몬이 있다고 가정해보자.
SSD를 갖고 있다고 표시된 모든 노드에서 이 데몬을 실행하는 데몬셋을 만든다.
노드 셀렉터를 사용해 데몬셋을 작성한다.
5초마다 표준출력으로 SSD OK를 출력하는 모의 ssd-monitor 프로세스를 실행하는 데몬셋을 생성한다. 미리 컨테이너 이미지를 준비했기 때문에 이미지를 사용하면 된다.
ssd-monitor-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector: # 파드 템플릿은 disk=ssd 레이블이 있는 노드를 선택하는 노드 셀렉터를 가진다.
disk: ssd
containers:
- name: main
image: luksa/ssd-monitor
luksa/ssd-monitor 컨테이너 이미지 기반으로 컨테이너를 한 개만 갖는 데몬셋을 정의한다. 이 파드의 인스턴스는 disk=ssd 레이블이 있는 각 노드에 생성될 것이다.
데몬셋 생성
$ kubectl create -f ssd-monitor-daemonset.yaml
daemonset.apps/ssd-monitor created
이제 disk=ssd 레이블을 노드 중 하나에 추가한다.
$ kubectl get node
]NAME STATUS ROLES AGE VERSION
gke-kubia-default-pool-c2f41b01-37p5 Ready <none> 33h v1.22.11-gke.400
gke-kubia-default-pool-c2f41b01-3gd0 Ready <none> 33h v1.22.11-gke.400
gke-kubia-default-pool-c2f41b01-wgt5 Ready <none> 33h v1.22.11-gke.400
$ kubectl label node gke-kubia-default-pool-c2f41b01-37p5 disk=ssd
node/gke-kubia-default-pool-c2f41b01-37p5 labeled
데몬셋으로 만든 파드 하나가 생성한 것을 볼 수 있다.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-flrrv 1/1 Running 0 76m
ssd-monitor-629hz 1/1 Running 0 54s
노드 레이블을 ssd에서 hdd로 변경해보자.
$ kubectl label node gke-kubia-default-pool-c2f41b01-37p5 disk=hdd --overwrite
node/gke-kubia-default-pool-c2f41b01-37p5 labeled
이전에 실행중인 파드가 Teminating되어 삭제가 된다.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-flrrv 1/1 Running 0 78m
ssd-monitor-629hz 1/1 Terminating 0 3m
데몬셋을 삭제하고 싶으면 다음과 같은 명령을 사용한다.(파드도 같이 삭제된다.)
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ssd-monitor 0 0 0 0 0 disk=ssd 11m
$ kubectl delete ds ssd-monitor
daemonset.apps "ssd-monitor" deleted
지금까지는 계속 실행돼야 하는 파드에 관해서만 이야기 했다. 작업을 완료한 후에는 종료되는 태스크만 실행하려는 경우가 있을 것이다.
쿠버네티스는 잡 리소스로 이런 기능을 지원한다. 파드의 컨테이너 내부에서 실행 중인 프로새스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는 파드를 실행할 수 있다. 일단 그렇게 되면 파드는 완료된 것으로 간주된다.
batch-job.yaml
apiVersion: batch/v1 # 잡은 batch api 그룹 v1버전에 속한다.
kind: Job
metadata:
name: batch-job
spec: # 파드 셀렉터를 지정하지 않았다(파드 템플릿의 레이블 기반으로 만들어진다.)
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure # 잡은 기본 재시작 정책을 사용할 수 없다.
containers:
- name: main
image: luksa/batch-job
kubectl create 명령으로 잡을 생성하면 즉시 파드가 시작된다.
$ kubectl create -f batch-job.yaml
job.batch/batch-job created
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
batch-job 0/1 51s 51s
$ kubectl get po
NAME READY STATUS RESTARTS AGE
batch-job-cv85s 1/1 Running 0 56s
시간이 지나면 더 이상 파드 목록에 표시되지 않고, 잡이 완료된 것으로 표시된다.
완료된 파드를 조회하면 Completed로 표시된다.
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
batch-job 1/1 2m4s 2m58s
$ kubectl get po
NAME READY STATUS RESTARTS AGE
batch-job-cv85s 0/1 Completed 0 2m54s
파드가 완료될 때 파드가 삭제되지 않는 이유는 해당 파드의 로그를 검사할 수 있게 하기 위해서다. 예를 들면 다음과 같다.
$ kubectl logs batch-job-cv85s
Mon Sep 5 15:55:12 UTC 2022 Batch job starting
Mon Sep 5 15:57:12 UTC 2022 Finished succesfully
잡은 두 개 이상의 파드 인스턴스를 생성해 병렬 또는 순차적으로 실행하도록 구성할 수 있다. 이는 잡 스펙에 completions와 parallelism 속성을 설정해 수행한다.
잡을 두번이상 실행해야 하는 경우 잡의 파드를 몇 번 실행할지를 completions에 설정한다.
multi-completion-batch-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: multi-completion-batch-job
spec:
completions: 5 # 5로 설정하면 이 잡은 다섯 개의 파드를 순차적으로 실행한다.
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
다섯 개의 파드가 성공적으로 완료할 때까지 계속한다.
파드 중 하나가 실패하면 잡이 새 파드를 생성하므로 잡이 전체적으로 다섯 개 이상의 파드를 생성할 수 있다.
잡 파드를 하나씩 차례로 실행하는 대신 잡이 여러 파드를 병렬로 실핼할 수도 있다.
잡 스펙의 parallelism 속성을 이용해 병렬로 실행할 파드 수를 지정한다.
multi-completion-parallel-batch-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: multi-completion-batch-job
spec:
completions: 5 # 이 잡은 다섯 개의 파드를 성공적으로 완료해야한다
parallelism: 2 # 두 개까지 병렬로 실행할 수 있다.
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
parallelism을 2로 설정하면 잡은 파드를 두 개 생성하고 병렬로 실행한다.
이 가운데 하나가 완료되면 다섯 개의 파드가 성공적으로 완료될 때 까지 잡이 다음 파일을 실행한다.
잡이 실행되는 동안 잡의 parallelism 속성을 변경할 수도 있다.
$ kubectl scale job multi-completion-parallel-batch-job --replicas 3
parallelism을 2에서 3으로 증가시켰기 때문에 다른 파드가 즉시 가동돼, 이제 세 개의 파드가 실행 중이다.
파드 스펙에 activeDeadlineSeconds 속성을 설정해 파드의 실행 시간을 제한할 수 있다. 파드가 이보다 오래 실행되면 시스템이 종료를 시도하고 잡을 실패한 것으로 표시한다.
잡 리소스를 생성하면 즉시 해당하는 파드를 실행한다. 그러나 많은 배치 잡이 미래의 특정 시간 또는 지정된 간격으로 반복 실행해야 한다. 리눅스나 유닉스 같은 운영체제에서 이런 작업을 크론 작업이라고 한다.
cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *" # 이 잡은 매일, 매시간 0, 15, 30, 45분에 실행한다.
jobTemplate: # 크론잡이 생성하는 잡 리소스의 템플릿
spec:
template:
metadata:
labels:
app: periodic-batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
크론의 스케줄 형식을 간단히 소개하자면 왼쪽에서부터 분/시/일/월/요일 이다
위 코드는 '분'을 나타내는 항목에 쉼표로 여러개의 '분'을 지정한 것이다.
잡 리소스는 대략 예정된 시간에 크론잡 리소스에서 생성된다. 그러면 잡은 파드를 생성한다. 잡이나 파드가 상대적으로 늦게 생성되고 실행될 수 있다. 예정된 시간을 너무 초과해 시작돼서는 안된다는 엄격한 요구 사항을 갖는 경우도 있다. 이런 경우 다음 예제처럼 크론잡 스펙의 startingDeadlineSeconds 필드를 지정해 데드라인을 설정할 수 있다.
apiVersion: batch/v1beta1
kind: CronJob
spec:
schedule: "0,15,30,45 * * * *"
startingDeadlineSeconds: 15 # 파드는 예정된 시간에서 늦어도 15초내 시작해야한다
...
예를 들어 10:30:00에 잡이 실행된 시간에 10:30:15까지 시작하지 않으면 잡이 실행되지 않고 실패로 돌아간다는 의미이다.