쿠버네티스 인 액션 - 디플로이먼트 : 선언적 애플리케이션 업데이트 (8)

hyeokjin·2022년 9월 10일
0

kubernetes

목록 보기
8/9
post-thumbnail

쿠버네티스 클러스터에서 실행되는 애플리케이션을 업데이트 하는 방법과 쿠버네티스가 어떻게 무중단 업데이트 프로세스로 전환하는 데 도움을 주는지 살펴보자 쿠버네티스는 레플리카셋 기능을 활용하는 디플로이먼트 리소스를 제공해 선언적인 애플리케이션 업데이트를 가능하게 한다.

애플리케이션을 선언적으로 업데이트하기 위한 디플로이먼트 사용하기

디플로이먼트는 낮은 수준의 개념으로 간주되는 레플리케이션컨트롤러 또는 레플리카셋을 통해 수행하는 대신 애플리케이션을 배포하고 선언적으로 업데이트하기 위한 높은 수준의 리소스다. 실제 파드는 디플로이먼트가 아닌 디플로이먼트의 레플리카셋에 의해 생성되고 관리된다.

디플로이먼트 생성

디플로이먼트를 생성하는 것은 레플리케이션컨트롤러를 만드는 것과 다르지 않다. 레이블 셀렉터, 원하는 레플리카 수, 파드 템플릿으로 구성된다.
또한 디플로이먼트 리소스가 수정될 때 업데이트 수행 방법을 정의하는 디플로이먼트 전략을 지정하는 필드도 있다.

kubia-deployment-v1.yaml

apiVersion: apps/v1		# 디플로이먼트는 API 그룹 app의 v1 버전이다
kind: Deployment		# kind는 Deployment이다.
metadata:
  name: kubia			# 디플로이먼트 이름에 버전을 포함할 필요가 없다.
spec:
  replicas: 3
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
  selector:
    matchLabels:
      app: kubia

디플로이먼트 리소스 생성

이 디플로이먼트를 만들기 전에 실행 중인 레플리케이션컨트롤러와 파드를 삭제하고 kubia 서비스는 유지한다. 다음과 같이 --all 스위치를 사용해 모든 레플리케이션컨트롤러를 삭제한다

$ kubectl get svc
NAME                 TYPE           CLUSTER-IP   EXTERNAL-IP    PORT(S)        AGE
kubernetes           ClusterIP      10.36.0.1    <none>         443/TCP        4d3h
kubia-loadbalancer   LoadBalancer   10.36.5.98   34.83.16.205   80:30498/TCP   50s

$ kubectl delete rc --all

$ kubectl create -f kubia-deployment-v1.yaml --record
deployment "kubia" created

create를 사용할떄 --record 명령줄을 꼭 포함시키자. 이 명령은 개정 이력에 명령어를 기록해 나중에 유용하게 사용할수 있다.

디플로이먼트 롤아웃 상태 출력

일반적인 kubectl get deployment와 kubectl describe deployment 명령어를 사용해 디플로이먼트 세부 사항을 볼 수 있지만, 디플로이먼트 상태를 확인하기 위해 특별히 만들어진 다른 명령어를 사용해 보자.

$ kubectl rollout status deployment kubia
deployment "kubia" successfully rolled out

결과에 따르면 디플로이먼트의 롤아웃이 성공적으로 수행됐으므로, 파드 레플리카 세개가 시작돼 실행되고 있다.

$ kubectl get po
NAME                       READY   STATUS      RESTARTS   AGE
kubia-74967b5695-6vstm     1/1     Running     0          61s
kubia-74967b5695-dckxd     1/1     Running     0          61s
kubia-74967b5695-skfjk     1/1     Running     0          61s

디플로이먼트에서 생성한 파드 세 개에는 이름 중간에 숫자 값이 추가로 포함된다. 디플로이먼트와 파드 템플릿의 해시값을 의미하며 레플리카셋이 이러한 파드를 관리함을 뜻한다.

디플로이먼트에서 생성한 레플리카셋을 살펴보자.

$ kubectl get replicasets
NAME               DESIRED   CURRENT   READY   AGE
kubia-74967b5695   3         3         3       87s

레플리카셋의 이름에도 해당 파드 템플릿의 해시값이 포함된다. 파드 템플릿의 해시값을 사용하면 디플로이먼트에서 지정된 버전의 파드 템플릿에 관해 항상 동일한 레플리카셋을 사용할 수 있다.

이 레플리카셋에 의해 생성된 파드 레플리카 세 개는 지금 실행 중이므로 새 파드의 레이블 서비스의 레이블 셀렉터와 일치하게 되므로 이전에 생성한 서비스를 사용해 액세스할 수 있다.

디플로이먼트 업데이트

디플로이먼트 리소스에 정의된 파드 템플릿을 수정하기만 하면 쿠버네티스가 실제 시스템 상태를 리소스에 정의된 상태로 만드는 데 필요한 모든 단계를 수행한다. 레플리케이션컨트롤러 또는 레플리카셋을 스케일 업 또는 스케일 다운하는 것과 마찬가지로 디플로이먼트 파드 템플릿에서 새 이미지 태그를 참조해 시스템이 의도하는 상태가 될 수 있도록 쿠버네티스에 맡기면 된다.

새로운 상태를 달성하는 방법은 디플로이먼트 전략에 의해 결정된다.
기본은 RollingUpate라는 롱링 업데이트 전략이다. 대안으로 Recreate 전략이 있는데, 레플리케이션컨트롤러의 파드 템플릿을 수정한 후 모든 파드를 삭제하는 것과 마찬가지로 한 번에 기존 모든 파드를 삭제한 뒤 새로운 파드를 만든다. 애플리케이션이 여러 버전을 병렬로 실행하는 것을 지원하지 않고 새 버전을 시작하기 전에 이전버전을 완전히 중지해야 하는 경우 이 전략을 사용한다.
반면 RollingUpdate 전략은 이전 파드를 하나씩 제거하고 동시에 새 파드를 추가해 전체 프로세스에서 애플리케이션을 계속 사용할 수 있도록 하고 서비스 다운 타임이 없도록 한다. 애플리케이션에서 이전 버전과 새 버전을 동시에 실행할 수 있는 경우에만 이 전략을 사용한다.

데모 목적으로 롤링 업데이트 속도 느리게 하기

업데이트 프로세스를 약간 느려지게 해서 실제 업데이트가 롤링 방식으로 수행되고 있음을 확인하자. 디플로이먼트에서 minReadySecons 속성을 설정하면 된다. 10초를 설정한다.

$ kubectl patch deployment kubia -p '{"spec":{"minReadySeconds":10}}'
deployment.apps/kubia patched

kubectl patch 명령어는 텍스트 편집기에서 정의를 편집하지 않고도 리소스 속성 한 두개 정도를 수정하는데 유용하다.
파드 템플릿을 변경해야 파드가 업데이트 되고 롤아웃이 시작된다.

롤링 업데이트 시작

먼저 다른 터미널에서 curl 요청을 다시 실행해 어떻게 진행되는지 확인한다. (서비스의 외부 IP로 조회한다.)

$ while true; do curl http://34.83.16.205; done
This is v1 running in pod kubia-74967b5695-6vstm
This is v1 running in pod kubia-74967b5695-dckxd
This is v1 running in pod kubia-74967b5695-6vstm
This is v1 running in pod kubia-74967b5695-skfjk
...

롤아웃을 시작하려면 파드 컨테이너에 사용된 이미지를 luksa/kubia:v2로 변경한다. 디플로이먼트 오브젝트의 전체 YAML을 편집하거나 patch 명령어를 사용해 이미지를 변경하는 대신 kubectl set image 명령어를 사용해 컨테이너가 포함된 모든 리소스를 수정할 수 있다.

$ kubectl set image deployment kubia nodejs=luksa/kubia:v2 
deployment.apps/kubia image updated

이 명령을 실행하면 kubia 디플로이먼트의 파드 템플릿이 업데이트 돼 nodejs 컨테이너에 사용된 이미지가 luksa/kubia:v2 로 변경된다.

curl 요청을 실행하면 처음에는 v1 파드만 요청을 받는 것을 확인할 수 있는데, 그리고 나서 모든 v1파드가 삭제된 후 v2 파드만 요청을 받을 때 까지 점점 더 많은 요청이 v2 파드로 요청을 보낸다.

$ while true; do curl http://34.83.16.205; done
This is v1 running in pod kubia-74967b5695-6vstm
This is v1 running in pod kubia-74967b5695-dckxd
This is v2 running in pod kubia-bcf9bb974-ttq28
This is v2 running in pod kubia-bcf9bb974-pdsjp
This is v2 running in pod kubia-bcf9bb974-pdsjp
This is v2 running in pod kubia-bcf9bb974-ttq28
...

레플리카셋을 조회하면 기존 레플리카셋과 새 레플리카셋을 나란히 볼 수 있다.

$ kubectl get rs
NAME               DESIRED   CURRENT   READY   AGE
kubia-74967b5695   3         3         3       3m41s
kubia-bcf9bb974    1         1         1       11s

$ kubectl get rs
NAME               DESIRED   CURRENT   READY   AGE
kubia-74967b5695   0         0         0       6m20s
kubia-bcf9bb974    3         3         3       2m50s

이전과 달리 기존 레플리카셋은 삭제되지 않고 여전히 존재한다. 그러나 직접 레플리카셋을 생성하지 않았으므로 레플리카셋을 신경 쓰지 않아도 된다.
비활성화된 레플리카셋의 목적은 따로 있다.

디플로이먼트 롤백

현재 이미지의 v2 버전을 사용하는데 v3 버전으로 처음 요청 네 개만 제대로 처리하도록 하는 버그를 만들어 보자.

app.js

const http = require('http');
const os = require('os');

var requestCount = 0;

console.log("Kubia server starting...");

var handler = function(request, response) {
  console.log("Received request from " + request.connection.remoteAddress);
  if (++requestCount >= 5) {
    response.writeHead(500);
    response.end("Some internal error has occurred! This is pod " + os.hostname() + "\n");
    return;
  }
  response.writeHead(200);
  response.end("This is v3 running in pod " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

보시다시피 다섯 번째와 그 이후 모든 요청은 Some internal error has occurred!... 라는 메시지와 함께 500 오류 코드를 반환한다.

이미지의 버전 3으로 변경해 새 버전을 배포한다.

$ kubectl set image deployment kubia nodejs=luksa/kubia:v3
deployment.apps/kubia image updated

status 로 롤아웃 진행 상황을 확인할 수 있다

$ kubectl rollout status deployment kubia
Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubia" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubia" rollout to finish: 1 old replicas are pending termination...
deployment "kubia" successfully rolled out

새 버전이 라이브상태로 변경되면 몇 번의 요청 후에 웹클라이언트가 오류를 출력한다.

$ while true; do curl http://34.83.16.205; done
This is v3 running in pod kubia-7bddb8bfc7-277pg
This is v3 running in pod kubia-7bddb8bfc7-xt6vt
This is v3 running in pod kubia-7bddb8bfc7-xt6vt
This is v3 running in pod kubia-7bddb8bfc7-dkmj2
This is v3 running in pod kubia-7bddb8bfc7-277pg
This is v3 running in pod kubia-7bddb8bfc7-xt6vt
This is v3 running in pod kubia-7bddb8bfc7-xt6vt
This is v3 running in pod kubia-7bddb8bfc7-dkmj2
This is v3 running in pod kubia-7bddb8bfc7-dkmj2
This is v3 running in pod kubia-7bddb8bfc7-dkmj2
Some internal error has occurred! This is pod kubia-7bddb8bfc7-xt6vt

롤아웃 되돌리기

사용자가 내부 서버 유류를 경험하게 둘 수 없으므로, 신속하게 조치를 취해야 한다. 디플로이먼트의 마지막 롤아웃을 취소하도록 지시해서 이적에 배포된 버전으로 쉽게 롤백할 수 있다.

$ kubectl rollout undo deployment kubia
deployment.apps/kubia rolled back

이렇게 하면 이플로이먼트가 이전 버전으로 롤백한다.

디플로이먼트 롤아웃 이력 표시

디플로이먼트는 개정 이력을 유지하므로 롤아웃의 롤백이 가능하다. 이력은 기본 레플리카셋에 저장된다.

$ kubectl rollout history deployment kubia
REVISION  CHANGE-CAUSE
1         kubectl set image deployment kubia nodejs=luksa/kubia:v1
3         <none>
4         <none>

디플로이먼트를 만들 때 사용한 --record 명령줄 옵션을 기억할 것이다. 이 옵션이 없으면 개정이력의 CHANGE-CAUSE 열어 비어 있어서 각 개정 뒤에 무엇이 있는지 알아내는 것이 어렵다.

특정 디플로이먼트 개정으로 롤백은 undo 명령에서 개정 번호를 지정해 특정 개정으로 롤백할 수 있다. 예를 들어 첫번째 버전으로 롤백하려면 다음 명령을 실행한다.

$ kubectl rollout undo deployment kubia --to-revision=1
deployment.apps/kubia rolled back

이는 디플로이먼트를 처음 수정했을 때 비활성화된 레플리카셋이 남아 있던 첫 번째 개정을 나타낸다.

$ while true; do curl http://34.83.16.205; done
This is v1 running in pod kubia-74967b5695-jxk7b
This is v1 running in pod kubia-74967b5695-fl76w
This is v1 running in pod kubia-74967b5695-jxk7b
This is v1 running in pod kubia-74967b5695-6pmbh
...

또한 개정 내역의 수는 디플로이먼트 리소스의 editionHistoryLimit 속성에 의해 제한된다. 기본값은 2로 설정돼 있으므로 일반적으로 현재와 이전 버전만 기록에 표시된다. 그보다 이전 레플리카셋은 자동으로 삭제된다.

롤아웃 속도 제어

maxSurge, maxUnavailable 속성은 디플로이먼트의 롤링 업데이트 중에 한 번에 몇 개의 파드를 교체할지를 결정한다.

다음 예제와 같이 디플로이먼트 strategy 속성의 rollingUpdate 속성 아래의 일부로 설정할 수 있다.

spec:
  strategy:
    rollingUpate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
      

좀 더 자세히 설명하면, maxSurge 속성은 디플로이먼트가 의도하는 레플리카 수보다 얼마나 많은 파드 인스턴스 수를 허용할 수 있는지 결정한다.
레플리카 수가 4로 설정된 경우 업데이트 중에 동시에 5개 이상의 파드 인스턴스가 실행되지 않는다.

maxUnavailable 속성은 업데이트 중에 의도하는 레플리카 수를 기준으로 사용할 수 없는 파드 인스턴스 수를 결정한다.

롤아웃 프로세스 일시 중지

애플리케이션 버전 v3에 대한 좋지 않은 경험을 한 후에 버그를 수정하고 버전 v4 이미지를 푸시했다고 가정해보자.

이미지를 luksa/kubia:v4로 변경해 롤아웃을 시작한 즉시 롤아웃을 일시 중지한다.

$ kubectl set image deployment kubia nodejs=luksa/kubia:v4
deployment.apps/kubia image updated

$ kubectl rollout pause deployment kubia
deployment.apps/kubia paused

새 파드 하나를 생성했지만 원본 파드도 계속 실행 중이어야 한다. 새 파드가 가동되면 서비스에 관한 모든 요청의 일부가 새 파드로 전달된다.

새 버전을 모든 사람에게 롤아웃하는 대신 하나 또는 적은 수의 이전 파드만 새 버전으로 바꾼다. 이렇게 하면 소수의 사용자만 초기에 새 버전을 사용하게 된다. 그런 다음 새 버전이 제대로 작동하는지 확인 후 나머지 모든 파드를 통해 롤아웃을 계속하거나 이전 버전으로 롤백할 수 있다.

$ while true; do curl http://34.83.16.205; done
This is v4 running in pod kubia-555774bf68-vz5lh
Some internal error has occurred! This is pod kubia-7bddb8bfc7-fd86v
Some internal error has occurred! This is pod kubia-7bddb8bfc7-ln478
This is v4 running in pod kubia-555774bf68-vz5lh
Some internal error has occurred! This is pod kubia-7bddb8bfc7-4qnbh
This is v4 running in pod kubia-555774bf68-vz5lh

롤아웃 재개

롤아웃 프로세스를 일시중지하면 클라이언트 요청 중 일부만 파드 v4에 도달하지만 대부분은 파드 v3을 호출한다. 새 버전이 제대로 작동한다고 확신하면 디플로이먼트를 다시 시작해 이전 파드를 모두 새 파드로 교체할 수 있다.

$ kubectl rollout resume deployment kubia
deployment.apps/kubia resumed
$ while true; do curl http://34.83.16.205; done
This is v4 running in pod kubia-555774bf68-vz5lh
This is v4 running in pod kubia-555774bf68-vz5lh
This is v4 running in pod kubia-555774bf68-9gwz9
This is v4 running in pod kubia-555774bf68-lglbg

잘못된 버전의 롤아웃 방지

디플로이먼트에서 설정한 minReadySeconds 속성을 기억하는가?
롤아웃 속도를 늦춰 롤링 업데이트 과정을 볼 수 있었다. 하지만 주요 기능은 오작동 버전의 배포를 방지하는 것이다.

파드를 사용 가능한 것으로 취급하기 전에 새로 만든 파드를 준비할 시간을 지정한다. 모든 파드의 레디니스 프로브가 성공하면 파드가 준비 된다. minReadySeconds가 지나기 전에 새 파드가 제대로 작동하지 않고 레디니스 프로브가 실패하면 새 버전의 롤아웃이 효과적으로 차단된다.

적절하게 구성된 레디니스 프로브와 적절한 minReadySeconds 설정으로 쿠버네티스는 버그가 있는 버전 v3을 배포하지 못하게 할 수 있다. 다음을 살펴보자.

버전 v3가 완전히 롤아웃 되는것을 방지

v3 버전을 다시 배포하지만 이번에는 적절한 레디니스 프로브가 정의돼 있어야 한다.

이미지를 변경하고 레디니스 프로브를 한 번에 추가하려면 kubectl apply 명령어를 사용한다. (현재 버전 v2를 여전히 사용중이라고 하자)

kubia-deployment-v3-with-readinesscheck.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  minReadySeconds: 10		# minReadySeconds를 10으로 설정
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0		# 디플로이먼트가 파드를 하나씩 교체하도록 maxUnavailable을 0으로 설정
    type: RollingUpdate
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v3
        name: nodejs
        readinessProbe:
          periodSeconds: 1	# 매초마다 실행될 레디니스 프로브를 정의한다.
          httpGet:	# 레디니스 프로브는 컨테이너에 HTTP GET 요청을 수행한다.
            path: /
            port: 8080

다음 apply 명령어로 YAML 파일에 정의된 모든 항목으로 디플로이먼트를 업데이트한다.

# kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml
deployment.apps/kubia configured

상태를 보면 하나의 파드가 새로 생성됐다고 표시돼 있으므로 해당 서비스가 호출돼야 한다. 확인해보자.

$ kubectl rollout status deployment kubia
Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated...

$ while true; do curl http://34.83.16.205; done   
This is v2 running in pod kubia-bcf9bb974-tgqfb
This is v2 running in pod kubia-bcf9bb974-xxnml
This is v2 running in pod kubia-bcf9bb974-xxnml
This is v2 running in pod kubia-bcf9bb974-xxnml
This is v2 running in pod kubia-bcf9bb974-j647t
This is v2 running in pod kubia-bcf9bb974-xxnml
This is v2 running in pod kubia-bcf9bb974-xxnml
This is v2 running in pod kubia-bcf9bb974-j647t
This is v2 running in pod kubia-bcf9bb974-tgqfb
This is v2 running in pod kubia-bcf9bb974-tgqfb
This is v2 running in pod kubia-bcf9bb974-j647t
...

아니다 계속해서 v2에 접근중이고, 파드 v3에 접근할 수 없다.
파드를 조회해 보자

$ kubectl get po
kubia-67d49c55dd-xnppw     0/1     Running     0          79s
kubia-bcf9bb974-j647t      1/1     Running     0          20m
kubia-bcf9bb974-tgqfb      1/1     Running     0          19m
kubia-bcf9bb974-xxnml      1/1     Running     0          19m

문제를 찾았다. 파드가 준비되지 않은 것으로 확인된다.
새 파드가 시작되자마자 레디니스 프로브가 매초마다 시작된다. 애플리케이션이 다섯번째 요청에서 레디니스 프로브가 실패하기 시작한다.
결과적으로 파드는 서비스의 엔드포인트에서 제거된다. curl 요청에서 서비스를 시작할 때까지 파드는 이미 준비되지 않은 것으로 표시되기 때문이다.

롤아웃 프로세스에서는 어떤지 살펴보자. rollout status 명령어는 하나의 새 레플리카만 시작됐음을 보여준다. 고맙게도 새 파드를 사용할 수 없으므로 롤아웃 프로세스가 계속 되지 않는다. 사용 가능한 것으로 간주되려면 10초이상 준비돼 있어야 한다. 사용 가능할 때 까지 롤라웃 프로세스는 새 파드를 만들지 않으며 maxUnavailable 속성을 0으로 설정했기 때문에 원래 파드도 제거하지 않는다.

롤아웃 데드라인 설정

기본적으로 롤아웃이 10분 동안 진행되지 않으면 실패한 것으로 간주된다.
describe deploy 명령어를 사용하면 ProgressDeadlineExceeded 조건이 표시된다.

$ kubectl describe deploy kubia
...
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ProgressDeadlineExceeded # 디플로이먼트를 진행하는 데 너무 오래 걸렸다
...

잘못된 롤아웃 중지

롤아웃이 계속 진행되지 않기 때문에, 롤아웃을 취소해 중단하는 것이다.

$ kubectl rollout undo deployment kubia
deployment.apps/kubia rolled back
profile
노옵스를향해

0개의 댓글