나는 오랫동안 Spring Boot 를 jar 로 말아서 서버에 올리는 방식으로 일했다. 배포는 "서버에 접속해서 기존 프로세스를 죽이고 새 jar 를 띄운다"였고, 그걸로 충분했다. 그러다 쿠버네티스 위에서 도는 서비스를 맡게 됐는데, ArgoCD 화면은 매일 보면서도 그 안에서 무슨 일이 벌어지는지는 몰랐다. Pod 가 왜 이름이 매번 바뀌는지, 왜 서...
나는 오랫동안 Spring Boot 를 jar 로 말아서 서버에 올리는 방식으로 일했다. 배포는 "서버에 접속해서 기존 프로세스를 죽이고 새 jar 를 띄운다"였고, 그걸로 충분했다. 그러다 쿠버네티스 위에서 도는 서비스를 맡게 됐는데, ArgoCD 화면은 매일 보면서도 그 안에서 무슨 일이 벌어지는지는 몰랐다. Pod 가 왜 이름이 매번 바뀌는지, 왜 서버에 SSH 로 들어갈 생각을 안 하는지.
이 시리즈는 그때부터 하나씩 밟아나간 기록이다. 첫 글에서 답할 질문은 하나다 — jar 를 서버에 올리는 방식은 정확히 어디서 무너지고, 쿠버네티스는 그중 무엇을 대신해 주는가.
쿠버네티스 얘기를 하려면 컨테이너부터다. 컨테이너는 애플리케이션을 어디서 돌리든 똑같이 돌도록 만든 격리된 실행 환경이다.
jar 배포에서 겪던 문제를 떠올리면 필요성이 바로 보인다. 로컬은 JDK 17, 개발 서버는 11 이 깔려 있어서 안 뜬다. 서버마다 타임존이 다르다. 누군가 서버에 직접 설치한 라이브러리에 의존하고 있었는데 그 서버를 재구축하니 안 뜬다. 전부 실행 환경이 코드 밖에 있어서 생기는 문제다.
컨테이너는 실행 환경을 코드와 같이 묶어서 이 문제를 없앤다.
eclipse-temurin:21-jre 위에 app.jar 를 얹은 것이 하나의 이미지다.VM 과 헷갈리기 쉬운데, 결정적 차이는 OS 커널을 새로 띄우느냐다. VM 은 게스트 OS 를 통째로 부팅하므로 수십 초가 걸리고 메모리도 GB 단위로 먹는다. 컨테이너는 호스트 커널을 공유하고 프로세스만 격리하므로 수 초 안에 뜨고 오버헤드가 훨씬 작다. 배포 한 번에 컨테이너 수십 개를 새로 띄우는 롤링 업데이트가 현실적인 건 이 가벼움 덕분이다.
컨테이너로 실행 환경 문제는 풀렸다. 그런데 서비스가 커지면 도커 단독으로는 감당이 안 되는 지점이 순서대로 나타난다.
| 막히는 지점 | 도커 단독 | 쿠버네티스가 대신하는 것 |
|---|---|---|
| 컨테이너가 죽음 | 사람이 알아채고 다시 띄움 | 죽으면 자동으로 새로 띄움 |
| 트래픽 증가 | 사람이 서버에 들어가 추가 실행 | replica 수를 늘리면 알아서 배치 |
| 무중단 배포 | 스크립트로 직접 구현 | 롤링 업데이트가 기본 기능 |
| 서버 여러 대 분산 | 어느 서버에 뭘 띄울지 사람이 관리 | 스케줄러가 여유 있는 노드에 배치 |
| 컨테이너 주소 찾기 | IP·포트를 사람이 관리 | Service 이름으로 호출 |
표를 세로로 읽으면 공통점이 보인다. 왼쪽 열은 전부 사람이 알아채고 사람이 조치하는 일이고, 오른쪽 열은 전부 시스템이 알아채고 시스템이 조치하는 일이다. 쿠버네티스가 파는 것은 기능 목록이 아니라 이 전환이다.
그래서 쿠버네티스를 한 문장으로 정의하면 이렇게 된다 — 컨테이너를 자동으로 배치하고, 유지하고, 스스로 복구하는 오케스트레이션 시스템.
쿠버네티스는 서버 여러 대를 하나의 자원 풀로 묶는다. 그 묶음이 클러스터(Cluster) 고, 안에는 역할이 다른 두 종류의 서버가 있다.
각 조각을 백엔드 개념에 붙여 보면 이해가 빠르다.
kubectl 도, 각 노드의 kubelet 도, 컨트롤러도 전부 여기를 통해서만 대화한다. 컴포넌트끼리 직접 연결되지 않고 API 서버 하나를 허브로 두는 구조라, 인증·권한·검증을 한 곳에서 건다.여기서 감각이 하나 바뀐다. 내가 특정 서버를 지목해서 앱을 올리는 게 아니다. 나는 "이 앱을 2개 띄워둬"라고 API 서버에 말할 뿐이고, 어느 노드에 놓을지는 스케줄러가 정한다. 그래서 Pod 가 어느 서버에 떠 있는지 평소에 신경 쓰지 않고, SSH 로 들어갈 생각도 하지 않게 된다.
정리하면 쿠버네티스로 넘어오면서 바뀌는 건 도구가 아니라 배포라는 행위의 정의다.
이 차이 때문에 장애 대응 방식도 달라진다. Pod 가 죽었을 때 되살리는 게 아니라 폐기하고 새로 만든다. 그래서 Pod 안에 중요한 걸 쌓아두면 안 되고, 로그도 파일로 남기지 않고 표준 출력으로 내보내 외부 수집기로 보낸다. 서버를 "고쳐 쓰는 가축이 아니라 갈아 끼우는 부품"으로 보는 전제가 깔려 있다.
도입하면 다 되는 것처럼 보이지만, 실제로 겪어보니 그렇지 않았다. 미리 알아두면 좋은 한계가 있다.
앞으로의 순서는 이렇다. 아래로 갈수록 구체적이다.
다음 글에서는 지금까지 계속 미뤄 둔 것 — "원하는 상태를 선언하면 시스템이 맞춘다"가 내부적으로 어떤 구조인지 — 를 본다. 이 원리 하나를 잡아두면 뒤의 Deployment · admission · ArgoCD 가 전부 같은 패턴의 변주로 읽힌다.