Replication

유웅조·2020년 9월 8일
0

kubernetes

목록 보기
2/4

레플리케이션과 그 밖의 컨트롤러: 관리되는 파드 배포

실환경에서는 수동으로 명령어를 입려하며 파드를 관리하지 않고, 자동으로 실행되고 안정적인 상태를 유지하도록 한다.

그러기 위해서 Replication Controller 또는 Deployement 와 같은 유형의 리소스를 생성해 실제 파드를 생성하고 관리한다.

이번엔 쿠버네티스가 컨테이너를 모니터링하고 실패하면 자동으로 다시 시작하는 방법을 살펴볼 것이다.

노드 전체에 장애가 발생하면 노드에 있는 파드는 유실되며 이전에 언급한 레플리케이션 컨트롤러나 그와 유사한 기능을 하는 컨트롤러가 해당 파드를 관리하지 않는 한 새로운 파드로 대체되지 않는다.

파드 안정적으로 유지하기

쿠버네티스의 장점 중 하나는 쿠버네티스에 컨테이너 목록을 제공하면 해당 컨테이너를 클러스터 어딘가에서 계속 실행되도록 할 수 있다는 것이다.

파드가 노드에 스케줄링되는 즉시, 해당 노드의 Kubelet 은 파드의 컨테이너를 실행하고 파드가 존재하는 한 컨테이너가 계속 실행되도록 할 것이다. 컨테이너의 주 프로세스가 죽게 되면 Kubelete 은 컨테이너를 다시 시작한다.

그렇다면 프로세스의 크래시가 없이 중단되는 경우, 예를 들어 메모리 반환이 되지 않아 더이상 애플리케이션이 더 이상 제대로 동작하지 않게되면 어떨까. 또 애플리케이션이 무한 루프나 교착 상태에 빠져서 응답을 하지 않는 상황이라면 어떨까. 컨테이너 내부의 애플리케이션의 상태를 체크해야 할 필요가 있을 것이다.

Liveness Probe

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

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

  • HTTP GET 프로브는 지정한 IP 주소, 포트, 경로에 HTTP GET 요청을 수행한다. 만약 오류 코드를 반환하거나 혹은 아예 응답하지 않으면 컨테이너를 다시 시작한다.
  • TCP 소켓 프로브는 컨테이너의 지정된 포트에 TCP 연결을 시도한다. 만약 연결에 성공하지 한으면 컨테이너를 다시 시작한다.
  • Exec 프로브는 컨테이너 내의 임의의 명령을 실행하고 명령의 종료 상태 코드를 확인환다. 상태 코드가 0이면 성공, 이외의 코드는 모두 실패로 간주한다.

HTTP 기반 Liveness Probe

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

위와 같은 방식으로 아주 기본적인 HTTP liveness probe를 설정할 수 있다.

kubectl describe를 통해 라이브니스 프로브에 관한 추가적인 정보를 볼 수 있다.

http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3

delay: 컨테이너가 시작된 후 바로 프로브가 시작된다는 것을 의미
timeout: 제한 시간. 컨테이너가 제한 시간 안에 응답해야 한다.
period: 몇 초 간격으로 프로브를 수행할 것인가?
failure: 해당 횟수만큼 실패하면 다시 시작

위의 추가적인 매개변수들은 프로브를 정의할 때 지정할 수 있다. 또한 일반적인 경우 초기 지연을 설정하지 않게 되면 컨테이너가 시작하자마자 요청을 받을 준비가 안 되어 있는 상태에서 프로브가 실행되어 실패하는 경우가 생긴다.

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

효과적인 Liveness Probe

  1. 효과적으로 라이브니스 프로브를 구성하기 위해서는 특정 URL(엔드포인트의 생사 유무를 확인할 수 있는)에 요청을 보내 애플리케이션이 살아 있는지를 파악할 수 있을 것이다.

  2. 외부 환경에 영향을 받지 않도록 해야 한다.
    예를 들어, 마이크로서비스의 경우 특히 다른 서버의 요청을 받아 애플리케이션의 요청을 처리하는 경우가 있다면 해당 엔드포인트는 애플리케이션 자체의 문제 이외에도 요청에 실패할 경우가 있다. 따라서 이런 경우에는 애플리케이션 재시작이 해결책이 되지 않는다.

  3. 프로브를 가볍게 유지해야 한다.
    라이브니스 프로브는 자주 실행되어야 하고, 대부분 1초 내에 빠른 속도로 완료되어야 한다. 따라서 너무 많은 연산 리소스를 사용해서는 안 되고, 최대한 가볍게 유지해야 한다. 더불어 컨테이너의 CPU 가동 시간을 제한하는 경우엔 프로브가 무거울 경우, 실제 애플리케이션 프로세스에서 사용할 수 있는 CPU 사용 시간이 줄어들게 될 수도 있다.

  4. 프로브 자체에 재시도를 구현하지 않는다.

라이브니스 프로브는 컨테이너에 문제가 생겼을 경우 재시작을 할 수 있다는 측면에서 매우 유용하게 사용될 수 있다.

하지만 이 프로브는 노드의 Kubelet 에서 수행되는 것으로 만약 노드 자체가 중단될 경우 작동하지 않게 된다.

이때 중단된 모든 파드의 대체 파드를 생성해야 하는 것은 컨트롤 플레인의 몫이다.

애플리케이션이 다른 노드에서 다시 시작되도록 하려면 Replication Controller 또는 이와 유사한 메커니즘으로 파드를 관리해야 한다.

Replication Controller

레플리케이션컨트롤러는 쿠버네티스 리소스 중 하나로, 파드가 항상 실행되도록 보장한다.

클러스터에서 노드가 사라지거나, 노드에서 파드가 제거된 경우 레플리케이션컨트롤러는 항상 사라진 파드를 감지해 교체 파드를 생성한다.

기본 개념

먼저 레플리케이션컨트롤러가 시작되면 실행 중인 파드 목록을 모니터링하기 시작한다.

  1. 의도된 파드의 수를 지정한다.
  2. 레이블 셀렉터와 매치되는 파드를 찾는다.
    1. 매치된 파드의 수 == 의도된 파드의 수 => 다시 1번 작업을 수행한다.
    2. 매치된 파드의 수 < 의도된 파드의 수 => 현재 템플릿에서 파드를 추가 생성
    3. 매치된 파드의 수 > 의도된 파드의 수 => 초과하는 파드 삭제

위에서 보면 레플리케이션컨트롤러의 핵심적인 3가지 요소가 등장하는데

  1. 레이블 셀렉터: 레플리케이션 컨트롤러의 범위에 있는 파드를 결정한다. (ex: app=kubia)
  2. 레플리카 수: 실행될, 의도된 파드의 수
  3. 파드 템플릿: 새로운 파드 레플리카를 만들 때 사용되는 템플릿

레플리케이션컨트롤러의 핵심 요소 중에 레이블 셀렉터와 파드 템플릿은 변경해도 기존 파드에는 영향을 미치지 않는다.

레이블 셀렉터를 변경하게 되면 기존 파드가 레플리케이션컨트롤러의 범위를 벗어나므로 컨트롤러가 더이상 해당 파드를 관리하지 않는다.

또한 파드를 생성한 뒤에는 파드 내의 이미지, 환경변수 및 기타 콘텐츠에 신경을 쓰지 않기 때문에 파드 템플릿을 변경하게 되면 새로운 파드를 생성할 때만 영향이 있게 된다.

장점

레플리케이션컨트롤러를 사용했을 때의 장점은 다음과 같다.

  1. 기존 파드가 사라지면 새 파드를 시작해 파드가 항상 실행되도록 한다.
  2. 클러스터 노드에 장애가 발생하면 장애가 발생한 노드에서 실행 중인 모든 파드에 관한 교체 복제본이 생성된다.
  3. 수동, 자동으로 파드를 쉽게 수평으로 확장할 수 있다.

생성

apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: onikss793/kubia
        ports:
        - containerPort: 8080

기본적인 Replication Controller 생성 yaml 이다.

파일을 API 서버에 게시하면, 쿠버네티스는 레이블 셀렉터 app: kubia와 일치하는 파드 인스턴스가 3개가 되도록 유지하는 레플리케이션컨트롤러를 생성한다.

만약 셀렉터를 지정하지 않으면 템플릿의 레이블로 자동 설정된다. 이와 같은 방법이 더욱 간결하게 yaml을 유지하는 방법이다.

레이블 활용

특정 파드에 어떤 장애가 있어 디버깅을 해야 한다고 가정해보자. 파드의 오작동을 파악했다면 디버깅을 위해 해당 파드의 레이블 셀렉터를 변경하여 레플리케이션컨트롤러가 새 파드로 교체하도록 한 다음, 충분히 디버깅을 진행해볼 수 있을 것이다.

반대로 레플리케이션컨트롤러의 레이블 셀렉터를 변경했다면 어떻게 될까? 변경된 레이블 셀렉터로 새로운 파드를 생성하게 될 것이다. 그리고 기존에 사용하던 파드들은 레플리케이션컨트롤러의 관리에서 벗어나게 된다.

파드 템플릿 활용

파드 템플릿은 언제든지 수정할 수 있다. 바뀐 템플릿을 적용하려면 기존 파드를 삭제하고 레플리케이션컨트롤러가 새로운 템플릿 기반으로 파드를 생성하도록 해야 한다.

kubectl edit rc {ReplicationControllerName} 을 활용하면 기본 에디터로 yaml를 수정할 수 있다.

파드의 수평적 scale up

스케일업의 원리는 매우 간단한데, replica의 수를 늘리는 것이 그 방법이다.

kubectl edit rc ...를 통해 replicas의 수를 늘리거나, kubectl scale rc {ReplicationControllerName} --replicas={number}로 명령할 수도 있다.

Replication Controller 삭제

kubectl delete rc {ReplicationControllerName} 을 이용해 레플리케이션컨트롤러를 삭제할 수 있다. 기본적으로 해당 RC의 관리 하에 있는 파드들은 모두 같이 삭제되지만 --cascade=false 옵션을 이용하면 파드들은 살릴 수도 있다.

Replica Set

초기에는 레플리케이션컨트롤러가 파드를 복제하고 노드 장애가 발생했을 때 reschedule 할 수 있는 유일한 수단이었다. 하지만 차세대 레플리케이션컨트롤러인 레플리카셋이 등장하면서 앞선 기능들을 모두 대체할 것이다.

일반적으로 레플리카셋을 직접 생성하지는 않고, 디플로이먼트 리소스를 생성할 때 자동으로 생성되게 한다.

다른 점?

레플리카셋은 레플리케이션컨트롤러와 똑같이 동작하지만 좀 더 풍부한 표현식을 사용하는 파드 셀렉터를 갖고 있다.

기본적으로 레플리카셋은 특정 레이블이 없는 파드나 레이블의 값과 상관없이 특정 레이블의 키를 갖는 파드를 매칭시킬 수 있다(예를 들어, env 라는 키를 갖고 있는 레이블을 모두 그룹으로 묶는다, env=*).

또 레플리카셋은 하나의 레플리카셋으로 두 개의 파드 세트를 모두 매칭시켜 하나의 그룹으로 취급할 수 있다. (예를 들어, env=production, env=dev 이 두 개의 label을 하나의 그룹으로 취급할 수 있다)

기본 정의

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: onikss793/kubia
  • 왜 apiVersion이 책의 예시와 다를까...?

추가적으로

matchExpressions:
      - keys: app
        operator: In
        values:
          - kubia

처럼 다양한 방식으로 표현식을 구성할 수 있다는 것이 레플리카셋의 장점이다.

기본적인 연산자는 다음과 같다.

  1. In: 레이블의 값이 지정된 값 중 하나와 일치
  2. NotIn: 레이블의 값이 지정된 값과 불일치
  3. Exists: 파드는 지정된 키를 가진 레이블을 포함해야 함 (value를 지정하지 않음)
  4. DoesNotExist: 파드는 지정된 키를 가진 레이블을 포함하지 않아야 함

데몬셋을 사용해 각 노드에서 정확히 한 개의 파드 실행하기

레플리케이션컨트롤러, 레플리카셋은 쿠버네티스 클러스터 내 어딘가에 지정된 수만큼의 파드를 실행하는데 사용되는데, 만약 클러스터의 모든 노드에 노드 당 하나의 파드만 실행되길 원하는 경우엔 어떻게 할까?

예를 들면, 모든 노드에서 로그 수집기와 리소스 모니터를 실행하려는 경우가 있을 것이다. 혹은 kube-proxy 프로세스와 같은 경우도 있을 것이다.

데몬셋으로 파드 실행

모든 클러스터 노드마다 파드를 하나만 실행시키길 원할 경우 데몬셋 오브젝트를 생성해야 한다.

데몬셋에 의해 생성되는 파드는 타깃 노드가 이미 지정돼 있고, 쿠버네티스 스케줄러를 건너뛰는 것을 제외하면 레플리케이션컨트롤러, 레플리카셋과 매우 유사하다.

하지만 데몬셋은 각 노드마다 의도된 파드의 복제본 수가 정해진 것이 아니라 파드 셀렉터와 일치하는 파드가 각 노드에서 실행 중인지 확인하는 역할을 한다.

그렇기 때문에 노드가 다운되면 데몬셋은 다른 곳에서 파드를 생성하는 것이 아니라 새 노드가 클러스터에 추가되면 그때 새 파드 인스턴스를 새 노드에 배포한다.

이는 수동으로 노드 내의 데몬셋의 파드를 지워도 동일하다.

더불어 데몬셋도 파드 템플릿으로 파드를 생성한다.

특정 노드에서만 파드 실행

데몬셋을 설정할 때 특정 노드를 지정하지 않으면 클러스터의 모든 노드에 파드를 배포한다.

만약 노드를 지정하고 싶다면 파드 템플릿에서 node-selector 속성을 지정하면 된다.

예시) SSD를 갖는 모든 노드에 `ssd-monitor`라는 데몬이 있다고 가정할때 SSD를 갖는 노드에 disk=ssd 라는 레이블을 추가하여 해당 노드만 선택해 모니터링 파드를 배포할 수 있다. 

기본 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

완료 가능한 단일 태스크를 수행하는 파드

지금까지 계속해서 실행되어야 하는 파드에 대해서 알아보았다면 만약 실행된 후 종료되어야하는 파드의 경우에는 어떻게 해야 할까.

쿠버네티시는 이럴 경우를 위해 잡 리소스를 지원한다.

컨테이너 내부에서 실행 중인 프로세스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는 파드를 실행할 수 있다.

만약 노드에 장애가 생겼다면 해당 노드에 있던 잡이 관리하는 파드는 레클리카셋 파드와 같은 방식으로 다른 노드로 다시 스케줄링된다.

만약 컨테이너 내의 프로세스에 장애가 발생할 경우엔 잡에서 컨테이너를 다시 시작할 것인지 설정할 수 있다.

기본적인 잡 리소스 정의

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

이외에도

spec:
  completions: {number}
  template:
    ...

위처럼 지정한 갯수만큼의 파드를 순차적으로 실행할 수도 있다. 파드가 완료되면 그 다음 파드가 생성되는 방식이다.

spec:
  completions: {number}
  parallelism: {number}
  template:
    ...

반면 다음과 같이 parallelism을 지정해주면 원하는 만큼 같은 파드를 병렬로 실행할 수 있다. 또한 잡이 실행하는 동안에 병렬로 실행될 파드의 갯수를 늘릴 수도 있다.

만약 잡에서 설정한 파드가 일정 시간이 지나도 종료되지 않고, 정체 불명의 상태에 빠질 경우엔 어떻게 할까?

이럴 경우를 위해 activeDeadlineSeconds 속성을 통해 파드의 실행 시간을 제한할 수 있다. 파드가 이 지정 시간보다 오래 실행되면 시스템이 종료를 시도하고 잡을 실패한 것으로 간주한다.

크론잡

리눅스의 크론잡을 동일하게 쿠버네티스에서도 지원한다. 이를 잘 활용하면 정해진 시간마다 주기적으로 어떠한 작업이 실행되도록 할 수 있을 것이다.

기본적인 크론잡 yaml

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: batcdh-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

0개의 댓글