블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

쿠버네티스 #4

Volume (디스크)

조대협 (http://bcho.tistory.com)


이번 글에서는 쿠버네티스의 디스크 서비스인 볼륨에 대해서 알아보도록 하겠다.

쿠버네티스에서 볼륨이란 Pod에 종속되는 디스크이다. (컨테이너 단위가 아님). Pod 단위이기 때문에, 그 Pod에 속해 있는 여러개의 컨테이너가 공유해서 사용될 수 있다.

볼륨 종류

쿠버네티스의 볼륨은 여러가지 종류가 있는데,  로컬 디스크 뿐 아니라, NFS, iSCSI, Fiber Channel과 같은 일반적인 외장 디스크 인터페이스는 물론, GlusterFS나, Ceph와 같은 오픈 소스 파일 시스템, AWS EBS, GCP Persistent 디스크와 같은 퍼블릭 클라우드에서 제공되는 디스크, VsphereVolume과 같이 프라이비트 클라우드 솔루션에서 제공하는 디스크 볼륨까지 다양한 볼륨을 지원한다.

자세한 볼륨 리스트는 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes 를 참고하기 바란다.


이 볼륨 타입을 구별해보면 크게 임시 디스크, 로컬 디스크 그리고 네트워크 디스크 등으로 분류할 수 있다.


Temp

Local

Network

emptyDir

hostPath

GlusterFS

gitRepo

NFS

iSCSI

gcePersistentDisk

AWS EBS

azureDisk

Fiber Channel

Secret

VshereVolume


그럼 각각에 대해서 알아보도록 하자

emptyDir

emptyDir은 Pod가 생성될때 생성되고, Pod가 삭제 될때 같이 삭제되는 임시 볼륨이다.

단 Pod 내의 컨테이너 크래쉬되어 삭제되거나 재시작 되더라도 emptyDir의 생명주기는 컨테이너 단위가 아니라, Pod 단위이기 때문에, emptyDir은 삭제 되지 않고 계속해서 사용이 가능하다.

생성 당시에는 디스크에 아무 내용이 없기 때문에, emptyDir  이라고 한다.

emptyDir의 물리적으로 노드에서 할당해주는 디스크에 저장이 되는데, (각 환경에 따라 다르다. 노드의 로컬 디스크가 될 수 도 있고, 네트워크 디스크등이 될 수 도 있다.) emptyDir.medium 필드에 “Memory”라고 지정해주면, emptyDir의 내용은 물리 디스크 대신 메모리에 저장이 된다.


다음은 하나의 Pod에 nginx와 redis 컨테이너를 기동 시키고, emptyDir 볼륨을 생성하여 이를 공유하는 설정이다.


apiVersion: v1

kind: Pod

metadata:

 name: shared-volumes

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: shared-storage

     mountPath: /data/shared

 - name: nginx

   image: nginx

   volumeMounts:

   - name: shared-storage

     mountPath: /data/shared

 volumes:

 - name : shared-storage

   emptyDir: {}


shared-storage라는 이름으로 emptyDir 기반의 볼륨을 만든 후에, nginx와 redis 컨테이너의 /data/shared 디렉토리에 마운트를 하였다.


Pod를 기동 시킨후에, redis 컨테이너의 /data/shared 디렉토리에 들어가 보면 당연히 아무 파일도 없는 것을 확인할 수 있다.

이 상태에서 아래와 같이 file.txt 파일을 생성하였다.



다음 nginx 컨테이너로 들어가서 /data/shared 디렉토리를 살펴보면 file.txt 파일이 있는 것을 확인할 수 있다.



이 파일은 redis 컨테이너에서 생성이 되어 있지만, 같은 Pod 내이기 때문에, nginx 컨테이너에서도 접근이 가능하게 된다.

hostPath

다음은 hostPath 라는 볼륨 타입인데, hostPath는 노드의 로컬 디스크의 경로를 Pod에서 마운트해서 사용한다. 같은 hostPath에 있는 볼륨은 여러 Pod 사이에서 공유되어 사용된다.

또한  Pod가 삭제 되더라도 hostPath에 있는 파일들은 삭제되지 않고 다른 Pod가 같은 hostPath를 마운트하게 되면, 남아 있는 파일을 액세스할 수 있다.


주의할점 중의 하나는 Pod가 재시작되서 다른 노드에서 기동될 경우, 그 노드의 hostPath를 사용하기 때문에, 이전에 다른 노드에서 사용한 hostPath의 파일 내용은 액세스가 불가능하다.


hostPath는 노드의 파일 시스템을 접근하는데 유용한데, 예를 들어 노드의 로그 파일을 읽어서 수집하는 로그 에이전트를 Pod로 배포하였을 경우, 이 Pod에서 노드의 파일 시스템을 접근해야 한다. 이러한 경우에 유용하게 사용할 수 있다.


아래는 노드의 /tmp 디렉토리를 hostPath를 이용하여 /data/shared 디렉토리에 마운트 하여 사용하는 예제이다.


apiVersion: v1

kind: Pod

metadata:

 name: hostpath

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data/shared

 volumes:

 - name : terrypath

   hostPath:

     path: /tmp

     type: Directory



이 Pod를 배포해서 Pod를 Id를 얻어보았다.


Pod Id를 통해서 VM을 아래와 같이 확인하였다.


VM에 SSH로 접속해서 /tmp/에 hello.txt 파일을 생성하였다.




다음, Pod의 컨테이너에서 마운트된 /data/shared 디렉토리를 확인해보면 아래와 같이 노드의 /tmp 디렉토리의 내용이 그대로 보이는 것을 볼 수 있다.


gitRepo

볼륨 타입중에 gitRepo라는 유용한 볼륨 타입이 하나 있어서 소개한다.

이 볼륨은 생성시에 지정된 git 리파지토리의 특정 리비전의 내용을 clone을 이용해서 내려 받은후에 디스크 볼륨을 생성하는 방식이다. 물리적으로는 emptyDir이 생성되고, git 레파지토리 내용을 clone으로 다운 받는다.




HTML과 같은 정적 파일이나 Ruby on rails, PHP, node.js 와 같은 스크립트 언어 기반의 코드들은 gitRepo 볼륨을 이용하여 손쉽게 배포할 수 있다.


apiVersion: v1

kind: Pod

metadata:

name: gitrepo-volume-pod

spec:

containers:

- image: nginx:alpine

  name: web-server

  volumeMounts:

  - name: html

    mountPath: /usr/share/nginx/html

    readOnly: true

  ports:

  - containerPort: 80

    protocol: TCP

volumes:

- name: html

  gitRepo:

       repository: https://github.com/luksa/kubia-website-example.git

       revision: master

       directory: .


이 설정은 https://github.com/luksa/kubia-website-example.git 의 master 리비전을 클론으로 다운받아서 /usr/share/nginx/html에 마운트 시키는 설정이다.


PersistentVolume and PersistentVolumeClaim

일반적으로 디스크 볼륨을 설정하려면 물리적 디스크를 생성해야 하고, 이러한 물리적 디스크에 대한 설정을 자세하게 이해할 필요가 있다.

쿠버네티스는 인프라에 대한 복잡성을 추상화를 통해서 간단하게 하고, 개발자들이 손쉽게 필요한 인프라 (컨테이너,디스크, 네트워크)를 설정할 수 있도록 하는 개념을 가지고 있다

그래서 인프라에 종속적인 부분은 시스템 관리자가 설정하도록 하고, 개발자는 이에 대한 이해 없이 간단하게 사용할 수 있도록 디스크 볼륨 부분에 PersistentVolumeClaim (이하 PVC)와 PersistentVolume (이하 PV)라는 개념을 도입하였다.


시스템 관리자가 실제 물리 디스크를 생성한 후에, 이 디스크를 PersistentVolume이라는 이름으로 쿠버네티스에 등록한다.

개발자는 Pod를 생성할때, 볼륨을 정의하고, 이 볼륨 정의 부분에 물리적 디스크에 대한 특성을 정의하는 것이 아니라 PVC를 지정하여, 관리자가 생성한 PV와 연결한다.


그림으로 정리해보면 다음과 같다.


시스템 관리자가 생성한 물리 디스크를 쿠버네티스 클러스터에 표현한것이 PV이고, Pod의 볼륨과 이 PV를 연결하는 관계가 PVC가 된다.


이때 주의할점은 볼륨은 생성된후에, 직접 삭제하지 않으면 삭제되지 않는다. PV의 생명 주기는 쿠버네티스 클러스터에 의해서 관리되면 Pod의 생성 또는 삭제에 상관없이 별도로 관리 된다. (Pod와 상관없이 직접 생성하고 삭제해야 한다.)

PersistentVolume

PV는 물리 디스크를 쿠버네티스에 정의한 예제로, NFS 파일 시스템 5G를 pv0003이라는 이름으로 정의하였다.




PV를 설정하는데 여러가지 설정 옵션이 있는데, 간략하게 그 내용을 살펴보면 다음과 같다.

  • Capacity
    볼륨의 용량을 정의한다. 현재는 storage 항목을 통해서 용량만을 지정하는데 향후에는 필요한 IOPS나 Throughput등을 지원할 예정이다.

  • VolumeMode
    VolumeMode는 Filesystem (default)또는 raw를 설정할 수 있는데, 볼륨이 일반 파일 시스템인데, raw 볼륨인지를 정의한다.

  • Reclaim Policy
    PV는 연결된 PVC가 삭제된 후 다시 다른 PVC에 의해서 재 사용이 가능한데, 재 사용시에 디스크의 내용을 지울지 유지할지에 대한 정책을 Reclaim Policy를 이용하여 설정이 가능하다.

    • Retain : 삭제하지 않고 PV의 내용을 유지한다.

    • Recycle : 재 사용이 가능하며, 재 사용시에는 데이타의 내용을 자동으로 rm -rf 로 삭제한 후 재사용이 된다.

    • Delete : 볼륨의 사용이 끝나면, 해당 볼륨은 삭제 된다. AWS EBS, GCE PD,Azure Disk등이 이에 해당한다.

Reclaim Policy은 모든 디스크에 적용이 가능한것이 아니라, 디스크의 특성에 따라서 적용이 가능한 Policy가 있고, 적용이 불가능한 Policy 가 있다.

  • AccessMode
    AccessMode는 PV에 대한 동시에 Pod에서 접근할 수 있는 정책을 정의한다.

    • ReadWriteOnce (RWO)
      해당 PV는 하나의 Pod에만 마운트되고 하나의 Pod에서만 읽고 쓰기가 가능하다.

    • ReadOnlyMany(ROX)
      여러개의 Pod에 마운트가 가능하며, 여러개의 Pod에서 동시에 읽기가 가능하다. 쓰기는 불가능하다.

    • ReadWriteMany(RWX)
      여러개의 Pod에 마운트가 가능하고, 동시에 여러개의 Pod에서 읽기와 쓰기가 가능하다.

위와 같이 여러개의 모드가 있지만, 모든 디스크에 사용이 가능한것은 아니고 디스크의 특성에 따라서 선택적으로 지원된다.


PV의 라이프싸이클

PV는 생성이 되면, Available 상태가 된다. 이 상태에서 PVC에 바인딩이 되면 Bound 상태로 바뀌고 사용이 되며, 바인딩된 PVC가 삭제 되면, PV가 삭제되는 것이 아니라  Released 상태가 된다. (Available이 아니면 사용은 불가능하고 보관 상태가 된다.)

PV 생성 (Provisioning)

PV의 생성은 앞에서 봤던것 처럼 yaml 파일등을 이용하여, 수동으로 생성을 할 수 도 있지만, 설정에 따라서 필요시마다 자동으로 생성할 수 있게 할 수 있다. 이를 Dynamic Provisioning (동적 생성)이라고 하는데, 이에 대해서는 PVC를 설명하면서 같이 설명하도록 하겠다.

PersistentVolumeClaim

PVC는 Pod의 볼륨과 PVC를 연결(바인딩/Bind)하는 관계 선언이다.

아래 예제를 보자 아래 예제는 PVC의 예제이다.



(출처 : https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims)


  • accessMode, VolumeMode는 PV와 동일하다.

  • resources는 PV와 같이, 필요한 볼륨의 사이즈를 정의한다.

  • selector를 통해서 볼륨을 선택할 수 있는데, label selector 방식으로 이미 생성되어 있는 PV 중에, label이 매칭되는 볼륨을 찾아서 연결하게 된다.


PV/PVC 예제

그러면 예제를 통해서 PV를 생성하고, 이 PV를 PVC에 연결한후에, PVC를 Pod에 할당하여 사용하는 방법을 살펴보도록 하자. 예제는 구글 클라우드 환경을 사용하였다.

1.물리 디스크 생성

먼저 구글 클라우드 콘솔에서 Compute Engine 부분에서 아래와 같이 Disks 부분에서 물리 디스크를 생성한다.


디스크를 pv-demo-disk라는 이름으로 생성하였다.

이때 주의할점은 디스크의 region과 zone이 쿠베네티스 클러스터가 배포된 region과 zone에 동일해야 한다.


2.생성된 디스크로 PV를 선언

생성된 디스크를 이용하여 PV를 생성한다. 아래는 PV를 생성하기 위한 yaml 파일이다.


existing-pd.yaml

apiVersion: v1

kind: PersistentVolume

metadata:

 name: pv-demo

spec:

 storageClassName:

 capacity:

   storage: 20G

 accessModes:

   - ReadWriteOnce

 gcePersistentDisk:

   pdName: pv-demo-disk

   fsType: ext4


PV의이름은 pv-demo이고, gcePersistentDisk에서 앞에서 생성한 pv-demo-disk 를 사용하도록 정의하였다.

파일을 실행하면, 아래와 같이 pv-demo로 PV가 생성된것을 확인할 수 있다.

3. 다음 PVC를 생성한다.

아래는 앞에서 생성한 pv-demo PV를 사용하는 PVC를 생성하는 yaml 파일이다. 하나의 Pod에서만 액세스가 가능하도록 accessMode를 ReadWriteOnce로 설정하였다.


existing-pvc.yaml

apiVersion: v1

kind : PersistentVolumeClaim

metadata:

 name: pv-claim-demo

spec:

 storageClassName: ""

 volumeName: pv-demo

 accessModes:

   - ReadWriteOnce

 resources:

   requests:

     storage: 20G


4. Pod를 생성하여, PVC를 바인딩

그러면 앞에서 생성한 PV와 PVC를 Pod에 생성해서 연결하자


existing-pod-redis.yaml

apiVersion: v1

kind: Pod

metadata:

 name: redis

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data

 volumes:

 - name : terrypath

   persistentVolumeClaim:

     claimName: pv-claim-demo


앞에서 생성한 PVC pv-claim-demo를 Volume에 연결한후, 이 볼륨을 /data 디렉토리에 마운트 하였다.

Pod를 생성한후에, 생성된 Pod에 df -k 로 디스크 연결 상태를 확인해 보면 다음과 같다.



/dev/sdb 가 20G로 생성되어 /data 디렉토리에 마운트 된것을 확인할 수 있다.

Dynamic Provisioning

앞에서 본것과 같이 PV를 수동으로 생성한후 PVC에 바인딩 한 후에, Pod에서 사용할 수 있지만, 쿠버네티스 1.6에서 부터 Dynamic Provisioning (동적 생성) 기능을 지원한다. 이 동적 생성 기능은 시스템 관리자가 별도로 디스크를 생성하고 PV를 생성할 필요 없이 PVC만 정의하면 이에 맞는 물리 디스크 생성 및 PV 생성을 자동화해주는 기능이다.




PVC를 정의하면, PVC의 내용에 따라서 쿠버네티스 클러스터가 물리 Disk를 생성하고, 이에 연결된 PV를 생성한다.

실 환경에서는 성능에 따라 다양한 디스크(nVME, SSD, HDD, NFS 등)를 사용할 수 있다. 그래서 디스크를 생성할때, 필요한 디스크의 타입을 정의할 수 있는데, 이를 storageClass 라고 하고, PVC에서 storage class를 지정하면, 이에 맞는 디스크를 생성하도록 한다.

Storage class를 지정하지 않으면, 디폴트로 설정된 storage class 값을 사용하게 된다.


동적 생성 방법은 어렵지 않다. PVC에 필요한 디스크 용량을 지정해놓으면, 자동으로 이에 해당하는 물리 디스크 및 PV가 생성이 된다. 아래는 동적으로 PV를 생성하는 PVC 예제이다.


dynamic-pvc.yaml

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

 name: mydisk

spec:

 accessModes:

   - ReadWriteOnce

 resources:

   requests:

     storage: 30Gi


다음 Pod를 생성한다.

apiVersion: v1

kind: Pod

metadata:

 name: redis

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data/shared

 volumes:

 - name : terrypath

   persistentVolumeClaim:

     claimName: mydisk


Pod를 생성한후에, kubectl get pvc 명령어를 이용하여, 생성된 PVC와 PV를 확인할 수 있다.

PVC는 위에서 정의한것과 같이 mydisk라는 이름으로 생성되었고, Volume (PV)는 pvc-4a…. 식으로 새롭게 생성되었다.

Storage class

스토리지 클래스를 살펴보자,

아래는  AWS EBS 디스크에 대한 스토리지 클래스를 지정한 예로, slow 라는 이름으로 스토리지 클래스를 지정하였다. EBS 타입은 io1을 사용하고, GB당 IOPS는 10을 할당하도록 하였고, 존은 us-east-1d와 us-east-1c에 디스크를 생성하도록 하였다.



아래는 구글 클라우드의 Persistent Disk (pd)의 예로, slow라는 이름으로 스토리지 클래스를 지정하고, pd-standard (HDD)타입으로 디스크를 생성하되 us-central1-a와 us-central1-b 존에 디스크를 생성하도록 하였다.



이렇게 정의한 스토리지 클래스는  PVC 정의시에, storageClassName에 적으면 PVC에 연결이 되고, 스토리지 클래스에 정해진 스펙에 따라서 물리 디스크와 PV를 생성하게 된다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 늦둥이해커 2018.06.11 16:22  댓글주소  수정/삭제  댓글쓰기

    좋은 내용 감사드립니다~~

  2. 민재's아빠 2019.01.09 08:21  댓글주소  수정/삭제  댓글쓰기

    좋은 내용 감사합니다

  3. 초보 2019.01.09 23:29  댓글주소  수정/삭제  댓글쓰기

    gitRepo 볼륨이 없어져 어떻게 구현할지 막막하네요.. 레퍼런스 찾기가 너무 어렵습니다 ㅠㅠ

  4. ㅠㅠ 2019.03.18 00:13  댓글주소  수정/삭제  댓글쓰기

    감사합니다! 정말 감사합니다!

쿠버네티스 #4

아키텍쳐


조대협 (http://bcho.tistory.com)


쿠버네티스에 대한 개념 이해가 끝났으면, 이제 쿠버네티스가 실제로 어떤 구조로 구현이 되어 있는지 아키텍쳐를 살펴보도록 하자. 아키텍쳐를 이용하면 동작 원리를 이해할 수 있기 때문에, 쿠버네티스의 사용법을 이해하는데 도움이 된다.




<그림. 쿠버네티스 아키텍쳐>

출처 https://kubernetes.io/docs/concepts/architecture/

마스터와 노드

쿠버네티스는 크게 마스터(Master)와 노드(Node) 두 개의 컴포넌트로 분리된다.

마스터는 쿠버네티스의 설정 환경을 저장하고 전체 클러스터를 관리하는 역할을 맏고있고, 노드는 파드나 컨테이너 처럼 쿠버네티스 위에서 동작하는 워크로드를 호스팅하는 역할을 한다.

마스터

쿠버네티스 클러스터 전체를 컨트럴 하는 시스템으로, 크게 다음과 API 서버, 스케쥴러, 컨트롤러 매니져, etcd 로 구성되어 있다.


API 서버

쿠버네티스는 모든 명령과 통신을 API를 통해서 하는데, 그 중심이 되는 서버가 API서버이다.

쿠버네티스의 모든 기능들을 REST API로 제공하고 그에 대한 명령을 처리한다.

Etcd

API 서버가 명령을 주고 받는 서버라면, 쿠버네티스 클러스터의 데이타 베이스 역할이 되는 서버로 설정값이나 클러스터의 상태를 저장하는 서버이다.  etcd라는 분산형 키/밸류 스토어 오픈소스 ()https://github.com/coreos/etcd) 로 쿠버네티스 클러스터의 상태나 설정 정보를 저장한다.

스케쥴러

스케쥴러는 Pod,서비스등 각 리소스들을 적절한 노드에 할당하는 역할을 한다.

컨트롤러 매니져

컨트롤러 매니저는 컨트롤러(Replica controller, Service controller, Volume Controller, Node controller 등)를 생성하고 이를 각 노드에 배포하며 이를 관리하는 역할을 한다.


DNS

그림에는 빠져있는데, 쿠버네티스는 리소스의 엔드포인트(Endpoint)를 DNS로 맵핑하고 관리한다. Pod나 서비스등은 IP를 배정받는데, 동적으로 생성되는 리소스이기 때문에 그 IP 주소가 그때마다 변경이 되기 때문에, 그 리소스에 대한 위치 정보가 필요한데, 이러한 패턴을 Service discovery 패턴이라고 하는데, 쿠버네티스에서는 이를 내부 DNS서버를 두는 방식으로 해결하였다.

새로운 리소스가 생기면, 그 리소스에 대한 IP와 DNS 이름을 등록하여, DNS 이름을 기반으로 리소스에 접근할 수 있도록 한다.

노드

노드는 마스터에 의해 명령을 받고 실제 워크로드를 생성하여 서비스 하는 컴포넌트이다.

노드에는 Kubelet, Kube-proxy,cAdvisor 그리고 컨테이너 런타임이 배포된다.

Kubelet

노드에 배포되는 에이전트로, 마스터의 API서버와 통신을 하면서, 노드가 수행해야 할 명령을 받아서 수행하고, 반대로 노드의 상태등을 마스터로 전달하는 역할을 한다.

Kube-proxy

노드로 들어오거는 네트워크 트래픽을 적절한 컨테이너로 라우팅하고, 로드밸런싱등 노드로 들어오고 나가는 네트워크 트래픽을 프록시하고, 노드와 마스터간의 네트워크 통신을 관리한다.

Container runtime (컨테이너 런타임)

Pod를 통해서 배포된 컨테이너를 실행하는 컨테이너 런타임이다. 컨테이너 런타임은 보통 도커 컨테이너를 생각하기 쉬운데, 도커 이외에도 rkt (보안이 강화된 컨테이너), Hyper container 등 다양한 런타임이 있다.

cAdvisor

cAdvisor는 각 노드에서 기동되는 모니터링 에이전트로, 노드내에서 가동되는 컨테이너들의 상태와 성능등의 정보를 수집하여, 마스터 서버의 API 서버로 전달한다.

이 데이타들은 주로 모니터링을 위해서 사용된다.


전체적인 구조 자체는 복잡하지 않다. 모듈화가 되어 있고, 기능 확장을 위해서 플러그인을 설치할 수 있는 구조로 되어 있다. 예를 들어 나중에 설명하겠지만 모니터링 정보를 저장하는 데이타베이스로는 많이 사용되는 Influx 데이타 베이스 또는 Prometheus 와 같은 데이타 베이스를 선택해서 설치할 수 있고 또는 커스텀 인터페이스 개발을 통해서, 알맞은 저장소를 개발하여 연결이 가능하다.






본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

쿠버네티스 #3

개념이해 (2/2) : 고급 컨트롤러


조대협 (http://bcho.tistory.com)



고급 컨트롤러

RC,RS,Deployment는 웹서버와 같은 일반적인 워크로드에 대해 Pod를 관리하기 위한 컨트롤러이다. 실제 운영환경에서는 웹서버와 같은 일반적인 워크로드 이외에,  데이타베이스,배치 작업, 데몬 서버와 같이 다양한 형태의 워크로드 모델이 존재하는데 이를 지원하기 위해서 쿠버네티스는 다양한 컨트롤러를 제공함으로써, Pod의 운영을 다양한 시나리오에 맞게 지원하고 있다.

DaemonSet

DaemonSet (이하 DS) 은 Pod가 각각의 노드에서 하나씩만 돌게 하는 형태로 Pod를 관리하는 컨트롤러이다. 아래 그림을 보자


RC나 RS에 의해서 관리되는 Pod 는 여러 노드의 상황에 따라서 일반적으로 비균등적으로 배포가 되지만,  DS에 의해 관리되는 Pod는 모든 노드에 균등하게 하나씩만 배포 된다.

이런 형태의 워크로드는 서버의 모니터링이나 로그 수집 용도로 많이 사용되는데, DS의 다른 특징중 하나는, 특정 Node들에만 Pod가 하나씩만 배포 되도록 설정이 가능하다.

앞에서 언급한 로그나 모니터링 시나리오에서 특정 장비에 대한 모니터링을 하고자 할 때 이런 시나리오가 유효하다. 예를 들어 특정 장비(노드)에만 Nvme SSD를 사용하거나 GPU를 사용할 경우에는 그 장비가 설치된 노드만을 모니터링하면 된다.



DS는 특정 노드에만 Pod를 배포할 수 있도록 , Pod의 “node selector”를 이용해서 라벨을 이용하여 특정 노드만을 선택할 수 있게 지원한다.

Job

워크로드 모델중에서 배치나 한번 실행되고 끝나는 형태의 작업이 있을 수 있다.

예를 들어 원타임으로 파일 변환 작업을 하거나, 또는 주기적으로 ETL 배치 작업을 하는 경우에는 웹서버 처럼 계속 Pod가 떠 있을 필요없이 작업을 할때만 Pod 를 띄우면 된다.

이러한 형태의 워크로드 모델을 지원하는 컨트롤러를 Job이라고 한다.


Job에 의해서 관리되는 Pod는 Job이 종료되면, Pod 를 같이 종료한다.

Job을 정의할때는 보통 아래와 같이 컨테이너 스펙 부분에 image 뿐만 아니라, 컨테이너에서 Job을 수행하기 위한 커맨드(command) 를 같이 입력한다.


apiVersion: batch/v1
kind: Job
metadata:
 name: pi
spec:
 template:
   spec:
     containers:
     - name: pi
       image: perl
       command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
     restartPolicy: Never
 backoffLimit: 4



Job 컨트롤러에 의해서 실행된 Pod 는 이 command의 실행 결과에 따라서 Job이 실패한지 성공한지를 판단한다. (프로세스의 exit 코드로 판단한다.)  Job이 종료되었는데, 결과가 실패라면,이 Job을 재 실행할지 또는 그냥 끝낼지를 설정에 따라서 결정한다.


Job이 끝나기 전에 만약에 비정상적으로 종료된다면 어떻게 될것인가?

아래 그림을 보자 쿠버네티스 클러스터에서 특정 노드가 장애가 났다고 가정하자, RC/RS에 의해서 관리되고 있는 Pod 는 자동으로 다른 노드에서 다시 자동으로 생성되서 시작될것이고, 컨트롤러에 의해 관리되고 있지 않은 Pod 는 다시 다른 노드에서 기동되지 않고 사라질것이다.

그렇다면 Job 에 의해서 관리되는 Pod는 어떻게 될것인가?



두가지 방법으로 설정할 수 있는데, 장애시 다시 시작하게 하거나 또는 장애시 다시 시작하지 않게 할 수 있다.

다시 시작의 개념은 작업의 상태가 보장되는것이 아니라, 다시 처음부터 작업이 재 시작되는 것이기 때문에 resume이 아닌 restart의 개념임을 잘 알아야하고, 다시 시작 처음부터 작업을 시작하더라도 데이타가 겹치거나 문제가 없는 형태라야 한다.


배치 작업의 경우 작업을 한번만 실행할 수 도 있지만, 같은 작업을 연속해서 여러번 수행하는 경우가 있다. (데이타가 클 경우 범위를 나눠서 작업하는 경우) 이런 경우를 위해서 Job 컨트롤러는 같은 Pod를 순차적으로, 여러번 실행할 수 있도록 설정이 가능하다. Job 설정에서 completion에 횟수를 주면, 같은 작업을 completion 횟수만큼 순차적으로 반복한다.


만약에 여러 작업을 처리해야 하지만 순차성이 필요없고 병렬로 처리를 하고 싶다면, Job설정에서 parallelism 에 동시 실행할 수 있는 Pod의 수를 주면, 지정된 수 만큼 Pod를 실행하여 completion 횟수를 병렬로 처리한다. 아래 그림은 completion이 5, parallelism이 2일때, 하나의 노드에서 모든 Pod가 실행된다고 가정했을때, 실행 순서를 보여주는 그림이다.



Cron jobs

Job 컨트롤러에 의해서 실행되는 배치성 작업들에 대해서 고려할 점중 하나는 이런 배치성 작업을 메뉴얼로 실행하는 것이 아니라, 주기적으로 자동화해서 실행할 필요가 있는데, 이렇게 주기적으로 정해진 스케쥴에 따라 Job 컨트롤러에 의해 작업을 실행해주는 컨트롤러로 cron jobs 컨트롤러가 있다.

cron jobs 컨트롤러는 Unix cron 명령어처럼, 시간에 따른 실행조건을 정의해놓을 수 있고, 이에 따라 Job 컨트롤러를 실행하여, 정의된 Pod를 실행할 수 있게 한다.


아래는 cron jobs 컨트롤러의 예제인데, job 컨트롤러와 설정이 다르지 않다.


apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: hello
spec:
 schedule: "*/1 * * * *"
 jobTemplate:
   spec:
     template:
       spec:
         containers:
         - name: hello
           image: busybox
           args:
           - /bin/sh
           - -c
           - date; echo Hello from the Kubernetes cluster
         restartPolicy: OnFailure



다른 점은 CronJob 스펙 설정 부분에 “schedule”이라는 항목이 있고 반복 조건을 unix cron과 같이 설정하면 된다.

StatefulSet

마지막으로, 1.9에 정식으로 릴리즈된 StatefulSet이 있다.

RS/RC나 다른 컨트롤러로는 데이타베이스와 같이 상태를 가지는 애플리케이션을 관리하기가 어렵다.

그래서 이렇게 데이타 베이스등과 같이 상태를 가지고 있는 Pod를 지원하기 위해서 StatefulSet 이라는 것이 새로 소개되었는데, 이를 이해하기 위해서는 쿠버네티스의 디스크 볼륨에 대한 이해가 필요하기 때문에 다음에 볼륨과 함께 다시 설명하도록 한다.


2회에 걸쳐서 쿠버네티스의 컴포넌트 개념들에 대해서 살펴보았고, 다음글에서는 쿠버네티스의 아키텍쳐에 대해서 간략하게 살펴보도록 하겠다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

쿠버네티스 #2

개념 이해 (1/2)


조대협 (http://bcho.tistory.com)


쿠버네티스를 공부하면서 가장 헷갈리는 부분이 용어와 컨셉이다. 이 컨셉만 잘 이해하면 쿠버네티스를 쉽게 이해하고 사용할 수 있지만, 적어도 내 기준에서는 문서들의 용어나 개념 설명이 다소 어려웠다.

쿠버네티스의 개념은 크게 오브젝트 두개의 개념에서 출발한다. 각각을 살펴보도록 하자

마스터와 노드

쿠버네티스를 이해하기 위해서는 먼저 클러스터의 구조를 이해할 필요가 있는데, 구조는 매우 간단하다. 클러스터 전체를 관리하는 컨트롤러로써 마스터가 존재하고, 컨테이너가 배포되는 머신 (가상머신이나 물리적인 서버머신)인 노드가 존재한다.


오브젝트

쿠버네티스를 이해하기 위해서 가장 중요한 부분이 오브젝트이다. 가장 기본적인 구성단위가 되는 기본 오브젝트(Basic object)와, 이 기본 오브젝트(Basic object) 를 생성하고 관리하는 추가적인 기능을 가진 컨트롤러(Controller) 로 이루어진다. 그리고 이러한 오브젝트의 스펙(설정)이외에 추가정보인 메타 정보들로 구성이 된다고 보면 된다.

오브젝트 스펙 (Object Spec)

오브젝트들은 모두 오브젝트의 특성 (설정정보)을 기술한 오브젝트 스펙 (Object Spec)으로 정의가 되고, 커맨드 라인을 통해서 오브젝트 생성시 인자로 전달하여 정의를 하거나 또는 yaml이나 json 파일로 스펙을 정의할 수 있다.

기본 오브젝트 (Basic Object)

쿠버네티스에 의해서 배포 및 관리되는 가장 기본적인 오브젝트는 컨테이너화되어 배포되는 애플리케이션의 워크로드를 기술하는 오브젝트로 Pod,Service,Volume,Namespace 4가지가 있다.


간단하게 설명 하자면 Pod는 컨테이너화된 애플리케이션, Volume은 디스크, Service는 로드밸런서 그리고 Namespace는 패키지명 정도로 생각하면 된다. 그러면 각각을 자세하게 살펴보도록 하자.

Pod

Pod 는 쿠버네티스에서 가장 기본적인 배포 단위로, 컨테이너를 포함하는 단위이다.

쿠버네티스의 특징중의 하나는 컨테이너를 개별적으로 하나씩 배포하는 것이 아니라 Pod 라는 단위로 배포하는데, Pod는 하나 이상의 컨테이너를 포함한다.


아래는 간단한 Pod를 정의한 오브젝트 스펙이다. 하나하나 살펴보면


apiVersion: v1

kind: Pod

metadata:

 name: nginx

spec:

 containers:

 - name: nginx

   image: nginx:1.7.9

   ports:

   - containerPort: 8090


  • apiVersion은 이 스크립트를 실행하기 위한 쿠버네티스 API 버전이다 보통 v1을 사용한다.

  • kind 에는 리소스의 종류를 정의하는데, Pod를 정의하려고 하기 때문에, Pod라고 넣는다.

  • metadata에는 이 리소스의 각종 메타 데이타를 넣는데, 라벨(뒤에서 설명할)이나 리소스의 이름등 각종 메타데이타를 넣는다

  • spec 부분에 리소스에 대한 상세한 스펙을 정의한다.

    • Pod는 컨테이너를 가지고 있기 때문에, container 를 정의한다. 이름은 nginx로 하고 도커 이미지 nginx:1.7.9 를 사용하고, 컨테이너 포트 8090을 오픈한다.


Pod 안에 한개 이상의 컨테이너를 가지고 있을 수 있다고 했는데 왜 개별적으로 하나씩 컨테이너를 배포하지 않고 여러개의 컨테이너를 Pod 단위로 묶어서 배포하는 것인가?


Pod는 다음과 같이 매우 재미있는 특징을 갖는다.


  • Pod 내의 컨테이너는 IP와 Port를 공유한다.
    두 개의 컨테이너가 하나의 Pod를 통해서 배포되었을때, localhost를 통해서 통신이 가능하다.
    예를 들어 컨테이너 A가 8080, 컨테이너 B가 7001로 배포가 되었을 때, B에서 A를 호출할때는 localhost:8080 으로 호출하면 되고, 반대로 A에서 B를 호출할때에넌 localhost:7001로 호출하면 된다.

  • Pod 내에 배포된 컨테이너간에는 디스크 볼륨을 공유할 수 있다.
    근래 애플리케이션들은 실행할때 애플리케이션만 올라가는것이 아니라 Reverse proxy, 로그 수집기등 다양한 주변 솔루션이 같이 배포 되는 경우가 많고, 특히 로그 수집기의 경우에는 애플리케이션 로그 파일을 읽어서 수집한다. 애플리케이션 (Tomcat, node.js)와 로그 수집기를 다른 컨테이너로 배포할 경우, 일반적인 경우에는 컨테이너에 의해서 파일 시스템이 분리되기 때문에, 로그 수집기가 애플리케이션이 배포된 컨테이너의 로그파일을 읽는 것이 불가능 하지만, 쿠버네티스의 경우 하나의 Pod 내에서는 컨테이너들끼리 볼륨을 공유할 수 있기 때문에 다른 컨테이너의 파일을 읽어올 수 있다.


위와 같이 애플리케이션과 애플리케이션에서 사용하는 주변 프로그램을 같이 배포하는 패턴을 마이크로 서비스 아키텍쳐에서 사이드카 패턴(Side car pattern)이라고 하는데, 이 외에도 Ambassador, Adapter Container 등 다양한 패턴이 있는데, 이는 나중에 다른 글에서 상세하게 설명하도록 한다.

Volume

Pod가 기동할때 디폴트로, 컨테이너마다 로컬 디스크를 생성해서 기동되는데, 이 로컬 디스크의 경우에는 영구적이지 못하다. 즉 컨테이너가 리스타트 되거나 새로 배포될때 마다 로컬 디스크는 Pod 설정에 따라서 새롭게 정의되서 배포되기 때문에, 디스크에 기록된 내용이 유실된다.

데이타 베이스와 같이 영구적으로 파일을 저장해야 하는 경우에는 컨테이너 리스타트에 상관 없이 파일을 영속적으로 저장애햐 하는데, 이러한 형태의 스토리지를 볼륨이라고 한다.

볼륨은 컨테이너의 외장 디스크로 생각하면 된다. Pod가 기동할때 컨테이너에 마운트해서 사용한다.


앞에서 언급한것과 같이 쿠버네티스의 볼륨은 Pod내의 컨테이너간의 공유가 가능하다.


웹 서버를 배포하는 Pod가 있을때, 웹서비스를 서비스하는 Web server 컨테이너, 그리고 컨텐츠의 내용 (/htdocs)를 업데이트하고 관리하는 Content mgmt 컨테이너, 그리고 로그 메세지를 관리하는 Logger라는 컨테이너이가 있다고 하자

  • WebServer 컨테이너는 htdocs 디렉토리의 컨테이너를 서비스하고, /logs 디렉토리에 웹 억세스 기록을 기록한다.

  • Content 컨테이너는 htdocs 디렉토리의 컨텐트를 업데이트하고 관리한다.

  • Logger 컨테이너는 logs 디렉토리의 로그를 수집한다.

이 경우 htdocs 컨텐츠 디렉토리는 WebServer와 Content 컨테이너가 공유해야 하고 logs 디렉토리는 Webserver 와 Logger 컨테이너가 공유해야 한다. 이러한 시나리오에서 볼륨을 사용할 수 있다.


아래와 같이 htdocs와 logs 볼륨을 각각 생성한 후에, htdocs는 WebServer와, Contents management 컨테이너에 마운트 해서 공유하고, logs볼륨은 Logger와 WebServer 컨테이너에서 공유하도록 하면된다.  



쿠버네티스는 다양한 외장 디스크를 추상화된 형태로 제공한다. iSCSI나 NFS와 같은 온프렘 기반의 일반적인 외장 스토리지 이외에도, 클라우드의 외장 스토리지인 AWS EBS, Google PD,에서 부터  github, glusterfs와 같은 다양한 오픈소스 기반의 외장 스토리지나 스토리지 서비스를 지원하여, 스토리지 아키텍처 설계에 다양한 옵션을 제공한다.

Service

Pod와 볼륨을 이용하여, 컨테이너들을 정의한 후에, Pod 를 서비스로 제공할때, 일반적인 분산환경에서는 하나의 Pod로 서비스 하는 경우는 드물고, 여러개의 Pod를 서비스하면서, 이를 로드밸런서를 이용해서 하나의 IP와 포트로 묶어서 서비스를 제공한다.


Pod의 경우에는 동적으로 생성이 되고, 장애가 생기면 자동으로 리스타트 되면서 그 IP가 바뀌기 때문에, 로드밸런서에서 Pod의 목록을 지정할 때는 IP주소를 이용하는 것은 어렵다. 또한 오토 스케일링으로 인하여 Pod 가 동적으로 추가 또는 삭제되기 때문에, 이렇게 추가/삭제된 Pod 목록을 로드밸런서가 유연하게 선택해 줘야 한다.

그래서 사용하는 것이 라벨(label)과 라벨 셀렉터(label selector) 라는 개념이다.


서비스를 정의할때, 어떤 Pod를 서비스로 묶을 것인지를 정의하는데, 이를 라벨 셀렉터라고 한다. 각 Pod를 생성할때 메타데이타 정보 부분에 라벨을 정의할 수 있다. 서비스는 라벨 셀렉터에서 특정 라벨을 가지고 있는 Pod만 선택하여 서비스에 묶게 된다.

아래 그림은 서비스가 라벨이 “myapp”인 서비스만 골라내서 서비스에 넣고, 그 Pod간에만 로드밸런싱을 통하여 외부로 서비스를 제공하는 형태이다.



이를 스펙으로 정의해보면 대략 다음과 같다.


kind: Service
apiVersion: v1
metadata:
 name: my-service
spec:
 selector:
   app: myapp
 ports:
 - protocol: TCP
   port: 80
   targetPort: 9376


  • 리소스 종류가 Service 이기 때문에, kind는 Service로 지정하고,

  • 스크립트를 실행할 api 버전은 v1으로 apiVersion에 정의했다.

  • 메타데이타에 서비스의 이름을 my-service로 지정하고

  • spec 부분에 서비스에 대한 스펙을 정의한다.

    • selector에서 라벨이 app:myapp인 Pod 만을 선택해서 서비스에서 서비스를 제공하게 하고

    • 포트는 TCP를 이용하되, 서비스는 80 포트로 서비스를 하되, 서비스의 80 포트의 요청을 컨테이너의 9376 포트로 연결해서 서비스를 제공한다.


Name space

네임스페이스는 한 쿠버네티스 클러스터내의 논리적인 분리단위라고 보면 된다.

Pod,Service 등은 네임 스페이스 별로 생성이나 관리가 될 수 있고, 사용자의 권한 역시 이 네임 스페이스 별로 나눠서 부여할 수 있다.

즉 하나의 클러스터 내에, 개발/운영/테스트 환경이 있을때, 클러스터를 개발/운영/테스트 3개의 네임 스페이스로 나눠서 운영할 수 있다. 네임스페이스로 할 수 있는 것은

  • 사용자별로 네임스페이스별 접근 권한을 다르게 운영할 수 있다.

  • 네임스페이스별로 리소스의 쿼타 (할당량)을 지정할 수 있다. 개발계에는 CPU 100, 운영계에는 CPU 400과 GPU 100개 식으로, 사용 가능한 리소스의 수를 지정할 수 있다.

  • 네임 스페이스별로 리소스를 나눠서 관리할 수 있다. (Pod, Service 등)


주의할점은 네임 스페이스는 논리적인 분리 단위이지 물리적이나 기타 장치를 통해서 환경을 분리(Isolation)한것이 아니다. 다른 네임 스페이스간의 pod 라도 통신은 가능하다.

물론 네트워크 정책을 이용하여, 네임 스페이스간의 통신을 막을 수 있지만 높은 수준의 분리 정책을 원하는 경우에는 쿠버네티스 클러스터 자체를 분리하는 것을 권장한다.


참고 자료 네임 스페이스에 대한 베스트 프랙틱스 : https://cloudplatform.googleblog.com/2018/04/Kubernetes-best-practices-Organizing-with-Namespaces.html

https://kubernetes.io/blog/2016/08/kubernetes-namespaces-use-cases-insights/

라벨

앞에서 잠깐 언급했던 것 중의 하나가 label 인데, 라벨은 쿠버네티스의 리소스를 선택하는데 사용이 된다. 각 리소스는 라벨을 가질 수 있고, 라벨 검색 조건에 따라서 특정 라벨을 가지고 있는 리소스만을 선택할 수 있다.

이렇게 라벨을 선택하여 특정 리소스만 배포하거나 업데이트할 수 있고 또는 라벨로 선택된 리소스만 Service에 연결하거나 특정 라벨로 선택된 리소스에만 네트워크 접근 권한을 부여하는 등의 행위를 할 수 있다.

라벨은 metadata 섹션에 키/값 쌍으로 정의가 가능하며, 하나의 리소스에는 하나의 라벨이 아니라 여러 라벨을 동시에 적용할 수 있다.


"metadata": {
 "labels": {
   "key1" : "value1",
   "key2" : "value2"
 }
}


셀렉터를 사용하는 방법은 오브젝트 스펙에서 selector 라고 정의하고 라벨 조건을 적어 놓으면 된다.

쿠버네티스에서는 두 가지 셀렉터를 제공하는데, 기본적으로 Equaility based selector와, Set based selector 가 있다.

Equality based selector는 같냐, 다르냐와 같은 조건을 이용하여, 리소스를 선택하는 방법으로

  • environment = dev

  • tier != frontend

식으로, 등가 조건에 따라서 리소스를 선택한다.

이보다 향상된 셀렉터는 set based selector로, 집합의 개념을 사용한다.

  • environment in (production,qa) 는 environment가 production 또는 qa 인 경우이고,

  • tier notin (frontend,backend)는 environment가 frontend도 아니고 backend도 아닌 리소스를 선택하는 방법이다.

다음 예제는 my-service 라는 이름의 서비스를 정의한것으로 셀렉터에서 app: myapp 정의해서 Pod의 라벨 app이 myapp 것만 골라서 이 서비스에 바인딩해서 9376 포트로 서비스 하는 예제이다.


kind: Service
apiVersion: v1
metadata:
 name: my-service
spec:
 selector:
   app: myapp
 ports:
 - protocol: TCP
   port: 80
   targetPort: 9376



컨트롤러

앞에서 소개한 4개의 기본 오브젝트로, 애플리케이션을 설정하고 배포하는 것이 가능한데 이를 조금 더 편리하게 관리하기 위해서 쿠버네티스는 컨트롤러라는 개념을 사용한다.

컨트롤러는 기본 오브젝트들을 생성하고 이를 관리하는 역할을 해준다. 컨트롤러는 Replication Controller (aka RC), Replication Set, DaemonSet, Job, StatefulSet, Deployment 들이 있다. 각자의 개념에 대해서 살펴보도록 하자.

Replication Controller

Replication Controller는  Pod를 관리해주는 역할을 하는데, 지정된 숫자로 Pod를 기동 시키고, 관리하는 역할을 한다.

Replication Controller (이하 RC)는 크게 3가지 파트로 구성되는데, Replica의 수, Pod Selector, Pod Template 3가지로 구성된다.

  • Selector : 먼저 Pod selector는 라벨을 기반으로 하여,  RC가 관리한 Pod를 가지고 오는데 사용한다.

  • Replica 수 :  RC에 의해서 관리되는 Pod의 수인데, 그 숫자만큼 Pod 의 수를 유지하도록 한다.예를 들어 replica 수가 3이면, 3개의 Pod만 띄우도록 하고, 이보다 Pod가 모자르면 새로운 Pod를 띄우고, 이보다 숫자가 많으면 남는 Pod를 삭제한다.

  • Pod를 추가로 기동할 때 그러면 어떻게 Pod를 만들지 Pod에 대한 정보 (도커 이미지, 포트,라벨등)에 대한 정보가 필요한데, 이는 Pod template이라는 부분에 정의 한다.




주의할점은 이미 돌고 있는 Pod가 있는 상태에서 RC 리소스를 생성하면 그 Pod의 라벨이 RC의 라벨과 일치하면 새롭게 생성된 RC의 컨트롤을 받는다. 만약 해당 Pod들이 RC에서 정의한 replica 수 보다 많으면, replica 수에 맞게 추가분의 pod를 삭제하고, 모자르면 template에 정의된 Pod 정보에 따라서 새로운 Pod를 생성하는데, 기존에 생성되어 있는 Pod가 template에 정의된 스펙과 다를지라도 그 Pod를 삭제하지 않는다. 예를 들어 기존에 아파치 웹서버로 기동중인 Pod가 있고, RC의 template은 nginx로 Pod를 실행하게 되어 있다하더라도 기존에 돌고 있는 아파치 웹서버 기반의 Pod를 삭제하지 않는다.


아래 예를 보자.


이 예제는 ngnix라는 이름의 RC를 정의한 것으로, label이 “app:ngnix”인 Pod들을 관리하고 3개의 Pod가 항상 운영되도록 설정한다.

Pod는 app:ngix 라는 라벨을 가지면서 이름이 ngnix이고 nginx 이미지를 사용해서 생성하고 컨테이너의 포트는 80 번 포트를 이용해서 서비스를 제공한다.

ReplicaSet

ReplicaSet은 Replication Controller 의 새버전으로 생각하면 된다.

큰 차이는 없고 Replication Controller 는 Equality 기반 Selector를 이용하는데 반해, Replica Set은 Set 기반의 Selector를 이용한다.

Deployment

Deployment (이하 디플로이먼트) Replication controller와 Replica Set의 좀더 상위 추상화 개념이다. 실제 운영에서는 ReplicaSet 이나 Replication Controller를 바로 사용하는 것보다, 좀 더 추상화된 Deployment를 사용하게 된다.

쿠버네티스 배포에 대한 이해

쿠버네티스의 Deployment 리소스를 이해하기 위해서는 쿠버네티스에서 Deployment 없이 어떻게 배포를 하는지에 대해서 이해를 하면 Deployment 를 이해할 수 있다.


다음과 같은 Pod와 RC가 있다고 하자


애플리케이션이 업데이트되서 새로운 버전으로 컨테이너를 굽고 이 컨테이너를 배포하는 시나리오에 대해서 알아보자. 여러가지 배포 전략이 있겠지만, 많이 사용하는 블루/그린 배포와 롤링 업데이트 방식 두가지 방법에 대해서 설명한다.

블루/그린 배포

블루/그린 배포 방식은 블루(예전)버전으로 서비스 하고 있던 시스템을 그린(새로운)버전을 배포한 후, 트래픽을 블루에서 그린으로 한번에 돌리는 방식이다.

여러가지 방법이 있지만 가장 손쉬운 방법으로는 새로운 RC을 만들어서 새로운 템플릿으로 Pod를 생성한 후에, Pod 생성이 끝나면, 서비스를 새로운 Pod로 옮기는 방식이다.


후에, 배포가 완료되고 문제가 없으면 예전 버전의 RC 와 Pod를 지워준다.

롤링 업그레이드

롤링 업그레이드 방식은 Pod를 하나씩 업그레이드 해가는 방식이다.

이렇게 배포를 하려면 먼저 새로운 RC를 만든후에, 기존 RC에서 replica 수를 하나 줄이고, 새로운 RC에는 replica 수를 하나만 준다.


라벨을 같은 이름으로 해주면 서비스는 자연히 새로운 RC에 의해 생성된 Pod를 서비스에 포함 시킨다.

다음으로 기존 RC의 replica를 하나 더 줄이고, 새로운 RC의  replica를 하나 더 늘린다.


그러면 기존 버전의 Pod가 하나더 서비스에서 빠지게 되고 새로운 버전의 Pod가 서비스에 추가된다.

마찬가지 작업을 반복하게 되면, 아래 그림과 같이 예전 버전의 Pod가 모두 빠지고 새 버전의 Pod만 서비스 되게 된다.


만약에 배포가 잘못되었을 경우에는 기존 RC의 replica 수를 원래대로 올리고, 새버전의 replicat 수를 0으로 만들어서 예전 버전의 Pod로 롤백이 가능하다.

이 과정은 kubectl rolling-update라는 명령으로 RC 단위로 컨트롤이 가능하지만, 그래도 여전히 작업이 필요하고, 배포 과정을 모니터링 해야 한다. 그리고 가장 문제는 kubectl rolling-update 명령은 클라이언트에서 실행 하는 명령으로, 명령어 실행중에 클라이언트의 연결이 끊어 지면 배포작업이 비정상적으로 끊어질 수 있는 문제가 있다.

그리고 마지막으로, 롤백과정 역시 수동 컨트롤이 필요할 수 있다.

그래서 이러한 과정을 자동화하고 추상화한 개념을 Deployment라고 보면 된다.

Deployment는 Pod 배포를 위해서 RC를 생성하고 관리하는 역할을 하며, 특히 롤백을 위한 기존 버전의 RC 관리등 여러가지 기능을 포괄적으로 포함하고 있다.



Deployment 에 대해서는 뒤에 다른 글에서 조금 더 자세하게 설명하도록 한다.


이글에서는 쿠버네티스를 이루는 기본적인 오브젝트와 이를 생성 제어하기 위한 기본적인 컨트롤러에 대해서 알아보았다.

다음 글에서는 조금 더 발전된 형태의 컨트롤러에 대해서 알아보기로 한다.




본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. ㅇㅇ 2018.10.04 15:29  댓글주소  수정/삭제  댓글쓰기

    좋은글 감사합니다. 물어볼게 있습니다. 기본오브젝트는 어떤 상황일때 생성을 하나요? 글읽는 중에 컨트롤러도 오브젝트 생성과 관리를 한다고 하는대 그렇다면 기본오브젝트는 안쓰이는건가요?

  2. 가그린민트 2019.01.26 23:40  댓글주소  수정/삭제  댓글쓰기

    아 정말 감사합니다! :)

  3. Greenllama 2019.05.13 16:47 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다! 스크랩 해도 될까요?

  4. 우미 2019.07.15 17:05  댓글주소  수정/삭제  댓글쓰기

    이번 학기에 배웠던 내용이 일목요연하게 정리되어있네요.
    잘 복습하고 갑니다.

  5. 감동에 도가니탕 2020.01.06 17:53  댓글주소  수정/삭제  댓글쓰기

    양질에 자료인것 같습니다.
    블로그에 참고자료로 써도될까요 ??
    하단에 밑에 출처는 남기겠습니다.
    다른 게시글도 가능한지 여줍고 싶습니다.




Kubernetes #1 - 소개

조대협 (http://bcho.tistory.com)

배경

도커와 쿠버네티스를 알게 된건 수년전인데, 근래에 들어서 다시 쿠버네티스를 보기 시작하였다.

컨테이너 기반의 환경은 배포에 장점이 있고 마이크로 서비스 아키텍쳐 구조에 잘 맞아들어가는 듯 싶지만, 컨테이너가 약간 빠르다는 장점은 있지만, 가상 머신으로도 충분히 패키징이 가능하고, 로컬의 개발환경을 동기화 시키는 장점은 vagrant 로도 충분하다는 생각을 가지고 있었다.


그리고 결정적으로 도커 컨테이너를 운용하기 위한 컨테이너 관리 환경이 그다지 성숙하지 못했었다. Mesosphere, Swarm, Kubernetes 등 다양한 환경이 나오기는 하였지만 기능적으로 부족한 부분도 많았고, 딱히 어떤 플랫폼이 대세라고 정해진것도 없었다.


마이크로 서비스 아키텍쳐 발전

그러나 근래에 들어서 재미있어지는 현상이 마이크로 서비스 아키텍쳐가 단순 개념에서 부터 점점 더 발전하기 시작하였고, 디자인 패턴과 이를 구현하기 위한 다양한 인프라 플랫폼들이 소개되기 시작하였다.

또한 서비스가 점점 작아지면서, 1~2 코어로도 운영할 수 있는 작은 서비스들이 다수 등장하게 되었고 이런 작은 서비스는 VM 환경으로 운영하기에는 낭비가 너무 심하다. (VM 이미지 크기도 너무 크고, 다양한 이미지를 VM으로 관리 배포하기에는 배포 속도등 다양한 문제가 발생한다.)


솔루션의 발전

배포 방식도 예전에 서버에 계속해서 애플리케이션 코드만 업데이트 하는 방식이 아니라, VM이나 컨테이너 단위로 배포하는 피닉스 서버 패턴과 이를 구현하기 위한 Spinnaker  와 같은 솔루션이 나오고 있고, 지능형 라우팅과 분산 트렌젝션 로그 추적을 하는 기능들이 Envoy 라는 솔루션으로 나오고 이를 중앙 통제하기 위한 Istio.io 와 같은 서비스 메쉬 솔루션 까지 나오기에 이르렀다.


데브옵스 모델의 성숙화

데브옵스 모델도 나온지는 오래되었지만, 운영을 데브옵스라는 이름으로 바꾼 것일뿐 실제적인 변화가 없는 팀들이 많았고, 또는 데브옵스라는 이름아래에서 개발팀이 개발과/운영 역할을 병행해서 하는 사례가 오히려 많았다.

이런 데브옵스의 개념도 근래에 들어서 정리가 되어가고 있는데, 개발팀이 개발과 시스템에 대한 배포/운영을 담당한다면, 데브옵스팀은 개발팀이 이를 쉽게할 수 있는 아랫단의 플랫폼과 자동화를 하는데 목표를 두는 역할로 역할이 명확해지고 있다.


이러한 배경에서 슬슬 컨테이너 기반의 환경이 실질적으로 적용될만하다는 것으로 판단하였고, 다시 컨테이너 환경에 대해서 살펴보기 시작하였다.

왜 하필이면 쿠버네티스인가?

그렇다면 Swarm,Mesosphere 가 아니라 왜 하필이면 쿠버네티스인가? 컨테이너 운용 환경은 여러 오픈소스에 의해서 표준이 없이 혼돈이었다가 작년말을 기점으로 해서 쿠버네티스가 de-facto 표준으로 되어가는 형국이다. 아래 트랜드 그래프에서 보면 알 수 있듯이 쿠버네티스의 트랜드가 지속적으로 올라가서 가장 높은 것을 확인할 수 있다.



또한 주요 클라우드 벤더인 아마존,구글,애저 모두 컨테이너 관리 환경을 쿠버네티스를 지원하는 정책으로 변화된것은 물론이고 IBM이나 시스코와 같은 온프렘(on-premise) 솔루션 업체들도 경쟁적으로 쿠버네티스를 지원하고 있다.

컨테이너 운영환경이 무엇인데?

컨테이너 (도커)에 필요성과 마이크로 서비스의 관계등에 대해서는 워낙 소개된 글들이 많아서 생략한다. 그렇다면 쿠버네티스가 제공하는 컨테이너 운영환경이란 무엇인가? 이를 이해하기 위해서는 먼저 컨테이너에 대해서 이해할 필요가 있는데, 컨테이너의 가장 대표적인 예로는 도커가 있다. 도커에 대한 자세한 설명은 링크를 참고하기 바란다.


그러면 단순하게 도커 컨테이너를 하드웨어나 VM에 배포하면 사용하면 되지 왜 컨테이너 운영환경이 필요한가?


작은 수의 컨테이너라면 수동으로 VM이나 하드웨어에 직접 배포하면 되지만, VM이나 하드웨어의 수가 많아지고 컨테이너의 수가 많아지면, 이 컨테이너를 어디에 배포해야 하는지에 대한 결정이 필요하다.

16 CPU, 32 GB 메모리 머신들에 컨테이너를 배포할때 컨테이너 사이즈가 2 CPU, 3 CPU, 8 CPU등 다양할 수 있기 때문에, 자원을 최대한 최적으로 사용하기 위해서 적절한 위치에 배포해야 하고, 애플리케이션 특성들에 따라서, 같은 물리 서버에 배포가 되어야 하거나 또는 가용성을 위해서 일부러 다른 물리서버에 배포되어야 하는 일이 있다. 이렇게 컨테이너를 적절한 서버에 배포해주는 역할을 스케쥴링이라고 한다.


이러한 스케쥴링 뿐만이 아니라 컨테이너가 정상적으로 작동하고 있는지 체크하고 문제가 있으면 재 기동등을 해주고, 모니터링, 삭제관리등 컨테이너에 대한 종합적인 관리를 해주는 환경이 필요한데, 이를 컨테이너 운영환경이라고 한다.

쿠버네티스란?

이런 컨테이너 운영환경중 가장 널리 사용되는 솔루션이 쿠버네티스 (Kubernetes, 약어로 k8s)라고 한다.

구글은 내부 서비스를 클라우드 환경에서 운영하고 있으며, 일찌감치 컨테이너 환경을 사용해왔다. 구글의 내부 컨테이너 서비스를 Borg라고 하는데, 이 구조를 오픈소스화한것이 쿠버네티스이다.

GO 언어로 구현이되었으며, 특히 재미있는 것은 벤더나 플랫폼에 종속되지 않기 때문에, 대부분의 퍼블릭 클라우드 (구글,아마존,애저)등에 사용이 가능하고 오픈 스택과 같은 프라이빗 클라우드 구축 환경이나 또는 베어메탈 (가상화 환경을 사용하지 않는 일반 서버 하드웨어)에도 배포가 가능하다.

이런 이유 때문에 여러 퍼블릭 클라우드를 섞어서 사용하는 환경이나 온프렘/퍼블릭 클라우드를 혼용해서 쓰는 환경에도 동일하게 적용이 가능하기 때문에 하이브리드 클라우드 솔루션으로 많이 각광 받고 있다.


흔히들 컨테이너를 이야기 하면 도커를 떠올리기 쉬운데, 도커가 물론 컨테이너 엔진의 대표격이기는 하지만 이 이외도 rkt나 Hyper container(https://hypercontainer.io/) 등 다양한 컨테이너 엔진들이 있으며, 쿠버네티스는 이런 다양한 컨테이너 엔진을 지원한다.

컨테이너 환경을 왜 VM에 올리는가?

온프렘 환경(데이타센터)에서 쿠버네티스를 올릴때 궁금했던점 중의 하나가, 바로 베어메탈 머신위에 쿠버네티스를 깔면 되는데, 보통 배포 구조는 VM(가상화 환경)을 올린 후에, 그 위에 쿠버네티스를 배포하는 구조를 갖는다. 왜 이렇게 할까 한동안 고민을 한적이 있었는데, 나름데로 내린 결론은 하드웨어 자원 활용의 효율성이다. 컨테이너 환경은 말그대로 하드웨어 자원을 컨테이너화하여 isolation 하는 기능이 주다. 그에 반해 가상화 환경은 isolation 기능도 가지고 있지만, 가상화를 통해서 자원 , 특히 CPU의 수를 늘릴 수 있다.


예를 들어 설명하면, 8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로, 8배일 경우 64 CPU로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하기 때문이 아닌가 하는 결론을 내렸다.

이 이외에도 스토리지 자원의 활용 용이성이나 노드 확장등을 유연하게 할 수 있는 장점이 있다고 한다.


다음 글에서는 쿠버네티스를 구성하는 컴포넌트들의 구성과 개념에 대해서 설명하도록 한다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 이거원 2018.09.14 15:51 신고  댓글주소  수정/삭제  댓글쓰기

    궁금한게 있어서 질문을 드립니다.
    '컨테이너 환경을 VM에 올린다'라고 하셨는데.
    여기서 말씀하신 VM이 docker를 말씀하시는건가요?
    그리고 docker가 맞다면, docker와 k8s를 함께 이용하는것을 의미하는게 맞는건지요?

    그게 아니라면 베어메탈 머신에 다른 VM환경을 구성하고 docker와 k8s 환경을 구축한다는 의미로 말씀하시는 건가요?

    제법 시일이 지난 글이라 기억이 잘 안 나실수도 있겠지만, 부탁드리겠습니다.

  2. 조대협 2018.09.15 13:49 신고  댓글주소  수정/삭제  댓글쓰기

    베어메탈위에 VM을 올리고 그 위에 도커를 올리는 구성을 이야기 합니다

  3. 최준영 2018.09.19 17:55  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 정리해주신 글 잘 읽었습니다. 한가지 질문 드리고자 합니다.
    `8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로...` 문장에 의문이 있습니다. 가상화에 대한 지식이 부족해서;;

    가상화 하고자 하는 서버가 8cpu이고 그 서버를 가상화 하면 16cpu, 64cpu 급의 성능을 낼 수 있단 말인가요?
    만약 그렇다면 애초에 8cpu인 서버가 어떻게 그런 성능향상을 이뤄낼 수 있는지 의문이 듭니다.

    질문 자체가 맞는지 모르겠지만 ㅠ ㄷ답변 부탁드립니다.

    • 김가상 2019.01.10 08:54  댓글주소  수정/삭제

      베어메탈 환경에 8Core일때 해당 환경에 바로 쿠버네티스를 구성하면 쿠버네티스에서 할당할수있는 자원만큼 8Core를 나눠쓰게되는데 8Core자체를 나눠서 VM화시키고 이를 쿠버네티스에서 다시 나눠쓰면 위에서 말씀하신것처럼 8Core를 잘게 나눠쓰는 효과가 나오게된단 이야기 같습니다.
      예를들면 8Core 베어베탈장비에 2코어짜리 VM을 올리고 여기에 쿠버네티스를 구성한 뒤 해당 환경에서 컨테이너에 가상코어를 할당하게되면 2코어를 잘게 쪼개서 쓸수있게되는게 아닐까 싶습니다.

  4. manggu 2018.11.19 11:43  댓글주소  수정/삭제  댓글쓰기

    VM자체의 CPU를 자유롭게(?) 변경 할 수 있기 때문에초기에는 8 vCPU만 주고 쿠버네티스를(한 그룹이나 프로젝트가 되겠죠) 운영하다가 필요에 따라서 VM의 코어를 더 배당하고 그걸 쿠버네이티스에 다시 사용하는 형식이 아닌가합니다.
    그리고 VM에 주는 vCPU가 반드시 보장된 리소스가 아님으로 실제 코어 보다 더 많이 배당을 하게 됩니다. 물런 모든 VM이 동시에 vCPU를 풀로 사용하기 시작하면 실제 그 만큼의 코아가 없기 때문에 퍼포먼스가 나오지는 않씁니다. 그러나 그렇게 사용되는 경우는 거의 없기 때문에 vCPU를 오버해서 VM에 배당하고 쿠버네이티스는 그게 실제 존재 한다고 사기(?) 당하면서 사용하게 될 것 같습니다.

  5. kispi 2019.04.16 16:15  댓글주소  수정/삭제  댓글쓰기

    너무 감사합니다... 저의 국내 구루 중 한 분이십니다

  6. 세판 2019.10.23 08:04  댓글주소  수정/삭제  댓글쓰기

    항상 잘 보고 있습니다~
    VM에 올리면 장애포인트나 느낄수없을 정도의 성능저하가 있을수 있겠지만 시스템 관리자 입장에서는 스냅샷, 리소스 관리 등의 메리트를 무시 못하겠군요