왜 Kubernetes 인가? How가 아닌 Why로 접근하는 Kubernetes 이야기

twkim913
NAVER Pay Dev Blog
Published in
14 min readAug 12, 2022

저는 네이버 파이낸셜 페이플랫폼에서 개발 업무를 담당하고 있는 김태우 입니다.

인터넷에서 k8s (KuberneteS, 중간에 여덟 글자가 들어가서 k8s입니다)과 관련된 많은 자료를 읽어봤지만, k8s이 무엇인지, 그리고 어떻게 사용 하는지 에 집중할 뿐 어떤 이유에서 k8s가 생겼는지, 그리고 우리는 왜 k8s를 사용해야 하는 지에 대해서는 설명한 글이 없어 (있어도 다소 딱딱하기에) 이번 글에서는 k8s의 ‘How’ 보다는 ‘Why’에 집중해 설명을 해 보려 합니다.

네이버는 이미 자체 k8s engine인 네이버 클라우드 플랫폼의 Kubernetes Service를 사용하고 있으며, 많은 네이버 서비스들이 이미 Naver Kubernetes로 이관 되었습니다.

형님들 다 잘 아실 거라고 믿어 의심치 않지만! 설명해 보겠습니다!

공식 사이트에 따르면 k8s는 컨테이너 오케스트레이션, 즉 컨테이너 관리 시스템입니다. 여기서 컨테이너는 docker에서 말하는 바로 그 container 입니다.

docker, 어디서 많이 들어본 이름이군요. 스터디에 나가보면 자기가 개발자라고 자랑하고 싶은 사람들이 아래의 고래 스티커를 자기 노트북에 붙이고 다니는 것도 봤던 것도 같습니다.

도커, 마스코트는 참 귀엽습니다.

docker와 k8s를 왜 쓰는지 이해하기 위해서는 가장 처음으로 돌아갈 필요가 있습니다. 태초에(?) 베어메탈이 있었습니다. (bare metal)

아니요, 이거 아닙니다.

베어메탈은 의미상으로는 어떠한 프로그램도 깔리지 않은 ‘프로그램 없는 그냥(bare) 철덩이(metal) 같은’ 상태의 서버 장비를 표현하는 말이지만, 보통 이런 빈 서버에 직접 리눅스 같은 OS 그리고 웹 서버 구축에 필요한 nginx나 mysql 같은 프로그램을 수동으로 설치해 서버를 사용하는 방식을 베어메탈 이라고 불렀습니다.

지금은 서버의 가상화가 많이 진행되어, ‘실제 서버실 컴퓨터에 프로그램을 설치해서 서버를 띄운다’ 는 건 사실 (저를 포함한) 클라우드 콘솔에서 서버를 띄우는 요즘 개발자들에게 있어서는 다소 생소한 이야기 입니다. 다만 당연히 해야 하는 이런 작업은 쉽지 않았습니다. (적어도 신입 때 회식 자리에서 들었던 ‘나 때는 말이야’에 따르면 그랬습니다)

예를 들어 서버 컴퓨터 1호에 리눅스 버전 1을 설치하고 프로그램 A,B를 설치 했을 때 문제 없이 돌아가던 서버가 프로그램 C를 설치하면 문제가 생깁니다, 문제가 뭘까요? 찾아보니 프로그램 C는 리눅스 버전 2에서만 사용이 가능하군요. 그럼 리눅스 버전을 2로 설치해서 다시 실행해 보도록 하겠습니다. 이제 프로그램 A에서 문제가 생기기 시작합니다. 찾아보니 프로그램 A는 리눅스 버전 2 에서 버그가 생기는 이슈가 있군요. 그럼 리눅스 버전 3 까지 올려 문제를 해결하면 더 이상 아무런 문제가 없을까요?

당연히 그렇지 않습니다, 1호기 서버가 잘 돌아가는 걸 확인한 뒤 위와 똑같은 세팅을 서버 컴퓨터 2호에 하고 정상 실행이 되지 않아 세 시간을 낭비한 뒤에야 서버 컴퓨터 2호기에서는 리눅스 버전 3 이 정상적으로 설치가 되지 않는 것을 알았습니다.

그리고 몇 일 밤을 새워가며 드디어 프로그램 A,B,C를 두 서버에 모두 설치하고 나서, 프로그램 D를 깔아야 할 때 알아버렸습니다. 프로그램 A는 버전 1.23인 프로그램 B를 써야 하고 프로그램 D는 버전 1.25인 프로그램 B를 써야 하며, 둘 다 버전 타협의 여지가 별로 없다는 것을….

최신 IDE가 제공하는 각종 기능과 가상화, 그리고 package import 자동화가 잘 되어 있는 요즘도 남의 소스코드를 내 컴퓨터에서 돌리는게 쉽지는 않다는 것을 생각해보면, 조금씩 다른 하드웨어에 조금씩 다른 리눅스 버전이 깔려있고, 조금씩 다른 버전의 프로그램을 설치해서 서로 충돌 없이 안정적으로 실행 된다는 게 얼마나 힘든 일인지 상상해 볼 수 있습니다.

또한, 서버에 사용되는 컴퓨터는 보통 강력하고 비싼 장비였기에, 그렇게 비싸고 좋은 장비에 프로그램을 조금만 설치해서 간단하고 안정적으로 운용 하는 것도 그렇게 좋은 방법이 아니었습니다. (적어도 회사 입장에서는 개발자들을 야근 시키는 게 서버를 놀리는 것보단 좋은 방법처럼 보였습니다.)

그렇기 때문에 개발자들은 매번 서버 버전 업데이트를 하거나 수정할 때마다 야근을 해가며 전전긍긍 하거나, 버전 업데이트 없이 2004년의 소스코드를 2002년에 나온 버전의 리눅스로 서비스하는 만행을 저지르는 고통스러운 양자택일을 해야 했습니다. (참고로 2004년 소스코드는 이 글에 나오는 다른 농담과는 다르게 저의 실제 목격담입니다)

이게 농담이 아니던 시대가 존재 했습니다(!)
동서고금을 막론하고!

그렇게 많은 개발자들이 고통 받는 와중에 새로운 돌파구가 나옵니다. Virtual Machine, 즉 우리가 간간히 보는 단어인 VM입니다. Virtual Machine과 위에서 설명한 Bare Metal의 차이는 아래 그림에서 볼 수 있습니다.

위의 그림에서 볼 수 있듯이 VM은 하이퍼바이저라는 플랫폼을 이용해 하나의 서버에 여러 개의 OS를 실행시킵니다. 하이퍼바이저는 하드웨어 자체에서 지원하는 경우도 있지만, 기본 OS 상에서 실행되는 경우도 있기에 하이퍼바이저‘’ 실행하는 기본 OS를 Host OS, 하이퍼바이저‘’ 실행하는 분리된 OS를 Guest OS 이라고 나눠서 지칭합니다.

Guest OS는 Hypervisor를 통해 구현된 가상의 OS 이기에 OS의 모든 환경을 일정하게 관리/복제하는 것이 가능합니다, 심지어는 OS에 설치된 프로그램이나 프로그램의 버전까지도 동일하게 복제할 수 있기에 모든 서버가 (비록 가상이지만) 동일한 하드웨어와 동일한 OS를 사용하도록 보장할 수 있었고, 그렇기에 VM을 사용하면 더이상 버전 호환성으로 고통 받을 필요도 없게 되었습니다.

또한, OS를 나눠서 사용 할 수 있기에 하나의 OS에 여러 개의 프로그램을 쑤셔 넣지 않고 하나의 OS에 필요한 프로그램 만을 설치해 사용할 수 있고, 심지어는 한 대의 서버가 필요에 따라 여러 다른 버전의 OS를 사용할 수도 있게 되었습니다.

이렇게 VM을 통해 우리는 호환성 지옥에서 영원히 빠져나온 것처럼 보였습니다.

VM이 서버의 호환성 문제를 해결해 줬지만, (언제나 그렇듯이) 문제는 아직 남아 있었습니다. 위의 그럼에서도 볼 수 있지만, VM은 기존 OS 위에 하이퍼바이저를 올리고 또 그 위에 가상화 된 Guest Os를 띄우는 방식으로 작동하기에 효율이 떨어졌습니다(엄밀한 비유는 아니지만 컴퓨터에서 콘솔 에뮬레이터로 게임을 해 본 경험이 있으시다면 이해가 빠를 것 같습니다)

비록 type-1 Hypervisor 와 같이 OS가 아닌 하드웨어 차원에서 가상화를 지원하는 방식도 있지만, 그런 방식의 효율 역시 베어메탈과 비교해보면 만족스럽지는 못했습니다.

VM의 비효율성을 해결하기 위해 docker가 새로 출시되었습니다, docker는 여러 개의 프로그램이 서로 격리되어 실행 된다는 부분에서는 vm이 기존에 제공하는 호환성 이슈를 동일하게 해결합니다, 또한 docker는 베어메탈과 거의 유사한 성능을 제공합니다, 어떻게 이런 형편 좋은 일이 가능할까요? 아래 그림을 봐 봅시다.

위에서 계속 설명한 바와 같이 VM은 서로 독립된 Guest OS를 사용하고 있습니다, 그에 반해 docker는 프로그램 실행에 필요한 binary 파일과 library 파일들 만을 가지고 있고, 자체적인 OS를 사용하기 위한 방법으로 kernel과 filesystem, library를 전부 포함한 Linux OS가 아닌 filesystem, library만을 포함하는 Linux Image를 사용하며, 커널은 Host OS의 커널을 공유하기에, 자체 커널이 없어 매우 가볍습니다. 또한 Guest OS위에서 구동 되지 않기에 CPU/Memory 같은 자원이 완벽히 분리되어 있지 않지만, Docker Engine이 CPU와 memory를 ‘개념’적으로 분리해 줍니다. 물리적인 분리가 아닌, 단순히 ‘개념적’ 분리이기에 원론적으로는 VM에 비해 보안 적으로 취약한 면이 있지만, VM에 비하면 훨씬 빠르고 유연한 자원 분배가 가능해 졌습니다.

설명이 다소 장황 해진 듯 하지만, 간단하게 설명하자면, VM은 OS를 격리하고, docker는 process를 격리합니다, 더 간단하게 설명하면 사용자 입장에서는 docker는 VM이랑 비슷한데 더 가볍고 빠릅니다. 😉

또한, docker는 linux container (LXC)의 개념을 빌려왔기 때문에 (심지어는 초기에는 개념이 아닌 기술 그 자체를 차용 했었습니다.) 각 격리된 프로세스들을 container 이라고 부릅니다.

VM의 Guest OS와 유사하게 container 역시 복사가 가능한 파일이고 (이 파일을 container image 이라고 부릅니다 ) docker Hub (https://hub.docker.com/ ) 이라는 사이트를 통해 공유가 가능합니다. 즉, 세계 수만 명의 docker 이용자들이 만들고 검증한 container image 를 한 줄의 다운로드 코드를 통해 다운 받아 서버에서 바로 활용이 가능하고, 또한 해당 container image를 기반으로 나만의 container image 를 만드는 것 역시 가능합니다!

위의 설명이 다소 장황 하시다면, 남이 만들어 놓은 서버의 container image를 코드 몇 줄로 내 컴퓨터에 다운 받아, 내 컴퓨터 혹은 아무 컴퓨터에서 완전히 동일하게 실행 가능합니다 (물론 실제로는 volume mount나, Port 연동처럼 번거로운 설정을 조금 해 주어야 합니다)

이제 드디어 k8s에 대해 설명할 차례입니다. 많은 경우 k8s와 docker를 섞어서 설명하기에, 많은 사람들이 저 둘 이 정확히 어떤 관계인지 혼란스러움을 느낍니다.

k8s 는 간단히 말하자면 container 오케스트레이션 툴, 조금 더 쉽게 말 하자면 container 관리 엔진입니다. 여기서 docker 관리 엔진이라고 말하지 않는 이유는, k8s와 docker는 가장 많이 사용되는 조합일 뿐 linux container 개념을 구현한 많은 docker이외의 container 엔진이 k8s와 호환 가능하기 때문입니다. (다만 간단한 설명을 위해, 여기서 는 docker container = container 이라고 가정하고 설명을 계속하겠습니다, 즉 k8s = docker 관리 엔진)

docker는 뛰어난 프로세스 격리 툴 이지만, 실제 docker를 운용하는 개발자들은 다양한 어려움에 맞닥뜨렸습니다.

docker가 가볍고 빨라도, docker 서버를 관리해주는 개발자는 걱정해야 할 포인트가 많았습니다. Docker 서버가 다운되면, 해당 서버는 트래픽을 차단한 후, 수동으로 죽은 Docker 서버를 찾아내 새로 서버를 띄워야 했습니다, 그리고 매번 서버 업데이트를 진행 할 때도 각각의 Docker 서버를 이미지만 바꿔 새로 올려야 했습니다. 실제로 전 회사에서 근무 할 때 대만의 컨텐츠제공자 측에서 그냥 단순한 Docker 서버를 사용해, 그쪽 Docker 서버가 죽어 새벽 동안 다섯 시간의 장애를 일으킨 적이 있었습니다.

또한 트래픽이 너무 많이 몰려 지금 서버로 처리가 힘들어지면, docker 서버를 증설해야 하고, 트래픽이 다시 줄면 docker 서버를 다시 줄이는 것처럼 여러가지 번거로운 작업을 해주어야 했습니다.

물론 저런 문제들이 기존 서버에서 없었던 것은 아닙니다. 기존 서버들도 여러가지 방법을 통해 위의 문제들을 해결하기 위해 힘써왔고, docker는 k8s 이라는 솔루션을 통해 위의 문제들을 해결했습니다.

k8s는 모든 Docker 관리 작업을 자동화한 플랫폼입니다, 모든 Docker관리 작업은 자동화 되었기에 유저는 k8s에서 어떠한 관리도 하지 않습니다. 유저는 (다소 거창한 표현일 수도 있지만) ‘선언’ 만을 합니다.

k8s에서 유저는 A 도커 서버를 5개를 띄우겠다는 선언을 합니다. 그럼 k8s는 아무 것도 없던 기존 환경에서 부지런하게 서버에 A 도커 서버를 5개를 띄웁니다. 또한 k8s는 A 도커 서버들을 열심히 관찰하며, 하나의 서버가 다운되기라도 하면 (서버 수: 5개->4개) 다시 A 도커 서버를 띄워 5개의 서버가 실행되는 상태를 유지하기 위해 노력합니다.

위의 설명에서 나온 노력이 k8s의 핵심인데, k8s는 “현재 상태”가 우리가 “선언한 상태”와 동일하게 만들도록 계속해서 노력합니다.

여러분이 원하는 곳으로 끊임없이 항해해 나아갑니다!

k8s에서 모든 개념은 object라는 단위로 구성되어 있고, 우리의 ‘선언’역시 하나의 오브잭트 입니다.

k8s가 관리하는 도커 서버도 Pod 라는 object로 존재하고, 도커 서버를 5개를 유지하겠다는 선언은 ReplicaSet이라는 object로 존재합니다. 즉, ReplicaSet은 N개의 Pod를 k8s에 유지하기 위해 노력합니다.

어떻게 각 Pod의 주소를 찾고 해당 Pod에 트래픽을 분배할지는 Service, Ingress Object가 관리합니다. 아 물론 정확히 말하면 Service Object나 Ingress Object를 생성하면, k8s는 우리가 선언한 트래픽 분배를 구현하는 Load Balancer를 생성하기 위해 ‘노력’ 합니다.

그리고 각 Pod의 배포 버전을 관리해주는 Deployment Object (즉, 업데이트를 진행해 주는 Object), 서버에 들어오는 traffic에 맞춰 Pod 수를 유기적으로 늘리거나 줄여주는 HPA Object등이 존재합니다.

k8s는 매우 다양한 Object를 제공합니다. (심지어는 우리가 직접 만든 Custom Object를 생성 할 수도 있습니다) 또한 이런 다양한 Object를 통해 우리는 우리가 원하는 상태를 자세하게 정의 할 수 있고, k8s는 24시간 계속해서 우리가 원하는 상태(서버가 정상적으로 서비스 되는 상태)를 유지하기 위해 노력합니다.

실제로 k8s는 훨씬 복잡하고 더 설명해야 할 부분도 많지만 (Node object, object를 선언하는 yaml 파일, pod Labeling, pod의 cpu/memory 분배, Pod 배치 전략, Deployment 전략, k8s monitoring) 이번 글에서는 왜 k8s가 나왔는지, k8s가 무엇 인지를 중점으로 설명하기에 너무 복잡한 설명은 적지 않겠습니다.

다만, 많은 분들이 오해하시는 부분이기에, 한 가지만 추가로 설명을 하고 넘어가자면, k8s는 엔진이 아닌 플랫폼이고, k8s는 자체 엔진을 제공하는 것이 아닌, k8s의 플랫폼을 구현한 Provider들을 통해 k8s를 제공합니다. 자주 들어보셨을 amazon Elastic Kubernetes Service(EKS), Google Kubernetes (GKE), Azure Kubernetes (AKS) 등이 이에 속합니다.

물론 저 중에서 가장 뛰어난 건 네이버에서 제공하는 Kubernetes engine인 Naver Kubernetes Service인 건 다들 아실 거라 믿어 의심치 않기에 이 부분에 있어서도 너무 긴 설명은 하지 않겠습니다. 😉

형님들 다 잘 아실 거라고 믿어 ‘다시’ 한번 믿어 의심치 않습니다!

이 글은 쿠버네티스의 자세한 스팩 이나 사용 방법이 아닌, 다소 추상적일 수도 있는 기술 흐름에 대한 글이기에, 제 개인적인 주관이 많이 포함 되어 있을 수 있습니다.

예를 들어, 저는 베어메탈 -> VM -> Docker -> k8s 이 흐름과 각 단계에서 의 불편함이 다음 단계를 만들었다는 요지로 설명을 했지만, 제 지인은

‘모놀리틱 서버가 MicroService적인 구조로 바뀌는 추세에서 개발자들이 MicroService에 딱 맞는 Docker를 채택했기에 Docker가 유행하고 k8s가 도입되었다’

라는 관점을 가지고 있었습니다.

실제로 MSA (MicroService Architecture)는 Docker와 매우 잘 맞는 설계 구조이기에 저러한 관점 또한 일리가 있다고 생각합니다.

독자 분들의 다양한 관점과 피드백을 기대하며, 이번 글은 이만 마치겠습니다, 감사합니다!

--

--