Kubernetes를 배워보자 8일차 - Pod Probe, ReplicationController, ReplicaSet, DeamonSet, Job, CronJob

1

kubernetes

목록 보기
8/13
post-custom-banner

Replication and other controllers: deploying managed pods

pod를 실행하고 계속해서 관리하는 데 있어서, 개발자가 직접 제어하는 것은 매우 번거로운 일이다. 자동으로 pod가 배포되고, 다운되면 다시 살려주는 리소스가 있으면 kubernetes 클러스터를 관리하는데 매우 쉬울 것이다. replication controller와 deployment라면 이것이 가능하다. 또한, pod를 실행하는 방법 중 한 가지 작업만 수행한 뒤 중지되는 파드도 존재한다.

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

pod는 kubernetes 클러스터에서 배포되기 시작하면, pod는 배포될 worker node가 할당되고 해당 worker node에서 pod의 컨테이너가 실행되도록 kubelet이 실행이 된다.

pod 리소스 시작 -> Woker Node 할당 -> Kubelet으로 container실행 -> pod 실행 

문제는 pod안의 컨테이너 중 하나가 에러가 발생하는 경우이다. kubelet은 컨테이너의 메인 프로세스가 죽는 상황이 발생하면, 이를 캐치하여 재시작해주는 로직이 있다. 그러나, 다음과 같이 container의 process가 죽는 문제가 아니라, 교착 상태, 메모리 누수와 같은 문제가 발생했을 때는 container의 process가 명백하게 죽었다는 시그널이 없어, kubernetes 클러스터는 이 사실을 알지 못한다. 때문에 application이 kubernetes에게 정상성을 알려주는 것이 아니라, application 외부인 kubernetes가 application의 상태를 체크하는 로직이 필요하다.

2. liveness probes

라이브니스 프로브는 pod의 컨테이너가 죽었는 지 살았는 지 확인하는 kubernetes에서 application으로 요청을 보내 정상성을 확보하는 로직을 제공해준다.

이후에 readiness probe도 배울텐데 이는 liveness probe와는 다르므로 주의하도록 하자.

kubernetes에서 3가지 매커니즘을 제공하여 container의 probe를 제공한다.
1. HTTP: http 방식으로 정해진 port, path에 맞게 GET요청을 보내고 2XX, 3XX status code가 오게된다면 성공이고, 이외의 status code가 온다면 실패한 것이므로 container를 kubelet에서 다시 시작하도록 로직을 수행한다.
2. TCP: 정해진 IP, port에 TCP연결 요청을 보내고 연결이 성공하면 성공이고, 실패한다면 container를 kubelet에서 다시 시작하도록 로직을 수행한다.
3. Exec: container내에서 정해진 명령어를 수행하여 결과 상태 코드가 0이면 성공이고 다른 코드가 발생하면 실패한 것으로 간주한다.

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

이제 HTTP 요청을 보내는 라이브니스 프로브를 만들어 pod를 배포해보도록 하자. luksa/kubia-unhealthy라는 이미지를 사용할 것이고, 이는 5번의 라이브니스 체크 요청은 정상적으로 체크하지만 그 이후는 500 status code를 반환하여 에러 상황을 만든다.

  • liveness-probe.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - name: kubia
    image: luksa/kubia-unhealthy
    livenessProbe:
      httpGet:
        path: /
        port: 8080

containerslivenessProbe를 넣어 httpGet을 만들어 넣으면 된다. 이는 liveness probe가 각 container마다 설정할 수 있다는 것을 알 수 있다.

4. 동작 중인 라이브니스 프로브 확인

이제 pod를 만들어보도록 하자.

kubectl create -f ./kubia-liveness-probe.yaml

다음으로 kubectl get pod -A를 통해서 pod의 정보를 확인해보도록 하자.

AMESPACE     NAME                               READY   STATUS    RESTARTS      AGE
default       kubia-liveness                     1/1     Running   6 (45s ago)   8m58s

계속해서 재시작된 것을 알 수 있다. 현재 로그를 확인하기 위해서는 kubectl logs로 확인할 수 있는데, 이전에 crash난 로그 정보를 알기 위해서는 --previous를 붙이면 된다.

kubectl logs kubia-liveness --previous

kubernetes cluster의 내용을 보기위해서 describe로 pod의 정보를 확인해보도록 하자.

kubectl describe pod kubia-liveness
...
  Normal   Killing         55m (x3 over 58m)      kubelet            Container kubia failed liveness probe, will be restarted
  Warning  BackOff         9m45s (x143 over 53m)  kubelet            Back-off restarting failed container kubia in pod kubia-liveness_default(2c02b75c-f83c-488a-9b9a-fb85b0b1d798)
  Warning  Unhealthy       4m12s (x49 over 58m)   kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500

계속해서 크래시가 발생하고 재시작되고를 반복하는 것을 알 수 있다. 이는 kubernetes에서 해당 container에 대한 liveness probe가 실패하여 container가 비정상적인 상태임을 감지해 재시작을 반복하였기 때문이다.

liveness probe에 추가적인 속성을 넣을 수 있는데, 가령 컨테이너가 시작된 후에 바로 컨테이너의 서버가 실행되지 않을 수 있다. 따라서, 이 delay시간을 설정할 수 있다.

livenessProbe:
    httpGet:
        path: /
        port: 8080
    initialDelaySeconds: 15

initialDelaySeconds가 15라면 초기에 컨테이너가 만들어지고 첫 번째 프로브 실행까지 15초를 delay하는 것이다.

5. ReplicationController

레플리케이션 컨트롤러는 쿠버네티스 리소스로 파드가 항상 실행되도록 보장한다. 즉, 실행 중인 파드 목록을 지속적으로 모니터링하고 의도한 파드 수와 동일한 수를 유지하도록 한다. pod의 특정 label만 맞으면 label selector에의해 레플리케이션 컨트롤러의 제어를 받게 된다.

레플리케이션 컨트롤러에는 3가지 요소가 있다.
1. label selector: 레플리케이션 컨트롤러의 범위에 있는 파드를 결정
2. replica count: 실행하려는 pod의 수
3. pod template: pod를 실행할 때의 template로 새로운 pod replica를 만들 때 사용한다.

레플리케이션 컨트롤러를 만드는 방법들은 다음과 같다.

  • 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

레플리케이션 컨트롤러는 kubernetes resource이기 때문에 ReplicationControllerkind를 적어준다. 레플리케이션 컨트롤러 3가지 요소는 다음과 같다.
1. spec.replicas: replica개수로 몇개의 pod를 지속적으로 만들고 관리할 지 결정한다. 위에서는 3개를 만들고 관리한다.
2. spec.selector: app: kubia는 label selctor로 app=kubia 라벨을 가진 pod를 관리한다는 것이다.
3. spec.template: 생성할 pod의 템플릿으로 metadatalabelapp: kubia로 갖는 것을 주목하도록 하자.

label selector를 지정하지 않는 것도 선택 가능한 옵션이다. selector를 지정하지 않으면 template의 label로 자동 설정된다. 사실 이러한 방식이 좀 더 간결한 yaml파일을 만들어낼 수 있다.

kubectl create -f 로 레플리케이션 컨트롤러를 생성할 수 있다.

kubectl create -f kubia-rc.yaml
replicationcontroller/kubia created

확인해보면 다음과 같다.

default            kubia-6r9n4                               0/1     ContainerCreating   0             3s
default            kubia-ggc6j                               0/1     ContainerCreating   0             3s
default            kubia-n9bgs                               0/1     ContainerCreating   0             3s

실제 3개의 pod가 생성된 것을 볼 수 있다. 이들이 레플리케이션 컨트롤러의 제어를 받으면 삭제해도 다시 추가 생성될 것이다.

kubectl delete pod kubia-n9bgs
pod "kubia-n9bgs" deleted

다시 생성되었는 지 확인해보도록 하자.

kubectl get po -A

default            kubia-6r9n4                               1/1     Running   0             9m30s
default            kubia-bws85                               1/1     Running   0             36s
default            kubia-ggc6j                               1/1     Running   0             9m30s

이전에는 없던 kubia-bws85 pod가 생성된 것을 볼 수 있다. 이는 replication controller가 replica개수 3개를 맞추기 위해 template에서 pod를 만들어낸 것이다.

kubectl get으로 레플리케이션 컨트러롤러의 정보를 얻을 수 있다.

kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
kubia   3         3         3       11m

DESIRED가 원했던 replica 수이고, CURRENT가 현재 pod의 개수이다. READY는 준비 상태인 pod가 몇개인지를 나타낸다.

더 자세한 정보는 kubectl describe 명령어로 확인할 수 있다.

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  12m    replication-controller  Created pod: kubia-n9bgs
  Normal  SuccessfulCreate  12m    replication-controller  Created pod: kubia-ggc6j
  Normal  SuccessfulCreate  12m    replication-controller  Created pod: kubia-6r9n4
  Normal  SuccessfulCreate  3m24s  replication-controller  Created pod: kubia-bws85

event부분을 통해서 현재까지 4개자의 pod가 생생되었고, 1개의 pod가 죽은 것을 알 수 있다.

재밌는 것은 pod를 일부러 삭제하는 경우가 아닌, node가 다운되는 경우 다운된 node에서 구동되던 pod를 중지시키고 새로운 pod를 동작시켜 replica수를 유지한다. node의 네트워크를 연결을 중단시키면 다음과 같이된다.

kubectl get nodes
NAME     STATUS     ROLES           AGE   VERSION
master   Ready      control-plane   22h   v1.28.2
node1    NotReady   <none>          22h   v1.28.2
node2    Ready      <none>          22h   v1.28.2

다음과 같이 node가 다운된 것을 볼 수 있다.

pod의 상태를 확인하면 node에 스케줄링되었던 pod들은 node에 연결이 안되었기 때문에 Unknown상태가 된다.

default            kubia-6r9n4                               0/1     Unknown     0             18m
default            kubia-bws85                               0/1     Unknown     0             9m32s
default            kubia-ggc6j                               1/1     Running     0             18m

이후 pod가 재생성되는 것을 볼 수 있다.

따라서, 노드에 장애가 발생한 경우, 인간의 개입없시도 시스템이 자동적으로 스스로 치유하는 것을 볼 수 있다.

재밌는 것은 replication controller의 지배를 받은 pod들은 label selector를 통해서 지배를 받는 것이기 때문에 label이 달라지면 replication controller의 영향에서 벗어날 수 있다. 즉 일반적인 pod가 되는 것이다.

레플리케이션 컨트롤러의 pod template는 언제든지 수정이 가능하다. 수정한 사항은 현재 배포된 pod에는 영향을 주지 않고, 새로 만들어질 pod에만 영향을 준다. 따라서, 기존 pod에 영향을 주고 싶다면 기존 pod를 삭제하여 다시 시작하도록 하는 것이 좋다.

kubectl edit rc kukia

template가 나오고 원하는대로 변경할 수 있다.

수평 파드 스케일링은 레플리케이션 컨트롤러가 항상 실행하도록 보장하는 복제본의 수를 늘렸다, 줄였다하는 것이다. replicas 필드 값을 변경하기만 하면 된다.

replicas를 5개로 늘려보도록 하자.

kubectl scale rc kubia --replicas=5

replicationcontroller/kubia scaled

조회해보면 다음과 같다.

default            kubia-6r9n4                               1/1     Running   1 (15m ago)   33m
default            kubia-bspcw                               1/1     Running   0             22s
default            kubia-bws85                               1/1     Running   1 (15m ago)   24m
default            kubia-ggc6j                               1/1     Running   0             33m
default            kubia-lgvpk                               1/1     Running   0             22s

이번에는 kubectl edit으로 replicas 수를 변경하도록 하자.

kubectl edit rc kubia

apiVersion: v1
kind: ReplicationController
metadata:
  creationTimestamp: "2023-09-19T13:11:23Z"
  generation: 2
  labels:
    app: kubia
  name: kubia
  namespace: default
  resourceVersion: "15714"
  uid: ffd5047f-c3cd-49b0-847a-b0fa81ab41f7
spec:
  replicas: 7
  selector:
    app: kubia
  template:
...

replicas를 5에서 7로 바꾼다음 저장하였다.

kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
kubia   7         7         7       35m

7개씩 늘어난 것으로 볼 수 있다.

kubectl edit보다는 kubectl scale과 같은 방식으로 스케일을 줄였다 늘였다하는 것이 편해보인다. 이는 선언적인 명령어 방식으로 kubernetes에게 원하는, 의도하는 상태를 지정하는 것이다.

줄이는 방법도 마찬가지이다.

kubectl scale rc kubia --replicas=3

replicationcontroller/kubia scaled

레플리케이션 컨트롤러의 삭제는 kubectl delete를 통해서도 가능하다. 레플리케이션 컨트롤러를 삭제하면 pod도 삭제된다. 그러나, 이를 원치않을 수 있다. 왜냐하면 레플리케이션 컨트롤러는 pod를 관리할 뿐 관리자가 죽는다고 pod까지 죽는 것을 원치않을 수 있기 때문이다. 이때 사용하는 option이 --cascade=false이다.

kubectl delete rc kubia --cascade=false
warning: --cascade=false is deprecated (boolean value) and can be replaced with --cascade=orphan.
replicationcontroller "kubia" deleted

이렇게 삭제하면 파드에 영향을 주지 않고 작업을 수행할 수 있으며, pod를 관리하는 replication controller가 없어도 서비스 중단없이 실행할 수 있다.

6. ReplicaSet

현재는 레플리케이션 컨트롤러를 거의 사용하지 않는다. 이유는 레플리카셋이 레플리케이션 컨트롤러를 거의 대체할 수 있으며 좀더 풍부한 표현식을 하는 파드 셀렉터를 갖고 있다. 더 나아가 나중에 배울 deployment에서는 ReplicaSet을 자동으로 만들어주어 직접 ReplicaSet을 만드는 일은 거의 없다. 그러나 학습에 있어서의 가치는 매우 풍부하다.

ReplicaSet은 특정 레이블의 키만으로도 pod를 매칭시킬 수 있다. 즉, replication controller가 key=value형식이 완전히 매칭되는 label selector를 갖는 반면에 ReplicaSet은 pod의 label이 지정한 key만 가지고 있어도 관리의 대상이 된다.

가장 대표적으로 replication controller는 env=prodenv=dev인 label을 동시에 매칭시킬 수 없다. 즉, label로 env key를 pod를 5개만 유지시키고 싶어도 env=prod, env=dev 각각 5개 씩만 유지가 가능하다는 것이다. 합쳐서 5개는 불가능하다. 반면 ReplicaSet은 env key 하나로 그룹핑을 할 수 있어서 env key를 가진 pod를 전체 5개만 유지가 가능하다.

ReplicaSet정의는 다음과 같다. kubia-replicaset.yaml파일을 만들어보도록 하자.

  • kubia-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia

ReplicaSetv1 API의 일부가 아니므로 API그룹, API버전을 명시해야해서 apps/v1로 API버전을 표현해야한다. apiVersion은 다음의 두 가지로 이루어져 있다.
1. API 그룹 (apps)
2. API 버전 (v1)

core API에 속하는 쿠버네티스 리소스들은 apiVersion에서 API그룹을 생략할 수 있어서 v1만 써줘도 되는 것을 볼 수 있다. 가령 pod의 경우는 v1만 써줘도 되었다.

selector.matchLabels부분이 ReplicationController와 다른 부분으로 labelkey만 매칭되어도 pod의 개수를 manipulation할 수 있다.

이제 생성해보도록 하자. 이전에 생성했던 ReplicationController이 삭제되었지만 pod는 그대로 유지되어 있을 것이다. 따라서, ReplicaSet을 생성한다고 해서, 새롭게 pod가 이루어지지 않을 것이다.

kubectl create -f ./kubia-replicaset.yaml
replicaset.apps/kubia created

kubectl describe rs
Name:         kubia
Namespace:    default
Selector:     app=kubia
Labels:       <none>
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:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:           <none>

어떤 파드도 새롭게 생기지 않은 것을 볼 수 있다. 이는 이전에 만들었던 ReplicationController에 의해 만들어진 3개의 pod가 모두 labelapp을 갖고 있기 때문이다.

이전에 레플리카셋이 레플리케이션 컨트롤러보다 더 다양한 label selector를 가진다고 했다. 이를 위해서는 selector.matchLabels가 아니라 selector.matchExpressions을 사용해야한다.

selector:
  matchExpressions:
    - key: app
      operator: In
      values:
        - kubia
        - gyu
        ...

selector.matchExpressions는 3가지 요소로 이루어져 있는데, keylabel의 key를 의미히고 operatorlabel의 key와 value의 관계를 말한다. In이라고 표현하면 keyvalue들 중에 하라도 매칭된다면 성립한다는 것을 의미한다. valuesvalue의 리스트로 app=kubia, app=gyu로 만들 수 있다.

operator는 다음 4가지 유효한 연산이 있다.
1. In: 레이블의 value가 하나라도 일치해야한다.
2. NotIn: 레이블의 값이 지정된 값과 일치하지 않아야 한다.
3. Exists: value는 중요하지 않고 key만 일치하면 된다는 것이다. 따라서 values필드에는 아무것도 지정하지 않는다.
4. DoesNotExist: 파드에 지정된 key를 가진 레이블이 포함되어 있지 않아야 한다. 따아서 values필드를 지정하지 않아야 한다.

이제 ReplicaSet에 대해서 알아보았다. 삭제하는 방법은 delete를 사용하면 된다.

kubectl delete rs kubia
replicaset.apps "kubia" deleted

7. DaemonSet

ReplicationController, ReplicaSet의 경우는 정해진 replicas수를 유지하는 것이 관전이다. 따라서, replicas 수가 5개라면 3개의 node로 이루어진 cluster에서 각 pod들은 램덤하게 나누어져 노드에 배포된다. 가령 node1은 pod3개 node2는 pod2개 node3은 pod 0개로 이루어 질 수 있다. 이때 node1의 pod 1개가 다운되면 replicas 수가 현재 4개로 되며 1개가 node1, node2, node3 3개의 node 중 하나에 랜덤하게 배포된다. 즉, 다시 자신이 죽었던 node1에 배포되는 것이 아니다.

그런데, 모든 node에 걸쳐 하나의 pod가 실행되었으면 하는 경우들이 있다. 가령, log 수집기라던지, 모니터링 프로그램이라면 각 node마다 한 개씩 파드가 있어야 한다. 이를 위해서는 ReplicaSet을 사용할 것이 아니라, DaemonSet을 사용해야한다. Daemon이라는 말은 사용자가 직접 제어하지 않고 백그라운드에서 돌면서 여러 작업을 하는 프로그램을 말한다. DaemonSet역시도 사용자가 직접 제어하는 것이 아니라, node마다 지정한 pod를 백그라운드에서 실행하도록 하는 것이다.

따라서, DaemonSetReplica수를 를 유지하지 않고 각 node마다 1개씩 유지한다. 따라서 node1에서 죽은 podDaemonSet의 관할을 받는다면 해당 pod는 node1에서 다시 부활하지 node2에서 실행되진 않는다.

물론, DeamonSet의 제어를 받을 node를 선택할 수도 있다. 이를 위해서는 node-selector를 사용하여 node를 지정하면 된다.

참고로, DaemonSet에 의해 관리받는 pod들은 스케줄링을 받지 않는데, 이는 DaemonSet이 관리하는 pod들은 스케줄러와 무관하게 동작한다는 것이다. 즉, 일반적으로 스케줄링되지 않는 노드에서도 시스템 서비스를 실행할 수 있어야 하기 때문에 DaemonSet을 사용하는 것이 바람직하다.

만약, node1, node2가 있는데 node1에서는 ssd로 구동되어 ssd-monitor라는 daemon이 실행되어야 한다고 하자. 그렇다면 DeamonSetnodeSelector를 이용하여 node를 지정하면 된다. 정리하면 다음과 같다.

node1 (ssd) -> ssd-monitor 대상
node2 (hdd) -> 대상 x

이를 위해서 node1disk: ssd라는 label을 만들어주도록 하자.

kubectl get nodes
NAME     STATUS   ROLES           AGE   VERSION
master   Ready    control-plane   12d   v1.28.2
node1    Ready    <none>          12d   v1.28.2
node2    Ready    <none>          12d   v1.28.2

kubectl label nodes node1 disk=ssd
node/node1 labeled

다음으로 DaemonSet을 만들어주도록 하자.

  • 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
      containers:
      - name: main
        image: luksa/ssd-monitor

DamonSet역시도 apps/v1으로 apiVersion을 가진다. 또한, spec에서 selector를 가지는데, 이는 생성할 template 파드의 label을 말한다. templateDamonSet으로 생성할 pod를 말하며 nodeSelector를 통해서 어떤 nodedamon으로 동작할 지 결정할 수 있다.

다음의 명령어로 DamonSet을 생성해보도록 하자.

kubectl create -f ./ssd-monitor-daemonset.yaml
daemonset.apps/ssd-monitor created

kubectl get daemonsets.apps -A
NAMESPACE       NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-system   calico-node       3         3         3       3            3           kubernetes.io/os=linux   12d
calico-system   csi-node-driver   3         3         3       3            3           kubernetes.io/os=linux   12d
default         ssd-monitor       1         1         1       1            1           disk=ssd                 2m12s
kube-system     kube-proxy        3         3         3       3            3           kubernetes.io/os=linux   12d

ssd-monitor damonSet이 잘만들어진 것을 확인할 수 있다. 그렇다면 node1의 label이 disk=ssd이기 때문에 제대로 pod가 한 개만 만들어졌는 지 확인해보도록 하자.

kubectl get po
NAME                READY   STATUS    RESTARTS   AGE
ssd-monitor-wsm78   1/1     Running   0          3m

node는 두 개지만 하나만 생성된 것을 확인할 수 있다. 그렇다면 node1disk=ssd라벨을 삭제해보면 어떨까? pod가 다운되는 지를 확인해보도록 하자.

kubectl label nodes node1 disk-
node/node1 unlabeled

다음으로 pod의 상태를 확인하면

kubectl get po
NAME                READY   STATUS        RESTARTS   AGE
`ssd-monitor-wsm78`   1/1     Terminating   0          4m45s

ssd-monitor-wsm78가 다운되는 것을 볼 수 있다.

8. Job

레플라케이션 컨트롤러와 레플리카셋, 데몬셋의 경우는 계속해서 pod를 관리하고 구동한다. 그러나 클러스터를 구성하는데 있어 초기 설정에만 잠시 실행되고, 특정 테스크를 완료하면 종료되어야 할 프로그램도 있다. 이런 경우 kubernetes에서는 job이라는 리소스를 통해서 구현할 수 있다. job은 pod를 생성하지만 pod가 일정 테스크를 완료하면 container가 종료되고 모든 container가 종료되면 pod도 종료된다. 모든 컨테이너가 실행이 완료되어 종료되었다면 성공적으로 pod가 실행에 완료된 것으로 간주되는 것이다. 이후에는 다시 실행되지 않는다.

한 가지 오해하면 안될 것은, cluster가 배포될 때 job이 가장 먼저 실행되고 다른 pod들이 이를 기다리는 것은 아니다. job이 한 번 실행되고 정상적으로 실행되었다면 종료된다는 것이 중요한 것이다.

만약, job에서 만들어낸 pod가 실행중에, node가 다운되거나 장애가 발생하면 replicaSet처럼 다시 다른 node에 해당 pod를 다시 실행시킨다. container의 종료코드가 에러로 반환되면 다시 실행시킬 수도 있는데 이 경우는 yaml파일에서 restartPolicy를 설정해야 한다.

job을 통해서 데이터를 어딘가에 저장하고, 데이터를 변환하여 전송할 수 있는데 우리의 경우는 2분간 sleep을 하는 job을 만들어보도록 하자.

  • exporter.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  template:
    metadata:
      labels:
        app: batch-job
    spec:
      restartPolicy: OnFailure
      containers:
      - name: main
        image: luksa/batch-job

apiVersion: batch/v1라는 것에 집중하도록 하자.

spec부분에서 replicaSet처럼 template를 만드는데 selector필드가 생략된 것을 볼 수 있다. selector를 써주어 label을 명시할 수 있지만, 만약 생략한 경우는 templatemetadata.labels를 타겟으로 자동 생성된다.

restartPolicy는 오직 OnFailureNever만 가능하다. job은 컨테이너 프로세스를 실행한 다음 종료되기 때문에 Always는 불가능하다.

이제 job을 실행해보도록 하자.

kubectl create -f ./batch-job
job.batch/batch-job created

kubectl get jobs.batch
NAME        COMPLETIONS   DURATION   AGE
batch-job   0/1           21s        21s

아직 컨테이너의 sleep이 2분이 지나지 않았기 때문에 2분간 기다리도록 하자.

2분이 지나면 다음과 같이 나오게 된다.

kubectl get jobs.batch
NAME        COMPLETIONS   DURATION   AGE
batch-job   1/1           2m3s       2m4s

kubectl get po -A
NAMESPACE          NAME                                      READY   STATUS      RESTARTS       AGE
default            batch-job-8bxhg                           0/1     Completed   0              2m26s

pod가 Completed상태가 되고 삭제되지 않은 이유는 해당 pod의 로그를 확인하기 위함이다. 즉, 로그를 확인할 수 있다.

이외에도 job의 pod를 여러번 실행하는 경우 completions로 순차적으로 몇 번 실행할 지 결정할 수 있으며 activeDeadlineSeconds로 job이 전체적인 pod 수행에 있어 제한 시간을 걸 수 있다. 가령 100s로 잡으면 100초 후에 모든 job의 모든 컨테이너가 실행이 완료되지 않으면 type: Failed가 되며, reason: DeadlineExceeded가 된다. 이외에도 job을 사용하는 여러 방법들이 있다. https://kubernetes.io/docs/concepts/workloads/controllers/job/

job을 삭제해보도록 하자.

kubectl delete -f ./batch-job
job.batch "batch-job" deleted

9. CronJob

job은 배포되면 바로 즉시 pod를 실행한다. 정상적으로 실행된 이후에는 다시 실행되지 않는다. 그러나 특정한 프로세스들은 설정된 시간마다 반복적으로 수행되고 종료되고를 반복해야하는 경우가 있다. 대표적으로 cron작업이 그렇다. 가령, 15분마다 임시 파일의 정보를 압축하여 다른 서버에 전송하고 삭제하기를 반복한다면 일반적은 job으로는 불가능하고 cron작업으로 job을 수행해야한다. kubernetes에서는 cron작업을 위해서 CronJob리소스를 제공한다. CronJob은 Job이지만 정해진 시간에 실행되고 종료되기를 반복한다.

15분마다 job을 수행하는 CronJob을 만들어보도록 하자.

  • cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: batch-job-every-fifteen-minutes
spec:
  schedule: "0,15,30,45 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: periodic-batch-job
        spec:
          restartPolicy: OnFailure
          containers:
          - name: main
            image: luksa/batch-job

apiVersionbatch/v1이며 spec에 cron작업에 필요한 schedule을 표현할 수 있다. 이는 일반적인 리눅스 cron과 같으며 간단하게 설명하면 다음과 같다.

spec:
  schedule: "분 시 일 월 요일"

위의 예제는 0분, 15분, 30분 ,45분 마다 매시, 매일, 매월, 매요일마다 실행되라는 의미이다.

만약 매달 첫째날 30분마다 실행하고 싶다면 다음과 같이 쓰면 된다.

spec:
  schedule: "0,30 * 1 * *"

spec.jobTemplate필드는 CronJob에서 쓰일 job리소스를 지정하는 부분이다.

CronJob역시도 Job과 같이 timeout 등을 설정할 수 있다. startingDeadlineSeconds을 설정하면 job이 실행되고 나서 n초가 지난 후까지 job이 완료되지 않으면 실패로 표시하는 것이다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: batch-job-every-fifteen-minutes
spec:
  schedule: "0,15,30,45 * * * *"
  startingDeadlineSeconds: 15

다음은 15초가 지난 후에도 job이 완료되지 않으면 job이 실패했다고 표시하는 것이다.

이제 15분마다 CronJob이 잘 실행되었는 지 확인해보도록 하자.

kubectl get po -A
NAMESPACE          NAME                                             READY   STATUS      RESTARTS      AGE
default            batch-job-every-fifteen-minutes-28269600-7jjdg   0/1     Completed   0             15m
default            batch-job-every-fifteen-minutes-28269615-hkmg9   1/1     Running     0             27s
...

15분 후에 Completed된 pod이외에 새로운 pod가 생겨나는 것을 볼 수 있다.

post-custom-banner

0개의 댓글