쿠버네티스 시작하기

Posted by yunki kim on January 23, 2023

  쿠버네티스를 검색하면 "쿠버네티스는 컨테이너를 오케스트레이션 하는 도구"라고 정의하고 있는 글을 심심치 않게 볼 수 있다. 하지만, 컨테이너가 뭔지, 오케스트레이션이 뭔지를 정확히 알지 못하기 때문에 저 문장 자체를 명확히 이해하지 못한다. 우선 저 문장을 이해하기 위해 필요한 개념들을 살펴보자.

  쿠버네티스를 이해하는 대 필요한 용어들을 정리하면 다음과 같다.

용어
컨테이너 앱이 구동되는 환경까지 감싸서 실행할 수 있도록 하는 격리 기술
컨테이너 런타임 컨테이너를 다루는 도구
도커 컨테이너를 다루는 도구 중 가장 유명한 것
쿠버네티스 컨테이너 런타임을 통해 컨테이너를 오케스트레이션 하는 도구
오케스트레이션 여러 서버에 걸친 컨테이너 및 사용하는 환경 설정을 관리하는 행위. 여러 시스템 전반에서 다양한 단계를 필요로 하는 프로세스 또는 워크플로우를 자동화 하는 방식.

  위 용어를 풀어서 "쿠버네티스는 컨테이너를 오케스트레이션 하는 도구"라는 정의에 대입하면 다음과 같이 쿠버네티스를 정의할 수 있다. "쿠버네티스는 앱이 구동되는 환경까지 감싸 실행하는 격리 기술이 여러 서버에 있을 때 이 기술과 환경 설정을 관리하는 도구다." 이전의 정의보다는 조금 더 이해하기 쉬워졌지만 여전히 다음과 같은 의문이 든다.

  - "앱이 구동되는 환경까지 감싸 실행하는 격리 기술"을 왜 VM이 아닌 컨테이너라 칭하는가? VM과 컨테이너는 어떤 차이가 존재하나?

  - 여러 서버에 걸친 컨테이너를 어떻게 관리하는가?

  - 어떤 환경 설정을 관리하는가?

  이제부터 이 의문들을 하나씩 파해쳐보자.

컨테이너

컨테이너와 가상머신

  컨테이너와 가상머신의 구조적 차이는 다음과 같다.

    위 구조를 보면 가장 눈에 띄는 부분이 OS 유무다. 가상 머신은 OS를 띄우는 반면, 컨테이너 구조는 별도의 OS를 띄우지 않는다. 즉, 컨테이너로 실행된 프로세스는 커널을 공유한다. 따라서 실행 속도가 빠르고 성상 손실이 거의 없다. 그에 반해 가상머신은 완전한 하나의 컴퓨터다. 따라서 애플리케이션 구동을 위해 H/W를 OS 위에서 애뮬레이션 하고, 매번 OS를 설치해야 하고, 리소스를 할당해줘야 하기 때문에 느리다.

  위 구조에서 Bin/Library는 애플리케이션 실행에 필요한 환경과 관련된 파일이며 hypervisor는 VM을 생성하고 구동하는 소프트웨이며 CPU, 메모리 등의 리소스를 처리하는 풀이다. 

  비록 컨테이너가 하나의 커널을 공유하지만 컨테이너 입장에서는 독립적인 환경 처럼 보이고 호스트 머신 입장에서는 프로세스로 인식된다. 이는 컨테이너가 리눅스 기준 리눅스 namespace 등의 격리 기술을 사용하는 운영체제 수준의 가상화 기술을 사용하기 때문이다. namespace는 프로세스를 실행할 때 시스템의 리소스를 분리해서 실행할 수 있게 도와주는 기능이다. namespace에 대해서는 별도의 포스팅으로 다루겠다.

리눅스 컨테이너

  위에서 언급했듯 리눅스 컨테이너는 운영체제 수준의 가상화를 사용하기 때문에 성능 손실이 거의 없다(속도가 빠르고 효율적이다). 이 이점과 별개로 다음과 같은 이점도 가지고 있다.

운영체제 수준의 가상화

  별도의 하드웨어 에뮬레이션 없이 리눅스 커널을 공유하기 때문에 게스트 OS가 필요하지 않다.

높은 이식성

  모든 컨테이너는 파일로 구성된 독자적인 실행 환경을 가지고 있다. 이는 이미지 형식으로 공유될 수 있기에 리눅스 커널을 사용하고 같은 컨테이너 런타임을 사용한다면 컨테이너 실행 환경을 공유하고 재현할 수 있다.

stateless

  하나의 컨테이너가 실행되는 환경은 다른 컨테이너에게 영향을 주지 않는다. 이미지 기반으로 컨테이터를 실행한다면 특정 실행 환경을 쉽게 재사용할 수 있다. 

리눅스 컨테이너를 사용하는 이유

  기존 서버 환경에서는 애플리케이션 실행을 위해 서버 컴퓨터의 상태를 지속적으로 관리해야 했다. 또 한, 사용하는 소프트웨어의 버전을 관리하는 리소스도 상당했다. 위에서 설명했듯이 컨테이너는 높은 이식성과 stateless라는 가지고 있기 때문에 컨테이너를 이용하면 애플리케이션 별로 독자적인 환경을 준비하고 관리하는 것이 가능해 셔 서버 관리가 수월해진다. 예를 들어 다음과 같은 것들이 가능해진다.

  - CI/CD: 컨테이너 이미지를 빌드해 배포하고 빠르게 롤백할 수 있다.

  - 환경의 일관성: 로컬이든 클라우드든 어디에서나 동일한 구동을 보장한다.

쿠버네티스

  위에서 언급했듯이 쿠버네티스는 컨테이너 및 사용하는 환경 설정을 관리하는 행위(오케스트레이션)를 가능케 하는 도구다. 여기서 말하는 행위는 다음과 같은 것들을 포함한다.

서비스 디스커버리와 로드 밸런싱

  쿠버네티스는 DNS 이름을 사용하거나 자체 IP 주소를 사용해 컨테이너를 노출할 수 있다. 컨테이너에 대한 트래픽이 많으면, 네트워크 트래픽을 로드밸런싱 하고 배포해서 배포가 안정적으로 이루어질 수 있다.

스토리지 오케스트레이션

  원하는 저장소 시스템을 자동으로 탑재할 수 있다.

자동화된 빈 패킹(bin packing)

  컨테이너화된 작업을 실행하는데 사용할 수 있는 클러스터 노드를 제공한다. 각 컨테이너가 필요로 하는 CPU와 RAM을 알려주면 컨테이너를 노드에 맞추어서 리소스를 가장 잘 활용하게 해 준다.

자동화된 복구(self-healing)

  실패한 컨테이너를 자동으로 재시작하고, 교체한다. 헬스체크(사용자 정의 상태 검사)에 응답하지 않는 컨테이너를 자동으로 죽인다.

시크릿과 구성 관리

  컨테이너 이미지를 재구성하지 않고 스택 구성에 시크릿을 노출하지 않고도 시크릿과 애플리케이션 구성을 배포 및 업데이트할 수 있다.

쿠버네티스 기본 사용법

쿠버네티스 클러스터

  쿠버네티스 클러스터는 컨테이너화된 애플리케이션을 실행하는 노드라는 워커 머신의 집합이다. 한 개 이상의 노드로 이루어져 있다. 클러스터 구조는 다음과 같다.

  - Control Plane: 클러스터 관리는 담당한다. 애플리케이션 스케줄링, 항상성 유지, rolling out 등에 관여한다.

  - Node: 쿠버네티스 클러스터 내 워커 머신으로 동작하는 VM 또는 물리적 컴퓨터다. 각 노드는 control plane과 통신하는 kublet이라는 에이전트를 갖는다. 노드는 control plane이 제공하는 쿠버네티스 API를 통해 control plane과 통신한다. 최종 사용자 역시 쿠버네티스 API를 통해 클러스터와 상호작용할 수 있다.

minikube를 이용한 실습

  minikube는 가벼운 쿠버네티스 구현체이다. 하나의 노드로 구성된 클러스터를 생성한다. 설치 후 다음과 같은 명령어를 통해 클러스터를 구동시킬 수 있다.

1
minikube start
cs

  이제 kubectl이라는 CLI를 이용해 쿠버네티스와 상호작용 해보자.

1
2
3
kubectl version # 설치된 클라이언트와 서버(쿠버네티스)의 버전을 확인한다.
kubectl cluster-info # 클러스터 정보를 조회한다
kubectl get nodes # 애플리케이션을 호스팅 할 수 있는 모든 노드를 보여준다.
cs

애플리케이션 배포하기

  클러스터 구동 후 컨테이너화된 애플리케이션을 배포하기 위해선 deployment 설정을 생성해야 한다. deployment는 쿠버네티스가 애플리케이션을 생성하는 방식과 업데이터 방식을 정의한다. control plane은 deployment에 존재하는 애플리케이션 인스턴스가 노드에서 실행되게 스케줄 한다.

  Deployment controller는 생성된 애플리케이션 인스턴스를 모니터링하고 인스턴스를 구동하는 노드 장애에 대비한 자동 복구 메커니즘을 제공한다.

  Deployment 생성 시 컨테이너 이미지와 복제 수를 지정해야 한다. 이 설정은 추후 deployment 업데이트로 변경할 수 있다.

kubectl 기본

  kubectl 문법은 "kubectl action resource"형태를 가진다 위에서 서술한 "kubectl get nodes"의 경우 "get"이 action이고 "nodes"가 resource이다. 또 한 "kubectl action resource --help"를 통해 추가 옵션을 확인할 수도 있다.

  다음과 같은 커맨드를 통해 애플리케이션을 쿠버네티스 상에서 배포할 수 있다.

1
2
3
4
5
6
# kubernets-bootcamp 는 deployment 이름
# --image 옵션으로 배포에 사용 될 이미지를 지정한다. url은 레파지토리 url이다.
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1
 
# deployment 항목 조회
kubectl get deployments
cs

    쿠버네티스 내부에서 실행되는 팟(pod)은 독립된 네트워크에 존재한다. 따라서 같은 클러스터 내에 존재하는 다른 팟들과 서비스들은 해당 팟을 접근할 수 있지만, 외부에서는 접근할 수 없다. kuberctl을 사용하는 것은 API 엔드포인트를 통해 애플리케이션과 상호작용한다는 의미다. 

  kuberctl은 다음과 같은 명령어로 클러스터 내부의 프라이빗 네트워크로 통하는 프락시를 생성한다. 

1
2
# 호스트와 클러스터 간의 커넥션을 생성한다, 
kubectl proxy
cs

    쿠버네티스 API는 각 팟에 대한 엔드 포인트를 자동으로 생성한다. 따라서 팟 이름과 생성한 프락시를 이용해 각 팟에 접근할 수 있다.

1
2
3
4
5
# 실행 중인 팟 리스트
kubectl get pods
 
# 팟 내부 
kubectl exec -it POD_NAME /bin/bash
cs

팟(pod)과 노드(node)

  팟은 한 개 이상의 애플리케이션 컨테이너로 이루어진 추상적 개념이며 쿠버네티스 애플리케이션에 최소 단위다. 같은 팟에 존재하는 컨테이너들은 다음과 같은 리소스를 공유한다.

  - 볼륨 등 공유 스토리지

  - 클러스터 IP 등 네트워킹

  - 컨테이너 이미지 버전, 사용할 포트 등 각 컨테이너 동작 방식에 대한 정보

  팟은 노드 상에서 동작한다. 노드는 쿠버네티스의 워커 머신을 의미한다. 하나의 노드는 여러 개의 팟을 가질 수 있고, 쿠버네티스 control plane은 클러스터 내 노드를 통해 팟에 대해 자동으로 스케줄링한다. 이때의 스케줄링은 각 노드가 사용 가능한 리소스를 고려해 발생한다.

  모든 쿠버네티스 노드는 다음과 같은 동작을 한다.

  - kubelet은, 쿠버네티스 control plane과 노드 간 통신을 담당하는 프로세스이며, 하나의 머신 상에서 동작하는 팟과 컨테이너를 관리한다.

  - 컨테이너 런타임은 레지스트리에서 컨테이너 이미지를 가져와 묶여 있는 것을 풀고 애플리케이션을 동작사키는 책임을 맡는다.

   

  다음은 배포된 애플리케이션 운용에 필요한 기본적인 명령어다.

1
2
3
4
5
6
kubectl get # 자원을 나열한다
kubectl describe # 자원에 대해 상세한 정보를 보여준다.
kubectl logs # 팟 내 컨테이너의 로그들을 출력한다
kubectl exec # 팟 내 컨테이너에 대한 명령을 실행한다.
kubectl exec POD_NAME -- env # 팟 내의 환경 변수 조회
kubectl exec -it POD_NAME -- bash # 팟 내부 접속
cs

앱 노출을 위해 서비스 이용하기

  팟은 라이프사이클을 갖는다. 워커 노드가 죽으면 해당 노드 내에서 동작하는 모든 팟들도 종료된다. 레플리카셋(replica set)은 지정한 팟 개수만큼 팟이 항상 실행되게 가용성을 보장한다. 또 한, 노드 장애 등의 이유로 파드를 사용할 수 없다면 다른 노드에 대한 파드를 다시 생성한다.

  쿠버네티스에서 서비스는 하나의 논리적 팟 셋과 팟들에 접근할 수 있는 정책을 정의하는 추상적인 개념이다. 서비스는 YAML(추천)이나 JSON을 이용해 정의된다. 각 팟들이 가진 고유의 IP는 서비스의 도움 없이 외부로 노출될 수 없다. 따라서 서비스를 사용해야 애플리케이션에 트래픽을 줄 수 있다. 서비스는 다음과 같은 타입을 갖고 각 타입마다 용도가 다르다.

  - ClusterIP(기본값): 클러스터 내에서 내부 IP에 대해 서비스를 노출한다. 오직 클러스터 내에서만 서비스가 접근될 수 있다.

  - NodePort: 외부에서 노드 IP의 특정 포트(<NodeIP>:<NodePort>)로 들어오는 요청을 감지해 해당 포트와 연결된 팟으로 트래픽을 전달한다.

 - LoadBalancer: 기존 클라우드에서 외부 로드밸런서를 생성하고 서비스에 고정 IP를 할당해 준다. NodePort의 상위 집합이다.

 - ExternalName: CNAME 레코드와 값을 반환해 서비스를 externalName 필드 내용에 매핑한다. 즉, 해당 타입을 이용해 생성한 서비스가 외부 도메인을 가리키게 한다. 프락시 설정을 하지 않으며 kube-dns v1.7 이상 또는 CoreDNS 0.08 이상을 사용해야 한다.

Service와 Label

  특정 파드를 생성하는 레플리카 셋을 YAML 파일로 만들고 구동하면 해당 파드들이 레플리카셋 정의에 명시한 만큼 생성된다. 또 한, 레플리카셋을 삭제하면 파드도 삭제된다. 이 때문에 레플리카셋과 파드가 연결되 있다고 오해하기 쉽다. 이 둘은 사실 느슨한 결합(loosely coupled)을 유지하고 있으며, 이를 위해 label selector를 이용하고 있다.

  label selector를 이해하기 위해 레플리카셋을 이용해 Nginx 파드를 생성하는 YAML 예시를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1 # YAML 파일에서 정의한 오브젝트의 API 버전
kind: ReplicaSet # 리스 종류
metadata: # 라벨, 주석, 이름 등 리소스의 부가 정보
  name: replicaset-nginx
spec: # 리소스 생성을 위한 자세한 정보
  replicas: 3 # 유지할 동일 파드 갯수
 selector:
    matchLabels:
      app: my-nginx-pods-label
# 윗 부분이 레플리카셋 정의
--------------------------------------------------
# 아래 부분이 파드 정의
  template: # 파드를 생성할 때 사용할 템플릿 정의. 일반적으로 파드 생성 방식과 같은 내용을 포함한다. 
    metadata:
      name: my-nginx-pod
      labels:
        app: my-nginx-pods-label
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
cs

  라벨은 파드 등 쿠버네티스 리소스를 분류할 때 사용할 수 있는 메타데이터다. 또 한, 서로 다른 오브젝트가 서로를 찾을 때도 사용한다. 레플리카셋은 spec.selector.matchLabels에 정의된 라벨을 통해 생성하야 하는 파드를 찾는다. spec.selector.matchLabels에 정의 된 라벨이 붙은 파드 갯수가 spec.replicas에 정의 된 개수보다 부족하다면 파드 템플릿의 내용을 이용해 파드를 생성한다.

 

 

참고자료

쿠버네티스 알아보기 1편: 쿠버네티스와 컨테이너, 도커에 대한 기본 개념

하이퍼바이저란?

컨테이너란? 리눅스의 프로세스 격리 기능

오케스트레이션이란?

쿠버네티스란 무엇인가?

시작하세요! 도커/쿠버네티스

쿠버네티스 기초 학습