구글의 실제 컨테이너 운영 모델 — Borg에서 쿠버네티스까지
2014년 구글의 엔지니어링 매니저 크레이그 맥러키(Craig McLuckie)와 조 베다(Joe Beda)가 쿠버네티스를 세상에 공개했을 때, 구글은 공식 블로그에 상징적인 선언을 남겼습니다:
“Everything at Google runs in containers.” (구글의 모든 것은 컨테이너 위에서 실행된다.)
도커가 2013년에 등장해 세상에 충격을 주기 훨씬 전부터, 구글은 이미 수십만 대의 서버 위에서 주당 수십억 개의 컨테이너를 띄우고 지우고 있었습니다. 검색 엔진, Gmail, 유튜브, 구글 맵스 등 구글의 모든 프로덕션 서비스는 보그(Borg) 라는 사내 전용 클러스터 관리 시스템 위에서 돌아가고 있었습니다.
쿠버네티스는 결코 책상머리에서 급조된 이상주의적 프로젝트가 아닙니다. 구글이 10년 이상 Borg를 운영하며 겪었던 수많은 대규모 장애와 운영 노하우, 그리고 실패한 아키텍처적 교훈(Lessons Learned)을 백지상태에서 완전히 새롭게 재설계한 오픈소스 결과물 이 바로 쿠버네티스입니다.
1. Borg 아키텍처: 쿠버네티스의 원형과 Jupiter 네트워크
Borg의 기본 구조를 보면 오늘날의 쿠버네티스 아키텍처와 소름 돋을 정도로 닮아 있습니다. 하지만 Borg 아키텍처를 진정으로 완성시킨 숨은 핵심은 소프트웨어 컨트롤 플레인 밑단을 받치고 있던 물리 하드웨어 네트워크, 즉 주피터(Jupiter) 패브릭 이었습니다.
flowchart TD
subgraph BorgMaster["🏛️ BorgMaster (컨트롤 플레인)"]
direction TB
subgraph MasterCore["인메모리 복제 & 리더십"]
Leader["👑 <b>BorgMaster (Leader)</b><br/>• 링크 상태 감시<br/>• 클라이언트 통신 수신"]
Paxos["💾 <b>Paxos 기반 인메모리 복제본</b><br/>• 5개 복제본 상태 합의<br/>• 수 분 단위 체크포인트 스냅샷"]
Leader <-->|"Paxos 합의 & 동기화"| Paxos
end
Scheduler["⚡ <b>Scheduler</b><br/>• 대기 작업 우선순위 큐 관리<br/>• 머신 채점(Scoring) 및 배치 결정"]
Leader --> Scheduler
end
subgraph Workers["🖥️ 노드 워커 계층 (수만 대의 서버)"]
Borglet["⚙️ <b>Borglet (노드 데몬)</b><br/>• Kubelet의 직계 원형<br/>• 프로세스 생명주기 제어<br/>• cgroups 자원 격리 & 헬스체크"]
end
subgraph Network["🌐 데이터센터 물리 네트워크 패브릭"]
Jupiter["🌊 <b>Jupiter Fabric</b><br/>• Clos 토폴로지 기반 1.3+ Pbps Bisection 대역폭<br/>• 데이터센터 전체를 거대한 '단일 스위치'로 가상화<br/>• Any-to-Any 무병목 최고속 통신"]
end
BorgMaster ==>|"Borg RPC (스케줄링 명령 및 상태 보고)"| Workers
Workers <===>|"초고속 패킷 전송 (위치 독립성 확보)"| Network
- BorgMaster: 쿠버네티스의
kube-apiserver+controller-manager에 해당합니다. 5개의 복제본으로 구성되며, 분산 합의 알고리즘(Paxos)을 통해 선출된 하나의 리더가 전체 클러스터 상태를 메모리에 유지합니다. - Scheduler: 작업을 대기 큐(Pending Queue)에 넣고, 우선순위가 높은 작업부터 가용 자원(CPU, RAM, 디스크 등)이 적합한 최적의 머신을 찾아 배치합니다.
- Borglet: 각 물리 서버 노드마다 실행되는 에이전트로, 쿠버네티스의
kubelet의 직계 조상입니다. 마스터로부터 지시를 받아 작업을 띄우고 헬스체크를 수행합니다. - Jupiter Network (하부 물리 네트워크 계층): Borg 아키텍처의 필수적인 하부 토대입니다. 데이터센터 내 수만 대의 서버 노드들을 묶어주는 구글 독자 개발의 초고속 SDN(소프트웨어 정의 네트워크) 패브릭입니다.
Jupiter 네트워크가 Borg 스케줄러를 해방시킨 방법
전통적인 데이터센터는 랙(Rack)과 스위치가 계층적 수목(Tree) 구조로 연결되어 있었습니다. 랙 내부 통신 속도는 빠르지만 다른 랙이나 스위치 너머로 통신할 때는 심각한 대역폭 병목(Oversubscription)이 발생했습니다. 따라서 전통적인 분산 스케줄러는 *“서로 빈번히 통신해야 하는 서비스들은 반드시 같은 랙이나 인접한 노드에 묶어서 배치해야 한다(Network Locality)“*는 무거운 제약 조건에 얽매여 있었습니다.
구글의 Jupiter 패브릭 은 Clos 네트워크(Fat-Tree) 토폴로지를 기반으로 단면 대역폭(Bisection Bandwidth) 1.3+ Pbps(초당 1,300테라비트) 를 제공하여 데이터센터 전체를 하나의 거대한 단일 스위치처럼 평평하게(Flat) 만들었습니다:
- 위치 독립성(Locality-free): 클러스터 내 1번 서버와 10,000번 서버 간의 통신 속도가 바로 옆 서버와 통신하는 속도와 완전히 동일합니다.
- 스케줄러의 자유: 네트워크 대역폭 병목이 사라지자, Borg 스케줄러는 “네트워크 위치(Rack)를 전혀 고려할 필요 없이, 오직 서버의 남는 CPU와 메모리 공간만 보고 컨테이너를 어디에든 자유롭게 쪼개어 배치” 할 수 있게 되었습니다.
Borg가 클러스터 전체 자원 사용률을 70–80%라는 경이적인 수준까지 끌어올릴 수 있었던 숨은 비결이 바로 이 Jupiter 네트워크의 뒷받침 덕분이었습니다.
2. 구글 인프라의 비밀: 프로덕션과 배치의 기적 같은 동거
구글처럼 수십만 대의 서버를 굴리는 기업에게 가장 무서운 적은 ‘놀고 있는 서버(낮은 자원 사용률)‘ 입니다.
대부분의 웹 서비스는 피크 타임(낮 시간대)과 한가한 시간대(새벽)의 트래픽 차이가 큽니다. 만약 피크 시간에 맞춰 서버를 독점 할당해 두면, 밤에는 데이터센터 자원의 70–80%가 아무 일도 하지 않고 전기만 먹게 됩니다.
구글은 이 문제를 해결하기 위해 데이터센터를 서비스별로 쪼개지 않고, 모든 서버를 하나의 거대한 풀로 합친 뒤 두 종류의 워크로드를 한 머신에 섞어서(Colocate) 돌렸습니다:
- Prod (Production) 작업:
- 검색, Gmail, 웹 서빙 등 사용자의 실시간 요청에 응답하는 서비스입니다.
- 절대 죽거나 지연(Latency)이 발생하면 안 되므로 최우선순위가 부여되고 CPU/메모리가 안정적으로 보장됩니다.
- Non-Prod (Batch / Best-Effort) 작업:
- 웹 인덱싱, 로그 분석, 머신러닝 학습, 비디오 트랜스코딩 같은 비실시간 배치 작업입니다.
- 지연되어도 상관없고 언제든 재시작 가능한 작업이므로, 여유 자원이 있을 때만 틈새로 끼어들어 실행됩니다.
오버커밋(Overcommit)과 무자비한 퇴거(Eviction)
개발자들은 항상 장애가 두려워 실제 필요한 것보다 2–3배 많은 CPU와 메모리를 요청(Resource Request)합니다.
Borg는 개발자의 말을 곧이곧대로 믿지 않습니다.
- 실제 사용량 추정: Borg는 머신러닝 및 통계 모델을 이용해 해당 Prod 서비스가 실제로 소비하는 자원을 실시간으로 측정합니다.
- 잉여 자원 판매: “이 Prod 컨테이너가 16코어를 요청했지만 실제론 4코어만 쓰는군. 남는 12코어 분량에 배치(Non-prod) 컨테이너 5개를 끼워 넣자!”
- 자원 충돌 시 무자비한 킬(Kill): 만약 낮 12시가 되어 Prod 서비스의 트래픽이 치솟으면, Borglet은 망설임 없이 같은 서버에서 돌던 배치 컨테이너를 강제 종료(Evict)하고 Prod 컨테이너에게 자원을 온전히 돌려줍니다.
죽은 배치 작업은 클러스터 어딘가의 또 다른 한가한 머신으로 튕겨 나가 처음부터 다시 돌거나 이어서 실행됩니다.
이 정교한 오버커밋 덕분에 구글은 수조 원에 달하는 데이터센터 서버 증설 비용을 절감 할 수 있었습니다. 이것이 바로 쿠버네티스의 QoS 클래스(Guaranteed, Burstable, BestEffort)와 Pod Eviction 메커니즘의 뿌리입니다.
3. Borg의 네트워크 관리: 포트 할당의 악몽과 대역폭 제어(BwE)
CPU와 메모리 스케줄링 못지않게 구글 엔지니어들을 끊임없이 괴롭혔던 영역이 바로 네트워크 관리 였습니다.
호스트 IP 공유와 포트 할당 지옥
Borg가 처음 설계되던 2000년대 초반에는 리눅스의 네트워크 네임스페이스 기술이 미성숙했습니다. 그 결과 Borg는 물리 머신 1대당 단 1개의 IP 주소만 부여하고, 그 머신에서 실행되는 모든 컨테이너가 호스트의 IP를 함께 공유 하는 방식을 채택했습니다.
이 단순한 타협은 엄청난 기술적 부채로 이어졌습니다:
- 동적 포트 할당과 포트 고갈: 동일한 머신에 띄워진 웹 서버들이 80이나 443 같은 표준 포트를 함께 바인딩할 수 없었습니다. 따라서 Borg 스케줄러는 컨테이너를 배치할 때마다 비어 있는 임의의 포트(예: 32145, 48921)를 동적으로 할당해 주어야 했습니다.
- 애플리케이션 코드 오염: 모든 구글 내부 서비스는 자신이 몇 번 포트에서 수신 대기(Listen)해야 하는지 명령행 플래그나 환경변수로 전달받아 코드 레벨에서 동적으로 바인딩하도록 강제되었습니다.
- BNS(Borg Name Service)의 필수화: 특정 서비스가 “어느 머신의 몇 번 포트”에서 돌고 있는지 찾기 위해 구글은 BNS 라는 거대한 디렉터리 서비스를 개발했습니다. 작업이 뜰 때마다
/bns/<클러스터>/<사용자>/<작업명>/<인덱스>형식의 RPC 주소를 실시간으로 등록하고 갱신해야 했습니다. - 보안 격리 및 방화벽 한계: 하나의 IP 안에 완전히 다른 보안 등급의 서비스들이 뒤섞여 있었기 때문에, IP 기반의 전통적인 네트워크 방화벽(ACL)이나 포트 기반 라우팅 규칙이 무력화되었습니다.
Jupiter 패브릭과 네트워크 대역폭 격리: BwE (Bandwidth Enforcer)
구글 데이터센터는 수만 대의 서버가 페타비트급 속도로 병목 없이 통신할 수 있는 세계 최고 수준의 네트워크 패브릭인 주피터(Jupiter) 네트워크를 자랑합니다.
하지만 아무리 백본망의 대역폭이 넓어도, 단일 호스트 내부에서 Prod 작업과 Batch 작업이 동일한 네트워크 인터페이스 카드(NIC)를 공유할 때 발생하는 간섭 은 매우 심각한 문제였습니다:
- 검색이나 유튜브 같은 실시간 Prod 서비스가 사용자 응답을 처리하는 와중에, 같은 서버에서 돌던 초대형 MapReduce나 인덱싱 배치 작업이 대용량 데이터를 네트워크로 뿜어내기 시작하면 네트워크 버퍼가 포화되어 지연 시간(Tail Latency)이 수십 배로 튀었습니다.
- CPU나 메모리는 리눅스 cgroups로 상한선을 묶어둘 수 있었지만, 당시 리눅스 커널의 네트워크 송수신 대역폭 제어는 초보적인 수준이었습니다.
구글은 이를 해결하기 위해 중앙 집중식 대역폭 할당 시스템인 BwE (Bandwidth Enforcer) 를 구축했습니다:
- 패킷 단위 우선순위 분류: 모든 네트워크 패킷에 우선순위 등급(Prod vs Non-prod)을 태깅합니다.
- 동적 대역폭 스로틀링(Throttling): 스위치나 호스트 NIC에 혼잡(Congestion)이 감지되면 중앙 제어기가 배치 작업 컨테이너들의 네트워크 송신 속도를 즉시 강제로 쥐어짜 내립니다. 이를 통해 실시간 서비스의 네트워크 패킷이 지연 없이 최우선 통과되도록 보장했습니다.
4. Borg에서 쿠버네티스로: 15년의 반성과 재설계
구글은 Borg의 후속작으로 트랜잭션 기반 저장소를 도입한 오메가(Omega) 를 연구했고, 이 두 시스템을 통해 배운 뼈아픈 교훈들을 모아 쿠버네티스를 완전히 새로 설계했습니다.
구글 엔지니어들이 2015년 ACM에 발표한 유명한 논문(“Large-scale cluster management at Google with Borg”)에서 밝힌 핵심 반성과 개선점은 다음과 같습니다:
① 컨테이너 1개보다 ‘파드(Pod)‘가 옳았다
초기 Borg에는 오직 단일 태스크(컨테이너) 개념만 있었습니다. 하지만 실제로는 메인 프로세스 옆에 로그를 수집하거나 메트릭을 긁어가는 보조 프로세스들이 반드시 붙어야 했습니다. 엔지니어들은 억지로 여러 프로세스를 하나의 셸 스크립트로 묶어 띄우는 등 변칙적인 방법을 썼습니다. 이 실수를 바로잡기 위해 쿠버네티스는 처음부터 ‘네트워크와 볼륨을 공유하는 컨테이너들의 논리적 그룹’인 파드(Pod) 를 가장 작은 배포 단위로 못 박았습니다.
② 선언적 설정(BCL)의 악몽과 극복
Borg는 설정을 정의하기 위해 BCL(Borg Configuration Language)이라는 복잡한 튜링 완전 DSL을 사용했습니다. BCL 파일이 수천 줄의 상속과 조건문으로 얽히면서, 나중에는 이 코드가 어떤 결과를 만들어낼지 작성자 본인도 예측할 수 없는 지경에 이르렀습니다. 쿠버네티스는 이 교훈을 통해 단순하고 선언적인 순수 데이터 포맷(JSON / YAML )과 API 서버를 채택했습니다.
③ Borg의 최대 실패작을 바로잡다: ‘IP-per-Pod’ 네트워크 모델
Borg의 엔지니어들은 훗날 입을 모아 “Borg의 가장 치명적인 설계 실수는 모든 태스크에 고유 IP를 주지 않고 머신의 포트를 쪼개 쓰게 한 것” 이라고 고백했습니다.
쿠버네티스를 설계할 때 구글 엔지니어들은 이 실수를 완전히 뜯어고쳤습니다:
- 모든 파드에 고유 IP 부여: 쿠버네티스 클러스터에서는 “모든 파드가 NAT 없이 클러스터 내부에서 직접 통신 가능한 저마다의 고유 IP를 가진다(IP-per-Pod)“ 는 원칙을 기본 규약으로 선언했습니다.
- 단순하고 깔끔해진 개발자 경험: 더 이상 포트 충돌이나 동적 포트 바인딩을 고민할 필요 없이 누구나 80/443 표준 포트를 마음껏 쓸 수 있게 되었고, 서비스 디스커버리 역시 깔끔한 DNS 조회로 표준화되었습니다.
- CNI(Container Network Interface) 표준의 탄생: 이 엄격한 네트워크 철학은 훗날 Calico, Cilium, Flannel 등 다양한 클러스터 네트워크 플러그인(CNI) 생태계가 번영하는 기반이 되었습니다.
5. 정리: 빅테크의 경험이 인류의 표준이 되다
구글의 Borg는 클라우드 컴퓨팅 역사상 가장 위대한 분산 시스템 중 하나입니다.
- 하드웨어 장애를 예외가 아닌 ‘일상(Normal State)‘ 으로 취급하고,
- 중앙 집중식 통제 대신 선언적 상태 유지와 자가 치유 를 도입했으며,
- Prod와 Batch의 동거를 통해 자원 효율을 극한으로 끌어올렸습니다.
그리고 구글은 자신들의 사내 독점 기술이었던 이 철학을 쿠버네티스라는 이름으로 오픈소스 생태계에 아낌없이 풀어주었습니다. 덕분에 오늘날 우리는 구글 본사에 입사하지 않고도, 구글이 15년간 천문학적인 비용을 치르며 터득한 클라우드 인프라의 정수를 무료로 누리고 있습니다.