쿠버네티스 입문의 모든 것

Jiwan Ahn·2023년 6월 1일
53

쿠버네티스

목록 보기
1/2
post-thumbnail

크고 아름다운 조타수 문양


서론

원래부터 쿠버네티스에 관심이 있어 한번 공부하고 싶었는데, 이번에 쿠버네티스 관련 발제를 해야할 일이 생겨 제대로 한번 공부하고 이를 정리해보았다. 이제 막 입문한 내 눈높이에 철저히 맞춰 작성했으니, 여러분들은 아마 더 이해하기 쉬울 것이다.

우선 쿠버네티스에 대해 설명하기 전에, 왜 등장했는지 그 배경부터 알아보자.

참고로 본론 부분의 이미지들은 모두 필자가 힘들게 그린 것이니 이 점 유념하기 바란다. (이 글의 핵심)


컨테이너 개발 시대의 도래

  1. 전통적인 배포 시대 (Traditional Deployment)
    • 한 물리 서버에 애플리케이션을 배포
    • 리소스 할당 문제 → 여러 개의 물리 서버 → 비용 증가!
  2. 가상화된 배포 시대 (Virtualized Deployment)
    • 한 물리 서버에 여러 가상 시스템을 실행하여 애플리케이션을 배포
    • 리소스 할당 문제 다소 해결 및 효율적인 리소스 활용
    • 호스트 운영체제 (Host OS) 위에 게스트 운영체제 (Guest OS)를 띄워 배포하므로
      상당한 오버헤드 발생!
  3. 컨테이너 개발 시대 (Container Deployment)
    • 여러 애플리케이션이 하나의 호스트 운영체제 커널을 사용
    • 독립적인 환경에서 애플리케이션 배포 및 개발 가능
    • 압도적인 리소스 효율성 및 높은 이식성, 가시성

만약 컨테이너를 수십만개를 사용한다면?

이런 컨테이너들을 자동으로 관리하는 것이 컨테이너 오케스트레이션 시스템


쿠버네티스의 등장

아름다운 건 한번 더 봐야지

: Google의 컨테이너 오케스트레이션 시스템 보그(Borg)를 오픈 소스 소프트웨어로 공개

⇒ 10년 이상 대규모 시스템을 운영해오면서 쌓은 노하우를 적용한 것으로, 완벽에 가까운 오케스테이션 성능 자랑

쿠버네티스의 인기 이유

  • 오픈 소스
  • 코드로 모든 것을 할 수 있음
  • 모든 환경에서 실행 가능
  • 다양한 배포 방식
  • 선언적 API

  • 모든 동작을 사용자가 직접 컨트롤 하는 것이 아닌, 원하는 상태를 선언함. (yaml 등)
  • 구체적인 동작은 쿠버네티스가 자동으로 상태를 맞춤
  • 설정한 상태와 차이를 확인 후, Desired State로 제어

쿠버네티스 아키텍쳐

(쿠버네티스 공식 홈페이지에서 소개하는 아키텍처) ⇒ 근데 뭔 소린지 하나도 모르겠으니 더 간단하게 보자!


지금부터 나오는 이미지들은 모두 필자가 힘겹게 그린 그림들이니, 잘 봐두도록 하자.


쿠버네티스 클러스터의 일반적인 구조

  • 보통 노드를 관리하는 마스터 세 개와, 실질적으로 작업을 수행하는 노드로 이루어져 있다.
  • 마스터 중 하나는 리더 마스터 역할로 전체적인 수행을 통솔한다.
  • 나머지 두 개의 마스터는 리더 마스터에 장애가 생기거나 어떤 문제가 생겼을 때, 수행을 대신한다.

Scheduler할당 가능한 노드를 선택하여 새로운 파드를 그 안에 실행
Kube-Controller Manager컨트롤러 각각을 실행하는 컴포넌트
Cloud Controller Manager클라우드 서비스와 연결 (Azure, AWS, Google Cloud 등)
API Server모든 요청이 이 컴포넌트를 거치며, 클러스터로 온 요청이 유효한지 검증 후 반환
etcd쿠버네티스 클러스터의 데이터베이스 역할 (키-값 저장소)

  • 노드는 Proxy, Kubelet, 그리고 Pod로 이루어져 있음
    • Proxy: 클러스턴 안 별도의 가상 네트워크 (비공개 네트워크로, expose 하지 않는 한 외부에서 접속 불가)
    • Kubelet: 파드 내 컨테이너들의 실행을 직접 관리 (파드 스펙을 이용하여 실행 및 헬스 체크 진행)
    • Pod: 쿠버네티스가 관리하는 컨테이너의 묶음 단위 (쿠버네티스는 파드 단위로 쿠버네티스를 관리한다.)
  • 마스터 안에도 Kubelet이 존재한다. (마스터에 있는 도커 안에 위에서 설명한 마스터용 컴포넌트가 존재)
  • Kubelet은 마스터의 Kube-apiserver와 통신하면서 파드의 생성, 관리, 삭제를 담당

오브젝트와 컨트롤러

  • 오브젝트: 하나의 의도를 담은 레코드 (파드, 서비스 볼륨, 네임스페이스)
  • 컨트롤러: 사용자가 바라는 상태와 현재 상태가 일치하도록 오브젝트들을 생성/삭제 (Object의 Status와 Spec이 일치하도록 제어)

오브젝트

네임스페이스

  • 클러스터를 논리적인 단위로 나눠서 사용
  • 클러스터 안에 존재하는 여러 개의 서비스, 파드, 컨트롤러 중, 일부를 묶어 하나의 네임스페이스로 지정
  • 한 네임스페이스는 쿠버네티스 관리자용 네임스페이스가 될 수 있고, 또 다른 네임스페이스는 모든 사용자가 접근할 수 있는 네임스페이스가 될 수 있다.
  • 쿠버네티스가 기본으로 제공하는 네임스페이스는 다음과 같다.
    • default: 기본 네임스페이스
    • kube-system: 쿠버네티스 관리용 파드나 설정이 있음
    • kube-public: 클러스터 안 모든 사용자가 읽을 수 있는 네임스페이스 (클러스터 사용량 등)
    • kube-node-lease: 각 노드의 임대 오브젝트들을 관리하는 네임스페이스
> 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: 
  • apiVersion : 사용하려는 쿠버네티스 버전
  • Kind : 어떤 종류의 오브젝트인지 명시 (Pod, Deployment, Ingress 등)
  • metadata : 오브젝트의 이름이나 레이블
  • spec: 어떤 컨테이너를 가지고 있으며 실행할 때 어떤 동작을 취해야 하는지 선언하는 필드
    (spec == status)
Scalars (strings/numbers)Sequences(arrays/lists)Mappings(hashes/dictionaries)
Name: KimBirth: 2019ProgrammingSkills:
- java
- python
- c
Data:
  Height: 170
  Weight: 80
  • “key: value”로 선언
  • 배열을 선언할 경우 ‘-’로 구별. (ProgrammingSkills의 첫번째 index: java, 두번째 index: python, 세번째 index: c)
  • 상위 필드와 하위 필드를 구분할 때, 하위 필드는 들여쓰기로 구분 (보통 띄어쓰기 두 번)
    ⇒ Data의 하위 필드에는 Height, Weight가 있음.

파드

: 쿠버네티스는 개별 컨테이너를 하나하나 관리하는 것이 아닌, “파드”라는 단위로 컨테이너를 묶어 관리함

특징:

  • 파드마다 고유의 IP가 존재.
    • 파드 안의 컨테이너들은 해당 IP를 공유한다.
    • 각 컨테이너는 포트 번호로 구별한다. (ex. 1.0.0.0:80, 1.0.0.0:81, 1.0.0.0:82)
  • 상호의존성이 높은 컨테이너들, 즉, 컨테이너들이 묶어 하나의 단위 수행을 할 수 있게끔 묶음 ( 웹 서버 + 로그 수집기 + 볼륨 컨테이너)
  • 따라서, 컨테이너들은 같은 목적으로 자원을 공유함.

파드 생성 방법

: 쿠버네티스는 “선언적 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을 선언한다.

  • 쿠버네티스 API 버전 1을 사용
  • 파드의 이름은 boaz-pod2
  • 다음과 같은 두 개의 컨테이너 생성
    • 컨테이너의 이름은 각각 c0, c1
    • 이미지는 동일하게 nginx 사용
    • 포트 번호는 각각 80,81
    • 두 컨테이너는 생성 후 “sh -c sleep 100” 명령어를 실행한다.
  • kubectl apply -f pod-sample.yaml 을 실행하여 파드를 생성한다.
  • kubectl get pods, kubectl describe pods boaz-pod2를 실행하여 알맞게 파드가 설정되었는지 확인한다.

힌트 :

  • 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

컨트롤러

: 파드들을 관리하는 역할. 어떤 목적을 가지고 관리하느냐에 따라 다양한 컨트롤러가 사용될 수 있음.

  • 웹 서비스와 같은 무중단 배포의 경우 : 레플리카세트, 디플로이먼트 등
  • 클러스터의 전체 노드에 같은 파드를 실행해야 하는 경우 : 데몬세트
  • 1회성 작업 : 잡
  • 주기적인 배치 작업 : 크론잡

⇒ 다양한 컨트롤러가 있어 목적에 맞게 유용한 사용이 가능한 것이 쿠버네티스 컨트롤러의 강점!

레플리카세트

: 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 앱을 배포할 때 사용하는 가장 기본적인 컨트롤러다.

레플리카세트와 차이점

  • 앱을 배포할 때 순차적 업데이트 (rolling update)를 진행할 수 있음.
  • 앱 배포 도중 잠시 멈췄다가 다시 배포할 수 있음
  • 앱 배포 후 이전 버전으로 롤백할 수 있음
  • 디플로이먼트는 레플리카세트의 상위 개념으로, 디플로이먼트를 생성하면 레플리카세트 또한 생성된다.

⇒ 즉, 더욱 세밀하게 앱 배포를 관리할 수 있다. (배포 기능을 세분화한 것이 디플로이먼트)

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

스테이트풀세트

: 상태가 있는 파드들을 관리하는 컨트롤러. “상태”는 다양하게 존재할 수 있다.

  • 파드가 순서대로 실행되는 순서. “첫번째 파드가 실행된 상태,” “두번째 파드가 실행된 상태” 등…
  • 순차적인 업데이트
  • 강제 종료가 아닌 정상 종료 (Graceful Termination, 프로세스가 모두 종료된 “상태”)

예시 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’ 라벨을 지정

서비스 유형

  • ClusterIP : 기본 유형이며 클러스터 안에서만 볼 수 있음 (비공개 주소)
  • NodePort : 클러스터의 각 노드에 외부에서 액세스 가능한 IP 주소 제공
  • LoadBalancer : 클라우드 제공업체로부터 부하 분산기를 추가하며 서비스에서 유입되는 트래픽을 내부에 있는 노드로 전달

ClusterIP

  • 컨테이너의 성질이 독립된 환경에서 돌아가기 때문에 서로 통신을 못하는 것처럼, 파드 역시 서로 통신이 불가함.
  • ClusterIP를 사용하면 각 파드들이 다른 리소스 (파드 등)와 통신할 수 있게 된다.
  • 단, 내부 IP이므로 외부에서는 통신할 수 없다.

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
  • 레이블이 nginx-for-svc인 파드들을 고정적으로 연결한다.
  • port: 80 ⇒ 서비스를 클러스터 안에서 지정된 80번 포트를 통해 내부적으로 노출한다. 즉, 이 포트로 보내진 트래픽은 서비스에 의해 선택된 파드로 전달된다.
  • targetPort: 80 ⇒ 파드로 전달되는 요청이 도달하는 포트. 이 포트를 listening하고 있어야 트래픽을 받을 수 있다.
> 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>
  • 위의 Endpoints에 주목하자. 이 Endpoints에 적힌 IP는 실제로 연결된 파드의 IP주소를 의미한다.

진짜 연결되어 있는지 확인하자.

> 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주소가 Endpoints와 연결된 것을 확인할 수 있다.
  • 이 파드들은 Cluster IP 서비스에 연결된 파드들임을 알 수 있다.
  • ClusterIP의 주소는 외부에서는 당연히 접속이 안되며, 클러스터 안에서 생성한 파드가 curl 명령어 등을 통해 내부에서 접속이 가능한 주소다.

NodePort

: 외부에서 노드 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

순서는 다음과 같다.

  • 외부에서 NodePort 서비스의 포트 30000을 통해 트래픽을 전송한다.
  • NodePort 서비스는 트래픽을 80번 포트를 통해 내부 클러스터의 특정 포트로 전달할 수 있도록 한다.
  • 전달받은 트래픽은 targetPort 8080으로 전달된다.

LoadBalancer

: 외부의 LoadBalancer와 연계해서 설치했을 때 사용한다. (참고로, 외부 LoadBalancer는 실제 장비다!)

  • 기존 값은 localhost로 되어 있다.
  • 만일 외부 로드밸런서와 연계되면 실제 외부에서 접근 가능한 IP가 나타난다.

배포 종류

이기종 배포 (Heterogenous Deployment) : 소프트웨어가 CPU나 GPU의 구분 없이, 즉 자원 종류의 구분없이 자유롭게 컴퓨터의 자원을 사용하며 배포를 하는 것을 뜻함.

⇒ Hybrid, Multi-Cloud, Infrastructure

이기종 배포는 다음과 같은 단일 환경 배포의 단점들을 극복할 수 있다.

  • 여유 리소스 부족 : 단일 환경에서는 리소스가 부족할 수 있음.
  • 제한된 지리적 범위 : 지리적으로 멀리 떨어진 사용자들이 하나의 배포에 접근해야 함.
  • 제한된 가용성
  • 공급업체 고착화
  • 유연하지 않은 리소스

순차적 업데이트 (Rolling 배포)

: 배포가 새 버전으로 업데이트되면 새 ReplicaSet이 만들어지고, 이전 복제본이 감소하면서 새 ReplicaSet이 늘어난다.

⇒ 즉, 업데이트 되지 않은 파드들은 점차 감소하고, 업데이트 된 파드들의 수는 점차 증가하며 업데이트를 순차적으로 이루는 것이다.

  • 장점
    • 각 인스턴스별로 배포가 진행되기 때문에 손쉽게 롤백이 가능하다
    • 추가적인 리소스가 필요없다
  • 단점
    • 기존 버전의 인스턴스가 감소하므로, 그만큼 부하가 감소한 인스턴스에게 몰린다.
    • 동시에 업데이트가 되는 것이 아닌, 구버전과 신버전이 공존하고 있어 균일한 서비스를 받지 못한다.

Canary 배포

: 모든 사용자가 아닌 일부 사용자에게 배포를 테스트 하려면 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
...
  • 장점
    • Canary 배포는 베타 테스터에게 테스트 버전 배포와 같은 일을 할 때 사용될 수 있다.
    • 따라서, A/B 테스트가 가능하다. (신버전을 위험 부담 없이 테스트가 가능하다.)
  • 단점
    • 트래픽 제어가 까다롭다
    • ⇒ 자칫하면 모든 트래픽 요청이 Canary 배포 버전에서 처리할 가능성이 있으므로, 베타 테스터들을 배포에 고정 시켜야 한다.
    • 세션 어피니티를 사용하여 베타 테스터들을 Canary 배포에 ‘고정’해야 하므로 난이도가 좀 있다.

Blue Green 배포

: 순차적으로 새 버전 파드들을 업데이트하는 것이 아닌, 이전 버전의 ‘blue’배포와 새 버전 ‘green’ 배포를 만들어 업데이트할 수 있다.

이 후, LoadBalancer가 Blue버전에서 새로운 Green 버전을 가리키도록 하는 것이 Blue Green 배포다.

즉, 모든 트래픽을 한번에 Green으로 가리키므로 동시다발적으로 신버전의 서비스를 사용가능하게 한다.

⇒ 단, 그 만큼 리소스가 배로 필요하므로 충분한 리소스가 존재한지 확인을 해야한다.


Reference

[k8s] Service - (ClusterIP, NodePort, LoadBalancer)

쿠버네티스에서 반드시 알아야 할 서비스(Service) 유형

Google Cloud Skills Boost

Production-Grade Container Orchestration

[Infra] 무중단 배포 방식(Rolling / BlueGreen / Canary)

profile
Engineer, to be a Pioneer.

3개의 댓글

comment-user-thumbnail
2023년 6월 5일

온고지신 - 과거에서 배울게 있으면 배우고 똑같은 실수를 반복하지 말자

답글 달기
comment-user-thumbnail
2023년 6월 9일

안황!안황!안황!

답글 달기
comment-user-thumbnail
2023년 6월 12일

유용(useful)한 정보(information) 잘(good) 보고(see) 갑니다(go) ~(tilde) ^^(eye smile)

답글 달기