[Kubernetes in Action] Replication and Other Controllers

2021. 9. 26. 22:25DevOps/Docker & Kubernetes

Kubernetes in Action 2nd Edition을 정리한 글입니다.

 

 

 

Overview

이전 포스팅에서 쿠버네티스의 Basic Building Block인 파드에 대해서 자세하게 살펴보았습니다. 이번 포스팅에서는 파드를 생성하고 관리하는 쿠버네티스 매커니즘인 ReplicationController, ReplicaSet, DaemonSets에 대해서 살펴보려고 합니다.

 

 

Keeping Pods Healthy

쿠버네티스의 장점 중 하나는 사용자가 제공하는 컨테이너들의 정보(어떤 컨테이너를 몇개씩 배포할 것인지)를 쿠버네티스에 제공하기만 하면 쿠버네티스가 이 정보대로 컨테이너들이 동작하도록 "보장"한다는 것입니다. 즉 사용자 입장에서는 파드를 생성한 후에 매뉴얼하게 이를 모니터링하고, 파드를 재시작하는 등의 불필요한 동작을 할 필요가 없다는 것입니다. 

 

 

Liveness probes

파드가 워커 노드에 스케쥴되면 해당 워커 노드의 Kubelet이 컨테이너를 동작시킵니다. 만약에 컨테이너의 메인 프로세스에서 크래시가 발생하면 Kubelet이 컨테이너를 재시작합니다. 하지만 실제 애플리케이션에서는 메인 프로세스에서 크래시가 발생하지 않더라도 Deadlock이나, infinite loop 등의 이슈로 애플리케이션이 제대로 된 동작을 하지 못하는 경우가 생기게 됩니다. 따라서 이러한 경우에도 Kubelet이 컨테이너를 재시작할 수 있도록 하기 위해서는 Application의 HealthCheck를 애플리케이션 바깥에서 해주어야 합니다. (애플리케이션이 자체적으로 에러를 던져서 프로세스를 종료시키는 것에 의존해서는 안된다는 의미입니다.)

 

 

쿠버네티스는 "Liveness Probes"를 사용해서 컨테이너의 Health Check를 수행합니다. 수행하는 방식에는 크게 3가지가 있으나 가장 자주 사용하는 방식인 HTTP만 다루도록 하겠습니다. HTTP방식의 Health Check를 사용하면 Health Check endpoint Api에 GET 요청을 보냈을 때 에러 코드 형식인 400~500번대 에러가 리턴되는 경우 Unhealthy하다고 판단하고 해당 파드 내의 컨테이너를 재시작합니다. 

 

 

실습을 위해 Kubernetes in Action 교재에서 제공하는 "luksa/kubia-unhealthy" 이미지를 사용하겠습니다. 이 이미지를 사용해 컨테이너를 실행하면 매 5번째 request마다 500 internal Server 에러를 리턴하게 됩니다. 아래 LivenessProbe에 HealthCheck endpoint(이 서버에서는 root path입니다. 일반적으로는 /health 등의 명시적인 엔드포인트를 사용합니다.)와 포트를 설정해 줍니다.

 

 

// liveness.yaml
apiVersion: v1
kind: Pod
metadata:
  name: yeoul-liveness
spec:
  containers:
  - image: luksa/kubia-unhealthy
    name: yeoul
    livenessProbe:
      httpGet:
        path: /
        port: 8080
kubectl apply -f liveness.yaml

 

 

실제로 파드를 생성하면 쿠버네티스는 default 값인 10초마다 서버의 8080포트의 '/' Path로 HTTP Get 요청을 보내게 되고 따라서 50초마다 에러를 받아서 파드내의 해당 컨테이너를 재시작합니다. (RESTARTS 값 참조). HealthCheck 주기와, InitialDelaySeconds등의 옵션은 쿠버네티스 공식 문서를 참고해주세요.

 

 

 

 

 

쿠버네티스가 컨테이너를 재시작하게 되면 완전히 새로운 컨테이너가 생성되게 됩니다. 따라서 새롭게 생성된 컨테이너에서는 이전 컨테이너가 왜 오류가 났는지에 대한 로그를 확인할 수 없으므로, "--previous" 옵션을 사용해서 이전 컨테이너의 에러 로그를 확인해야 합니다.

 

 

kubectl logs yeoul-liveness --previous

 

 

Wrap up

정리하면, 쿠버네티스는 컨테이너의 메인 프로세스에서 크래시가 발생하거나 Liveness Probe가 실패했을 때, 컨테이너를 재시작 함으로써 사용자의 컨테이너가 항상 제대로 동작할 수 있도록 보장합니다. 이는 해당 파드를 호스팅하는 노드의 Kubelet에서 수행되기 때문에 Kubernetes 마스터 노드의 Control Plane과는 아무런 연관이 없는 행위입니다. 따라서 해당 파드가 배포된 노드 자체에서 크래시가 발생할 경우 컨테이너의 정상 동작을 보장할 수 없게 됩니다. 

 

 

이러한 이유로 Liveness Probe만으로는 파드의 정상동작을 항상 보장할 수는 없으며 이를 위해서 ReplicationController가 등장하게 됩니다. (Deployment도 있으나 이는 추후 다른 포스팅에서 다루도록 하겠습니다.)

 

 

Introducing ReplicationControllers

ReplicationController(이하 RC)는 파드가 "항상" 제대로 동작할 수 있도록 보장하는 쿠버네티스 리소스입니다. RC를 사용하면 노드에서 크래시가 발생하더라도 다른 노드에서 해당 파드를 재시작함으로써 파드의 실행을 보장합니다. 또한 이름에서 알 수 있듯(Replication) 파드의 복사본의 개수를 명시하면 항상 해당 개수의 파드가 동작하는 것을 보장함으로써 Horizontal Scaling도 지원합니다.

 

 

 

 

 

ReplicationController's Reconcilation Loop

RC는 크게 다음 3가지 요소로 구성됩니다.

  • label selector: RC에 속한 Pod를 가려내기 위한 selector
  • replica count: 사용자가 명시한 Pod의 복사본 개수
  • pod template: 새로운 파드 복사본을 생성할때 필요한 template

 

 

RC를 생성하는 아래의 yaml 파일을 보면 replicas, selector, template 필드를 확인하실 수 있습니다. luksa/kubia라는 이미지를 사용하는 app:yeoul 레이블을 가진 파드 템플릿을 사용하며 replica(파드의 복제본)의 개수가 3개로 명시되어 있습니다. 실제로 해당 RC yaml 파일을 실행하면 다음과 같이 3개의 파드가 생성됩니다. 

 

 

// rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: yeoul
spec:
  replicas: 3
  selector:
    app: yeoul
  template:
    metadata:
      labels:
        app: yeoul
    spec:
      containers:
        - name: yeoul
          image: luksa/kubia
          ports:
            - containerPort: 8080

 

 

 

 

제일 위에 있는 파드를 지우더라도 RC가 새로운 파드를 생성하는 것을 확인할 수 있는데 이는 RC가 아래의 매커니즘을 따라 yaml파일에 기술된 대로 항상 3개의 파드를 유지하려고 하기 때문입니다. 파드가 지워질 때 RC는 해당 사실을 알게 되고, 이는 RC로 하여금 실제 파드의 개수와 설정된 파드의 개수를 비교하여 부족한 만큼 파드를 생성하는 과정을 통해 항상 올바른 개수의 파드를 유지합니다. 

 

 

 

 

 

Moving pods in and out of the scope of a ReplicationController

파드 자체는 RC와 묶여있지 않습니다. 오로지 label에 한정된 개념이기 때문에 label을 변경하면 해당 파드는 더 이상 RC의 통제를 받지 않으며, RC는 설정 조건을 맞추기 위해 템플릿으로부터 새로운 파드를 생성합니다.

 

 

제일 위의 파드의 Label을 app=yeoul-test로 수정해보겠습니다. 파드의 레이블 수정은 kubectl label command를 사용하면 됩니다. yeoul-test로 레이블이 바뀐 파드가 바로 RC에서 빠져나가고, RC는 설정의 Replication조건을 맞추기 위해 바로 새로운 파드를 생성한 것을 확인할 수 있습니다.

 

 

 

 

이와 반대로 RC의 labelSelector를 변경하는 것도 가능합니다. 하지만 이렇게 할 경우 기존의 RC에 속해있는 파드가 모두 RC의 Scope 밖으로 벗어나기 때문에 해당 파드들을 모두 Manual하게 삭제해주어야 합니다.

 

 

Using ReplicaSets

위에서 열심히 RC에 대해서 설명했지만 Kubernetes에서는 ReplicaSet의 사용을 권장합니다. ReplicaSet은 RC의 새로운 버전으로서의 역할을 수행하며, 결국에는 Deprecated될 것입니다. ReplicaSet(이하 RS)는 RC와 사실상 동일한 개념이지만 RC에서 한정적으로 제공했던 LabelSelector기능을 조금 더 구체적으로 사용할 수 있도록 도와줍니다. 

 

 

RS를 생성하기 위한 yaml 파일도 RC의 yaml 파일과 거의 동일합니다. 차이점은 labelSelector 대신에 matchLabels 옵션이 생겼는데, 이는 기존 RC가 정확하게 해당 label을 가지고 있는 Pod만 Select할 수 있었던 것 과는 다르게 특정 label들 중 하나라도 있는지, 특정 label이 존재하지 않는지, 등의 조금 더 복합적인 상황에서의 selection을 가능하게 도와줍니다. 자세한 설정은 쿠버네티스 공식문서를 참고해 주세요 

 

 

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

 

 

DaemonSets

RC와 RS모두 특정한 개수의 파드를 Kubernetes cluster의 워커노드의 특정 위치에 상관없이 배포하는 것을 가능하게 합니다. 하지만 Log Collector나 Resource Monitor등의 프로세스를 담은 파드를 모든 노드에 하나씩 배포해야 하는 경우가 있습니다. 이 경우를 위해 Kubernetes에서는 DaemonSet을 제공합니다. (공식문서)

 

데몬셋은 그 정의상 (노드당 하나씩 파드 배포) TargetNode를 명시할 필요가 없습니다. 따라서 쿠버네티스 스케쥴러에 스케쥴 될 필요가 없습니다. 특정 노드가 크래시를 발생시켰을 때 해당 노드에 생성되었던 파드가 죽지만, 데몬셋은 다른 노드에 새로운 파드를 생성하지 않으며, 만약 새로운 노드가 생성되었을 경우에는 즉시 특정 노드에 파드를 "하나" 생성합니다.

 

 

RC, RS와 마찬가지로, 데몬셋도 selector를 통해 특정 노드에만 배포하는 것이 가능하며, 이는 node-selector를 통해 적용할 수 있습니다.

 

// dc.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

 

 

Reference

https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

 

Configure Liveness, Readiness and Startup Probes

This page shows how to configure liveness, readiness and startup probes for containers. The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch a deadlock, where an application is running, but unable t

kubernetes.io

 

반응형