이번 실습에서는 다음과 같은 과정을 배운다.
kubectl
도구 사용 연습- 배포 yaml 파일 만들기
- 배포 시작, 업데이트 및 확장
- 배포 및 배포 스타일 업데이트 연습
배포 소개
이기종 배포에서는 일반적으로 특정한 기술/운영상의 요구를 충족하기 위해 2개 이상의 상이한 인프라 환경 또는 리전을 연결하는 경우도 있다. 예를 들어서 내 데이터 서버는 회사 내에 안전하게 보관하고, 이 데이터를 외부 사용자에게 보여주는 프론트엔드는 클라우드 환경에서 구축하고 싶을 수도 있다. 이러한 이기종 배포는 온프레미스와 클라우드가 결합된 경우는 '하이브리드 클라우드 환경'으로, 여러 개의 클라우드가 결합되어 있으면 '멀티 클라우드 환경'으로 불린다.
이러한 이기종 배포는 가용성을 높이고 지리적 범위를 넓힐 수 있다는 장점이 있지만, 한편으로는 프로세스의 취약성을 높이고 내결함성을 저하시킬 수도 있다. 이번 실습에서는 이기종 배포를 위한 몇 가지 일반적인 사용 사례를 Kubernetes로 연습하여 이기종 배포에 대해서 배워본다.
영역 설정 및 샘플 코드 가져오기
다음 명령어를 실행하여 GCP 영역을 설정하고, 로컬 영역을 랩에서 설정한 영역(지금 실습에서는 us-west3-b가 설정됨)로 대체한다. 그 후 실습에서 제공해주는 샘플 코드를 Google Cloud에서 가져와준다.
# 영역 설정
gcloud config set compute/zone us-west3-b
# 샘플 코드 가져오기
gsutil -m cp -r gs://spls/gsp053/orchestrate-with-kubernetes .
cd orchestrate-with-kubernetes/kubernetes
참고로 이렇게 영역 설정을 해줘야 하는 이유는 이전 글에서도 말했듯이 쿠버네티스 클러스터와 Cloud Shell은 다른 것이기 때문이다(이전 글은 아래에).
따라서 Cloud Shell에만 접속한 상황이라면 아직 클러스터는 만들어지지 않았으므로, 리전을 설정하고 클러스터를 만들어줘야 한다. 다음과 같이 노드 3개가 있는 클러스터를 만든다. 클러스터 생성까지는 약 5분 정도 걸린다.
gcloud container clusters create bootcamp \
--machine-type e2-small \
--num-nodes 3 \
--scopes "https://www.googleapis.com/auth/projecthosting,storage-rw"
작업 1. 배포 객체에 관해 알아보기
배포를 시작하기 전에 먼저 배포 객체에 대해 알아본다. explain
명령어를 사용하면 배포 객체에 관해 알 수 있다.
kubectl explain deployment
kubectl explain deployment --recursive # 모든 필드 보기
kubectl explain deployment.metadata.name # 개별 필드 보기
작업 2. 배포 만들기
이제 deployments/auth.yaml
구성 파일을 업데이트해보자. 실습에서는 vi 편집기를 썼지만, nano 편집기나 아무 원하는 편집기를 사용해도 된다.
vi deployments/auth.yaml # 파일 편집
i # 편집기 시작
배포의 containers 섹션에 있는 image의 태그를 다음과 같이 2.0.0에서 1.0.0으로 변경한다. 변경 후에는 Esc
를 눌러 vi 입력 모드를 빠져 나온 후, :wq
를 입력하여 변경 사항 저장후 종료한다.
containers:
- name: auth
image: "kelseyhightower/auth:2.0.0"
# image: kelseyhightower/auth:1.0.0 로 변경
ports:
- name: http
containerPort: 80
- name: health
containerPort: 81
변경한 뒤 배포 구성 파일을 출력해보면 다음과 같다.
cat deployments/auth.yaml
# 출력 내용
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
spec:
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
track: stable
spec:
containers:
- name: auth
image: "kelseyhightower/auth:1.0.0"
ports:
- name: http
containerPort: 80
- name: health
containerPort: 81
resources:
limits:
cpu: 0.2
memory: "10Mi"
livenessProbe:
httpGet:
path: /healthz
port: 81
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /readiness
port: 81
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
이렇게 생성한 구성 파일은 kubectl create
명령어를 실행하면 배포를 만들 수 있다.
kubectl create -f deployments/auth.yaml # 배포 만들기
kubectl get deployments # 생성 여부 확인
배포가 생성되면, Kubernetes에서는 배포에 관한 ReplicaSet를 만든다. ReplicaSet가 생성될 때 Kubernetes에서 단일 포드를 생성하므로, kelseyhightower
이미지를 실행하는 파드 하나가 생성되어야 한다. kubectl get
명령어를 통해 배포에 관한 ReplicaSet과 pod가 생성되었는지 확인할 수 있다.
다음으로는 서비스를 만들 차례이다. 배포를 생성할 때와 같이 kubectl create
명령어를 사용해서 인증 서비스를 만든다.
kubectl create -f services/auth.yaml
같은 방식으로 hello와 frontend 배포를 만들고 노출하면 된다.
# hello 배포 만들고 노출
kubectl create -f deployments/hello.yaml
kubectl create -f services/hello.yaml
# frontend 배포 만들고 노출
kubectl create secret generic tls-certs --from-file tls/
kubectl create configmap nginx-frontend-conf --from-file=nginx/frontend.conf
kubectl create -f deployments/frontend.yaml
kubectl create -f services/frontend.yaml
참고로 frontend에서는 배포와 서비스 뿐만 아니라 Secret과 ConfigMap이라는 것을 같이 만들게 되는게, 이것들이 무엇인지는 나중에 쿠버네티스 중급반에 가서 배우게 된다.
서비스를 만들고 나면 kubectl get
으로 외부 IP를 확인하고 해당 IP에 curl
명령어를 던짐으로써 프런트엔드와 상호작용한다. EXTERNAL-IP 필드가 채워지길 기다렸다가 생성되면 해당 IP로 curl
을 던지면 된다.
해당 IP를 호출하면 hello 응답을 다시 받게된다👋 또한 kubectl의 출력 템플릿 기능을 사용하여 curl을 한 줄 명령어로 사용할 수도 있다.
curl -ks https://`kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"`
작업 3. 배포 확장
이제 배포가 생성되었으므로 실행 중인 파드를 확장할 수 있다. spec.replicas
필드를 업데이트하면 된다. 먼저 kubectl explain
명령어를 사용하여 필드에 관한 설명을 확인하고, kubectl scale
명령어를 사용해서 해당 필드를 업데이트 한다.
kubectl explain deployment.spec.replicas # 설명 확인
kubectl scale deployment hello --replicas=5 # 필드 업데이트
원래 replica는 1개였으므로 1개의 파드가 실행 중이었다. 그러나 scale
명령어를 통해 replica를 5개로 늘렸으므로 이제 새로운 파드가 시작된다. 1~2분 정도 기다려서 새로운 파드가 실행되기를 기다린 후, kubectl get
명령어를 통해서 파드가 5개 실행 중인지를 확인한다. 그리고 다시 애플리케이션을 파드 3개로 축소한 후, 파드 개수가 맞는지를 다시 확인한다.
kubectl get pods | grep hello- | wc -l # 파드 개수 5개 확인
kubectl scale deployment hello --replicas=3 # 애플리케이션 축소
kubectl get pods | grep hello- | wc -l # 파드 개수 3개 확인
이렇게 kubectl create
를 통해서 배포와 서비스를 생성하고, kubectl scale
를 통해서 파드를 스케일 업 또는 다운하는 방법을 알아보았다.
다음부터 소개될 방법은 쿠버네티스의 배포 전략(업데이트 전략)에 관련된 내용이다. 배포 전략을 직접 실습하는 내용이 위주이므로, 각 배포 전략의 의미나 장단점에 대해서 먼저 알고 싶으면 다음 글을 참고하길 바란다.
작업 3. 순차적 업데이트(Rolling update)
배포에서 활용할 수 있는 업데이트 메커니즘에는 순차적 업데이트가 있다. 이는 배포가 새 버전으로 업데이트되면 새로운 ReplicaSet이 만들어지고, 이전 ReplicaSet의 복제본이 감소하면서 새 ReplicaSet의 복제본 수가 천천히 증가하는 방식이다.
순차적 업데이트 트리거하기
순차적 업데이트 방식으로 배포를 업데이트하려면 다음과 같이 kubectl edit 명령어를 실행하여 구성 파일의 내용을 변경해주면 된다.
kubectl edit deployment hello
# 다음과 같이 변경
...
containers:
image: kelseyhightower/hello:2.0.0
...
그 후에 kubectl get
명령어를 통해서 replicaset의 상태를 확인한다.
kubectl get replicaset
kubectl rollout history 명령어를 통해서 출시 기록을 확인할 수도 있다.
kubectl rollout history deployment/hello
출시 전 후 히스토리를 잘 비교해보면 이전 버전인 1에 이어서 새로운 버전인 2가 출시된 것을 알 수 있다.
순차적 업데이트 일시중지하기
실행 중인 출시에 문제가 생기면 이를 일시중지하여 업데이트를 중지해야 한다. kubectl rollout pause
를 사용하면 업데이트를 일시중지할 수 있다. 일시중지한 후 kubectl rollout status
나 kubectl get pods
명령어를 통해서 상태를 확인할 수 있다.
kubectl rollout pause deployment/hello # 지금 중지
kubectl rollout status deployment/hello # 출시 상황 확인
# 파드에서 직접 확인
kubectl get pods -o jsonpath --template='{range .items[*]}{.metadata.name}{"\t"}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
순차적 업데이트 재개하기
위에서 순차적 업데이트가 일어나고 있는 와중에 출시를 일시중지했으므로 일부 파드는 새 버전이고 일부 파드는 이전 버전인 상태로 남아 있다. 이 경우에는 출시를 다시 시작하거나 업데이트를 아예 롤백할 수도 있다. 우선 kubectl rollout resume
을 통해서 업데이트를 재개한다.
kubectl rollout resume deployment/hello # 업데이트 재개
kubectl rollout status deployment/hello # 상태 확인
업데이트 롤백하기
그런데 새 버전에서 심각한 버그가 발견되어서 업데이트를 재개하는 것이 아니라 아예 이전 버전으로 롤백해 새로 배포되었던 파드를 모두 없애야 하는 상황이라고 가정하자. 그러면 kubectl rollout undo
를 통해서 이전 버전으로 돌릴 수 있다.
kubectl rollout undo deployment/hello # 업데이트 롤백하기
kubectl rollout history deployment/hello # 롤백 기록 확인하기
# 파드에서 직접 확인
kubectl get pods -o jsonpath --template='{range .items[*]}{.metadata.name}{"\t"}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
kubectl history
명령어의 결과를 위에서 확인했던 것과 비교해 보면 1, 2 였던 숫자가 2, 3 이 된 것을 확인할 수 있다. 이전 버전인 1로 돌아간 것 뿐이지만 쿠버네티스는 이를 새로운 리비전으로 처리하고, 롤백된 배포의 이전 리비전은 표시하지 않기 때문이다. 따라서 버전 1을 버전 2로 업데이트하고, 다시 이를 버전 1로 롤백하면 1, 2가 출력되는 것이 아니라 2, 3이 출력된다.
작업 4. Canary 배포
가끔 인스타그램 등을 사용하다보면 나에게만 새로운 UI/UX가 보이는 경우가 있다(인스타그램을 예시로 든 이유는 하도 UI/UX를 바꿔대서...). 이는 일부 사용자에게만 먼저 배포한 후 그 결과에 따라서 전체 사용자에게 배포를 확장하거나 아니면 배포를 취소하기 위해서이다. 이처럼 쿠버네티스에서 프로덕션 환경에서 일부 사용자를 대상으로 새 배포를 테스트하려면 Canary 배포를 사용할 수 있다. Canary 배포를 사용하면 작은 규모의 일부 사용자에게만 변경 사항을 릴리스하여 새로운 릴리스와 관련된 위험을 완화할 수 있다.
Canary 배포 만들기
Canary 배포는 새 버전의 배포와 함께 기존의 안정적인 배포를 동시에 대상으로 삼는 서비스로 구성될 수 있다. 먼저 Canary 배포를 위한 yaml 파일을 확인해본다.
cat deployments/hello-canary.yaml
# 파일 내용
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-canary
spec:
replicas: 1
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
track: canary
# 2.0.0 버전을 사용하여 서비스 선택기의 버전과 일치시키기
version: 2.0.0
spec:
containers:
- name: hello
image: kelseyhightower/hello:2.0.0
ports:
- name: http
containerPort: 80
- name: health
containerPort: 81
...
이제 Canary 배포를 만들고 해당 배포를 확인해보자. Canary 배포를 생성하면 hello
와 hello-canary
두 가지 배포가 생성된다. hello 서비스는 app:hello
선택기가 있는 프로덕션 배포 및 Canary 배포의 pod를 모두 선택한다. 그래서 두 가지 배포가 모두 서비스되지만, Canary 배포가 포드 수가 더 적기 때문에 더 적은 수의 사용자에게 표시된다.
Canary 배포 확인하기
curl
을 통해서 hello 버전을 확인할 수 있다.
curl -ks https://`kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"`/version
그런데 curl
을 여러 번 실행하다 보면 흥미로운 사실을 알 수 있는데, 그건 바로 일부 요청은 hello 1.0.0에서 제공하고 이룹 요청은 hello 2.0.0에서 제공한다는 점이다. 지금은 1.0.0 버전이 파드가 3개, 2.0.0 버전이 파드가 1개 있으므로, 2.0.0 버전은 평균적으로 25%의 확률로 접근 가능하다.
프로덕션 환경의 Canary 배포 - 세션 어피니티(Sticky session)
그런데 위에서 curl을 던져본 결과에서 알 수 있듯이, 사용자는 Canary 배포와 프로덕션 배포를 랜덤하게 접근하게 된다. 그런데 만약 어떤 사용자가 Canary 배포에 접근했다면 계속 해당 배포에만 머무도록 하고 싶을 때는 어떻게 해야 할까? 예를 들어, 새로운 애플리케이션 UI를 카나리아 배포에 적용했다면 매번 랜덤하게 배포를 접근하게 될 시 사용자에게 혼동을 줄 수 있다. 이럴 때는 해당 사용자를 한 배포에 '고정'해야 한다.
서비스와 함께 세션 어피티니(스티키 세션이라고도 함)를 만들면 동일한 사용자에게 항상 동일한 버전을 제공할 수 있다. 서비스에 sessionAffinity
필드를 추가하고 ClientIP
로 설정하면 IP 주소가 동일한 모든 클라이언트는 동일한 버전의 hello 애플리케이션으로 요청을 보내게 된다.
kind: Service
apiVersion: v1
metadata:
name: "hello"
spec:
sessionAffinity: ClientIP
selector:
app: "hello"
ports:
- protocol: "TCP"
port: 80
targetPort: 80
이 실습에서는 세션 어피니티를 테스트하지는 않지만, 이런 방식이 있다는 것을 알아두는 건 중요하다.
작업 5. Blue/Green 배포
순차적 업데이트는 최소한의 오버헤드, 최소한의 성능 영향, 최소한의 다운타임으로 애플리케이션을 배포할 수 있기 때문에 가장 좋은 업데이트 방식이다. 그러나 새로운 배포를 모두 완료한 후에 로드 밸런서만 수정해서 모든 트래픽이 한꺼번에 새 버전을 가리키도록 하는 것이 유리한 경우도 있다. 이 경우에는 Blue/Green 배포가 도움이 된다.
Kubernetes에서는 이전의 'blue' 버전용 배포와 새로운 'green' 버전용 배포를 만들어 업데이트할 수 있다. 'blue' 버전에 기존 hello 배포를 사용하면 라우터 역할을 하는 서비스를 통해 배포에 액세스하게 된다. 새 'green' 버전이 가동 및 실행되면 서비스를 업데이트하여 이 버전을 사용하도록 전환하게 된다.
하지만 Blue/Green 배포의 주요 단점은 애플리케이션을 호스팅하려면 클러스터에 최소 2배의 리소스가 필요하다는 점이다. 따라서 배포를 진행하기 전에 먼저 클러스터에 충분한 리소스가 있는지 확인해야 한다.
서비스
Blue/Green 배포를 실습해보기 위해서 우선 기존의 hello 서비스를 사용하되 app:hello, version: 1.0.0으로 선택기를 업데이트한다. kubectl apply
명령어를 통해서 서비스를 업데이트한다.
kubectl apply -f services/hello-blue.yaml
Blue/Green 배포를 사용하여 업데이트하기
Blue/Green 배포 스타일을 지원하기 위해 새 버전용으로 새로운 'green' 배포를 만든다. Green 배포에서 버전 라벨과 이미지 경로를 업데이트한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-green
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
track: stable
version: 2.0.0
spec:
containers:
- name: hello
image: kelseyhightower/hello:2.0.0
ports:
- name: http
containerPort: 80
- name: health
containerPort: 81
resources:
limits:
cpu: 0.2
memory: 10Mi
livenessProbe:
httpGet:
path: /healthz
port: 81
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /readiness
port: 81
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
Green 배포를 만든 후, 해당 배포가 제대로 시작됐고 아직 1.0.0 버전을 사용하고 있는지 확인한다.
kubectl create -f deployments/hello-green.yaml
curl -ks https://`kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"`/version
확인 후에는 이제 서비스가 새로운 버전을 가리키도록 업데이트한다. 서비스가 업데이트되면 'green' 배포가 즉시 사용되며, 항상 새 버전이 사용되고 있는 것을 확인할 수 있다.
Blue/Green 롤백
위에서 순차적 업데이트를 롤백했던 것처럼, Blue/Green 배포도 필요 시 다시 롤백할 수 있다. 그러나 kubectl undo를 사용하는 방식이 아니라 로드 밸런서 서비스를 다시 이전 버전을 가리키도록 만들면 된다(왜냐하면 이전 버전이 아직 남아 있으므로).
kubectl apply -f services/hello-blue.yaml # 업데이트 롤백
curl -ks https://`kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"`/version
서비스를 롤백하자마자 바로 이전 버전을 사용하고 있는 것을 확인할 수 있다.
이러한 배포 방식을 활용하여 한 번에 버전 전환을 마쳐야 하는 애플리케이션에서 Blue/Green 배포를 활용할 수 있다.