본문 바로가기
Framework and Tool/Kubernetes

Kubernetes - 개요

by ocwokocw 2022. 10. 2.

- 출처: Kubernetes in action

- 모놀리스 to 마이크로서비스

과거에는 대부분의 시스템들이 모놀리스 형태였다. 하지만 점차 시스템들이 대형화되면서 모놀리스 시스템의 단점이 드러났다.
 
모놀리스 시스템은 해당 구성요소들이 너무 강하게 결합되어있기 때문에 시스템 구성요소의 일부분만 변경해도 전체 배포 과정을 거쳐야 한다.
 
또한 만약 시스템의 사용자가 많아지면 확장해야하기 마련인데 수직 형태의 증설(Scale up)은 CPU나 RAM을 2배로 올린다고 성능이 선형적으로 정확히 2배로 증가하지 않기 때문에 한계가 존재한다. 그래서 수평 형태의 확장(Scale out) 을 보통 선택하는데 어플리케이션 코드가 크게 변경되어야 하는 경우도 있어서 이 역시 쉽지 않다.
 
이를 극복하기 위해 나온 개념이 마이크로 서비스인데 어플리케이션을 작은 단위의 마이크로서비스로 쪼개고 각 서비스를 독립적인 프로세스에서 실행시키는 형태이다.
 
이렇게 서비스가 나누어지게 되면 모놀리스에서는 함수 호출로 가능했던 작업들이 서비스간의 통신으로 해결해야하는 상황이 생긴다. 이런 상호 통신시의 인터페이스는 보통 2가지 형태로 나뉜다.
 
  • Http: 동기적으로 동작해야 하는 경우 사용
  • AMQP(MQ):  비동기적으로 동작하는 경우
 
마이크로서비스 형태가 되면서 각 구성요소의 특징에 따라 서로 다른 언어로 개발되거나 일부분만 빠르게 재배포하는 작업들이 가능해졌다. 또한 확장측면에서 모놀리스 시스템은 전체 시스템을 확장해야해서 부담이 크지만 마이크로서비스 같은 경우 확장할 필요가 있는 구성요소만 확장할 수 있는 장점도 존재한다.
 
마이크로서비스가 항상 장점만 존재하는것은 아니며 아래와 같은 경우 단점도 존재한다.
 
  • 구성요소가 적은 경우 복잡하진 않지만 시스템의 구성요소가 늘어나면 관리가 어려우며 요소간 종속성이 생길 수 있다.
  • 서로간의 통신을 해야한다.
  • 여러 프로세스가 분산되어 실행호출 디버그나 추적이 힘들다. 단 이런 문제는 Zipkin과 같은 분산 추적 시스템으로 해결이 가능하긴하다.

- 컨테이너

앱이 커다란 구성요소로 이루어져있는 경우 각 구성요소에 가상머신(VM)을 할당할 수 있다. 하지만 VM에는 한계점이 존재하는데, 구성요소가 많아지면 요소마다 VM을 지원하기에 하드웨어 리소스 부담이 너무 크다는것이다. 이는 장비의 비용적인 측면 뿐만 아니라 시스템 관리자의 업무도 그만큼 많아진다는것을 의미한다.
 
반면 Linux 의 컨테이너 기술의 경우 동일 Host 내에서 여러 서비스를 실행하게 된다. VM과 동일하게 각 서비스들이 격리 되지만 오버헤드가 훨씬적다.
 
VM과 컨테이너가 서비스들을 격리시키는 목적이 똑같다면 어떤 차이가 존재하는가?
 
 
 
우선 VM의 경우 각 시스템의 구성요소 프로세스에 더불어 시스템 프로세스 자원 사용량이 추가된다.
 
예를 들어 3개의 VM을 실행한다고 가정해보자. 이 3개의 VM에 있는 완전 분리된 OS가 동일 베어메탈 HW를 공유하게 된다. Host OS + 하이퍼바이저위에 VM이 3개 올라가고 각 VM에는 Guest OS가 또 올라가며 해당 Guest OS에서 앱이 구동된다. VM내의 앱은 VM내의 Guest OS 커널 시스템 콜을하고, 커널은 하이퍼바이저를 통해 Host CPU의 x86 명령어를 수행한다.
 
반면 컨테이너는 동일 커널에서 시스템 콜을 수행한다.
 
가상머신은 컨테이너보다 무겁긴 하지만 자체적인 리눅스 커널을 갖고 있어 동일 커널을 이용하는 컨테이너보다 보안 위험이 낮다는 이점은 있다.
 

- 컨테이너 격리

컨테이너도 VM 처럼 리소스를 격리하는데 이를 지원하는 2 가지 매커니즘이 있다.
 
  • namespace: 각 프로세스가 시스템(파일, 프로세스, 네트워크 인터페이스, Host 이름)의 독립적인 뷰만 보게 제한한다.
  • cgroup: 컨트롤 그룹이라고 하는 cgroup을 통해 프로세스가 사용하는 리소스를 제한한다.
 
Linux는 최초구동시 1개의 namespace를 갖게 된다. 모든 시스템 리소스(파일 시스템, 프로세스 ID, 사용자 ID, 네트워크 인터페이스 등)은 1개의 namespace에 속한다.
 
프로세스를 실행할때에는 해당 namespace 중 하나에서 실행되며 프로세스는 동일한 namespace 내의 리소스만 볼 수 있다. namespace의 종류는 여러가지이기 때문에 프로세스는 하나의 namespace에만 종속되는것은 아니며 종류는 아래와 같다.
 
  • 마운트(mnt)
  • 프로세스 ID(pid)
  • 네트워크(net)
  • 프로세스 간 통신(ipc)
  • 호스트와 도메인 이름(uts)
  • 사용자 ID(user)
 
예를 들어 uts는 프로세스가 사용할 호스트 이름과 도메인 명을 결정한다. 서로 다른 uts namespace 에서 서로 다른 프로세스를 각각 지정하면 이 두 프로세스는 다른 시스템에서 실행중인것처럼 간주된다.
 
 
cgroup은 linux 커널 기능이며, 프로세스가 설정된 양 이상의 CPU, 메모리, 네트워크 대역폭을 사용할 수 없게 제한한다.

- Docker

Docker는 컨테이너를 여러 시스템에 이식하기 쉽게하는 컨테이너 플랫폼이라고할 수 있다.
 
예를 들어 RHEL(Red hat enterprise linux) OS 전체 파일과 앱을 패키징하면 이를 Fedora나 다른 리눅스 배포한 서버에서 실행해도 앱 입장에서는 마치 RHEL상에서 실행된다고 생각할 수 있다.
 
Docker는 결국 앱을 패키징, 배포, 실행하는 플랫폼이며 주요 3가지 개념은 아래와 같다.
 
  • 이미지: App + 환경을 패키징
  • 레지스트리: 다른 컴퓨터와 이미지를 공유 및 저장하기 위한 저장소
  • 컨테이너: Docker 기반 컨테이너 이미지에서 생성되는 일반적인 Linux 컨테이너
 
Docker가 일반적으로 사용되는 흐름은, 개발자가 개발 머신에서 앱을 패키징화해서 이미지를 만들고 레지스트리에 Push, 운영 머신에서는 이 레지스트리에 저장된 이미지를 다운받아 컨테이너를 생성하게 된다.
 
Docker에 이미지가 있듯이 가상머신에도 이미지가 있다. 다만 Docker에 이미지는 레이어로 구성이 되어 있다. Docker 이미지는 다른 이미지 위에서 빌드된다. 다른 2개의 이미지가 동일 부모 이미지를 사용할 수 있다. Host에는 이미지가 한번만 저장되어서 빌드 속도가 향상되고 저장공간이 줄어들게 된다.
 
컨테이너 이미지에는 VM 이미지 대비 한계점이 존재한다. 이론적으로는 컨테이너 이미지가 Docker를 실행하는 리눅스 시스템에서 사용가능하긴 하다. 하지만 컨테이너화된 앱이 특정 커널 버전에 종속되어 있다면 모든 시스템에서 동작하지는 않는다. 앞에서 설명했듯이 컨테이너는 Host와 동일 커널을 사용하기 때문이다. x86 Arch.를 사용하는 앱이 ARM 에서 도커가 실행된다고 해서 컨테이너화할 수는 없는것이다. 이런 경우에는 VM이 필요하다.
 
마지막으로 Docker는 컨테이너 플랫폼이라고 했다. Docker 용어에 익숙하다보니 프로세스 격리를 Docker가 한다고 착각할 수 있는데 격리는 Linux namespace와 cgroup이 행하는것이다.

- 쿠버네티스

쿠버네티스는 컨테이너화된 앱을 쉽게 배포하고 관리할 수 있게 해주는 SW 시스템이다.
 
쿠버네티스는 마스터 노드와 여러 개의 워커 노드로 구성된다. 쿠버네티스가 앱을 배포하는 과정을 정말 간단하게 표현하자면, "개발자가 앱 매니페스트 파일을 마스터 노드(Control Plane) 에 게시하면 마스터 노드는 워커 노드에 이를 배포한다"고 할 수 있다.
 
 
 
위의 그림은 쿠버네티스의 아키텍처를 표현한것이다. 쿠버네티스는 2가지 종류의 노드가 있다.
 
우선 Control Plane (마스터 노드)은 
 
  • API 서버: Control Plane과 사용자간의 통신을 담당
  • Scheduler: 앱 배포를 담당
  • Controller manager: 구성요소 복제본, 워커노드 추적, 노드 장애처리 등
  • etcd: 클러스터 구성을 저장하는 저장소
 
또한 노드(워커 노드)는
 
  • 컨테이너 런타임: 컨테이너를 실행하는 Docker, rtk 등
  • Kubelet: API 서버와 통신하며 노드 컨테이너 관리
  • Kube-proxy: 앱 구성요소간 네트워크 트래픽 로드밸런싱
 
쿠버네티스에서 앱이 실행되는 과정은 아래와 같다.
 
  • 앱 컨테이너 패키징
  • 레지스트리에 게시
  • API 서버에서 앱 디스크립션 게시
 
앱 디스크립션에는 컨테이너 이미지나 앱 구성요소 포함 이미지, 구성요소간 통신방법, 복제본 수, 동일서버에 배포되어야 하는 구성요소등 여러사항이 기술되어 있다.
 
이 디스크립션을 참고하여 컨테이너가 실행된다. 우선 스케줄러가 리소스를 계산하여 사용가능한 워커노드에 저장된 컨테이너를 할당한다. kubelet은 컨테이너 런타임에 필요한 컨테이너 이미지를 가져와서 컨테이너를 실행하게 지시한다.
 
쿠버네티스는 단순하게 앱을 실행했다고 해서 자신의 본분을 끝내지 않는다. 배포상태와 디스크립션의 여부를 지속적으로 확인한다. 만약 프로세스가 중단되거나 응답이 없으면 자동으로 재시작한다. 또한 워커노드가 다운 되면 노드 내 모든 컨테이너를 새로 스케줄링한다.
 
또한 복제본을 Scaling 해주는데 단순히 복제본 수를 지정하는것에 더해서 CPU나 메모리 혹은 기타 metric 기반으로 복제본 수를 자동 조정하게 할 수 있다.
 
만약 동일 서비스를 제공하는 컨테이너라면 하나의 고정 IP주소를 노출하고 해당 주소를 클러스터에서 실행중인 모든 앱에 노출한다. kube-proxy는 서비스를 제공하는 모든 컨테이너에서 연결 되도록 로드밸런싱 하는 역할을 수행한다. 서비스 IP 주소는 일정하게 유지되기 때문에 컨테이너가 클러스터내에서 이동해도 항상 연결할 수 있다.
 
 

'Framework and Tool > Kubernetes' 카테고리의 다른 글

Kubernetes - Label 및 Annotation  (0) 2022.10.09
Kubernetes - Pod  (0) 2022.10.09
Kubernetes - deploy app tutorial  (0) 2022.10.06
Kubernetes - cluster 생성  (0) 2022.10.06
Kubernetes - Docker basic  (0) 2022.10.06

댓글