해당 포스팅은 "쿠버네티스 인 액션"을 공부하고 정리한 글입니다. 모든 내용은 해당 도서를 기준으로 합니다.
⬛ 1장 쿠버네티스 소개
몇 년 전만 해도 대부분의 소프트웨어 애플리케이션은 하나의 프로세스 또는 몇 개의 서버에 분산된 프로세스로 실행되는 거대한 모놀리스였다.
모놀리스 레거시 애플리케이션은 점차 마이크로서비스라는 독립적으로 실행되는 더 작은 구성 요소로 세분화되고 있다.
마이크로서비스는 서로 분리 돼 있기 때문에 개별적으로 개발, 배포, 업데이트, 확장을 할 수 있다.
하지만 배포 가능한 구성 요소 수가 많아지고 데이터 센터의 규모가 커지면서 전체 시스템을 원활하게 구성, 관리, 유지하는 일이 점점 어려워졌다.
이런 구성 요소의 서버 배포를 자동으로 스케줄링하고 구성, 관리, 장애 처리를 포함하는 자동화가 필요해 졌고 이것이 쿠버네티스가 등장한 이유이다.
쿠버네티스는 개발자가 운영 팀의 도움 없이도 자신의 애플리케이션을 원하는 만큼 자주 배포할 수 있도록 하며, 하드웨어 인프라를 추상화하고 데이터 센터 전체를 하나의 거대한 컴퓨팅 리소스로 제공한다.
쿠버네티스는 대부분의 사내 데이터 센터 뿐만 아니라 클라우드 공급자가 구축, 운영하는 대규모 데이터 센터에서 사용될 때 그 진가를 발휘한다.
◼️ 1.1 쿠버네티스와 같은 시스템이 필요한 이유
▪️ 1.1.1 모놀리스 애플리케이션에서 마이크로 서비스로 전환
모놀리스 애플리케이션은 모든 것이 서로 강하게 결합돼 있고 전체가 하나의 운영체제 프로세스로 실행되기 때문에 하나의 개체로 개발, 배포, 관리되어야 한다.
모놀리스 애플리케이션의 일부분이 수평적으로 확장하기 매우 어렵거나 불가능하고 어떻게든 분할할 수 없다면 전체 애플리케이션을 확장할 수 없다.
각 마이크로서비스는 독립적인 프로세스로 실행되며 단순하고 잘 정의된 인터페이스(API)로 다른 마이크로서비스와 통신한다.
마이크로서비스는 일반적으로 RESTful API를 제공하는 HTTP와 같은 동기 프로토콜과 AMQP와 같은 비동기 프로토콜로 통신한다.
모놀리스 애플리케이션의 구성 요소가 확장 불가능한 경우 애플리케이션을 마이크로서비스 형태로 분할해 수평 확장을 가능하게 하거나 수평 확장이 불가능한 경우 수직으로 확장할 수 있다.
마이크로서비스의 단점은 구성 요소가 많아지면 배포 조합의 수 뿐만 아니라 구성 요소 간의 상호 종속성 수가 훨씬 더 많아지므로 배포 관련 결정이 점점 어려워진다.
마이크로서비스를 배포할 때 전체가 하나의 시스템처럼 동작할 수 있도록 누군가 또는 무언가가 제대로 구성해야 한다.
마이크로서비스 아키텍처의 구성 요소는 독립적으로 배포될 뿐만 아니라 독립적인 방식으로 개발된다.
애플리케이션이 서로 다른 버전의 동일한 라이브러리를 필요로 하는 경우 애플리케이션 구성 요소 간 종속성의 차이는 불가피하다.
동일한 호스트에 배포해야 하는 구성 요소 수가 많을수록 모든 요구 사항을 충족시키려 모든 종속성을 관리하기가 더 어려워진다.
▪️ 1.1.3 지속적인 배포로 전환: 데브옵스와 노옵스
과거 개발팀의 업무는 애플리케이션을 만들고 이를 배포(CD)하고 관리하며 계속 운영하는 운영 팀에 넘겨주는 것이었다.
개발자, 품질 보증(QA), 운영 팀이 전체 프로세스에서 협업해야 한다는 의미이다. 이런 작업 방식을 데브옵스(DevOps)라고 부른다.
최신 버전의 애플리케이션을 더 자주 릴리스 하려면 배포 프로세스를 간소화해야 하는데 가장 좋은 방법은 개발자가 운영 담당자를 기다리지 않고 직접 애플리케이션을 배포하는 것이다.
개발자는 새로운 기능을 만들고 사용자 경험을 향상시키고자 하며 운영팀은 프로덕션 환경 배포와 애플리케이션이 실행되는 하드웨어 인프라를 담당한다.
하드웨어 인프라를 알지 못하더라도 운영 팀을 거치지 않고 개발자가 애플ㄹ케이션을 직접 배포하는 방식이 가장 이상적이며 이를 노옵스(NoOps)라고 한다.
쿠버네티스를 사용하면 이 모든 것을 해결할 수 있다.
◼️ 1.2 컨테이너 기술 소개
쿠버네티스는 애플리케이션을 격리하는 기능을 제공하기 위해 리눅스 컨테이너 기술을 사용한다.
▪️1.2.1 컨테이너 이해
리눅스 컨테이너는 동일한 호스트 시스템에서 여러 개의 서비스를 실행할 수 있으며 동시에 서로 다른 환경을 만들어줄 뿐만 아니라 가상머신과 유사하게 서로 격리하지만 오버헤드가 훨씬 적다.
컨테이너에서 실행되는 프로세스는 다른 모든 프로세스와 마찬가지로 호스트 운영체제 내에서 실행된다. 하지만 컨테이너의 프로세스는 여전히 다른 프로세스와 격리돼 있다.
컨테이너는 훨씬 더 가벼워서 동일한 하드웨어에서 더 많은 수의 소프트웨어 구성 요소를 실행할 수 있다.
가상머신은 구성 요소 프로세스 뿐만 아니라 시스템 프로세스를 실행해야 하기 때문에 추가 컴퓨팅 리소스가 필요하다.
컨테이너는 호스트 os에서 실행되는 하나의 격리된 프로세스에 지나지 않으며 애플리케이션이 소비스하는 리소스만 소비하고 추가 프로세스의 오버헤드는 없다.
가상 머신의 오버헤드로 인해 각 애플리케이션 별로 하나의 VM을 전용으로 사용하기에는 리소스가 충분하지 않기 때문에 각 가상머신에 여러 애플리케이션을 그룹으로 배포하는 경우가 종종 있다.
컨테이너를 사용하면 아래 그림 1.4와 같이 애플리케이션 마다 하나의 컨테이너를 가질 수 있다. 따라서 동일한 베어메탈 머신에서 더 많은 애플리케이션을 적재할 수 있게 된다.
호스트에 가상머신 세개를 실행하면 세 개의 완전히 분리된 운영체제가 실행되고 동일한 베어메탈 하드웨어를 공유하게 된다.
이런 가상머신 아래에는 물리적 하드웨어 리소스를 각 가상머신 내부의 운영체제에서 사용할 수 있는 더 작은 리소스로 나누는 호스트 OS와 하이퍼바이저가 있다.
해당 가상머신 내에서 실행되는 애플리케이션이 가상머신의 게스트 OS 커널에 대한 시스템 콜을 수행하면 커널은 하이퍼바이저로 호스트의 물리적 CPU에서 x86 명령을 수행한다.
반면에 컨테이너는 호스트 OS에서 실행되는 동일한 커널에서 시스템 콜을 수행한다. 이 커널은 호스트의 CPU에서 x86 명령을 수행하는 유일한 커널이다. CPU는 가상머신과 같은 방식으로 어떠한 종류의 가상화도 필요가 없다.
하드웨어 리소스가 제한된 경우 격리하려는 프로세스가 적은 경우에만 가상머신을 사용할 수 있기 때문에 컨테이너는 모두 동일한 커널을 호출함으로 보안 위허밍 발생할 수 있지만 더 많은 수의 격리된 프로세스를 실행하려면 컨테이너의 오버헤드가 낮기 때문에 컨테이너를 선택하는 것이 좋다.
각 가상머신은 자체 시스템 서비스를 실행하지만 컨테이너는 모두 동일한 OS에서 실해오디므로 컨테이너는 시스템 서비스를 실행하지 않는다.
즉, 컨테이너를 실행하려면 가상머신처럼 부팅할 필요 없이 컨테이너에서 실행되는 프로세스는 즉시 시작된다.
컨테이너 격리는 두가지 매커니즘으로 가능하다.
첫번째는 리눅스네임스페이스로 각 프로세스가 시스템(파일, 프로세스, 네트워크 인터페이스, 호스트 이름 등)에 대한 독립된 뷰만 볼 수 있도록 한다. 두번째는 리눅스 컨트롤 그룹으로 프로세스가 사용할 수 있는 리소스(cpu, 메모리, 네트워크 대역폭 등) 양을 제한한다.
네임스페이스의 종류는 다음과 같다.
- 마운트, 프로세스 ID, 네트워크, 프로셋 간 통신, 호스트와 도메인 이름, 사용자 ID
각 네임스페이스는 특정 리소스 그룹을 격리하는 데 사용 된다.
컨테이너 격리의 나머지 부분은 컨테이너가 사용할 수 있는 시스템 리소스의 양을 제한하는 것이다. 이는 프로세스의 리소스 사용을 제한하는 리눅스 커널 기능인 cgroups로 이뤄진다.
▪️1.2.2 도커 컨테이너 플랫폼 소개
도커는 컨테이너를 여러 시스템에 쉽게 이식 가능하게 하는 최초의 컨테이너 시스템이다.
도커로 패키징된 애플리케이션을 실행하면 함께 제공된 파일 시스템 내용을 정확하게 볼 수 있다.
이는 가상머신에 운영체재를 설치하고 그 안에 애플리케이션을 설치한 다음 가상머신 이미지를 배포하고 실행하는 가상머신 이미지를 만드는 것과 유사하다. 가상머신을 사용해 애플리케이션을 격리하는 대신 리눅스 컨테이너 기술로 가상머신과 거의 동일한 수준의 격리를 제공한다.
컨테이너 이미지가 여러 이미지에서 공유되고 재사용될 수 있는 레이어로 구성되어 있는 도커 기반 컨테이너 이미지는 동일한 레이러를 포함하는 다른 컨테이너 이미지를 실행할 때 다른 에이어가 이미 다운로드 된 경우 이미지의 특정 레이어만 다운로드 하면 된다.
도커는 애플리케이션을 패키징, 배포, 실행하기 위한 플랫폼이다.
도커를 사용하면 이 패키지를 중앙 저장소로 전송할 수 있으며 도커를 실행하는 모든 컴퓨터에 전송할 수 있다.
도커의 세가지 주요 개념은 다음과 같다.
- 이미지 : 애플리케이션과 해당 환경을 패키지화 한 것이다.
- 레지스트리 : 도커 이미지를 저장하고 다른 사람이나 컴퓨터 간에 해당 이미지를 쉽게 공유할 수 있는 저장소이다.
- 컨테이너 : 도커 기반 컨테이너 이미지에서 생성된 일반적인 리눅스 컨테이너이다.
개발자는 먼저 이미지를 만든 다음 레지스트리로 푸시한다. 그런 다음 도커가 실행되는 다른 컴퓨터로 이미지를 가져와 이미지를 생행할 수 있다. 도커는 이미지를 기반으로 격리된 컨테이너를 만들고 이미지의 일부로 지정된 바이너리 실행파일을 실행한다.
가상머신에서 실행될 때와 두 개의 별도 컨테이너로 실행될 때 애플리케이션 A와 B가 동일한 바이너리, 라이브러리에 접근할 수 있다.
가상머신에서는 두 애플리케이션이 모두 동일한 파일시스템(가상 머신의 파일 시스템)에서 실행되지만 컨테이너는 격리된 자체 파일 시스템이 있다.
레이어로 구성된 도커 이미지는 다른 이미지 위에 빌드되며 두 개의 다른 이미지는 기본 이미지로 동일한 부모 이미지를 사용할 수 있어 다른 이미지에는 정확히 동일한 레이어가 포함될 수 있다.
이렇게 하면 첫번째 이미지의 일부로 전송한 레이어를 다른 이미지를 전송할 때 다시 전송할 필요가 없어 네트워크로 이미지를 배포하는 속도가 빨라진다.
또 레이어는 이미지의 스토리지 공간을 줄이는데에도 도움이 된다. 각 레이어는 동일 호스트에 한번만 저장이 되기 때문에 파일을 공유하더라도 여전히 서로 격리 돼 있는데 이것은 컨테이너 이미지 레이어가 읽기 전용이기 때문이다.
컨테이너의 프로세스가 기본 레이어 중 하나에 있는 파일에 쓰면 전체 파일의 복사본의 최상위 레이어에 만들어지고 프로세스는 복사본에 쓴다.
컨테이너 이미지는 도커를 실행하는 모든 리눅스 시스템에서 실행될 수 있지만 호스트에서 실행되는 모든 컨테이너가 호스트의 리눅스 커널을 사용한다. 이는 머신이 다른 버전의 리눅스 커널로 실행되거나 동일한 커널 모듈을 사용할 수 없는 경우 애플리케이션이 실행될 수 없다.
특정 하드웨어 아키텍처용으로 만들어진 컨테이너화 된 애플리케이션은 해당 아키텍처 시스템에서만 실행될 수 있다.
◼️1.3 쿠버네티스 소개
▪️ 1.3.1 쿠버네티스의 기원
구글은 보그(Borg)라는 내부 시스템을 개발해 애플리케이션 개발자와 시스템 관리자가 수천 개의 애플리케이션과 서비스를 관리하는데 도움을 줬다.
개발과 관리를 단순화 할 뿐만 아니라 인프라 활용률을 크게 높일 수 있었고 이는 운영비용을 수백만 달러를 절약할 수 있었다.
2014년 보그, 오메가, 기타 내부 구글 시스템으로 얻은 경험을 기반으로 하는 오픈소스 시스템인 쿠버네티스를 출시했다.
▪️ 1.3.2 넓은 시각으로 쿠버네티스 바라보기
쿠버네티스는 컨테이너화 된 애플리케이션을 쉽게 배포하고 관리하 ㄹ수 있게 해주는 소프트웨어 시스템으로 각 호스트에 애플리케이션을 수동으로 배포하지 않고도 이기종 애플리케이션을 실행할 수 있다.
쿠버네티스를 사용하면 모든 노드가 하나의 거대한 컴퓨터인 것처럼 수천 대의 컴퓨터 노드에서 소프트웨어 애플리케이션을 실행할 수 있다.
쿠버네티스 시스템은 마스터 노드와 여러 워커 노드로 구성되며 개발자가 애플리케이션 매니페스트를 마스터에 게시하면 쿠버네티스는 해당 애플리케이션을 워커 노드 클러스터에 배포한다.
개발자는 특정 애플리케이션이 함께 실행되도록 지정할 수도 있고 쿠버네티스는 여러 애플리케이션을 동일한 워커 노드에 배포한다. 다른 애플리케이션은 클러스터에 걸쳐서 분산되지만 배포된 위치에 상관없이 동일한 방식으로 서로 통신할 수 있다.
쿠버네티스는 클러스터 어딘가에 컨테이너화 된 애플리케이션을 실행하고 모든 애플리케이션이 계속 실행되도록 한다.
애플리케이션은 어떤 노드에서 실행되든 상관이 없기 때문에 쿠버네티스는 언제든지 애플리케이션을 재배치하고 애플리케이션을 조합함으로써 리소스를 수동 스케줄링 보다 훨씬 더 잘 활용할 수 있다.
▪️ 1.3.3 쿠버네티스 클러스터 아키텍처 이해
마스터 노드는 전체 쿠버네티스 시스템을 제어하고 관리하는 쿠버네티스 컨트롤 플레인을 실행한다.
워크 노드는 실제 배포되는 컨테이너 애플리케이션을 실행한다.
컨트롤 플레인은 클러스터를 제어하고 작동시킨다.
쿠버네티스 API 서버는 사용자, 컨트롤 플레인 구성 요소와 통신한다.
스케줄러는 애플리케이션의 배포를 담당하며 컨트롤러 매니저는 구성 요소 복제본, 워커 노드 추적, 노드 장애 처리 등과 같은 클러스터단의 기능을 수행한다.
ETCD는 클러스터 구성을 지속적으로 저장하는 신뢰할 수 있는 분산 데이터 저장소이다.
워커 노드는 컨테이너화 된 애플리케이션을 실행하는 시스템이다.
컨테이너를 실행하는 도커 또는 다른 컨테이너 런타임으로 API 서버와 통신하고 노드의 컨테이너를 관리하는 kubelet이다.
애플리케이션 구성 요소 간에 네트워크 트래픽을 로드밸런싱하는 쿠버네티스 서비스 프록시이다.
▪️ 1.3.4 쿠버네티스에서 애플리케이션 실행
쿠버네티스에서 애플리케이션을 실행하려면 애플리케이션을 하나 이상의 컨테이너 이미지로 패키징하고 해당 이미지를 레지스트리로 푸시한 다음 쿠버네티스 API 서버에 애플리케이션 디스크립션을 게시해야 한다.
이 디스크립션에는 컨테이너 이미지, 애플리케이션 구성 요소가 포함된 이미지, 해당 구성 요소가 서로 통신하는 방법, 동일 서버에 함께 배치돼야 하는 구성 요소와 같은 정보가 포함된다.
API 서버가 애플리케이션 디스크립션을 처리할 때 스케줄러는 각 컨테이너에 필요한 리소스를 계산하고 해당 시점에 각 노드에 할당되지 않은 리소스를 기반으로 사용 가능한 워커 노드에 지정된 컨테이너를 할당한다.
그런 다음 해당 노드의 kubelet은 컨테이너 러낱임에 필요한 컨테이너 이미지를 가져와 컨테이너를 실행하도록 지시한다.
애플리케이션 디스크립터는 세 개 세트로 그룹화된 네 개의 컨테이너를 가진다. 이 세트를 파드라고 하는데 처음 두 파드는 각각 하나의 컨테이너만 가지고 마지막 파드에는 두 개의 컨테이너가 있다.
디스크립터를 쿠버네티스에 제출한 후 각 파드의 지정된 복제본 수를 사용 가능한 워크 노드로 할당한다. 노드의 kubelet은 도커 이미지 레지스트리에서 컨테이너 이미지를 가져와 컨테이너를 실행하도록 지시한다.
애플리 케이션이 실행되면 쿠버네티스는 애플리케이션의 배포 상태가 사용자가 제공한 디스크립션과 일치하는지 지속적으로 확인한다.
인스턴스가 제대로 작동하지 않으면 쿠버네티스가 자동으로 다시 시작하며 워커 노드 전체가 종료 되거나 액세스 할 수 없게 되면 쿠버네티스는 이 노드에서 실행 중인 모든 컨테이너 노드를 새로 스케줄링 하고 새로 선택한 노드에서 실행한다.
애플리케이션이 실행되는 동안 복제본 수를 늘릴지 줄일지 결정할 수 있으며 최적의 복제본 수를 결정하는 작업을 쿠버네티스에 맡길 수도 있다.
실행 중인 토드가 정지됐거나 다른 컨테이너를 위한 공간을 만들려고 노드에서 제거됐기 때문에 쿠버네티스는 컨테이너를 클러스 안에서 이동시킬 수도 있다.
클라이언트가 특정 서비스를 제공하는 컨테이너를 쉽게 찾을 수 있도록 쿠버네티스에 동일한 서비스를 제공하는 컨테이너를 알려주면 쿠버네티스는 하나의 고정 IP 주소로 모든 컨테이너를 노출하고 해당 주소를 클러스텡서 실행 중인 모든 애플리케이션에 노출한다.
이는 환경 변수로 제공되지만 클라이언트는 오래 전부터 사용된 DNS로 서비스 IP를 조회할 수 있다. kube-proxy는 서비스를 제공하는 모든 컨테이너에서 서비스 연결이 로드밸런싱 되도록 한다.
서비스의 IP 주소는 일정하게 유지되므로 클라이언트는 컨테이너가 클러스터 내에서 이동하더라도 컨테이너에 항상 연결 할 수 있다.
▪️ 1.3.5 쿠버네티스 사용의 장점
컨테이너화 된 애플리케이션은 이미 실행에 필요한 모든 것이 포함돼 있으므로 시스템 관리자는 애플리케이션을 배포하고 실행하기 위해 아무것도 설치할 필요가 없다.
쿠버네티스가 배포된 모든 노드에서는 시스템 관리자의 도움 없이 즉시 애플리케이션을 실행할 수 있다.
쿠버네티스는 모든 워커 노드를 하나의 배포 플랫폼으로 제공하기 때문에 클러스터를 구성하는 서버에 관해 알 필요가 없다.
모든 도느는 이제 애플리케이션이 해당 노드를 사용하기를 기다리는 하나의 컴퓨팅 리소스가 된다.
서버에 수동으로 애플리케이션을 실행하는 대신 쿠버네티스를 설정하고 애플리케이션을 실행함으로써 인프라와 애플리케이션을 분리할 수 있다.
쿠버네티스에 애플리케이션을 실행하도록 지시하면 애플리케이션의 리소스 요구 사항에 대한 디스크립션과 각 노드에서 사용 가능한 리소스에 따라 애플리케이션을 실행할 가장 적합한 노드를 선택할 수 있다.
클러스터에서 실행되는 다른 애플리케이션 구성 요소를 혼합해 클러스터 노드에 배치할 수 있으며 언제든지 클러스터 간 애플리케이션을 이동할 수 있기 때문에 훨씬 더 잘 인프라를 활용할 수 있다.
쿠버네티스는 노드 장애 발생 시 자동으로 애플리케이션을 다른 노드로 스케줄링 한다.
쿠버네티스가 실행 중인 경우 클라우드 제공업체의 API로 쉽게 노드를 추가하면 배포된 애플리케이션의 부하에 따라 전체 클러스터 크기를 자동으로 확장하거나 축소할 수 있다.
쿠버네티스를 사용하면 개발자는 시스템 관리자 도움없이 쿠버네티스로 애플리케이션을 배포할 수 있으며 시스템 관리자는 쿠버네티스가 고장 난 노드를 자동으로 처리하도록 함으로써 더 편하게 관리를 할 수 있게 된다.