크고 아름다운 조타수 문양
원래부터 쿠버네티스에 관심이 있어 한번 공부하고 싶었는데, 이번에 쿠버네티스 관련 발제를 해야할 일이 생겨 제대로 한번 공부하고 이를 정리해보았다. 이제 막 입문한 내 눈높이에 철저히 맞춰 작성했으니, 여러분들은 아마 더 이해하기 쉬울 것이다.
우선 쿠버네티스에 대해 설명하기 전에, 왜 등장했는지 그 배경부터 알아보자.
참고로 본론 부분의 이미지들은 모두 필자가 힘들게 그린 것이니 이 점 유념하기 바란다. (이 글의 핵심)
만약 컨테이너를 수십만개를 사용한다면?
이런 컨테이너들을 자동으로 관리하는 것이 컨테이너 오케스트레이션 시스템
아름다운 건 한번 더 봐야지
: Google의 컨테이너 오케스트레이션 시스템 보그(Borg)를 오픈 소스 소프트웨어로 공개
⇒ 10년 이상 대규모 시스템을 운영해오면서 쌓은 노하우를 적용한 것으로, 완벽에 가까운 오케스테이션 성능 자랑
(쿠버네티스 공식 홈페이지에서 소개하는 아키텍처) ⇒ 근데 뭔 소린지 하나도 모르겠으니 더 간단하게 보자!
지금부터 나오는 이미지들은 모두 필자가 힘겹게 그린 그림들이니, 잘 봐두도록 하자.
쿠버네티스 클러스터의 일반적인 구조
Scheduler | 할당 가능한 노드를 선택하여 새로운 파드를 그 안에 실행 |
---|---|
Kube-Controller Manager | 컨트롤러 각각을 실행하는 컴포넌트 |
Cloud Controller Manager | 클라우드 서비스와 연결 (Azure, AWS, Google Cloud 등) |
API Server | 모든 요청이 이 컴포넌트를 거치며, 클러스터로 온 요청이 유효한지 검증 후 반환 |
etcd | 쿠버네티스 클러스터의 데이터베이스 역할 (키-값 저장소) |
> kubectl get namespaces
NAME STATUS AGE
default Active 52d
kube-node-lease Active 52d
kube-public Active 52d
kube-system Active 52d
> kubectl get pods --all-namepsaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-565d847f94-fbgcs 1/1 Running 9 (21h ago) 52d
kube-system coredns-565d847f94-hdkp8 1/1 Running 10 (21h ago) 52d
kube-system etcd-docker-desktop 1/1 Running 4 (5d19h ago) 52d
kube-system kube-apiserver-docker-desktop 1/1 Running 4 (5d19h ago) 43d
kube-system kube-controller-manager-docker-desktop 1/1 Running 4 (5d19h ago) 52d
kube-system kube-proxy-slh9c 1/1 Running 4 (5d19h ago) 52d
kube-system kube-scheduler-docker-desktop 1/1 Running 26 (21h ago) 52d
kube-system storage-provisioner 0/1 Error 9 (36d ago) 52d
kube-system vpnkit-controller 0/1 ContainerStatusUnknown 495 52d
: 클러스터의 오브젝트나 컨트롤러가 어떤 상태인지를 선언하는 YAML 형식
⇒ 위에서 설명한 “선언적 API”의 특징을 볼 수 있음
apiVersion: v1
kind: Pod
metadata:
spec:
Scalars (strings/numbers) | Sequences(arrays/lists) | Mappings(hashes/dictionaries) |
---|---|---|
Name: KimBirth: 2019 | ProgrammingSkills: - java - python - c | Data: Height: 170 Weight: 80 |
: 쿠버네티스는 개별 컨테이너를 하나하나 관리하는 것이 아닌, “파드”라는 단위로 컨테이너를 묶어 관리함
특징:
: 쿠버네티스는 “선언적 API”를 사용하므로, 파드 생성 방법 역시 “선언적”
⇒ 즉, 파드의 상태 (spec)를 선언한 후, 쿠버네티스가 자동으로 파드의 상태(status)를 바람직한 상태 (spec)으로 제어하도록 함.
💡 선언은 주로 YAML 파일을 사용하여 spec을 정의한다.파드 설정 예시:
apiVersion: v1
kind: Pod
metadata:
name: boaz-pod
spec:
containers:
- name: c0
image: nginx
ports:
- containerPort: 80
다음과 같이 pod-sample.yaml 파일을 작성한 후, 아래의 명령어를 실행하여 파드를 실행한다.
> kubectl apply -f pod-sample.yaml
> kubectl get pods
NAME READY STATUS RESTARTS AGE
boaz-pod 1/1 Running 0 6s
⇒ 정상적으로 boaz-pod가 생성된 것을 알 수 있다. 컨테이너 이름과 포트, 이미지가 우리가 선언한 대로 잘 만들어졌는지 자세히 살펴보자.
> kubectl describe pods boaz-pod
...
Containers:
c0:
Container ID: docker://91b6cefa6fb508c956e24c21304b831065291087ee1e1a70aee32d44b7985151
Image: nginx
Image ID: docker-pullable://nginx@sha256:480868e8c8c797794257e2abd88d0f9a8809b2fe956cbfbc05dcc0bca1f7cd43
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 52s default-scheduler Successfully assigned default/boaz-pod to docker-desktop
Normal Pulling 52s kubelet Pulling image "nginx"
Normal Pulled 50s kubelet Successfully pulled image "nginx" in 1.957161834s
Normal Created 50s kubelet Created container c0
Normal Started 50s kubelet Started container c0
⇒ c0의 이름을 지니고 nginx 이미지를 사용하는 컨테이너 하나가 boaz-pod 내에 생성되었음을 알 수 있다.
파드는 기본적으로 비공개 IP주소가 부여되므로 port-forward를 통해 로컬 포트를 매핑해야 함.
kubectl port-forward boaz-pod 10080:80
⇒ 10080번 로컬 포드와 80번 파드 내부 포트를 매핑
**파드를 만들어보자!**
: vi pod-sample2.yaml을 생성하여 다음 조건에 맞는 파드 spec을 선언한다.
힌트 :
spec.containers[] 또한 배열이므로 ‘-’를 사용한다.
명령어는 “command: [’sh’, ‘-c’, ‘sleep 100’]”
들여쓰기 기준은 띄어쓰기 두 칸
정답
apiVersion: v1
kind: Pod
metadata:
name: boaz-pod
spec:
containers:
- name: c0
image: nginx
command: ['sh', '-c', 'sleep 100']
ports:
- containerPort: 80
- name: c1
image: nginx
command: ['sh', '-c', 'sleep 100']
ports:
- containerPort: 81
: 파드들을 관리하는 역할. 어떤 목적을 가지고 관리하느냐에 따라 다양한 컨트롤러가 사용될 수 있음.
⇒ 다양한 컨트롤러가 있어 목적에 맞게 유용한 사용이 가능한 것이 쿠버네티스 컨트롤러의 강점!
: Spec에 명시된 파드의 갯수만큼 파드들이 항상 실행될 수 있도록 자동으로 파드의 수를 조절한다.
⇒ 만일 어떤 파드가 장애가 생겨 종료될 경우, 새로운 파드를 재생성하여 갯수를 맞춘다.
예시 yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-replicaset
spec:
template:
metadata:
name: nginx-replicaset
labels:
app: nginx-replicaset
spec:
containers:
- name: nginx-replicaset
image: nginx
ports:
- containerPort: 80
replicas: 3 #유지할 파드의 수도
selector: #관리할 파드의레이블, 하위 설정을 기준으로 레이블을 판단한다.
matchLabels:
app: nginx-replicaset
yaml을 작성한 후, 레플리카 세트를 생성한다.
> kubectl apply -f replicaset-sample.yaml
> kc get pods
NAME READY STATUS RESTARTS AGE
nginx-replicaset-bjlzq 0/1 ContainerCreating 0 2s
nginx-replicaset-fxmtc 0/1 ContainerCreating 0 2s
nginx-replicaset-hnv8f 0/1 ContainerCreating 0 2s
⇒ 레플리카 세트가 생성되자마자 파드 세 개가 생성된 것을 알 수 있다. 그럼 하나를 지워보자
> kc delete pods nginx-replicaset-hnv8f
pod "nginx-replicaset-bjlzq" deleted
> kc get pods
NAME READY STATUS RESTARTS AGE
nginx-replicaset-fxmtc 1/1 Running 0 48s
nginx-replicaset-hnv8f 1/1 Running 0 48s
nginx-replicaset-w4p7l 0/1 ContainerCreating 0 2s
⇒ 새로운 파드 “nginx-replicaset-w4p7l” 가 생성된 것을 알 수 있다.
앞서 말했듯, 레플리카세트는 파드의 레이블을 통해 자신이 생성한 파드의 갯수를 파악한다고 했다. 그럼 하나를 바꿔보자.
> kubectl edit pod nginx-replicaset-w4p7l
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2023-05-21T16:17:22Z"
generateName: nginx-replicaset-
labels:
app: nginx-replicaset2323 #레이블을 아무거나 바꿔보자
> kc get pods
NAME READY STATUS RESTARTS AGE
nginx-replicaset-7fhch 0/1 ContainerCreating 0 2s
nginx-replicaset-fxmtc 1/1 Running 0 3m7s
nginx-replicaset-hnv8f 1/1 Running 0 3m7s
nginx-replicaset-w4p7l 1/1 Running 0 2m21s
⇒ “nginx-replicaset-w4p7l”는 자신의 파드가 아니라고 여겨, 세 개를 맞추기 위해 새로운 파드를 생성한 것을 알 수 있다.
💡 유동적인 IP대신 Label을 통해 파드를 식별하는 쿠버네티스의 특징을 알 수 있다.: stateless 앱을 배포할 때 사용하는 가장 기본적인 컨트롤러다.
레플리카세트와 차이점
⇒ 즉, 더욱 세밀하게 앱 배포를 관리할 수 있다. (배포 기능을 세분화한 것이 디플로이먼트)
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx-deployment
annotations:
kubernetes.io/change-cause: version 1.10.1
spec:
replicas: 3
selector:
matchLabels:
app: nginx-deployment
template:
metadata:
labels:
app: nginx-deployment
spec:
containers:
- name: nginx-deployment
image: nginx
ports:
- containerPort: 80
> kubectl apply -f deployment-nginx.yaml
deployment.apps/nginx-deployment created
> kubectl get deploy,rs,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 2/3 3 2 13s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-dfcbd5fd6 3 3 2 12s
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-dfcbd5fd6-575x7 1/1 Running 0 12s
pod/nginx-deployment-dfcbd5fd6-lzlps 1/1 Running 0 12s
pod/nginx-deployment-dfcbd5fd6-tgn25 0/1 ContainerCreating 0 12s
: 디플로이먼트가 생성되며, 파드 갯수를 관리하는 레플리카세트 또한 생성된 것을 확인할 수 있다.
: 디플로이먼트는 많은 앱 배포 기능을 가지고 있다. 그 중, rolling update 롤백 기능을 한번 보자.
디플로이먼트를 업데이트 한다.
> kubectl edit deploy nginx-deployment
...
creationTimestamp: null
labels:
app: nginx-deployment
spec:
containers:
- image: nginx:1.10.1 #nginx에서 nginx:1.10.1로 바꾼다.
imagePullPolicy: Always
name: nginx-deployment
ports:
- containerPort: 80
:wq를 통해 변경사항 저장 후, 다음 명령어를 통해 상황을 확인한다.
> kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-748b8dbccc-mxp5w 1/1 Running 0 17s
nginx-deployment-748b8dbccc-vmb8k 0/1 ContainerCreating 0 2s
nginx-deployment-dfcbd5fd6-575x7 1/1 Running 0 5m20s
nginx-deployment-dfcbd5fd6-lzlps 1/1 Running 0 5m20s
: 구 버전의 파드들이 하나둘씩 신 버전의 파드들로 대체되고 있음을 확인할 수 있다. 이미지 변경 내역을 확인해보자.
> kubectl rollout history deploy nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 version 1.10.1
2 version 1.10.1
자세히 보기 위해 —revision=숫자를 통해 확인한다.
> kubectl rollout history deploy nginx-deployment --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
Labels: app=nginx-deployment
pod-template-hash=748b8dbccc
Annotations: kubernetes.io/change-cause: version 1.10.1
Containers:
nginx-deployment:
Image: nginx:1.10.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
⇒ 특정 리비전의 상세 내역을 확인할 수 있다. 그럼 이제 롤백 해보자.
> kubectl rollout undo deploy nginx-deployment
deployment.apps/nginx-deployment rolled back
> kc get deploy,rs,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 3/3 3 3 9m45s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-748b8dbccc 1 1 1 4m41s
replicaset.apps/nginx-deployment-dfcbd5fd6 3 3 2 9m44s
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-748b8dbccc-bb8f8 1/1 Running 0 4m22s
pod/nginx-deployment-dfcbd5fd6-2mkcc 1/1 Running 0 7s
pod/nginx-deployment-dfcbd5fd6-pbvg7 0/1 ContainerCreating 0 1s
pod/nginx-deployment-dfcbd5fd6-xlfdx 1/1 Running 0 4s
: 아까 구버전이었던 pod/nginx-deployment-dfcbd5fd6로 다시 파드들이 대체되고 있음을 확인할 수 있다.
> kubectl rollout history deploy nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
2 version 1.10.1
3 version 1.10.1
⇒ 새로운 리비전 3이 생성되었음을 확인할 수 있다.
: 클러스터 전체 노드에 특정 파드를 실행시키고자 할 때 사용하는 컨트롤러 (ex. 로그 수집 파드)
예시 yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-elasticsearch
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: fluent/flunetd-kubernetes-daemonset:elasticsearch
env:
- name: testenv
value: value
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
: 상태가 있는 파드들을 관리하는 컨트롤러. “상태”는 다양하게 존재할 수 있다.
예시 yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-statefulsets-service
labels:
app: nginx-statefulsets-service
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx-statefulsets-service
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx-statefulset
serviceName: "nginx-statefulsets-service"
replicas: 3 #세 파드는 순차적으로 시작되거나 종료된다. => 상태
template:
metadata:
labels:
app: nginx-statefulset
spec:
terminationGracePeriodSeconds: 10 #정상 종료 최대 시간을 10초 부여 => 이 또한 상태
containers:
- name: nginx-statefulset
image: nginx
ports:
- containerPort: 80
name: web
: 파드는 영구적이지 않으므로 장애가 생기거나 중단될 경우 문제가 발생할 수 있음. 따라서 다시 파드와 접속할 경우, 다시 시작된 파드로 인해 변경된 IP주소를 알아내기 쉽지 않음
⇒ 서비스는 파드를 위한 안정적인 엔드포인트를 제공함. 라벨에 따라 파드를 감지하고 이를 expose 시킴.
⇒ 즉, 동적으로 변하는 파드에 고정적으로 접근할 때 사용하는 방법이 쿠버네티스 서비스
서비스 yaml 예시
kind: Service
apiVersion: v1
metadata:
name: "monolith"
spec:
selector:
app: "monolith"
secure: "enabled"
ports:
- protocol: "TCP"
port: 443
targetPort: 443
nodePort: 31000
type: NodePort
⇒ 라벨 “app=monolith, secure=enabled”을 보는 selector을 통해, 해당 라벨이 붙여진 파드를 감지하고 31000포드와 443포트를 매핑시킴. 노드 포트 31000은 고정 포트이며, 443 내부 포트와 매핑되어, 외부에서 노드 포트를 통해 접근할 수 있다.
라벨 지정
kubectl label pods secure-monolith 'secure=enabled'
⇒ secure-monolith파드에 ‘secure=enabled’ 라벨을 지정
서비스 유형
Cluster IP의 성질을 확실히 알기 위해 디플로이먼트와 서비스를 생성해보자.
> kubectl create deployment nginx-for-svc --image=nginx --replicas=2 --port=80
⇒ 포트번호에 유의하자.
apiVersion: v1
kind: Service
metadata:
name: clusterip-service
spec:
type: ClusterIP
selector:
app: nginx-for-svc # 식별할 레이블
ports:
- protocol: TCP
port: 80
targetPort: 80
> kc get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
clusterip-service ClusterIP 10.108.248.51 <none> 80/TCP 89s
⇒ 비공개 IP이므로 External-IP가 공백으로 비워져 있다.
> kc describe service clusterip-service
Name: clusterip-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx-for-svc
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.108.248.51
IPs: 10.108.248.51
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.1.0.129:80,10.1.0.130:80
Session Affinity: None
Events: <none>
진짜 연결되어 있는지 확인하자.
> kc get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-for-svc-64c8d59887-krgx7 1/1 Running 0 32s 10.1.0.130 docker-desktop <none> <none>
nginx-for-svc-64c8d59887-qffpd 1/1 Running 0 32s 10.1.0.129 docker-desktop <none> <none>
: 외부에서 노드 IP의 특정 포트로 들어오는 요청을 감지하여, 해당 포트와 연결된 파드로 트래픽을 전달
apiVersion: v1
kind: Service
metadata:
name: nodeport-service
spec:
type: NodePort
selector:
app: nginx-for-svc
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30000
순서는 다음과 같다.
: 외부의 LoadBalancer와 연계해서 설치했을 때 사용한다. (참고로, 외부 LoadBalancer는 실제 장비다!)
이기종 배포 (Heterogenous Deployment) : 소프트웨어가 CPU나 GPU의 구분 없이, 즉 자원 종류의 구분없이 자유롭게 컴퓨터의 자원을 사용하며 배포를 하는 것을 뜻함.
⇒ Hybrid, Multi-Cloud, Infrastructure
이기종 배포는 다음과 같은 단일 환경 배포의 단점들을 극복할 수 있다.
: 배포가 새 버전으로 업데이트되면 새 ReplicaSet이 만들어지고, 이전 복제본이 감소하면서 새 ReplicaSet이 늘어난다.
⇒ 즉, 업데이트 되지 않은 파드들은 점차 감소하고, 업데이트 된 파드들의 수는 점차 증가하며 업데이트를 순차적으로 이루는 것이다.
: 모든 사용자가 아닌 일부 사용자에게 배포를 테스트 하려면 Canary 배포를 사용하면 된다.
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
...
: 순차적으로 새 버전 파드들을 업데이트하는 것이 아닌, 이전 버전의 ‘blue’배포와 새 버전 ‘green’ 배포를 만들어 업데이트할 수 있다.
이 후, LoadBalancer가 Blue버전에서 새로운 Green 버전을 가리키도록 하는 것이 Blue Green 배포다.
즉, 모든 트래픽을 한번에 Green으로 가리키므로 동시다발적으로 신버전의 서비스를 사용가능하게 한다.
⇒ 단, 그 만큼 리소스가 배로 필요하므로 충분한 리소스가 존재한지 확인을 해야한다.
Reference
[k8s] Service - (ClusterIP, NodePort, LoadBalancer)
쿠버네티스에서 반드시 알아야 할 서비스(Service) 유형
온고지신 - 과거에서 배울게 있으면 배우고 똑같은 실수를 반복하지 말자