8.2.1 서비스의 필요성
앞서 8.1장에서 우리는 쿠버네티스 네트워킹의 기본 원칙 중 하나로 “모든 파드는 고유한 IP 주소를 가진다”는 점을 배웠습니다. 이는 각 파드가 네트워크 상에서 독립적으로 식별되고 통신할 수 있는 기반을 마련해 주지만, 동시에 한 가지 중요한 문제점을 안고 있습니다. 바로 파드 IP 주소의 비영속성(ephemeral nature)입니다. 즉, 파드에 할당된 IP 주소는 영구적이지 않고, 파드의 생명주기에 따라 언제든지 변경될 수 있다는 의미입니다.
8.2.1.1 IP 기반에서 서비스 기반으로 변경되어야 하는 이유는
IT 환경이 IP 주소 기반에서 서비스 기반으로 반드시 변경되어야 하는 이유를 설명드리겠습니다. 이는 단순히 기술적인 트렌드를 넘어, 현대 애플리케이션의 복잡성과 동적인 클라우드 네이티브 환경에 대응하기 위한 필연적인 진화 과정이라고 할 수 있습니다.
전통적인 IT 환경에서는 특정 기능을 제공하는 서버나 애플리케이션에 접근하기 위해 해당 서버의 고정된 IP 주소를 사용하는 것이 일반적이었습니다. 마치 특정 가게를 찾아갈 때 그 가게의 물리적인 주소를 알고 찾아가는 것과 같았습니다. 하지만 이러한 IP 기반 접근 방식은 다음과 같은 여러 가지 한계와 문제점을 드러내기 시작했습니다.
1. IP 주소의 비영속성 및 동적 변화:
- 클라우드 환경의 역동성: 클라우드 환경에서는 가상 머신이나 컨테이너(파드)가 필요에 따라 동적으로 생성되고 삭제되며, 장애 발생 시 자동으로 다른 위치에 재생성될 수 있습니다. 이때마다 IP 주소는 변경될 가능성이 매우 높습니다. 만약 클라이언트가 특정 IP 주소에 의존하여 통신한다면, 이러한 변화에 매우 취약해져 서비스 중단으로 이어질 수 있습니다.
- 오토 스케일링: 트래픽 변화에 따라 애플리케이션 인스턴스의 수가 자동으로 늘어나거나 줄어드는 오토 스케일링 환경에서는, 새로운 인스턴스가 생성될 때마다 새로운 IP 주소가 할당됩니다. 이 모든 IP 주소를 실시간으로 추적하고 관리하는 것은 거의 불가능에 가깝습니다.
- 블루/그린 배포, 카나리 배포: 새로운 버전의 애플리케이션을 배포할 때, 기존 버전과 새로운 버전이 동시에 실행되면서 점진적으로 트래픽을 전환하는 고급 배포 전략에서는 IP 주소 기반으로는 이러한 유연한 트래픽 관리가 어렵습니다.
2. 로드 밸런싱 및 서비스 디스커버리의 복잡성:
- 수동 로드 밸런싱: 여러 대의 서버로 서비스를 확장했을 경우, 클라이언트는 어떤 서버의 IP 주소로 요청을 보내야 할지 결정해야 합니다. 이는 클라이언트 측에 복잡한 로드 밸런싱 로직을 구현하거나, 별도의 하드웨어/소프트웨어 로드 밸런서를 수동으로 구성하고 관리해야 하는 부담을 야기합니다.
- 서비스 디스커버리의 어려움: 새로운 서비스가 추가되거나 기존 서비스의 위치(IP 주소)가 변경되었을 때, 다른 서비스들이 이를 자동으로 인지하고 통신하기 어렵습니다. DNS를 수동으로 업데이트하거나, 설정 파일을 계속 변경해야 하는 등 운영 부담이 큽니다.
3. 인프라 의존성 증가 및 이식성 저하:
- 특정 IP에 대한 강한 결합: 애플리케이션 코드나 설정 파일에 특정 IP 주소가 하드코딩되어 있는 경우, 인프라 환경이 변경되거나 다른 데이터센터, 다른 클라우드로 이전할 때마다 해당 IP 주소를 모두 수정해야 하는 번거로움이 발생합니다. 이는 애플리케이션의 이식성을 크게 저해합니다.
- 네트워크 구성 변경의 어려움: 네트워크 토폴로지나 IP 주소 체계가 변경될 경우, IP 주소에 의존하는 모든 시스템에 영향을 미치므로 변경 작업이 매우 어렵고 위험해집니다.
4. 마이크로서비스 아키텍처와의 부조화:
- 다수의 동적 서비스: 마이크로서비스 아키텍처에서는 수십, 수백 개의 작은 서비스들이 독립적으로 배포되고 확장되며, 서로 복잡하게 통신합니다. 각 서비스의 모든 인스턴스 IP를 추적하고 관리하는 것은 IP 기반 방식으로는 거의 불가능하며, 서비스 간의 결합도(coupling)를 높여 민첩성을 떨어뜨립니다.
이러한 문제점들을 해결하고, 현대적인 IT 환경의 요구사항을 충족시키기 위해 서비스 기반 아키텍처로의 전환이 필연적입니다. 서비스 기반 접근 방식은 다음과 같은 핵심적인 이점을 제공합니다.
- 추상화 및 위치 투명성: 서비스는 실제 애플리케이션 인스턴스(서버, 파드)의 물리적인 위치(IP 주소, 포트)를 추상화합니다. 클라이언트는 구체적인 IP 주소 대신 논리적인 서비스 이름(예: order-service, user-database)을 통해 서비스에 접근합니다. 서비스의 실제 구현체가 어디서 실행되든, 몇 개가 실행되든 클라이언트는 알 필요가 없습니다. 이는 마치 우리가 특정 웹사이트에 접속할 때 그 웹사이트의 서버 IP 주소를 몰라도 도메인 이름만 알면 되는 것과 같습니다.
- 안정적인 엔드포인트 제공: 서비스는 자신만의 고유하고 안정적인 주소(예: 쿠버네티스의 클러스터 IP 또는 DNS 이름)를 제공합니다. 백엔드 인스턴스의 IP 주소가 변경되거나 개수가 변하더라도, 서비스의 주소는 그대로 유지되므로 클라이언트는 항상 동일한 주소로 서비스에 접근할 수 있습니다.
- 자동화된 서비스 디스커버리: 서비스 레지스트리(예: 쿠버네티스 DNS, Consul, Eureka)와 통합되어, 서비스들은 자신의 위치 정보를 동적으로 등록하고 다른 서비스들은 필요한 서비스를 이름으로 쉽게 찾아 연결할 수 있습니다. 이는 수동 설정의 오류를 줄이고 시스템의 동적인 변화에 빠르게 적응할 수 있도록 합니다.
- 내장된 로드 밸런싱: 서비스는 자신에게 연결된 여러 백엔드 인스턴스들에게 들어오는 요청을 자동으로 분산시키는 로드 밸런싱 기능을 기본적으로 제공하거나 쉽게 통합할 수 있습니다. 이를 통해 특정 인스턴스에 부하가 집중되는 것을 방지하고 서비스의 가용성과 성능을 향상시킵니다.
- 유연성 및 확장성 향상: 서비스 기반 아키텍처는 애플리케이션의 개별 구성 요소를 독립적으로 확장하거나 업데이트하기 용이하게 만듭니다. 새로운 버전의 서비스를 배포하거나, 특정 서비스의 인스턴스 수를 늘리더라도 다른 서비스에 미치는 영향을 최소화할 수 있습니다.
- 인프라 독립성 및 이식성 증대: 애플리케이션이 논리적인 서비스 이름을 통해 통신하므로, 기본 인프라(온프레미스, 특정 클라우드)가 변경되더라도 애플리케이션 코드나 설정을 크게 변경할 필요 없이 서비스를 이전하거나 확장할 수 있습니다.
결론적으로, IT 환경이 IP 기반에서 서비스 기반으로 변경되어야 하는 이유는 현대 애플리케이션의 동적인 특성, 분산 시스템의 복잡성, 그리고 빠른 변화에 대한 민첩한 대응 요구 때문입니다. IP 주소라는 물리적이고 구체적인 식별자에 의존하는 방식은 더 이상 이러한 요구사항을 감당하기 어렵습니다. 반면, 서비스라는 논리적이고 추상화된 접근 방식은 시스템의 복잡성을 효과적으로 관리하고, 변화에 유연하게 적응하며, 개발과 운영의 효율성을 극대화할 수 있는 강력한 기반을 제공합니다. 쿠버네티스가 ‘서비스’라는 개념을 네트워킹의 핵심 요소로 채택한 것도 바로 이러한 이유 때문이며, 이는 클라우드 네이티브 시대로 나아가는 데 있어 필수적인 패러다임 전환이라고 할 수 있습니다.
8.2.1.2 파드 IP의 비영속성 문제 해결
여러분이 프론트엔드 웹 애플리케이션을 개발했고, 이 프론트엔드 애플리케이션은 백엔드 API 서비스를 호출하여 데이터를 가져와야 합니다. 백엔드 API 서비스는 여러 개의 파드로 구성되어 확장성과 안정성을 확보하고 있다고 가정해 보겠습니다. 만약 프론트엔드 애플리케이션이 백엔드 API 파드들의 실제 IP 주소를 직접 사용하여 통신하려고 한다면 어떤 문제들이 발생할 수 있을까요?
- 파드 재생성 시 IP 변경: 백엔드 API 파드 중 하나에 문제가 생겨 쿠버네티스에 의해 자동으로 재시작되거나, 디플로이먼트에 의해 새로운 버전으로 롤링 업데이트되는 경우, 새로 생성된 파드는 이전 파드와 다른 새로운 IP 주소를 할당받을 가능성이 매우 높습니다. 프론트엔드 애플리케이션이 이전에 알고 있던 IP 주소는 더 이상 유효하지 않게 되므로, 통신에 실패하게 됩니다.
- 스케일 아웃/인 시 IP 목록 관리의 어려움: 백엔드 API 서비스에 대한 부하가 증가하여 파드의 수를 늘리거나(스케일 아웃), 부하가 줄어들어 파드의 수를 줄이는(스케일 인) 경우, 프론트엔드 애플리케이션은 실시간으로 변경되는 백엔드 파드들의 IP 주소 목록을 계속해서 추적하고 업데이트해야 합니다. 이는 매우 복잡하고 오류가 발생하기 쉬운 작업입니다.
- 로드 밸런싱의 부재: 프론트엔드 애플리케이션이 여러 백엔드 파드 중 어떤 파드에 요청을 보내야 할지 직접 결정해야 합니다. 이는 클라이언트 측에 복잡한 로드 밸런싱 로직을 구현해야 함을 의미하며, 특정 파드에 요청이 몰리거나 장애가 발생한 파드에 계속해서 요청을 보내는 등의 문제가 발생할 수 있습니다.
- 노드 장애 시 IP 유실: 특정 노드에 장애가 발생하여 해당 노드에서 실행되던 백엔드 파드들이 다른 건강한 노드로 옮겨져 재스케줄링되면, 이 파드들은 당연히 새로운 IP 주소를 할당받게 됩니다. 기존 IP 주소는 더 이상 접근 불가능하게 됩니다.
이처럼 파드 IP 주소의 비영속성은 애플리케이션 간의 안정적이고 지속적인 통신을 어렵게 만듭니다. 파드들은 언제든지 나타나고 사라질 수 있는 ‘가축(cattle)’과 같이 취급되도록 설계되었기 때문에, 특정 파드의 IP 주소에 직접 의존하는 것은 클라우드 네이티브 환경의 동적인 특성과 맞지 않습니다.
바로 이러한 파드 IP 주소의 비영속성 문제를 해결하고, 변화무쌍한 파드 집합에 대해 안정적이고 예측 가능한 단일 접근 지점(single, stable point of access)을 제공하기 위해 쿠버네티스는 서비스(Service)라는 강력한 추상화 계층을 도입했습니다.
서비스는 다음과 같은 방식으로 이 문제를 해결합니다.
- 안정적인 가상 IP 주소 (클러스터 IP) 제공: 서비스는 생성될 때 자신만의 고유하고 안정적인 가상 IP 주소(이를 클러스터 IP라고 합니다)와 DNS 이름을 할당받습니다. 이 클러스터 IP는 서비스가 존재하는 동안에는 변경되지 않습니다. 클라이언트 애플리케이션은 더 이상 개별 파드의 IP 주소를 알 필요 없이, 이 서비스의 클러스터 IP(또는 DNS 이름)와 서비스 포트만을 사용하여 요청을 보낼 수 있습니다.
- 파드 집합에 대한 프록시 및 로드 밸런싱: 서비스는 레이블 셀렉터(Label Selector)를 사용하여 자신이 담당해야 할 백엔드 파드들의 그룹을 동적으로 식별하고 추적합니다. 서비스로 들어온 요청은 해당 서비스에 연결된 건강한(ready) 파드들 중 하나로 자동으로 전달(프록시)되며, 이 과정에서 기본적인 로드 밸런싱(보통 라운드 로빈 방식)이 수행됩니다. 따라서 클라이언트는 백엔드 파드들의 실제 개수나 IP 주소 변화에 대해 전혀 신경 쓸 필요가 없습니다.
- 서비스 디스커버리 지원: 쿠버네티스는 클러스터 내부 DNS 시스템(예: CoreDNS)과 서비스를 긴밀하게 통합합니다. 서비스가 생성되면 해당 서비스의 이름으로 DNS 레코드가 자동으로 등록되어, 다른 파드들이 서비스 이름을 통해 클러스터 IP를 쉽게 조회하고 접근할 수 있도록 합니다. (예: my-backend-service.my-namespace.svc.cluster.local)
결론적으로, 서비스는 변화하는 파드들의 실제 위치와 신원을 감추고, 그 위에 안정적이고 논리적인 서비스 엔드포인트를 제공하는 ‘추상화 계층’이라고 할 수 있습니다. 이는 마치 우리가 특정 회사의 고객센터에 전화할 때, 실제 상담원의 이름이나 직통 번호를 알 필요 없이 대표 전화번호 하나만 알고 있으면 항상 연결될 수 있는 것과 유사합니다. 대표 전화번호(서비스의 클러스터 IP)는 변하지 않지만, 그 뒤에서 실제로 전화를 받는 상담원(파드)은 여러 명일 수도 있고, 교대 근무를 할 수도 있으며, 때로는 자리를 비울 수도 있습니다. 하지만 고객(클라이언트) 입장에서는 항상 동일한 대표 번호로 일관된 서비스를 제공받는 것처럼 느끼게 됩니다.
이러한 서비스의 역할 덕분에, 쿠버네티스 환경에서 마이크로서비스 아키텍처를 구축하고 운영하는 것이 훨씬 더 용이해집니다. 각 마이크로서비스는 자신만의 안정적인 서비스 엔드포인트를 가질 수 있고, 다른 서비스들은 이 엔드포인트를 통해 서로 느슨하게 결합(loosely coupled)되어 통신할 수 있게 됩니다. 이는 시스템 전체의 유연성, 확장성, 그리고 회복탄력성을 크게 향상시키는 핵심적인 요소입니다.