쿠버네티스 인 액션 - 레플리케이션과 그 밖에 컨트롤러 : 관리되는 파드 배포 (3)

hyeokjin·2022년 9월 5일
0

kubernetes

목록 보기
3/9
post-thumbnail

파드를 수동으로 생성, 감독, 관리하는 방법을 배웠지만 실환경에서는 배포한 애플리케이션이 자동으로 실행되고 수동적인 개입 없이도 안정적인 상태로 유지되길 원할 것이다.
이렇게 하기위해 파드를 직접 생성하는 일은 거의 없을 것이다. 대신 레플리케이션컨트롤러 또는 디플로이먼트와 같은 유형의 리소스를 생성해 실제 파드를 생성하고 관리한다.

파드를 안정적으로 유지하기

쿠버네티스를 사용하면 얻을 수 있는 주요 이점은 쿠버네티스에 컨테이너 목록을 제공하면 해당 컨테이너를 클러스터 어딘가에서 게속 실행되도록 할 수 있다는 것이다.
컨테이너 주 프로세스에 크래시가 발생하면 kubelet이 컨테이너를 다시 시작한다. 쿠버네티스가 애플리케이션을 자동으로 다시 시작하므로, 애플리케이션에서 특별한 작업을 하지 않아도 쿠버네티스에서 애플리케이션을 실행하는 것만으로도 자동으로 치유할 수 있는 능력이 주어진다.

그러나 때때로 애플리케이션은 프로세스의 크래시 없이도 작동이 중단되는 경우가 있다. 일례로 자바 애플리케이션이 메모리 누수가 있어서 OutofMemoryError를 발생시키기 시작하더라도 JVM 프로세스는 계속 실행될 것이다.
제대로 동작하지 않는다는 신호를 쿠버네티스에게 신호를 보내서, 쿠버네티스가 다시 시작하도록 하는 방법이 있으면 좋을 것이다.

예를 들어 애플리케이션이 무한 루프나 교착 상태에 빠져서 응답을 하지 않는 상황이라면, 이런 경우 애플리케이션이 다시 시작되도록 하려면 애플리케이션 내부의 기능에 의존하지 말고 외부에서 애플리케이션의 상태를 체크해야한다.

라이브니스 프로브 소개

쿠버네티스는 라이브니스 프로브를 통해 컨테이너가 살아 있는지 확인할 수 있다. 파드의 스펙에 각 컨테이너 라이브니스 프로브를 지정할 수 있다.
쿠버네티스는 주기적으로 프로브를 실행하고 프로브가 실패할 경우 컨테이너를 다시 시작한다.

쿠버네티스는 세 가지 메커니즘을 사용해 컨테이너 프로브를 실행한다.

  1. HTTP GET 프로브는 지정한 IP 주소, 포트, 경로에 요청을 수행한다. 프로브가 응답을 수신하고 응답 코드가 오류를 나타내지 않으면 프로브가 성공했다고 간주된다.
    서버가 오류 응답코드를 반환하거나 응답하지 않으면 프로브가 실패한 것으로 간주돼 컨테이너를 다시 시작한다.

  2. TCP 소켓 프로브는 컨테이너의 지정된 포트에 TCP 연결을 시도한다. 연결에 성공하면 프로브가 성공한 것이고, 그렇지 않으면 컨테이너가 다시 시작된다.

  3. Exec 프로브는 컨테이너 내의 임의의 명령을 실행하고 명령의 종료 상태 코드를 확인한다. 상태코드가 0이면 프로브가 성공한 것이다. 모든 다른 코드는 실패로 간주된다.

HTTP 기반 라이브니스 프로브 생성

이전에 만든 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

다음과 같이 셀렉터에 표현식을 추가할 수 있다.

  • In은 레이블의 값이 지정된 값 중 하나와 일치해야한다.
  • NotIn은 레이블의 값이 지정된 값과 일치하지 않아야한다.
  • Exists는 파드는 지정된 키를 가진 레이블이 포함돼야 한다. 이 연산자를 사용할 때는 값 필드를 지정하지 않아야한다.
  • DoesNotExist는 파드에 지정된 키를 가진 레이블이 포함돼 있지 않아야 한다. 마찬가지로 값 필드를 지정하지 않아야한다.

여러 표현식을 지정하는 경우 셀렉터가 파드와 매칭되기 위해서는 모든 표현식이 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까지 시작하지 않으면 잡이 실행되지 않고 실패로 돌아간다는 의미이다.

profile
노옵스를향해

0개의 댓글