2023.07.21 코드스테이츠 72회차. ( 쿠버네티스 워크로드 )
파드 (Pods)
파드(Pod)는 쿠버네티스의 배포 가능한 가장 작은 컴퓨팅 유닛
파드는 그 자체로 하나의 논리적인 호스트 이다
파드는 다음을 포함할 수 있습니다. 마치 도커 컨테이너처럼 파드 내에서 다음 요소들은 격리 된다
- 하나 이상의 애플리케이션 컨테이너
- IP 주소
- 볼륨과 같은 공유 스토리지
쿠버네티스에서는 “워크로드 리소스”를 만들기 위해 YAML 파일과 같은 리소스 정의 파일을 사용하는 것이 보통이다
워크로드란?
쿠버네티스에서는 “쿠버네티스상에서 작동되는 애플리케이션”을 의미하며, 클라우드 분야에서는 “어떤 애플리케이션을 실행할 때 필요한 IT 리소스의 집합”이라는 의미로 통용된다
마찬가지로 파드를 생성하기 위해 파드를 정의할 때 다음과 같은 형태의 YAML 파일을 사용할 수 있다
다음 YAML을 따라서 만들고, 적당한 디렉터리에 simple-pod.yaml 로 저장해보자.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
파드 적용하기
위 파드 정의 파일을 실제로 만들기 위해서는, 다음 명령을 사용한다
kubectl apply -f simple-pod.yaml
위 명령을 통해 pod/nginx created 라는 결과가 출력되었다면, 즉시 명령어 kubectl get pods 를 통해 상태를 확인해 보자.
STATUS를 통해 컨테이너가 만들어지고(ContainerCreating), 컨테이너가 실행 중인(Running) 상태를 확인할 수 있다
디플로이먼트 (Deployment)
쿠버네티스에서 Deployment는 한글로 번역하지 않습니다. 흔히 우리는 배포라고 하면 서비스의 노출을 떠올리지만, 쿠버네티스의 Deployment는 서비스 노출의 의미가 아닙니다. 쿠버네티스에서의 Deployment는 파드의 교체/배치(placement)와 관련된 명세이다
파드의 진실
쿠버네티스에서는 사실 직접 사용자가 개별 파드를 만들 일이 그리 많지 않습니다. 왜냐하면 파드는 일시적이고, 언제나 삭제될 수 있음을 감안하고 만들기 때문입니다. 예를 들어, 파드가 실행되는 공간인 노드가 만일 실패하는 경우, 그 안에서 실행되는 파드 역시 사용할 수 없게된다
사실 컨테이너를 수동으로 만들고 관리하는 것은, 그냥 도커만 단독으로 사용해도 충분히 할 수 있는 일이다
쿠버네티스의 핵심은 컨테이너를 오케스트레이션하는 것으로, 파드 장애시 자동 복구하거나, 복제하거나 하는 등의 일을 자동으로 처리하는 데에 있다
AWS로 따지면 ECS(Elastic Container Service)가 하는 일과 비슷합니다. 이 둘은, 컨테이너의 로드 밸런싱과 오토 스케일링과 같은 일을 담당한다
파드는 디플로이먼트, 스테이트풀셋, 데몬셋을 이용해 관리하는 것이 바람직하며 해당 워크로드 리소스는 파드 템플릿을 항상 포함하고 있다
디플로이먼트의 예시
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
# 여기서부터 파드 템플릿입니다.
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
# 여기까지 파드 템플릿입니다.
디플로이먼트는 파드를 업데이트하기 위한 선언적 명세이다
디플로이먼트 리소스를 통해 다음을 할 수 있다
- (레플리카셋, 즉 복제본 구성을 이용하여) 파드를 원하는 개수만큼 실행시킬 수 있습니다.
- (제어판 Control Plane을 이용하여) 파드를 업데이트할 수 있습니다.
- 마찬가지로, 파드를 롤백하는 것도 가능합니다.
애플리케이션의 여러 복제본이 존재할 경우, 이 각각의 복제본을 새 버전으로 업데이트하는 방법으로 다음과 같은 배포 전략이 있다
- 재생성 (Recreate): 이전 버전을 삭제하고 새 버전 생성
- 블루/그린 배포: 한 번에 이전 버전에서 새 버전으로 연결을 전환
- 롤링 배포: 이전 버전을 scale down하고, 새 버전을 scale up 하는 방식으로 단계별로 교체, 롤아웃(rollout)이라고 부릅니다.
- 카나리 배포: 새 버전이 잘 작동한다고 판단되면, 이전 버전을 교체
위에서 만든 디플로이먼트 정의 파일을 적용하기 위해서는, 다음 명령을 사용한다
kubectl apply -f <디플로이먼트_파일>
서비스
파드를 외부로 노출시키기
클러스터 안에 파드는 각각 고유의 IP를 가지고 있지만, 직접 우리가 내부망에 접속할 수 있는 것은 아니다
서비스 리소스를 사용하면 파드에서 실행 중인 애플리케이션을 클러스터 외부에서 접근할 수 있습니다. 또한 서비스를 사용하여 클러스터 내부에서 사용할 수 있는 서비스만 게시할 수 있습니다. 쿠버네티스에서 서비스는 파드의 집합에 접근할 수 있는 정책을 정의하는 추상적 개념입니다. 서비스 리소스가 정의된 YAML 파일에 selector라는 것을 이용해 서비스할 대상 타겟을 설정할 수 있습니다.
앞서 디플로이먼트를 통해 파드의 복제본을 원하는 개수만큼 실행시킬 수 있다고 언급했습니다. 서비스 리소스는 이러한 파드 집합에 접근할 수 있게 하며, 파드가 교체되거나, 어떤 특정 파드에 문제가 생긴 경우에도 사용 가능한 파드를 찾아 알아서 접속할 수 있게 돕습니다.
서비스 만들어 보기
다음은 서비스 리소스의 예제
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx # 배포하려는 파드를 지정합니다. 당연히 파드가 이미 실행 중이어야 합니다.
type: LoadBalancer
ports:
- name: nginx
protocol: TCP
port: 80
targetPort: 80
우리에게 가장 익숙한 LoadBalancer로 서비스를 만들고, 백엔드에 cozserver라는 이름을 가진 파드 집합에 연결되도록 지정했습니다. 적용된 결과를 확인해 보자.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cozserver LoadBalancer 10.111.181.232 <pending> 80:30185/TCP 12s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2m14s
minikube에서는 EXTERNAL-IP가 pending 상태로 진전이 되지 않는다
minikube tunnel 명령을 이용하면, EXTERNAL-IP가 127.0.0.1로 설정되며, 그때부터 http://localhost 로 접속이 가능해진다
실습.
시나리오
CozServer라는 간단한 WAS가 있습니다. 버전 1.0은 정상적으로 잘 작동하는 애플리케이션입니다. 여기에 디플로이먼트 명세를 적용해서 파드의 레플리카를 배포할 것입니다. 이후에 2.0 버전을 만들고, 디플로이먼트를 이용해 업데이트를 적용한다
여기서 우리는 3.0 버전에 의도적으로 버그를 만들어서 3.0 버전에 문제가 발생하면, 2.0 버전으로 롤백해야 하는 상황을 만들어 보자.
버전 롤백하기
Docker Hub에 donghyunele/cozserver:1.0 이라는 이름의 파드가 존재합니다. CozServer는 컨테이너 안에서 8080 포트를 통해 열려 있다
docker run -p 8080:8080 donghyunele/cozserver:1.0
STEP 1: 파드
이 단계에서는 파드를 수동으로 만들어 볼 것입니다. 하지만 보통 파드는 하나하나 만들지 않는다
donghyunele/cozserver:1.0 이미지를 바탕으로 파드를 만들어 보자.
cozserver-pod.yaml 에 파드 명세를 적고, 파드가 kubectl에 의해 실행되어야 한다
kubectl apply -f hello-pod.yaml 명령어를 실행
- kubectl get pods 명령어를 통해 진행 상태를 확인할 수 있습니다.
- kubectl describe pod 파드이름 명령어를 통해 IP와 다양한 정보들을 확인할 수 있습니다.
STEP 2: 디플로이먼트를 이용한 1.0 배포
앞서 개별 파드를 yaml로 작성된 명세를 통해 생성했습니다. 그러나, 파드는 확장성을 바탕으로 가용성을 위해 언제든 대체될 수 있습니다. 따라서 디플로이먼트로 CozServer를 구성하는 것이 바람직하다
1.0 이미지를 바탕으로 파드를 디플로이먼트를 생성 해봅시다.
## cozserver-deployment-v1.yaml 에 디플로이먼트 명세를 적고, kubectl에 의해 실행되어야 합니다.
배포 이력을 남기기 위해 --record 옵션을 추가해서 배포합시다.
STEP 3: 서비스
아무리 클러스터 내에 파드를 만든다 해도, 노출하지 않으면 소용이 없습니다. 서비스 리소스를 만들어서 파드를 외부로 노출해 줘야 합니다.
이때 서비스 리소스의 타입은 LoadBalancer를 사용할 것입니다. 앞서 안내한 대로, 컨테이너 포트는 8080을 사용합니다.
## cozserver-service.yaml 에 서비스 명세를 적고, kubectl에 의해 실행되어야 합니다.
- 로컬 환경에서 테스트하기 위해서는 터널이 필요합니다.
- 새로운 터미널을 열어줍니다.
- minikube tunnel 명령을 이용해 터널을 뚫어줍시다.
http://localhost:8085 를 이용해 웹사이트 화면이 보이면 성공이다
STEP 4: 2.0 배포, 롤아웃
기존의 cozserver-deployment-v1.yaml를 그대로 복사해서 cozserver-deployment-v2.yaml라는 이름으로 새로 생성해준다
이제, 1.0 대신 새로운 파일을 이용해 기존의 CozServer 1.0 버전이 아닌, 2.0 버전을 롤링 업데이트를 통해 배포할 것이다
## ozserver-deployment-v2.yaml 에 2.0 버전을 이용한 디플로이먼트 명세를 적고, kubectl에 의해 적용되어야 합니다. 배포 전략은 RollingUpdate를 사용할 것입니다. donghyunele/cozserver:2.0 이미지를 사용한다
kubectl get all
배포되는 과정을 확인할 수 있다
DESIRED, CURRENT, READY 등을 확인할 수 있습니다.
kubectl rollout history deployment 디플로이먼트이름
원하는 디플로이먼트의 배포 이력을 확인할 수 있습니다.
다음 버전의 cozserver-deployment-v2.yaml을 만들고 적용해 보세요.
# cozserver-deployment-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cozserver
namespace: default
labels:
app: cozserver
spec:
selector:
matchLabels:
app: cozserver
replicas: 3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: cozserver
spec:
containers:
- name: springboot
image: donghyunele/cozserver:2.0
ports:
- containerPort: 8080
명령어 kubectl get all과 history를 사용해 어떻게 출력되는지 확인해 보세요
- localhost:8085에 접속하여 내용이 바뀌었는지 확인해 봅니다.
- kubectl get all에서 진행 상황을 확인해 볼 수 있습니다.
새로운 버전이 배포되었다면 성공이다!!
- 마지막으로 롤아웃 히스토리를 확인해 봅시다. 다음 명령을 통해 이력을 확인할 수 있습니다.
kubectl rollout history deployment cozserver
STEP 5: 3.0 배포와 2.0으로의 롤백
3.0 배포를 위해서 cozserver-deployment-v3.yaml 에 3.0 버전을 이용한 디플로이먼트 명세를 적고, kubectl을 통해 배포를 시도해보자
# cozserver-deployment-v3.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cozserver
namespace: default
labels:
app: cozserver
spec:
selector:
matchLabels:
app: cozserver
replicas: 3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: cozserver
spec:
containers:
- name: springboot
image: donghyunele/cozserver:3.0
ports:
- containerPort: 8080
이제 http://localhost:8085 요청을 통해 3.0 버전을 확인해 보세요. 몇 번 요청을 시도하면, 처음에는 잘 되는 것 같아 보인다
현업에서도 비슷한 경우가 매우 많습니다. 배포를 하고 나면, 처음에는 그럴싸하게 잘 된 것으로 보이지만, 사용하다 보면 버그로 인해 internal 에러를 마주치게 되는 경우가 허다합니다.
이처럼 새 버전에 무언가 문제가 발생한다면, 이전 버전으로 롤백을 해야 합니다.
롤아웃 히스토리에 따르면, 2번 리비전까지는 정상적으로 작동했습니다. 2번으로 롤백을 시도해야 합니다.
kubectl rollout undo deployment/cozserver --to-revision=2 를 이용해 롤백해 보자.
kubectl get all 명령어를 실행했을 때 한 개의 DESIRED, CURRENT, READY가 3이어야지만 정상적으로 변경이 된 것이다
롤백후 히스토리를 보면 v3과 v2의 순서가 바뀌어 있다.