컨테이너와 Kubernetes
1. 컨테이너
컨테이너는 도커에서 다룬 내용과 같이 어플리케이션을 패키징하여 실행하는 논리적인 공간을 가리킵니다. 각 컨테이너는 개별적인 커널을 갖지 않고 호스트의 커널이 할당하는 자원을 빌려 사용합니다. VM과 컨테이너의 개념이 다소 혼동될 수 있으나, 개별적으로 커널을 가지고 있는지 혹은 호스트의 커널을 빌려 명령을 수행하는지를 비교하시면 좀 더 명확하게 이해하실 수 있습니다.
1.1 오버헤드 감소
우리가 보통 커널에서 명령을 처리하는 데에는 각종 리소스가 투입됩니다. 가령, 인간의 삶에 비유하자면 출근을 해서 하는 업무에 들어가는 에너지가 100이라고 할 때, 세면을 하고 옷을 입고 출근 지하철을 타는 등의 부가적인 에너지가 10이 든다고 가정해 봅시다. 여기서 본 업무 외에 추가적으로 소요된 에너지 10을 오버헤드(Overhead) 라고 합니다. 일반적인 상황에서 코어 업무가 아닌 오버헤드성 리소스가 늘어나는 것은 바람직하지 않습니다. 처리속도 지연, 가용 메모리 누수 등의 이슈를 초래하기 때문이죠. 아무리 효율적인 아키텍처라도 오버헤드를 0으로 만들 수는 없습니다. 이 오버헤드를 최소화 하여 시스템의 부하를 줄이는 방법에 대해 많은 사람들이 이를 고민합니다. 컨테이너를 활용하는 경우 어플리케이션이 개별 커널을 구동하지 않으므로 VM의 방식과 비교하여 오버헤드 규모를 축소해서 서비스를 운영할 수 있습니다.
1.2 개발 효율성 증대
개발(development)과 운영(production) 환경은 엄밀히 분리되어야 함과 동시에 모든 사항을 빠짐없이 반영하여야 합니다. 만약 이 두 환경이 유기적으로 연동되어 있지 않으면 개발 단계에서 테스트를 거치지 않은 소스가 반영 혹은 미반영 되어 장애를 야기할 수 있습니다. 이 두 환경을 동일 선상에 놓기 위해서는 OS를 비롯한 각종 세팅이 완전히 같아야 합니다. 실제로 상이한 개발/운영 환경의 리스크로 인해 장애가 발생하는 경우는 서비스의 규모를 막론하고 매우 빈번하게 발생합니다.
컨테이너는 환경변수 등으로 개발/운영 환경을 제어하며, 이외 대부분의 항목을 동일하게 구성할 수 있습니다. 즉, 환경의 차이로 인해 발생하는 장애 리스크를 줄일 수 있는 것이죠. 물론 API 및 DBMS의 연동은 개발과 운영이 혼재되지 않도록 잘 관리해야 합니다.
2. Kubernetes와 컨테이너
쿠버네티스는 한마디로 컨테이너를 효율적으로 관리할 수 있도록 도와주는 툴 입니다. 그렇다면 우리는 컨테이너와 쿠버네티스의 관계성에 대해 파악해보아야 합니다. 공부를 하다보면 도커와 쿠버네티스는 어떻게 구분되는지, 그리고 기타 컨테이너 런타임은 도대체 어떻게 적용되는 것인지 혼동이 됩니다.
하나의 서비스를 운영하는 데에는 수많은 컨테이너가 필요합니다. 장애를 예방하기 위해서는 동일한 서비스를 하는 컨테이너를 복수로 생성해놓기도 하죠. 다수의 컨테이너를 사람이 일일히 제어하는 것은 굉장히 쉽지 않은 일입니다. 구글은 대규모 컨테이너 기반 클러스터를 관리하기 위해 Borg(Kubernetes의 전신)라는 시스템을 개발, 도입하게 됩니다. 이를 기반으로 구글 클라우드의 엔지니어들이 Go 언어를 사용해 개발된 오픈소스 프로젝트가 바로 쿠버네티스 입니다.
쿠버네티스 아키텍처에서는 컨테이너를 그룹화하여 pod(파드) 라고 부릅니다. 이 파드는 하나 혹은 여러 개의 컨테이너로 구성되어 있으며, 개수에 상관없이 마치 하나처럼 취급됩니다. Deployment(디플로이먼트)는 버전 등을 포함한 컨테이너의 설정을 반영하는 기능을 하며, 쿠버네티스에서는 디플로이먼트를 기반으로 클러스터에 파드를 생성하고 정의된 상태를 유지합니다. 디플로이먼트에서 다뤄지는 여러 설정 중 중요한 것을 이르자면 주저없이 Replica(레플리카)를 꼽을 수 있습니다. 이는 일종의 복사본의 개념으로 동일한 내용의 컨테이너를 총 몇 개 생성할 지를 결정합니다.
Service(서비스) 라는 이름의 설정 파일은 어떤 컨테이너로 외부에서 인입되는 트래픽을 유도하는 지를 결정하는 로드 밸런서를 생성하는 데에 사용됩니다. 유저는 쿠버네티스를 통해 운용되는 서비스에 접속할 때 이 로드 밸런서를 통하게 되며, 클래스터 내 각 호스트의 어떤 컨테이너가 목적지인지를 안내합니다.
2.1 오퍼레이션 자동화
쿠버네티스에게 전달하는 명령어의 집합(docker compose와 유사)인 디플로이먼트에는 컨테이너로 하여금 유지되기를 희망하는 상태 를 정의합니다. 가령 정의된 CPU 사용량, 메모리 사용량 등을 기반으로 파드는 레플리카의 수를 자동으로 제어할 수 있습니다. 최소 운영 레플리카를 3개로 두었다가 일정 기준 이상이 넘으면 5개, 7개 등으로 확장이 가능한 것입니다.
2.2 확장성(scalability) 제고
시중에는 서비스를 디플로이 할 수 있는 수많은 서비스가 공개되어 있습니다. Heroku, AWS Elastic Beanstalk 과 같이 PaaS(Platform as a Service) 형태의 서비스에는 개발한 소스코드와 설정(config) 파일만 같이 올리면 모든 것을 알아서 처리해줍니다. MVP(Minimum Viable Product, 최소 기능 제품)를 구축하거나 스토리지 용량이 크지 않은 경우, 24시간 작동하지 않아도 비즈니스에 큰 해를 끼치지 않는 경우라면 위와 같은 형태의 서비스를 활용하는 것도 좋은 선택지 중 하나입니다. 하지만, 지속적으로 유저가 증가하고 비즈니스 로직이 복잡해진다면 PaaS 에서 지원하는 범위를 초과하게 됩니다. 다른 서비스로 갑작스럽게 마이그레이션을 준비해야 하는 난처한 상황이 발생할 수 있는 것이죠.
서비스를 설계할 당시 대량의 유저를 타겟으로 하거나 서비스의 규모가 점진적으로 확대되는 모델인 경우 확장성을 고려해야 합니다. 이는 MCU(Maximum Current Users, 최대 동시 접속 사용자) 및 DAU(Daily Active Users, 일일 활성 사용자) 등의 지표를 통해 사전에 설계할 수 있습니다.
쿠버네티스는 확장에 대한 고민을 덜 수 있도록 서비스의 규모와 상관 없이 수많은 컨테이너를 스케쥴링하고 job(잡)을 부여할 수 있도록 설계되어 있습니다. 구글은 매주 수십억 개의 컨테이너를 생성한다고 하죠. 확장성이 좋다는 표현을 더 구체적으로 설명하면 여러 호스트로 이루어진 클러스터에 필요한 만큼 컨테이너를 생성하고 이를 설정한 규칙에 따라 운용할 수 있다고 할 수 있습니다.
3. Kubernetes가 필요한 경우
쿠버네티스의 도입은 어느 시점에 필요한 것일까요? 서비스의 성격과 규모에 따라, 혹은 제공 중인 서비스의 현황에 따라 도입이 효율적일 수도 있고 비용만 초래할 수도 있습니다. 이를 사전에 분석하기 위해서는 몇가지 개념에 대한 이해가 필요합니다
Stateless와 Stateful, 모두 State(상태) 라는 공통의 토픽을 다루고 있습니다. 상태를 이해하면 이 두 가지 개념도 쉽게 이해가 가능합니다. 여기서 말하는 상태는 클라이언트 측 정보를 의미합니다. Stateless 에서는 서버가 요청을 전송한 클라이언트의 정보를 저장하지 않습니다.
우리가 통신에서 사용하고 있는 HTTP 프로토콜이 바로 Stateless 한 방식으로 작동합니다. 비연결적인 특성을 가지고 있기 때문에 HTTP를 통해 서버에 전송되는 요청이 완료되면 즉시 연결이 해제되며, 이후 전달되는 새로운 요청의 payload에 따라 응답을 클라이언트 측에 전송합니다. 즉 전송된 각 요청이 서로 배타적으로 수행되는 것이죠. 하지만 이렇게 이전의 상태가 유실되는 경우 로그인을 매번 다시 해야하거나 장바구니에 넣어놓은 상품이 모두 초기화 되는 등의 불편을 겪게됩니다. 그래서 Stateless한 HTTP 통신에 Stateful한 요소를 더하고자 나오게 된 것이 쿠키와 세션 입니다. 쿠키는 클라이언트에 저장되는 서버의 정보, 세션은 서버에 저장되는 클라이언트의 정보입니다. 클라이언트에서 서버에 접속 시, 세션이 무한정 서버 측에 저장되는 것을 방지하기 위해 쿠키를 일부 사용하며, 기존에 로그인했던 웹 사이트에 재접속시 자동으로 ID/PW가 입력되는 것, 팝업 창에서 n일 동안 다시보지 않기
등을 체크하는 것이 바로 이에 해당됩니다. 세션은 서비스 이용에 꼭 필요한 정보한 정보를 서버 측에 저장해 놓는 것이며, 클라이언트에서 접속 시 payload의 쿠키를 확인하고 고유한 세션ID를 부여하여 다시 클라이언트 측에 전달합니다. 우리가 포탈사이트에서 페이지를 여기저기 옮겨다녀도 로그인이 풀리지 않는 것은 바로 로그인 세션이 서버 측에 저장되어 접속을 종료할 때까지 유지하기 때문입니다.
Stateful은 방식은 Stateless와는 다르게 클라이언트의 정보를 서버에 저장합니다. 일반적으로 데이터베이스 서버에 적용되는 개념이며, TCP, FTP, TELNET 방식의 통신이 이에 해당합니다. Stateful한 서버에서는 요청에 대한 응답이 종료될 때까지 연결을 유지하려고 합니다. 마치 전화 통화를 하는 것과 같죠. 이를 가장 단적으로 보여주는 예시가 바로 TCP 3 Way-Handshake 입니다. 현재 수많은 레거시 어플리케이션은 대부분 Stateful 하게 구성되어 있으나, 서비스 확장성 및 장애 관리의 용이성 등을 제고하기 위한 Stateless 기반 마이크로서비스 아키텍처가 폭넓게 도입되는 추세입니다. 위에서 설명드렸던 컨테이너도 Stateless한 특성을 가지고 있으며 Stateful한 어플리케이션도 컨테이너에서 실행하기 위한 리패키징 등의 움직임이 확산되고 있습니다.
쿠버네티스의 도입은 반드시 모든 서비스에 필요한 것은 아닙니다. 새로운 아키텍처 하에서 서비스를 운영하는 것에는 장점과 더불어 많은 챌린지를 동반하게 되죠. 하지만 트레이드오프를 감안하더라도 다음의 항목에 해당하는 경우라면 쿠버네티스 도입에 따른 이점을 충분히 가질 수 있습니다.
3.1 인프라 다변화
현재 우리가 사용하고 있는 서비스는 온프레미스 서버를 비롯하여 VPC, AWS 등이 제공하는 클라우드 서버 등을 활용하고 있습니다. 레거시 형태의 아키텍처에서는 서비스 자체가 구축된 인프라와 밀접하게 연결되어 있어 스케일을 확장하거나 타 인프라로 마이그레이션 하는 것이 쉽지 않았습니다. 하지만 어플리케이션 배포 주기가 빨라지고 사용자의 요구가 빠르게 증가함에 따라 다양한 도메인으로 서비스가 확장되면서 특정 인프라에 의존적인 형태로는 한계에 봉착할 수 밖에 없습니다. 쿠버네티스는 어플리케이션과 인프라 사이에 묶여 있는 사슬을 끊어내고 확장성을 증대하는 데에 큰 역할을 하였습니다. AWS 혹은 GCP 등의 클라우드 서비스 제공자에 대한 잠재적 종속성도 적지 않게 해소하였습니다.
3.2 신속한 배포 절차
배포를 하기 위해서는 굉장히 많은 시간이 소요됩니다. 개발자가 배포를 위해서 수행해야 하는 스크립트도 업무에서 큰 영역을 차지하였죠. 배포 프로세스 자체가 무거워지다보니 빠르게 요구사항을 반영하는 데에도 걸림돌이 되었습니다. 쿠버네티스에서는 배포 대상의 되는 컨테이너를 효율적으로 다루는 여러 기능을 제공합니다. 필요한 개발계 서버가 있으면 빠르게 증설이 가능하며, 설정에 따라 자동으로 실행 컨테이너수나 CPU 자원을 할당할 수 있습니다. 더불어 각 노드를 헬스체킹하여 에러가 발생한 컨테이너를 정상 컨테이너로 대체하는 self-healing 기능 및 신규버전을 운영 환경에 롤아웃 하였을때 장애가 발생하였을때 다시 이전 버전으로 롤백하는 기능도 제공하고 있습니다. 지금도 많은 곳에서 배포 모니터링 및 롤백은 수작업으로 수행하는 경우가 적지 않지만 쿠버네티스에서는 이러한 작업을 모두 자동화하여 관리합니다. 마지막으로 카나리 업데이트를 지원하는데, 이는 이전버전과 신규버전을 운영환경에서 동시에 테스트 해볼 수 있는 환경을 구성하는 것을 의미합니다. 동일한 운영환경에서 변경된 소스 부분을 병렬적으로 검증할 수 있기 때문에 대고객 서비스에서 발생할 수 있는 잠재적 장애 요소를 사전에 예방할 수 있습니다.
Kubernetes : https://kubernetes.io/
The Twelve-Factor app : https://12factor.net/
Red Hat 쿠버네티스(Kubernetes)란? 개념, 성능, 사용 방법 및 차이점 : https://www.redhat.com/ko/topics/containers/what-is-kubernetes
Abhishek Verma, Luis Pedrosa, Madhukar R. Korupolu, David Oppenheimer, Eric Tune John Wilkes, “Large-scale cluster management at Google with Borg”: Proceedings of the European Conference on Computer Systems (EuroSys), 2015
Microservices in Action, 1st ed. by Morgan Bruce, Paulo A. Pereira (Manning, 2018)
Kubernetes for Developers, 1st ed. by William Denniss (Manning, 2021)