8.2 서비스 (Service): 파드 집합에 대한 안정적인 엔드포인트 제공
앞서 8.1장에서 우리는 쿠버네티스 네트워킹의 기본 원칙, 특히 “모든 파드는 고유한 IP를 가진다”는 중요한 개념을 배웠습니다. 하지만 이 파드 IP 주소는 파드가 재시작되거나 다른 노드로 옮겨갈 때마다 변경될 수 있는 비영속적인(ephemeral) 특성을 가지고 있습니다. 만약 우리가 특정 기능을 제공하는 애플리케이션 파드 그룹에 지속적으로 접근해야 한다면, 이렇게 수시로 변하는 파드 IP를 직접 추적하고 관리하는 것은 거의 불가능에 가깝습니다. 마치 단골 식당의 전화번호가 매일 바뀐다면 우리가 그 식당에 예약 전화를 걸기 매우 어려운 것과 마찬가지입니다.
바로 이러한 파드 IP의 비영속성 문제를 해결하고, 논리적으로 동일한 기능을 수행하는 파드 집합에 대해 안정적이고 고정된 접근 지점(엔드포인트)을 제공하기 위해 쿠버네티스가 제공하는 핵심적인 추상화 계층이 바로 **서비스(Service)**입니다. 서비스는 클라이언트(다른 파드 또는 외부 사용자)가 실제 백엔드 파드들의 IP 주소나 개수 변화에 대해 신경 쓸 필요 없이, 마치 단일하고 변하지 않는 대상과 통신하는 것처럼 느끼게 해줍니다. 또한, 서비스는 여러 개의 백엔드 파드들에게 들어오는 요청을 분산시키는 기본적인 로드 밸런싱 기능도 함께 제공합니다.
이번 8.2장에서는 쿠버네티스 애플리케이션 간의 통신과 외부 노출의 핵심이라고 할 수 있는 서비스에 대해 깊이 있게 탐구할 것입니다.
- 먼저 8.2.1 서비스의 필요성 (파드 IP의 비영속성 문제 해결)에서는 왜 우리가 파드 IP를 직접 사용하는 대신 서비스를 사용해야 하는지, 파드의 동적인 생명주기가 애플리케이션 간 통신에 어떤 어려움을 주는지 구체적인 예를 통해 살펴볼 것입니다. 이를 통해 서비스라는 추상화 계층이 등장하게 된 근본적인 이유와 그 가치를 명확히 이해하게 될 것입니다.
- 다음으로 8.2.2 서비스 타입 (Type)에서는 서비스가 클러스터 내부 및 외부로 노출되는 방식에 따라 어떤 종류들이 있는지 알아볼 것입니다. 클러스터 내부에서만 사용되는 기본적인 ClusterIP 타입, 각 노드의 특정 포트를 통해 서비스를 노출하는 NodePort 타입, 클라우드 제공업체의 외부 로드밸런서를 자동으로 프로비저닝하여 서비스를 외부 인터넷에 노출하는 LoadBalancer 타입, 그리고 외부의 다른 서비스를 현재 클러스터 내의 DNS 이름으로 참조할 수 있게 해주는 ExternalName 타입까지, 각 서비스 타입의 특징과 사용 사례를 자세히 비교 분석합니다.
- 이어서 8.2.3 서비스와 셀렉터 (Selector)에서는 서비스가 어떻게 자신과 연결될 파드들을 찾아내고 관리하는지 그 핵심 메커니즘인 레이블과 셀렉터에 대해 다시 한번 강조하며 살펴볼 것입니다. 서비스 명세에 정의된 셀렉터가 파드의 레이블과 어떻게 매칭되어 엔드포인트 목록을 동적으로 유지하는지, 그리고 셀렉터 없이 수동으로 엔드포인트를 지정하는 방법(주로 외부 서비스를 통합할 때 사용)에 대해서도 간략히 언급할 것입니다.
- 마지막으로 8.2.4 [실습] 다양한 서비스 타입 생성 및 테스트에서는 직접 YAML 파일을 작성하여 앞서 배운 다양한 서비스 타입(ClusterIP, NodePort, LoadBalancer – 클라우드 환경이거나 Minikube 등에서 지원 시)을 생성하고, 실제로 이 서비스들을 통해 백엔드 파드에 접근하여 각 타입의 동작 방식을 눈으로 확인하고 체감하는 시간을 가질 것입니다. 이 실습을 통해 서비스의 강력함과 편리함을 직접 경험하고, 실제 애플리케이션 배포 시 어떤 서비스 타입을 선택해야 할지에 대한 감을 잡으실 수 있을 겁니다.
서비스는 쿠버네티스에서 마이크로서비스 아키텍처를 구현하고, 애플리케이션 간의 느슨한 결합(loose coupling)을 가능하게 하는 핵심적인 구성 요소입니다. 이 장을 통해 서비스의 개념과 활용법을 확실히 익히신다면, 쿠버네티스 위에서 더욱 안정적이고 확장 가능한 애플리케이션을 구축하고 운영하는 데 큰 자신감을 얻게 될 것입니다.
8.2.1 서비스의 필요성
앞서 8.1장에서 우리는 쿠버네티스 네트워킹의 기본 원칙 중 하나로 “모든 파드는 고유한 IP 주소를 가진다”는 점을 배웠습니다. 이는 각 파드가 네트워크 상에서 독립적으로 식별되고 통신할 수 있는 기반을 마련해 주지만, 동시에 한 가지 중요한 문제점을 안고 있습니다. 바로 파드 IP 주소의 비영속성(ephemeral nature)입니다. 즉, 파드에 할당된 IP 주소는 영구적이지 않고, 파드의 생명주기에 따라 언제든지 변경될 수 있다는 의미입니다.
8.2.1.1 IP 기반에서 서비스 기반으로 변경되어야 하는 이유는
IT 환경이 IP 주소 기반에서 서비스 기반으로 반드시 변경되어야 하는 이유를 설명드리겠습니다. 이는 단순히 기술적인 트렌드를 넘어, 현대 애플리케이션의 복잡성과 동적인 클라우드 네이티브 환경에 대응하기 위한 필연적인 진화 과정이라고 할 수 있습니다.
전통적인 IT 환경에서는 특정 기능을 제공하는 서버나 애플리케이션에 접근하기 위해 해당 서버의 고정된 IP 주소를 사용하는 것이 일반적이었습니다. 마치 특정 가게를 찾아갈 때 그 가게의 물리적인 주소를 알고 찾아가는 것과 같았습니다. 하지만 이러한 IP 기반 접근 방식은 다음과 같은 여러 가지 한계와 문제점을 드러내기 시작했습니다.
- 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)되어 통신할 수 있게 됩니다. 이는 시스템 전체의 유연성, 확장성, 그리고 회복탄력성을 크게 향상시키는 핵심적인 요소입니다.
8.2.2 서비스 타입 (Type)
쿠버네티스 서비스(Service)는 파드 집합에 대한 안정적인 접근 지점을 제공하여 파드 IP의 비영속성 문제를 해결한다고 앞서 설명드렸습니다. 그런데 이 ‘접근 지점’이라는 것이 항상 동일한 방식으로 제공되는 것은 아닙니다. 서비스가 어떤 범위에서, 어떤 방식으로 노출될 것인지에 따라 쿠버네티스는 여러 가지 서비스 타입(Type)을 제공하며, 사용자는 자신의 요구사항에 맞는 타입을 선택하여 서비스를 정의할 수 있습니다.
마치 우리가 특정 장소로 가는 방법을 선택할 때, 내부 도로만 이용할지, 고속도로를 탈지, 아니면 비행기를 이용할지를 결정하는 것과 유사합니다. 각 서비스 타입은 서로 다른 특징과 사용 사례를 가지고 있으므로, 이들을 정확히 이해하는 것은 애플리케이션을 올바르게 노출하고 외부와 통신하는 데 매우 중요합니다.
쿠버네티스 서비스 명세(spec) 내의 type 필드를 통해 원하는 서비스 타입을 지정할 수 있으며, 주로 사용되는 네 가지 주요 서비스 타입은 다음과 같습니다. 이제 각 타입이 어떤 특징을 가지고 있으며 어떤 상황에 적합한지 자세히 살펴보겠습니다.
| 서비스 타입 | 접근 가능 범위 | 외부 노출 여부 | 주요 용도 및 특징 | 필요 조건 및 주의사항 |
|---|---|---|---|---|
| ClusterIP | 클러스터 내부 | X | – 기본 서비스 타입 – 클러스터 내부에서만 접근 가능 – 파드 간 통신에 사용 | – 외부에서 접근 불가 – 내부 DNS 이름으로 접근 가능 |
| NodePort | 클러스터 외부 | O | – 각 노드의 고정 포트를 통해 외부에 서비스 노출 – 클러스터 외부에서 특정 노드의 IP와 포트를 통해 접근 가능 | – 포트 범위는 30000~32767 – 보안 및 포트 충돌에 주의 필요 |
| LoadBalancer | 클러스터 외부 | O | – 클라우드 제공자의 로드밸런서를 통해 외부에 서비스 노출 – 외부 IP를 통해 접근 가능 – 내부적으로 NodePort와 ClusterIP 서비스 생성 | – 클라우드 환경에서만 사용 가능 – 클라우드 제공자의 로드밸런서 지원 필요 |
| ExternalName | 클러스터 내부 | X | – 외부 서비스를 클러스터 내부 DNS 이름으로 매핑 – 실제로는 프록시가 아니라 DNS CNAME 레코드 설정 – 외부 데이터베이스 등과 연동 시 사용 | – 외부 서비스의 DNS 이름 필요 – 클러스터 내부에서만 접근 가능 |
| Ingress | 클러스터 외부 | O | – HTTP/HTTPS 트래픽을 수신하여 URL, 도메인, 경로 등에 따라 내부 서비스로 라우팅 – 여러 서비스에 대한 트래픽 라우팅, SSL 종료(TLS), 리버스 프록시 기능 제공 | – Ingress Controller 설치 필요 – 복잡한 라우팅 규칙 설정 가능 |
8.2.2.1 ClusterIP: 클러스터 내부에서만 접근 가능한 가상 IP (기본값)
ClusterIP는 쿠버네티스 서비스의 기본 타입입니다. 만약 서비스 YAML 파일에서 spec.type 필드를 명시적으로 설정하지 않으면, 쿠버네티스는 해당 서비스를 ClusterIP 타입으로 간주합니다.
ClusterIP 타입의 서비스는 이름에서 알 수 있듯이, 오직 클러스터 내부에서만 접근 가능한 고유한 가상 IP 주소(클러스터 IP)를 할당받습니다. 이 클러스터 IP는 서비스가 존재하는 동안에는 변경되지 않는 안정적인 주소이며, 클러스터 내의 다른 파드나 애플리케이션들이 이 IP 주소와 서비스 포트를 사용하여 해당 서비스에 요청을 보낼 수 있습니다.

주요 특징 및 동작 방식:
- 내부 통신 전용: ClusterIP는 클러스터 외부 네트워크에서는 직접 접근할 수 없습니다. 오로지 클러스터 내부의 다른 파드들 간의 통신, 예를 들어 마이크로서비스 아키텍처에서 프론트엔드 서비스가 백엔드 서비스를 호출하거나, 여러 백엔드 서비스들이 서로 데이터를 주고받는 용도로 사용됩니다.
- 가상 IP 할당: 서비스가 생성되면, 쿠버네티스 컨트롤 플레인(API 서버)은 미리 정의된 서비스 IP 주소 대역(Service CIDR)에서 사용 가능한 IP 주소를 이 서비스에 할당합니다. 이 IP 주소는 실제 네트워크 인터페이스에 바인딩되는 것이 아니라, 각 노드에서 실행되는 kube-proxy 컴포넌트에 의해 관리되는 가상 IP입니다.
- kube-proxy의 역할: 각 노드의 kube-proxy는 API 서버를 통해 서비스와 해당 서비스에 연결된 엔드포인트(실제 파드들의 IP와 포트) 정보를 감시합니다. 그리고 ClusterIP로 들어오는 트래픽을 실제 백엔드 파드들 중 하나로 전달(로드 밸런싱)하기 위한 네트워킹 규칙(예: iptables 규칙, IPVS 규칙)을 해당 노드에 설정합니다. 따라서 클러스터 내의 어떤 노드에서든 ClusterIP로 요청을 보내면, kube-proxy에 의해 적절한 백엔드 파드로 라우팅됩니다.
- DNS 통합: ClusterIP 타입의 서비스가 생성되면, 쿠버네티스 내부 DNS 시스템(예: CoreDNS)은 해당 서비스의 이름으로 DNS A 레코드(또는 SRV 레코드)를 자동으로 생성합니다. 예를 들어, my-service라는 이름의 서비스가 my-namespace 네임스페이스에 생성되었다면, 클러스터 내의 다른 파드들은 my-service.my-namespace.svc.cluster.local (또는 같은 네임스페이스 내에서는 my-service만으로도)이라는 DNS 이름을 사용하여 이 서비스의 클러스터 IP를 조회하고 접근할 수 있습니다. IP 주소를 직접 사용하는 것보다 이 DNS 이름을 사용하는 것이 훨씬 더 권장되는 방식입니다.
사용 사례:
- 클러스터 내부에서 실행되는 마이크로서비스 간의 통신 (예: 웹 티어에서 API 티어로, API 티어에서 데이터베이스 티어로 호출)
- 내부적으로만 사용되는 관리 도구나 대시보드 노출
- 스테이트풀셋과 함께 사용되는 헤드리스 서비스(Headless Service, clusterIP: None으로 설정)의 기반 (이 경우, 서비스 IP는 없지만 각 파드에 대한 DNS 레코드가 생성됨)
ClusterIP는 쿠버네티스 서비스의 가장 기본적인 형태이며, 대부분의 내부 통신 요구사항을 만족시키는 핵심적인 구성 요소입니다.
8.2.2.2 NodePort: 각 노드의 특정 포트를 통해 외부에서 접근 가능
ClusterIP 타입의 서비스는 클러스터 내부에서만 접근 가능하므로, 만약 클러스터 외부의 클라이언트(예: 개발자의 로컬 머신, 외부 시스템)가 이 서비스에 접근해야 한다면 다른 방법이 필요합니다. 이때 사용할 수 있는 옵션 중 하나가 바로 NodePort 타입의 서비스입니다.

NodePort 타입의 서비스는 ClusterIP 타입의 모든 기능을 포함하면서, 추가적으로 클러스터 내의 모든 워커 노드의 특정 고정 포트(NodePort)를 통해 외부에서 서비스에 접근할 수 있도록 경로를 열어줍니다. 즉, 외부 클라이언트는 <어떤 노드의 IP 주소>:
주요 특징 및 동작 방식:
- ClusterIP 자동 생성: NodePort 타입으로 서비스를 생성하면, 쿠버네티스는 먼저 해당 서비스에 대한 ClusterIP를 자동으로 할당합니다. (즉, NodePort 서비스는 내부적으로 ClusterIP 서비스를 확장한 형태입니다.)
- 모든 노드에 포트 개방: 그 다음, 쿠버네티스는 클러스터 내의 모든 워커 노드(그리고 때로는 마스터 노드에도)에 특정 포트 번호(이것이 바로 NodePort입니다)를 할당하고, 이 포트로 들어오는 외부 트래픽을 해당 서비스의 ClusterIP와 서비스 포트로 전달하도록 kube-proxy에 의해 네트워킹 규칙을 설정합니다.
- NodePort 범위: 사용자가 NodePort를 직접 지정할 수도 있지만, 보통은 쿠버네티스가 미리 정해진 범위(기본값: 30000-32767) 내에서 사용 가능한 포트를 자동으로 할당합니다. 이 범위는 클러스터 구성 시 변경할 수 있습니다.
- 외부 접근 경로: 외부 클라이언트는 클러스터 내 어떤 노드의 IP 주소를 사용하든 (예:
: , : 등), 동일한 NodePort 번호를 통해 해당 서비스에 접근할 수 있습니다. 요청을 받은 노드는 해당 트래픽을 서비스의 ClusterIP로 전달하고, 이는 다시 적절한 백엔드 파드로 로드 밸런싱됩니다.
사용 사례:
- 개발 또는 테스트 목적으로 클러스터 외부에서 애플리케이션에 빠르게 접근해야 할 때 (예: 로컬 머신에서 테스트)
- 외부 로드 밸런서가 아직 준비되지 않았거나 사용할 수 없는 환경에서 서비스를 임시로 노출해야 할 때
- 클라우드 환경이 아닌 온프레미스 환경에서 외부 로드 밸런서 없이 서비스를 노출하고자 할 때 (이 경우, 외부에서 노드 IP로 직접 접근 가능해야 함)
- 상태를 가진 애플리케이션(예: 특정 게임 서버)의 각 인스턴스에 직접 고정 포트로 접근해야 하는 특수한 경우 (일반적이지는 않음)
고려 사항:
- NodePort는 각 노드의 IP를 직접 사용하므로, 만약 특정 노드에 장애가 발생하면 해당 노드 IP를 통한 접근은 실패할 수 있습니다. 따라서 고가용성을 위해서는 NodePort 앞에 별도의 외부 로드 밸런서를 구성하는 것이 일반적입니다.
- 노드에 직접 포트를 열기 때문에 보안상 취약점이 될 수 있습니다. 방화벽 규칙 등을 통해 필요한 접근만 허용하도록 주의해야 합니다.
- 사용 가능한 포트 범위가 제한적입니다.
NodePort는 서비스를 외부로 노출하는 비교적 간단한 방법이지만, 프로덕션 환경에서는 보통 더 강력하고 유연한 LoadBalancer 타입이나 인그레스(Ingress)를 사용하는 것이 권장됩니다.
8.2.2.3 LoadBalancer: 클라우드 로드밸런서 연동 (로컬 환경에서는 MetalLB 등 필요)
클라우드 환경(예: AWS, GCP, Azure)에서 쿠버네티스 클러스터를 운영하고 있다면, 서비스를 외부 인터넷에 안정적으로 노출하는 가장 일반적이고 권장되는 방법은 LoadBalancer 타입의 서비스를 사용하는 것입니다.

LoadBalancer 타입의 서비스는 NodePort 타입의 기능을 포함하면서, 추가적으로 클라우드 제공업체가 제공하는 외부 로드 밸런서(External Load Balancer)를 자동으로 프로비저닝하고 이 로드 밸런서를 통해 서비스에 접근할 수 있도록 구성합니다. 클라우드 로드 밸런서는 고유한 공인 IP 주소를 가지며, 외부 인터넷으로부터 들어오는 트래픽을 서비스의 NodePort(또는 다른 내부 경로)를 통해 클러스터 내의 워커 노드들로 분산시켜 줍니다.
주요 특징 및 동작 방식:
- NodePort 및 ClusterIP 자동 생성: LoadBalancer 타입으로 서비스를 생성하면, 쿠버네티스는 먼저 해당 서비스에 대한 ClusterIP와 NodePort를 자동으로 생성하고 구성합니다. (즉, LoadBalancer 서비스는 내부적으로 NodePort 서비스를 확장한 형태입니다.)
- 클라우드 로드밸런서 프로비저닝: 쿠버네티스 클러스터가 클라우드 제공업체의 환경에서 실행 중이고, 해당 클라우드 제공업체의 컨트롤러 매니저(cloud controller manager)가 올바르게 구성되어 있다면, 서비스 생성 요청을 감지하고 클라우드 플랫폼에 외부 로드 밸런서 생성을 요청합니다.
- 외부 IP 주소 할당: 클라우드 로드 밸런서가 성공적으로 생성되면, 고유한 공인 IP 주소(External IP)가 할당됩니다. 이 IP 주소는 서비스의 status.loadBalancer.ingress 필드에 기록되며, kubectl get service <서비스이름> 명령을 통해 확인할 수 있습니다.
- 트래픽 라우팅: 외부 클라이언트는 이 할당된 외부 IP 주소를 사용하여 서비스에 접근합니다. 클라우드 로드 밸런서는 수신된 트래픽을 클러스터 내 워커 노드들의 해당 서비스 NodePort로 전달하고, 이는 다시 서비스의 ClusterIP를 거쳐 최종적으로 백엔드 파드들로 로드 밸런싱됩니다.
사용 사례:
- 클라우드 환경(AWS, GCP, Azure, DigitalOcean 등)에서 실행되는 쿠버네티스 애플리케이션을 외부 인터넷에 안정적으로 노출하고 싶을 때 가장 표준적인 방법입니다.
- 외부 사용자에게 고가용성과 확장성을 갖춘 단일 진입점(entry point)을 제공하고자 할 때 사용합니다.
로컬 환경 또는 온프레미스 환경에서의 LoadBalancer 타입:
만약 클라우드 환경이 아닌 로컬 개발 환경(Minikube, kind 등)이나 직접 구축한 온프레미스 쿠버네티스 클러스터에서 LoadBalancer 타입의 서비스를 생성하면, 기본적으로는 외부 로드 밸런서가 자동으로 프로비저닝되지 않으므로 EXTERNAL-IP가
이러한 환경에서 LoadBalancer 타입의 서비스를 실제로 동작시키기 위해서는 MetalLB와 같은 베어메탈 로드 밸런서 구현체를 클러스터에 설치해야 합니다. MetalLB는 미리 정의된 IP 주소 풀에서 외부 IP를 할당하고, ARP(Address Resolution Protocol)나 BGP(Border Gateway Protocol)를 사용하여 해당 IP로의 트래픽을 클러스터 노드로 라우팅하는 역할을 합니다. 이를 통해 온프레미스 환경에서도 클라우드와 유사한 LoadBalancer 서비스 경험을 얻을 수 있습니다. (Minikube의 경우 minikube tunnel 명령을 통해 임시로 LoadBalancer 서비스를 테스트할 수도 있습니다.)
LoadBalancer 타입은 서비스를 외부에 노출하는 가장 강력하고 편리한 방법 중 하나이지만, 클라우드 제공업체의 로드 밸런서 사용에 따른 비용이 발생할 수 있다는 점을 고려해야 합니다.
8.2.2.4 ExternalName: 외부 서비스를 CNAME 형태로 클러스터 내부에 등록
지금까지 설명한 ClusterIP, NodePort, LoadBalancer 타입의 서비스들은 모두 쿠버네티스 클러스터 내부의 파드들을 대상으로 하는 서비스였습니다. 하지만 때로는 클러스터 외부에 존재하는 기존 서비스 (예: 외부 데이터베이스, 외부 API, 다른 클라우드 서비스 등)를 마치 클러스터 내부의 서비스처럼 동일한 방식으로 참조하고 싶을 때가 있습니다. 이때 유용하게 사용될 수 있는 특별한 서비스 타입이 바로 ExternalName입니다.

ExternalName 타입의 서비스는 앞선 세 가지 타입과는 동작 방식이 매우 다릅니다. 이 타입은 실제로 프록시 역할을 하거나 로드 밸런싱을 수행하지 않습니다. 대신, 서비스의 DNS 이름을 **외부 서비스의 DNS 이름(CNAME 레코드와 유사하게)**으로 단순히 매핑(alias)해주는 역할만 합니다.
주요 특징 및 동작 방식:
- 셀렉터 없음, 포트 정의 없음: ExternalName 타입의 서비스는 특정 파드를 선택하기 위한 selector를 사용하지 않으며, 서비스 포트(ports 필드)도 정의하지 않습니다. 오직 spec.externalName 필드에 매핑할 외부 서비스의 실제 DNS 이름을 지정합니다.
- CNAME 매핑: 클러스터 내부 DNS 시스템(예: CoreDNS)은 ExternalName 타입의 서비스 이름(예: my-external-db.my-namespace.svc.cluster.local)에 대한 DNS 요청을 받으면, spec.externalName에 정의된 외부 DNS 이름(예: prod-rds-instance.abcdef123456.us-west-2.rds.amazonaws.com)을 가리키는 CNAME 레코드를 반환합니다. (정확히는, 내부 DNS는 CNAME을 반환하고, 실제 외부 DNS 이름에 대한 A 레코드 조회는 클라이언트의 DNS 리졸버가 이어서 수행하게 됩니다.)
- 클라이언트의 직접 통신: 결과적으로, 클러스터 내의 파드가 ExternalName 서비스의 DNS 이름을 사용하여 접속을 시도하면, 실제로는 spec.externalName에 지정된 외부 서비스의 IP 주소로 직접 통신하게 됩니다. 쿠버네티스 서비스는 이 과정에서 어떠한 트래픽 중개도 하지 않습니다.
사용 사례:
- 클러스터 외부에서 운영 중인 레거시 시스템이나 관리형 데이터베이스(예: AWS RDS, Google Cloud SQL)를 쿠버네티스 내부 애플리케이션들이 일관된 서비스 이름으로 참조하고 싶을 때 사용합니다.
- 애플리케이션 코드 변경 없이, 외부 서비스의 실제 주소가 변경되더라도 ExternalName 서비스의 externalName 필드만 수정하여 유연하게 대응하고 싶을 때 유용합니다. (애플리케이션은 항상 내부 서비스 이름만 사용)
- 개발 환경에서는 클러스터 내부의 목(mock) 서비스를 사용하고, 프로덕션 환경에서는 실제 외부 서비스를 사용하도록 환경별로 ExternalName 서비스의 대상을 다르게 설정하여 애플리케이션 구성을 관리할 수 있습니다.
고려 사항:
- ExternalName 서비스는 실제 트래픽 라우팅이나 로드 밸런싱 기능을 제공하지 않으므로, 이러한 기능은 외부 서비스 자체에서 제공되거나 별도로 구성되어야 합니다.
- ExternalName에 지정된 외부 DNS 이름이 유효하고 접근 가능한지 확인해야 합니다.
ExternalName은 클러스터 내부와 외부 서비스 간의 경계를 부드럽게 연결하고, 애플리케이션이 외부 의존성을 일관된 방식으로 관리할 수 있도록 돕는 유용한 도구입니다.
지금까지 살펴본 네 가지 서비스 타입 – ClusterIP, NodePort, LoadBalancer, ExternalName – 은 각각 고유한 목적과 특징을 가지고 있으며, 쿠버네티스에서 애플리케이션을 다양한 방식으로 노출하고 연결하는 데 핵심적인 역할을 합니다. 어떤 서비스 타입을 선택할지는 애플리케이션의 접근성 요구사항, 실행 환경(클라우드, 온프레미스), 보안 고려사항, 그리고 운영 편의성 등을 종합적으로 고려하여 결정해야 합니다.
8.2.3 서비스와 셀렉터 (Selector)
쿠버네티스 서비스(Service)가 파드 집합에 대한 안정적인 접근 지점을 제공하고, 클러스터 IP라는 고정된 가상 IP를 통해 이 파드들에게 트래픽을 전달한다는 것은 이제 잘 이해하셨을 겁니다. 그렇다면 서비스는 어떤 파드들이 자신에게 속한 백엔드 그룹인지 어떻게 알고, 이 변화무쌍한 파드들의 목록을 어떻게 최신 상태로 유지하는 걸까요? 마치 식당의 매니저가 어떤 요리사들이 오늘 출근했고, 누가 현재 주문을 받을 수 있는 상태인지 실시간으로 파악해야 하는 것과 같습니다.
이 질문에 대한 답의 핵심에 바로 쿠버네티스의 강력한 리소스 관리 메커니즘인 레이블(Label)과 셀렉터(Selector)가 있습니다. 서비스는 이 레이블과 셀렉터를 사용하여 자신이 라우팅해야 할 대상 파드 그룹을 동적으로 식별하고 연결합니다. 이 느슨한 결합(loose coupling) 방식은 쿠버네티스의 유연성과 확장성을 뒷받침하는 매우 중요한 특징입니다.
이번 절에서는 서비스가 어떻게 셀렉터를 활용하여 파드 그룹과 연결되는지, 그리고 특별한 경우 셀렉터 없이 서비스를 사용하는 방법은 무엇인지 자세히 살펴보겠습니다.
8.2.3.1 레이블 셀렉터를 이용한 파드 그룹 연결
쿠버네티스에서 레이블(Label)은 파드(Pod)를 비롯한 모든 오브젝트에 사용자가 임의로 부여할 수 있는 ‘키-값’ 쌍의 메타데이터입니다. 마치 물건에 태그를 붙이는 것처럼, 이 레이블은 오브젝트의 특징이나 용도를 나타내는 데 사용됩니다. 예를 들어, app: my-backend, tier: api, environment: production 과 같이 특정 애플리케이션, 계층, 환경 등을 나타내는 레이블을 파드에 부여할 수 있습니다.
- 셀렉터(Selector)는 이러한 레이블을 기반으로 특정 조건을 만족하는 오브젝트들을 찾아내고 그룹화하는 메커니즘입니다. 서비스는 바로 이 셀렉터를 사용하여 자신이 트래픽을 전달해야 할 대상 파드들의 집합을 결정합니다.
서비스 명세(spec) 내의 selector 필드에 특정 레이블 조건을 정의하면, 쿠버네티스 컨트롤 플레인(특히 엔드포인트 컨트롤러 – Endpoint Controller 또는 최신 버전에서는 엔드포인트 슬라이스 컨트롤러 – EndpointSlice Controller)은 지속적으로 클러스터 내의 모든 파드들을 감시합니다. 그리고 서비스의 selector와 일치하는 레이블을 가진 파드들을 찾아내어, 이 파드들의 실제 IP 주소와 포트 정보를 담은 엔드포인트(Endpoints) 오브젝트 (또는 엔드포인트 슬라이스(EndpointSlice) 오브젝트)를 자동으로 생성하고 관리합니다.
구체적인 동작 흐름은 다음과 같습니다.
- 파드에 레이블 부여: 먼저, 서비스를 제공할 파드들을 생성할 때 (보통 디플로이먼트나 스테이트풀셋의 파드 템플릿을 통해) 해당 파드들을 식별할 수 있는 적절한 레이블을 부여합니다. 예를 들어, app: my-web-server 라는 레이블을 웹 서버 파드들에 부여합니다.
- 서비스에 셀렉터 정의: 서비스를 생성할 때, spec.selector 필드에 위에서 파드에 부여한 레이블과 일치하는 조건을 명시합니다. 예를 들어, selector: 와 같이 정의합니다.
- 엔드포인트(슬라이스) 자동 관리:
- 쿠버네티스의 엔드포인트(슬라이스) 컨트롤러는 이 서비스의 selector를 보고, 클러스터 내에서 app: my-web-server 레이블을 가진 모든 파드들을 찾아냅니다.
- 이때, 찾아낸 파드들 중에서 실제로 요청을 받을 준비가 된(Ready 상태이고, 종료 중이 아닌) 파드들의 IP 주소와 (서비스에 정의된 targetPort에 해당하는) 컨테이너 포트 정보만을 모아 해당 서비스와 이름이 같은 엔드포인트(슬라이스) 오브젝트에 기록합니다.
- 만약 새로운 파드가 생성되어 app: my-web-server 레이블을 가지게 되거나, 기존 파드가 삭제되거나, 또는 파드의 준비 상태가 변경되면, 엔드포인트(슬라이스) 컨트롤러는 이 변화를 즉시 감지하여 엔드포인트(슬라이스) 오브젝트를 최신 상태로 업데이트합니다.
- kube-proxy의 역할: 각 노드에서 실행되는 kube-proxy는 이 엔드포인트(슬라이스) 오브젝트의 변경 사항을 감시하고 있다가, 서비스의 클러스터 IP로 들어오는 트래픽을 엔드포인트(슬라이스)에 등록된 실제 파드 IP와 포트로 전달하기 위한 네트워킹 규칙(iptables, IPVS 등)을 해당 노드에 업데이트합니다.
이러한 레이블 셀렉터 기반의 동적 연결 방식은 다음과 같은 강력한 이점을 제공합니다.
- 느슨한 결합 (Loose Coupling): 서비스는 특정 파드의 IP 주소나 이름에 직접 의존하지 않고, 오직 레이블을 통해 논리적으로 연결됩니다. 따라서 파드가 재생성되어 IP 주소가 변경되거나, 파드의 개수가 스케일 아웃/인 되어도 서비스는 자동으로 새로운 파드들을 감지하고 트래픽을 전달할 수 있습니다.
- 자동화된 서비스 디스커버리: 개발자나 운영자는 어떤 파드가 어떤 서비스에 속하는지를 레이블을 통해 명확하게 정의하고 관리할 수 있으며, 쿠버네티스는 이를 기반으로 서비스와 파드 간의 연결을 자동으로 유지합니다.
- 롤링 업데이트 및 블루/그린 배포 지원: 디플로이먼트를 사용하여 애플리케이션을 업데이트할 때, 새로운 버전의 파드는 새로운 레이블을 가질 수도 있고 (이 경우 서비스 셀렉터도 변경 필요), 또는 기존 레이블을 그대로 유지하면서 점진적으로 교체될 수도 있습니다. 서비스는 이러한 변화에 맞춰 엔드포인트 목록을 자동으로 업데이트하므로, 무중단 업데이트가 가능해집니다.
예를 들어, 다음과 같은 서비스와 디플로이먼트 YAML이 있다고 가정해 봅시다.
서비스 YAML (my-service.yaml):
클립보드에 복사
Syntax Highlighter
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app # 이 서비스는 'app: my-app' 레이블을 가진 파드를 찾습니다.
ports:
-
protocol: TCP
port: 80 # 서비스가 노출하는 포트 (클러스터 IP의 포트)
targetPort: 8080 # 파드 내부 컨테이너가 리스닝하는 포트
디플로이먼트 YAML (my-deployment.yaml):
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 3
selector: # 디플로이먼트 자체의 셀렉터
matchLabels:
app: my-app
template: # 파드 템플릿
metadata:
labels:
app: my-app # 파드에 'app: my-app' 레이블을 부여합니다. 이것이 서비스의 selector와 매칭됩니다!
spec:
containers:
- name: my-app-container
image: my-app-image:latest
ports:
- containerPort: 8080 # 컨테이너는 8080 포트에서 리스닝합니다.
이 경우, my-app-service는 app: my-app 레이블을 가진 모든 파드들을 자신의 백엔드로 인식하고, 해당 파드들의 8080번 포트로 트래픽을 전달하게 됩니다. my-app-deployment에 의해 생성되는 3개의 파드들은 모두 app: my-app 레이블을 가지므로, 이 서비스의 대상이 됩니다.
이처럼 레이블과 셀렉터는 쿠버네티스 서비스가 동적이고 확장 가능한 방식으로 파드 그룹과 상호작용할 수 있도록 하는 핵심적인 접착제 역할을 합니다.
8.2.3.2 셀렉터 없는 서비스 (직접 Endpoints 관리)
대부분의 경우, 쿠버네티스 서비스는 레이블 셀렉터를 사용하여 클러스터 내의 파드들을 동적으로 찾아내고 엔드포인트(Endpoints) 오브젝트를 자동으로 관리합니다. 하지만 특별한 상황에서는 서비스에 selector 필드를 정의하지 않고, 대신 엔드포인트 오브젝트를 수동으로 직접 생성하고 관리하는 방식을 사용할 수도 있습니다. 이를 “셀렉터 없는 서비스(Service without selectors)”라고 부릅니다.
셀렉터 없는 서비스는 다음과 같은 경우에 유용하게 사용될 수 있습니다.
-
**클러스터 외부의 서비스를 쿠버네티스 내부 서비스처럼 참조:**만약 쿠버네티스 클러스터 외부에서 실행 중인 기존의 레거시 서비스나 데이터베이스가 있고, 클러스터 내부의 애플리케이션들이 이 외부 서비스를 마치 클러스터 내부의 다른 서비스처럼 동일한 서비스 디스커버리 메커니즘(예: DNS 이름)을 통해 접근하게 하고 싶을 때 사용할 수 있습니다.이 경우, 먼저 서비스 YAML 파일에서 selector 필드를 생략하거나 주석 처리합니다. 그리고 이 서비스와 동일한 이름 및 네임스페이스를 가진 엔드포인트 오브젝트를 수동으로 생성하고, 이 엔드포인트 오브젝트의 subsets 필드에 실제 외부 서비스의 IP 주소와 포트 정보를 직접 명시합니다.
클립보드에 복사
Syntax Highlighter
예시: 셀렉터 없는 서비스 YAML (my-external-db-service.yaml)
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
ports:
-
protocol: TCP
port: 5432 # 서비스가 노출할 포트
targetPort는 엔드포인트에 직접 지정하므로 여기서는 생략 가능하거나 동일하게 설정
예시: 위 서비스에 대한 수동 엔드포인트 YAML (my-external-db-endpoints.yaml)
apiVersion: v1
kind: Endpoints
metadata:
name: external-db # 서비스 이름과 동일하게 지정!
subsets:
-
addresses:
- ip: “192.168.100.10” # 외부 데이터베이스 서버의 실제 IP
ports:
-
port: 5432 # 외부 데이터베이스 서버의 실제 포트
protocol: TCP
이렇게 구성하면, 클러스터 내부의 파드가 external-db라는 서비스 이름으로 DNS 조회를 할 때, 쿠버네티스 DNS는 이 서비스의 클러스터 IP를 반환하고, 해당 클러스터 IP로 향하는 트래픽은 kube-proxy에 의해 수동으로 정의된 엔드포인트 (192.168.100.10:5432)로 전달됩니다. (주의: ExternalName 타입의 서비스와는 목적이 유사하지만 동작 방식이 다릅니다. ExternalName은 CNAME 리다이렉션을 수행하는 반면, 셀렉터 없는 서비스와 수동 엔드포인트는 여전히 클러스터 IP를 통해 프록시됩니다.)
-
-
**다른 네임스페이스에 있는 서비스를 현재 네임스페이스에서 다른 이름으로 참조:**어떤 경우에는 특정 네임스페이스에 있는 서비스를 다른 네임스페이스에서 마치 현재 네임스페이스에 있는 것처럼 다른 이름으로 참조하고 싶을 수 있습니다. 이때도 셀렉터 없는 서비스를 생성하고, 해당 서비스에 대한 엔드포인트를 수동으로 구성하여 원래 서비스의 클러스터 IP를 가리키도록 할 수 있습니다.
-
**매우 특수한 서비스 라우팅 또는 마이그레이션 시나리오:**표준적인 셀렉터 기반 방식으로는 구현하기 어려운 복잡한 서비스 라우팅 규칙을 적용하거나, 서비스를 점진적으로 다른 백엔드로 마이그레이션하는 과정에서 일시적으로 엔드포인트를 수동 제어해야 하는 매우 특수한 상황에서 사용될 수 있습니다.
고려 사항:
- 셀렉터 없는 서비스를 사용하고 엔드포인트를 수동으로 관리하는 것은 쿠버네티스의 자동화된 동적 관리 이점을 활용하지 못하게 됩니다. 외부 서비스의 IP 주소나 포트가 변경되면 엔드포인트 오브젝트를 수동으로 업데이트해야 하는 부담이 있습니다.
- 엔드포인트 오브젝트를 직접 관리하는 것은 오류가 발생하기 쉽고, 관리 오버헤드가 크므로 정말 필요한 경우에만 신중하게 사용해야 합니다.
- 대부분의 경우, 클러스터 외부 서비스를 참조할 때는 ExternalName 타입의 서비스를 사용하는 것이 더 간결하고 권장되는 방법일 수 있습니다.
결론적으로, 서비스와 셀렉터는 쿠버네티스에서 파드 그룹에 대한 안정적인 접근성을 제공하는 핵심적인 메커니즘입니다. 레이블 셀렉터를 통한 동적 엔드포인트 관리는 대부분의 사용 사례에 적합하며 쿠버네티스의 강력함을 잘 보여줍니다. 셀렉터 없는 서비스와 수동 엔드포인트 관리는 특수한 상황에서 유용할 수 있지만, 그에 따른 관리 부담을 충분히 인지하고 사용해야 합니다. 이들의 동작 원리를 정확히 이해하는 것은 쿠버네티스 기반의 애플리케이션을 설계하고 운영하는 데 있어 매우 중요합니다.
8.2.4 [실습] 다양한 서비스 타입 생성 및 테스트
이론으로 쿠버네티스 서비스의 필요성과 다양한 타입(ClusterIP, NodePort, LoadBalancer, ExternalName), 그리고 서비스가 셀렉터를 통해 어떻게 파드와 연결되는지 자세히 살펴보았습니다. 이제는 직접 손으로 다양한 타입의 서비스를 만들어보고, 실제로 이 서비스들을 통해 애플리케이션에 접근해보면서 그 동작 방식을 체감해 볼 시간입니다! 이번 실습에서는 간단한 웹 애플리케이션을 배포하고, 이 애플리케이션을 위해 ClusterIP 타입과 NodePort 타입의 서비스를 각각 생성하여 어떻게 접근할 수 있는지 확인할 것입니다. (LoadBalancer 타입은 클라우드 환경이나 MetalLB와 같은 추가 구성이 필요하므로, 여기서는 기본적으로 테스트 가능한 두 가지 타입에 집중하겠습니다. 만약 환경이 지원된다면 LoadBalancer 타입도 동일한 방식으로 테스트해볼 수 있습니다.)
본 실습을 위해서는 이전 실습들과 마찬가지로 쿠버네티스 클러스터와 kubectl CLI가 준비되어 있어야 합니다. Minikube, kind, Docker Desktop의 쿠버네티스 기능 또는 클라우드 제공업체의 관리형 서비스를 사용하시면 됩니다. 실습을 위해 간단한 Nginx 웹 서버를 백엔드 파드로 사용할 것입니다.
자, 그럼 쿠버네티스 서비스의 마법을 직접 경험하러 떠나볼까요?
8.2.4.1 ClusterIP 서비스 생성 및 클러스터 내부에서 접속 확인 (exec 활용)
가장 먼저, 클러스터 내부에서만 접근 가능한 기본 서비스 타입인 ClusterIP 서비스를 생성해 보겠습니다. 이 서비스는 클러스터 내의 다른 파드들이 백엔드 Nginx 파드 그룹에 접근할 수 있는 안정적인 엔드포인트를 제공할 것입니다.
1단계: 백엔드 애플리케이션 배포 (디플로이먼트 생성)
먼저 서비스의 대상이 될 간단한 Nginx 웹 서버 파드들을 디플로이먼트를 사용하여 2개 생성하겠습니다. 이 디플로이먼트의 파드들은 app: my-nginx-app 이라는 레이블을 갖도록 설정합니다.
아래 내용을 my-nginx-deployment.yaml 파일로 저장해 주세요.
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: my-nginx-app # 디플로이먼트가 관리할 파드의 레이블
template:
metadata:
labels:
app: my-nginx-app # 파드에 적용될 레이블, 서비스의 selector와 일치시킬 예정
spec:
containers:
- name: nginx-container
image: nginx:1.25
ports:
- containerPort: 80 # Nginx는 80번 포트에서 리스닝합니다.
터미널에서 다음 명령어로 디플로이먼트를 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f my-nginx-deployment.yaml
잠시 후 kubectl get pods -l app=my-nginx-app 명령으로 2개의 Nginx 파드가 Running 상태인지 확인합니다.
2단계: ClusterIP 서비스 생성
이제 위에서 배포한 Nginx 파드 그룹(app: my-nginx-app 레이블을 가진 파드들)을 위한 ClusterIP 타입의 서비스를 생성하겠습니다.
아래 내용을 my-clusterip-service.yaml 파일로 저장해 주세요.
클립보드에 복사
Syntax Highlighter
apiVersion: v1
kind: Service
metadata:
name: my-nginx-clusterip-svc
spec:
type: ClusterIP # 명시적으로 ClusterIP 타입을 지정 (생략해도 기본값으로 ClusterIP가 됨)
selector:
app: my-nginx-app # 이 서비스는 'app: my-nginx-app' 레이블을 가진 파드를 대상으로 합니다.
ports:
-
protocol: TCP
port: 80 # 서비스가 노출할 포트 (클러스터 IP의 80번 포트)
targetPort: 80 # 파드 내부 컨테이너가 리스닝하는 포트 (Nginx의 80번 포트)
터미널에서 다음 명령어로 서비스를 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f my-clusterip-service.yaml
“service/my-nginx-clusterip-svc created” 메시지가 출력되면 성공입니다. 생성된 서비스의 정보를 확인해 보겠습니다.
클립보드에 복사
Syntax Highlighter
kubectl get service my-nginx-clusterip-svc
또는 kubectl get svc my-nginx-clusterip-svc
출력 결과를 보면 TYPE이 ClusterIP로 되어 있고, CLUSTER-IP 필드에 고유한 가상 IP 주소가 할당된 것을 확인할 수 있습니다. PORTS는 80/TCP로 표시될 것입니다.
3단계: 클러스터 내부에서 접속 확인 (exec 활용)
ClusterIP 서비스는 클러스터 외부에서는 직접 접근할 수 없습니다. 따라서 이 서비스가 제대로 동작하는지 확인하려면 클러스터 내부의 다른 파드에서 이 서비스로 접속을 시도해야 합니다. 이를 위해 우리는 kubectl exec 명령어를 사용하여, 임시로 실행되는 간단한 유틸리티 컨테이너(예: busybox) 내부에서 wget이나 curl과 같은 도구로 ClusterIP 서비스에 HTTP 요청을 보내볼 것입니다.
먼저, ClusterIP 서비스의 클러스터 IP 주소를 확인합니다 (위 kubectl get service 명령 결과에서 확인). 예를 들어 10.100.200.5라고 가정하겠습니다. (실제로는 DNS 이름을 사용하는 것이 더 좋습니다.)
이제 임시 busybox 파드를 실행하고, 그 파드 내부의 셸로 접속하여 wget 명령을 실행합니다. (이 명령은 파드를 만들고 바로 접속한 후, 종료하면 파드도 자동으로 삭제되는 방식입니다.)
클립보드에 복사
Syntax Highlighter
kubectl run tmp-busybox —rm -it —image=busybox:1.28 — sh
위 명령을 실행하면 busybox 파드 내부의 셸 프롬프트 (/ #)가 나타날 것입니다. 여기서 다음 wget 명령을 실행하여 ClusterIP 서비스의 IP 주소와 80번 포트로 접속을 시도합니다. (만약 wget이 없다면 apk add –no-cache curl 등으로 curl을 설치 후 사용 가능합니다.)
클립보드에 복사
Syntax Highlighter
busybox 셸 내부에서 실행
wget -q -O - http://<my-nginx-clusterip-svc의-CLUSTER-IP>:80
또는 서비스 DNS 이름 사용 (권장):
wget -q -O - http://my-nginx-clusterip-svc.default.svc.cluster.local:80
(만약 다른 네임스페이스에 서비스를 만들었다면, default 대신 해당 네임스페이스 이름 사용)
성공적으로 접속되었다면, Nginx의 기본 환영 페이지 HTML 내용이 터미널에 출력될 것입니다. 이는 ClusterIP 서비스가 정상적으로 요청을 수신하여 백엔드 Nginx 파드 중 하나로 전달했음을 의미합니다. 여러 번 실행하면 로드 밸런싱되어 다른 Nginx 파드로 요청이 전달될 수도 있습니다 (단, Nginx 기본 페이지는 동일하므로 눈에 띄는 차이는 없을 수 있습니다).
exit 명령으로 busybox 셸을 빠져나오면 임시 파드는 자동으로 삭제됩니다. 이처럼 ClusterIP 서비스는 클러스터 내부 통신을 위한 안정적이고 편리한 방법을 제공합니다.
8.2.4.2 NodePort 서비스 생성 및 외부 브라우저/curl로 접속 확인
이번에는 클러스터 외부에서도 각 노드의 특정 포트를 통해 서비스에 접근할 수 있도록 하는 NodePort 타입의 서비스를 생성해 보겠습니다. 백엔드 애플리케이션은 앞서 8.2.4.1에서 배포한 my-nginx-deployment를 그대로 사용합니다.
1단계: NodePort 서비스 생성
app: my-nginx-app 레이블을 가진 Nginx 파드 그룹을 위한 NodePort 타입의 서비스를 생성합니다.
아래 내용을 my-nodeport-service.yaml 파일로 저장해 주세요.
클립보드에 복사
Syntax Highlighter
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport-svc
spec:
type: NodePort # 서비스 타입을 NodePort로 지정합니다.
selector:
app: my-nginx-app
ports:
-
protocol: TCP
port: 80 # 서비스의 ClusterIP가 내부적으로 사용할 포트
targetPort: 80 # 파드 내부 컨테이너가 리스닝하는 포트
nodePort: 30080 # 선택 사항: 특정 NodePort 번호를 지정할 수 있습니다.
# 생략하면 쿠버네티스가 30000-32767 범위 내에서 자동으로 할당합니다.
터미널에서 다음 명령어로 서비스를 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f my-nodeport-service.yaml
“service/my-nginx-nodeport-svc created” 메시지가 출력됩니다. 생성된 서비스의 정보를 확인합니다.
클립보드에 복사
Syntax Highlighter
kubectl get service my-nginx-nodeport-svc
출력 결과를 보면 TYPE이 NodePort로 되어 있고, CLUSTER-IP도 할당되어 있습니다. 가장 중요한 것은 PORT(S) 컬럼을 보면 80:
2단계: 외부 브라우저 또는 curl로 접속 확인
이제 클러스터 외부에서 이 NodePort 서비스를 통해 Nginx 웹 서버에 접속해 보겠습니다. 이를 위해서는 클러스터 워커 노드 중 하나의 IP 주소를 알아야 합니다.
다음 명령어로 노드 목록과 IP 정보를 확인할 수 있습니다.
클립보드에 복사
Syntax Highlighter
kubectl get nodes -o wide
INTERNAL-IP 또는 EXTERNAL-IP (만약 외부에서 접근 가능한 IP가 있다면) 컬럼에서 워커 노드의 IP 주소를 확인합니다. (Minikube를 사용 중이라면 minikube ip 명령으로 클러스터 IP를 확인할 수 있고, 이 IP가 노드 IP 역할을 합니다. Docker Desktop의 경우 localhost로 접근 가능할 수 있습니다.)
확인된 노드 IP 주소 (예: 192.168.49.2 – Minikube 예시)와 앞서 확인한
클립보드에 복사
Syntax Highlighter
curl http://<노드_IP>:
예시: curl http://192.168.49.2:31234
성공적으로 접속되었다면, Nginx의 기본 환영 페이지가 보이거나 HTML 내용이 출력될 것입니다. 이는 외부에서 노드의 특정 포트(NodePort)를 통해 서비스에 접근했고, 서비스가 이 요청을 다시 백엔드 Nginx 파드로 전달했음을 의미합니다. 다른 워커 노드의 IP 주소와 동일한
이처럼 NodePort 서비스는 클러스터 외부에서 애플리케이션에 접근할 수 있는 간단한 방법을 제공합니다. 하지만 앞서 언급했듯이, 프로덕션 환경에서는 보통 NodePort 앞에 외부 로드 밸런서를 두거나, LoadBalancer 타입의 서비스 또는 인그레스(Ingress)를 사용하는 것이 더 권장됩니다.
이번 실습을 통해 우리는 ClusterIP 서비스와 NodePort 서비스를 직접 생성하고, 각 타입의 서비스가 어떻게 동작하며 어떻게 접근할 수 있는지 확인했습니다. 이러한 경험은 쿠버네티스에서 애플리케이션을 배포하고 노출하는 다양한 시나리오에 대한 이해를 높이는 데 큰 도움이 될 것입니다. 만약 클라우드 환경을 사용하고 계신다면, type: LoadBalancer로 서비스를 생성하여 클라우드 로드밸런서가 프로비저닝되고 외부 IP가 할당되는 과정도 직접 확인해 보시길 권장합니다.
8.3 인그레스 (Ingress): HTTP/S 트래픽 외부 노출 및 라우팅
앞서 우리는 서비스(Service)의 NodePort나 LoadBalancer 타입을 사용하여 클러스터 외부에서 애플리케이션에 접근하는 방법을 배웠습니다. NodePort는 각 노드의 특정 포트를 열어주지만, 고가용성을 위해서는 별도의 외부 로드 밸런서가 필요하고 포트 관리의 번거로움이 있습니다. LoadBalancer 타입은 클라우드 환경에서 외부 로드 밸런서를 자동으로 프로비저닝해주지만, 각 서비스마다 별도의 로드 밸런서와 공인 IP 주소가 할당되어 비용 부담이 커질 수 있고, 주로 L4(TCP/UDP) 레벨의 로드 밸런싱만 제공한다는 한계가 있습니다.
만약 우리가 여러 개의 HTTP/HTTPS 기반 마이크로서비스들을 운영하고 있고, 이들을 단일 외부 IP 주소와 도메인 이름을 통해 접근하도록 하면서, URL 경로 또는 호스트 이름에 따라 각기 다른 서비스로 지능적으로 라우팅하고 싶다면 어떻게 해야 할까요? 또한, SSL/TLS 암호화와 같은 웹 서비스에 필수적인 기능들을 중앙에서 관리하고 싶다면 어떤 방법이 있을까요?
바로 이러한 요구사항을 충족시키기 위해 쿠버네티스가 제공하는 강력한 기능이 인그레스(Ingress)입니다. 인그레스는 클러스터 외부에서 들어오는 HTTP(S) 요청을 내부의 서비스들로 전달하는 L7(애플리케이션 계층) 로드 밸런서 역할을 하며, 마치 클러스터의 정문에서 방문객(요청)의 목적지(URL 경로, 호스트명)를 확인하고 적절한 안내 데스크(서비스)로 안내하는 똑똑한 안내원과 같습니다. 인그레스는 단순히 트래픽을 전달하는 것을 넘어, 다양한 라우팅 규칙, SSL/TLS 종료, 가상 호스팅 등의 고급 기능을 제공하여 웹 애플리케이션 노출을 훨씬 더 유연하고 효율적으로 관리할 수 있게 해줍니다.
이번 8.3장에서는 쿠버네티스에서 HTTP/S 트래픽을 외부로 노출하고 관리하는 표준적인 방법인 인그레스에 대해 자세히 알아볼 것입니다.
- 먼저 8.3.1 인그레스의 역할과 장점에서는 인그레스가 왜 필요한지, 그리고 LoadBalancer 타입의 서비스와 비교하여 어떤 구체적인 장점들(예: 단일 IP로 다수 서비스 노출, URL 경로 및 호스트 기반의 정교한 L7 라우팅, SSL/TLS 인증서 중앙 관리 및 종료)을 제공하는지 살펴볼 것입니다. 이를 통해 인그레스가 어떻게 웹 서비스 운영의 복잡성과 비용을 줄여주는지 이해하게 될 것입니다.
- 다음으로 8.3.2 인그레스 컨트롤러 (Ingress Controller)에서는 인그레스 리소스에 정의된 규칙들을 실제로 해석하고 적용하여 외부 트래픽을 처리하는 핵심 컴포넌트인 ‘인그레스 컨트롤러’에 대해 알아볼 것입니다. 인그레스 리소스 자체는 단지 ‘규칙의 명세’일 뿐이며, 이 규칙을 실제로 구현하는 것은 별도의 인그레스 컨트롤러 파드(예: Nginx Ingress Controller, Traefik Ingress Controller, HAProxy Ingress 등)입니다. 대표적인 인그레스 컨트롤러들의 종류와 특징을 간략히 소개하고, K3s나 Rancher Desktop과 같은 경량 쿠버네티스 배포판들이 기본적으로 어떤 인그레스 컨트롤러(주로 Traefik)를 내장하고 활용하는지에 대해서도 설명하여, 독자 여러분이 사용하는 환경에서 인그레스를 쉽게 시작할 수 있도록 안내할 것입니다.
- 이어서 8.3.3 인그레스 리소스 정의에서는 인그레스 규칙을 어떻게 YAML 파일로 정의하는지 그 명세 구조를 자세히 살펴볼 것입니다. 특정 호스트 이름이나 URL 경로로 들어오는 요청을 어떤 백엔드 서비스로 전달할지를 정의하는 rules 필드, 그리고 HTTPS 통신을 위한 SSL/TLS 인증서를 어떻게 설정하고 관리하는지를 정의하는 tls 필드 등 인그레스 리소스의 핵심 구성 요소들을 배우게 됩니다.
- 마지막으로 8.3.4 [실습] 인그레스를 이용한 경로 기반 라우팅 설정에서는 직접 두 개의 서로 다른 간단한 웹 애플리케이션(디플로이먼트와 서비스로 구성)을 배포하고, 인그레스 리소스를 생성하여 /a 경로로 들어오는 요청은 첫 번째 애플리케이션으로, /b 경로로 들어오는 요청은 두 번째 애플리케이션으로 라우팅하도록 설정해 볼 것입니다. 그리고 실제로 외부에서 단일 IP 주소(또는 localhost)를 통해 각 경로로 접속하여 라우팅이 올바르게 동작하는지 확인하며 인그레스의 강력한 경로 기반 라우팅 기능을 체감하는 시간을 가질 것입니다.
인그레스는 쿠버네티스에서 웹 기반 마이크로서비스들을 외부 세계에 노출하고 관리하는 데 있어 사실상의 표준으로 자리 잡고 있습니다. 이 장을 통해 인그레스의 개념과 활용법을 확실히 익히신다면, 복잡한 웹 애플리케이션 아키텍처도 쿠버네티스 위에서 훨씬 더 효율적이고 세련된 방식으로 운영할 수 있게 될 것입니다.
8.3.1 인그레스란 무엇인가?
쿠버네티스 클러스터 내에서 실행되는 애플리케이션들을 외부 세계에 노출하는 방법은 여러 가지가 있습니다. 앞서 살펴본 서비스(Service)의 NodePort나 LoadBalancer 타입도 그중 하나입니다. NodePort는 각 노드의 특정 포트를 통해 서비스를 노출하지만, 고가용성이나 사용자 친화적인 접근을 위해서는 별도의 외부 로드 밸런서가 필요합니다. LoadBalancer 타입은 클라우드 환경에서 외부 로드 밸런서를 자동으로 생성해주지만, 이는 주로 L4(TCP/UDP) 레벨에서 동작하며, 각 서비스마다 별도의 로드 밸런서와 공인 IP 주소가 할당되어 비용 효율성이 떨어질 수 있다는 단점이 있습니다. 특히 HTTP(S) 기반의 웹 서비스가 많아질수록 이러한 문제점은 더욱 두드러집니다.
바로 이러한 상황에서 인그레스(Ingress)가 그 진가를 발휘합니다. 인그레스는 클러스터 외부에서 들어오는 HTTP 및 HTTPS 트래픽을 관리하고, 이를 클러스터 내부의 적절한 서비스로 라우팅하는 API 오브젝트입니다. 마치 클러스터의 정문에 위치한 매우 지능적인 안내 데스크와 같아서, 방문객(HTTP 요청)의 목적지(URL 경로, 호스트 헤더 등)를 파악하여 가장 적절한 부서(서비스)로 안내해주는 역할을 합니다.

인그레스는 단순히 트래픽을 전달하는 것을 넘어, L7(애플리케이션 계층) 레벨의 다양한 라우팅 규칙, SSL/TLS 암호화 처리, 가상 호스팅 등 웹 서비스 운영에 필수적인 고급 기능들을 제공함으로써, 서비스 노출을 훨씬 더 유연하고 효율적이며 비용 효과적으로 만들어줍니다.
그렇다면 인그레스가 구체적으로 어떤 역할들을 수행하며, 다른 서비스 노출 방식에 비해 어떤 장점들을 가지고 있는지 자세히 살펴보겠습니다.
8.3.1.1 단일 IP 주소로 다수 서비스 노출
쿠버네티스 환경에서 개발되고 운영되는 애플리케이션들은 종종 다수의 마이크로서비스로 구성되어 각기 다른 기능을 수행합니다. 이러한 각 마이크로서비스를 외부 세계에 노출해야 할 때, 전통적인 방식이나 쿠버네티스의 LoadBalancer 타입 서비스를 개별적으로 사용한다면 여러 가지 비효율과 관리의 복잡성에 직면하게 됩니다. 바로 이 지점에서 인그레스(Ingress)가 제공하는 가장 핵심적인 장점 중 하나인 “단일 외부 IP 주소(또는 단일 로드 밸런서)를 통해 클러스터 내의 여러 다양한 서비스들을 통합적으로 외부로 노출”할 수 있는 능력이 빛을 발합니다. 이는 마치 하나의 거대한 백화점 건물(단일 외부 IP 주소를 가진 진입점)이 정문 하나만을 가지고 있으면서도, 그 안에는 의류, 식품, 가전제품 등 수많은 종류의 독립된 상점(클러스터 내부의 개별 마이크로서비스)들이 입점해 있고, 방문객들은 이 정문을 통해 들어와 안내판이나 내부 경로(인그레스 라우팅 규칙)에 따라 원하는 상점을 찾아갈 수 있는 것과 매우 유사한 개념입니다.
만약 우리가 쿠버네티스의 LoadBalancer 타입 서비스를 사용하여 클러스터 내의 각 마이크로서비스(예: 사용자 인증 서비스, 상품 정보 서비스, 주문 처리 서비스, 고객 지원 서비스 등)를 개별적으로 외부 인터넷에 노출한다고 가정해 봅시다. 이 경우, 각 서비스마다 별도의 외부 로드 밸런서 인스턴스가 프로비저닝되고, 각 로드 밸런서에는 고유한 공인 IP 주소가 할당됩니다. 예를 들어, 10개의 독립적인 마이크로서비스를 LoadBalancer 타입으로 노출한다면, 이론적으로 10개의 서로 다른 외부 로드 밸런서와 10개의 공인 IP 주소가 필요하게 됩니다. 이러한 방식은 다음과 같은 명확한 단점들을 야기합니다.
- 클라우드 비용 증가: 대부분의 퍼블릭 클라우드 제공업체(AWS, GCP, Azure 등)는 외부 로드 밸런서 인스턴스 사용 및 공인 IP 주소 할당에 대해 시간당 또는 월별 요금을 부과합니다. 서비스의 수가 증가함에 따라 이러한 비용은 선형적으로, 때로는 그 이상으로 증가하여 전체 클라우드 인프라 운영 비용에 상당한 부담을 줄 수 있습니다.
- 공인 IP 주소 고갈 및 관리 복잡성: IPv4 주소는 이미 고갈 상태에 가까워지고 있으며, 각 서비스마다 공인 IP를 할당하는 것은 귀중한 IP 자원의 낭비로 이어질 수 있습니다. 또한, 이렇게 다수의 공인 IP 주소를 관리하고, 각 IP 주소에 대한 DNS 레코드(예: A 레코드 또는 CNAME 레코드)를 설정하며, 방화벽 규칙을 개별적으로 적용하는 것은 매우 번거롭고 오류가 발생하기 쉬운 운영 작업입니다.
- 확장성 및 유연성 저하: 새로운 마이크로서비스를 추가하거나 기존 서비스를 변경할 때마다 새로운 로드 밸런서를 프로비저닝하거나 기존 설정을 수정해야 하므로, 시스템의 확장성과 변경에 대한 민첩성이 떨어질 수 있습니다.
하지만 인그레스를 도입하면 이러한 문제점들을 효과적으로 해결할 수 있습니다. 인그레스는 일반적으로 단 하나의 외부 로드 밸런서(이 로드 밸런서는 실제로는 인그레스 컨트롤러 파드가 외부로 노출되는 지점 역할을 합니다)를 통해 클러스터로 들어오는 모든 HTTP 및 HTTPS 트래픽을 수신합니다. 그리고 인그레스 리소스에 정의된 다양한 라우팅 규칙(예: 호스트 이름, URL 경로 기반)에 따라 이 수신된 트래픽을 내부의 각기 다른 서비스로 지능적으로 분배하고 전달합니다. 즉, 외부에서 볼 때는 단일한 진입점(IP 주소 또는 도메인 이름)만이 존재하지만, 내부적으로는 이 진입점을 통해 수많은 개별 서비스들이 마치 독립된 것처럼 외부에 서비스를 제공할 수 있게 되는 것입니다.
이러한 “단일 IP 주소(또는 단일 로드 밸런서)를 통한 다수 서비스 노출” 방식은 다음과 같은 구체적이고 강력한 이점들을 제공합니다.
- 획기적인 비용 절감: 가장 직접적인 효과는 필요한 외부 로드 밸런서 인스턴스와 공인 IP 주소의 수를 극단적으로 줄일 수 있다는 점입니다. 이론적으로 단 하나의 로드 밸런서와 IP 주소만으로도 수십, 수백 개의 HTTP(S) 기반 마이크로서비스를 외부로 노출할 수 있으므로, 클라우드 인프라 비용을 크게 절감할 수 있습니다. 이는 특히 예산이 제한적인 스타트업이나 비용 최적화가 중요한 대규모 시스템에서 매우 중요한 고려 사항입니다.
- IP 주소 및 DNS 관리의 단순화: 관리해야 할 외부 IP 주소의 수가 현저히 줄어들기 때문에, DNS 레코드 설정(예: 와일드카드 DNS 레코드 *.example.com을 단일 인그레스 IP로 향하게 설정) 및 IP 주소 할당/회수 관리가 훨씬 단순하고 용이해집니다. 이는 운영팀의 업무 부담을 줄이고, 설정 오류로 인한 서비스 장애 가능성을 낮추는 데 기여합니다.
- 통합된 단일 진입점(Single Entry Point) 제공: 클러스터로 들어오는 모든 웹 서비스 트래픽이 하나의 논리적인 지점(인그레스 컨트롤러)을 통해 유입되므로, 이 지점에서 트래픽 모니터링, 실시간 로깅, 접근 제어, 웹 애플리케이션 방화벽(WAF) 적용, DDoS 공격 방어 등과 같은 보안 및 운영 관련 정책을 중앙에서 일관되게 적용하고 관리하기가 매우 용이해집니다. 각 서비스별로 이러한 정책을 개별적으로 설정하고 관리하는 것보다 훨씬 효율적이고 강력한 통제력을 확보할 수 있습니다.
- TLS/SSL 인증서 관리의 중앙화: 여러 도메인 또는 하위 도메인에 대한 TLS/SSL 인증서도 인그레스 컨트롤러 한 곳에서 중앙 집중적으로 관리하고, TLS 종료(Termination)를 수행할 수 있습니다. 이를 통해 인증서 발급, 갱신, 적용 과정을 단순화하고, 각 백엔드 서비스는 암호화/복호화 부담에서 벗어나 핵심 비즈니스 로직 처리에 집중할 수 있게 됩니다. (이는 8.3.1.3에서 더 자세히 다룰 예정입니다.)
이처럼 인그레스는 여러 개의 독립적인 서비스를 마치 하나의 통합된 애플리케이션처럼 외부 사용자에게 효율적으로 제공할 수 있도록 지원함으로써, 인프라 자원의 효율적인 사용을 극대화하고 운영의 복잡성을 크게 낮추는 데 핵심적인 역할을 합니다. 이는 특히 오늘날과 같이 수많은 마이크로서비스로 구성된 복잡하고 동적인 애플리케이션 환경에서 그 가치가 더욱 빛을 발하는 매우 유용한 특징이라고 할 수 있습니다. 따라서 쿠버네티스에서 HTTP(S) 기반 서비스를 외부로 노출할 때는 LoadBalancer 타입의 서비스보다는 인그레스를 우선적으로 고려하는 것이 일반적인 모범 사례로 여겨지고 있습니다.
8.3.1.2 L7 로드 밸런싱 (URL 경로, 호스트 기반 라우팅)
쿠버네티스에서 서비스를 외부로 노출하는 방법 중 하나인 LoadBalancer 타입의 서비스는 매우 유용하지만, 주로 OSI 7계층 모델의 L4(전송 계층) 수준에서 동작합니다. 이는 주로 IP 주소와 TCP/UDP 포트 번호만을 기준으로 들어오는 트래픽을 백엔드 파드들로 분배하는 역할을 한다는 의미입니다. L4 로드 밸런서는 빠르고 효율적이지만, 애플리케이션 자체의 내용이나 HTTP(S) 프로토콜의 세부적인 정보(예: URL 경로, HTTP 헤더)를 이해하고 이를 기반으로 트래픽을 지능적으로 제어하는 데는 한계가 있습니다.
반면, 인그레스(Ingress)는 OSI 모델의 L7(애플리케이션 계층)에서 동작하며, 바로 이 L4 로드 밸런서의 한계를 극복하고 훨씬 더 정교하며 유연한 로드 밸런싱 및 라우팅 규칙을 제공합니다. 인그레스는 HTTP(S) 프로토콜의 구조와 내용을 깊이 이해하고, 들어오는 각 요청의 헤더 정보(예: 요청된 호스트 이름, 접근하려는 URL 경로, 쿠키, 사용자 에이전트 등)를 분석하여, 이 정보를 바탕으로 어떤 내부 서비스로 트래픽을 전달할지 결정할 수 있는 능력을 갖추고 있습니다. 이는 마치 우편물을 단순히 주소지만 보고 배달하는 것이 아니라, 편지 봉투 안에 적힌 수신 부서나 담당자 이름까지 확인하여 정확한 목적지로 전달하는 것과 같습니다.
인그레스가 제공하는 대표적이면서도 매우 강력한 L7 라우팅 기능은 다음과 같습니다.
호스트 기반 라우팅 (Host-based Routing) 또는 가상 호스팅 (Virtual Hosting):
이 기능은 동일한 외부 IP 주소와 동일한 포트(일반적으로 HTTP의 경우 80번, HTTPS의 경우 443번)로 여러 개의 서로 다른 웹사이트나 애플리케이션 서비스를 동시에 호스팅할 수 있게 해줍니다. 인그레스 컨트롤러는 들어오는 HTTP 요청의 Host 헤더 값을 확인하여, 이 헤더에 명시된 도메인 이름(예: api.example.com, blog.example.com, shop.example.com)에 따라 요청을 각각 다른 내부 쿠버네티스 서비스로 라우팅합니다.
예를 들어, 다음과 같은 시나리오를 상상해 볼 수 있습니다.
- 사용자가 웹 브라우저에 https://api.example.com/users를 입력하면, 인그레스 컨트롤러는 Host 헤더가 api.example.com인 것을 확인하고 이 요청을 api-service라는 내부 서비스로 전달합니다.
- 다른 사용자가 https://blog.example.com/latest-posts를 입력하면, 인그레스 컨트롤러는 Host 헤더가 blog.example.com인 것을 보고 이 요청을 blog-service라는 다른 내부 서비스로 전달합니다.
- 또 다른 사용자가 https://shop.example.com/products/123을 입력하면, Host 헤더 shop.example.com에 따라 shop-service로 요청이 전달됩니다.이는 마치 하나의 물리적인 웹 서버에서 여러 개의 서로 다른 도메인 이름을 가진 웹사이트들을 동시에 운영하는 전통적인 가상 호스팅 기술과 유사한 개념을 쿠버네티스 환경에서 구현하는 것입니다. 이 기능을 통해 기업은 단일 외부 로드 밸런서와 IP 주소만으로도 여러 부서의 웹사이트, 다양한 마이크로서비스 엔드포인트, 또는 서로 다른 고객사를 위한 애플리케이션 인스턴스들을 효율적으로 관리하고 서비스할 수 있게 됩니다. 이는 IP 주소 자원의 절약뿐만 아니라 DNS 관리의 단순화라는 부수적인 이점도 가져다줍니다.
경로 기반 라우팅 (Path-based Routing):
호스트 기반 라우팅이 서로 다른 도메인 이름을 구분하는 것이라면, 경로 기반 라우팅은 동일한 도메인 이름으로 들어오는 요청이라도 URL의 경로(path) 부분에 따라 요청을 서로 다른 내부 서비스로 분기시키는 기능입니다. 인그레스 컨트롤러는 요청 URL에서 호스트 이름 뒤에 오는 경로 문자열을 분석하고, 미리 정의된 규칙에 따라 가장 적합한 백엔드 서비스를 선택합니다.
예를 들어, www.example.com이라는 단일 호스트 이름을 사용하는 애플리케이션이 있고, 이 애플리케이션은 여러 마이크로서비스로 구성되어 있다고 가정해 봅시다.
- https://www.example.com/api/users 와 같이 /api/users로 시작하는 모든 요청은 사용자 정보를 처리하는 user-api-service로 전달됩니다.
- https://www.example.com/api/products 와 같이 /api/products로 시작하는 모든 요청은 상품 정보를 처리하는 product-api-service로 전달됩니다.
- https://www.example.com/blog/ 와 같이 /blog/로 시작하는 모든 요청은 블로그 콘텐츠를 제공하는 blog-app-service로 전달됩니다.
- 그리고 위 규칙들에 해당하지 않는 https://www.example.com/ (기본 경로 또는 루트 경로) 요청은 메인 웹사이트를 제공하는 main-website-service로 전달될 수 있습니다.이는 마치 대형 쇼핑몰의 안내 데스크에서 고객이 찾고자 하는 물품의 종류(경로)를 듣고, “가전제품은 3층, 의류는 2층, 식품은 지하 1층으로 가세요”라고 각기 다른 매장(서비스)으로 안내하는 것과 같습니다. 이 기능을 통해 개발팀은 하나의 통합된 애플리케이션 도메인 주소 체계 내에서도 각 마이크로서비스들을 기능별로 논리적으로 분리하고, 독립적으로 개발, 배포, 확장할 수 있는 유연성을 확보하게 됩니다. 또한, API 게이트웨이 패턴을 구현하는 데 있어 핵심적인 역할을 수행하기도 합니다.
이러한 정교한 L7 라우팅 기능은 특히 마이크로서비스 아키텍처(MSA)를 채택한 현대적인 애플리케이션 환경에서 그 중요성이 더욱 커집니다. MSA에서는 수많은 작고 독립적인 서비스들이 각자의 고유한 기능과 API 엔드포인트(URL 경로)를 가질 수 있는데, 인그레스는 이러한 개별 서비스들을 외부 사용자에게 일관되고 체계적인 방식으로 노출하는 통합된 관문 역할을 수행합니다. 사용자는 복잡한 내부 서비스 구조를 알 필요 없이, 잘 정의된 외부 URL을 통해 필요한 기능에 접근할 수 있게 되는 것입니다.
뿐만 아니라, 인그레스의 L7 라우팅 기능은 A/B 테스팅, 카나리 배포(Canary Release), 블루/그린 배포(Blue/Green Deployment)와 같이 특정 사용자 그룹이나 특정 비율의 트래픽만을 새로운 버전의 서비스로 점진적으로 전환하여 테스트하거나 배포하는 고급 배포 전략을 구현하는 데도 매우 효과적으로 활용될 수 있습니다. 예를 들어, 특정 HTTP 헤더(예: User-Agent 또는 커스텀 헤더) 값을 기준으로 트래픽을 분기하거나, 요청의 가중치(weight)를 두어 일부 트래픽만 새로운 버전으로 보내는 등의 복잡한 시나리오를 인그레스 규칙과 인그레스 컨트롤러의 기능을 통해 구현할 수 있습니다.
결론적으로, 인그레스가 제공하는 L7 로드 밸런싱 및 라우팅 기능은 단순한 트래픽 전달을 넘어, 애플리케이션의 구조를 반영하고, 사용자 경험을 향상시키며, 배포 전략의 유연성을 높이는 등 쿠버네티스 환경에서 웹 서비스를 운영하는 데 있어 핵심적인 가치를 제공합니다. 이는 쿠버네티스를 단순한 컨테이너 오케스트레이션 도구를 넘어, 지능적인 애플리케이션 딜리버리 플랫폼으로 만들어주는 중요한 요소 중 하나라고 할 수 있습니다.
8.3.1.3 SSL/TLS 종료 (Termination)
오늘날 우리가 인터넷을 통해 주고받는 정보의 민감성과 중요성이 그 어느 때보다 강조되면서, 웹 서비스에서의 보안은 선택이 아닌 필수가 되었습니다. 특히 사용자의 개인 정보, 금융 정보, 로그인 자격 증명 등과 같이 민감한 데이터가 오가는 통신은 반드시 암호화되어야 하며, 이를 위해 HTTPS(HyperText Transfer Protocol Secure) 프로토콜이 널리 사용됩니다. HTTPS는 HTTP 통신을 SSL(Secure Sockets Layer) 또는 TLS(Transport Layer Security) 프로토콜을 사용하여 암호화함으로써, 데이터가 중간에 가로채이거나 변조되는 것을 방지하고 통신의 기밀성과 무결성을 보장합니다.
쿠버네티스 환경에서 이러한 HTTPS 기반의 안전한 웹 서비스를 제공하고자 할 때, 인그레스(Ingress)는 매우 중요한 역할인 SSL/TLS 종료(Termination) 기능을 제공하여 암호화 통신의 관리 효율성과 시스템 성능을 크게 향상시킬 수 있도록 지원합니다.
SSL/TLS 종료란, 외부 클라이언트(예: 사용자의 웹 브라우저)와 쿠버네티스 클러스터의 진입점 역할을 하는 인그레스 컨트롤러 사이의 네트워크 통신은 암호화된 HTTPS 프로토콜을 사용하지만, 인그레스 컨트롤러와 클러스터 내부의 실제 백엔드 애플리케이션 파드(Pod) 사이의 통신은 암호화되지 않은 일반 HTTP 프로토콜을 사용하도록 구성하는 방식을 의미합니다. 다시 말해, 클라이언트로부터 들어오는 암호화된 HTTPS 트래픽은 인그레스 컨트롤러에서 복호화(decryption)되고, 이 복호화된 HTTP 트래픽이 내부 서비스로 전달됩니다. 반대로, 내부 서비스로부터 나오는 응답은 인그레스 컨트롤러에서 다시 암호화(encryption)되어 클라이언트에게 HTTPS로 전달됩니다. 즉, SSL/TLS 핸드셰이크 및 암호화/복호화와 관련된 모든 부담스러운 작업이 인그레스 컨트롤러 레벨에서 처리되고 종료(terminated)되는 것입니다.
인그레스 컨트롤러에서 SSL/TLS 종료를 수행하는 방식은 다음과 같은 실질적이고 강력한 장점들을 제공합니다.
- **SSL/TLS 인증서의 중앙 집중적 관리:**웹사이트나 서비스가 HTTPS 통신을 하기 위해서는 공인된 인증 기관(Certificate Authority, CA)으로부터 발급받은 SSL/TLS 인증서(공개키, 개인키 포함)가 필요합니다. 만약 클러스터 내의 여러 마이크로서비스 각각이 개별적으로 HTTPS를 지원해야 한다면, 각 서비스(또는 파드)마다 별도의 인증서를 설치하고, 주기적으로 갱신하며, 보안 설정을 관리해야 하는 엄청난 운영 부담이 발생합니다.하지만 인그레스를 사용하면, 이러한 모든 서비스에 대한 SSL/TLS 인증서를 인그레스 컨트롤러 한 곳에서 중앙 집중적으로 관리할 수 있습니다. 쿠버네티스는 시크릿(Secret)이라는 특별한 오브젝트를 제공하여 SSL/TLS 인증서와 개인키와 같은 민감한 정보를 안전하게 저장할 수 있도록 하며, 인그레스 리소스 명세의 tls 필드를 통해 이 시크릿을 참조하여 특정 호스트 이름(도메인)에 대한 HTTPS 통신을 활성화할 수 있습니다. 이를 통해 인증서 발급, 배포, 갱신(예: Let’s Encrypt와 cert-manager 통합을 통한 자동 갱신) 등의 관리 작업을 훨씬 더 단순하고 효율적으로 수행할 수 있으며, 인증서 관리 오류로 인한 서비스 중단 위험도 줄일 수 있습니다.
- **백엔드 애플리케이션 파드의 부담 경감 및 성능 향상:**SSL/TLS 핸드셰이크 과정 및 데이터 암호화/복호화 작업은 생각보다 상당한 CPU 연산 자원을 소모합니다. 만약 이러한 작업을 클러스터 내부의 모든 백엔드 애플리케이션 파드들이 직접 수행해야 한다면, 각 파드는 자신의 핵심 비즈니스 로직을 처리하는 데 사용할 수 있는 CPU 자원의 일부를 암호화 연산에 할애해야 하므로 전반적인 애플리케이션 성능 저하로 이어질 수 있습니다.인그레스 컨트롤러에서 SSL/TLS 종료를 수행하면, 이러한 계산 집약적인 암호화 관련 작업을 인그레스 컨트롤러가 전담하게 됩니다. 결과적으로, 실제 애플리케이션 로직을 수행하는 백엔드 파드들은 암호화 관련 부담에서 완전히 벗어나, 오직 자신들의 핵심 기능 처리에만 CPU 자원을 집중할 수 있게 되어 애플리케이션의 응답 속도 및 전체 처리량(throughput) 향상에 긍정적인 영향을 미칠 수 있습니다. 특히 트래픽이 많은 대규모 서비스에서는 이러한 성능상의 이점이 더욱 두드러지게 나타날 수 있습니다.
- **일관된 SSL/TLS 보안 정책 적용 및 관리 용이성:**모든 외부 HTTPS 트래픽이 인그레스 컨트롤러라는 단일 지점을 통해 클러스터로 유입되므로, 이 지점에서 SSL/TLS 프로토콜 버전(예: TLS 1.2, TLS 1.3 강제), 허용할 암호화 스위트(cipher suite) 목록, HSTS(HTTP Strict Transport Security) 헤더 설정 등과 같은 보안 관련 설정을 일관되게 적용하고 관리하기가 매우 용이해집니다. 각 서비스별로 보안 설정을 개별적으로 관리하는 것보다 훨씬 더 강력하고 통일된 보안 정책을 유지할 수 있으며, 새로운 보안 취약점이 발견되었을 때도 인그레스 컨트롤러 레벨에서 신속하게 대응할 수 있습니다.
- **최신 프로토콜(HTTP/2, gRPC 등)로의 유연한 전환 지원:**일부 고급 인그레스 컨트롤러(예: Nginx Ingress Controller, Traefik, Envoy 기반 컨트롤러 등)는 SSL/TLS 종료 지점에서 클라이언트와는 HTTP/2 또는 gRPC와 같은 최신 고성능 프로토콜로 통신하면서, 내부 백엔드 애플리케이션과는 기존의 HTTP/1.1 프로토콜로 통신하도록 프로토콜 변환(protocol translation)을 지원하기도 합니다. 이를 통해 백엔드 애플리케이션 코드를 크게 변경하지 않고도, 외부 사용자에게는 최신 프로토콜의 장점(예: 멀티플렉싱, 헤더 압축, 서버 푸시 등)을 제공하여 사용자 경험을 향상시킬 수 있는 유연성을 확보할 수 있습니다.
물론, 모든 상황에서 인그레스에서의 SSL/TLS 종료가 최선의 선택은 아닐 수 있습니다. 예를 들어, 클러스터 내부 네트워크의 보안 수준을 신뢰할 수 없거나, 규제 준수 요구사항 등으로 인해 애플리케이션 서버까지의 엔드-투-엔드 암호화(end-to-end encryption)가 반드시 필요한 경우에는 인그레스에서 SSL/TLS 연결을 그대로 통과(pass-through)시키고, 각 백엔드 파드에서 직접 SSL/TLS를 처리하도록 구성해야 합니다. 이러한 방식을 SSL/TLS 패스스루(pass-through)라고 하며, 이 경우 인그레스 컨트롤러는 L4 레벨에서 TCP 트래픽을 단순히 전달하는 역할만 수행하게 됩니다.
하지만 대부분의 일반적인 웹 서비스 시나리오에서는, 인그레스 컨트롤러에서의 SSL/TLS 종료 방식이 운영의 효율성, 성능상의 이점, 그리고 관리의 편의성 측면에서 훨씬 더 많은 장점을 제공합니다. 이는 쿠버네티스 환경에서 HTTPS 기반의 안전하고 효율적인 웹 서비스를 구축하고 운영하는 데 있어 인그레스가 얼마나 중요한 역할을 하는지를 잘 보여주는 예시라고 할 수 있습니다.
8.3.2 인그레스 컨트롤러 (Ingress Controller)
앞서 우리는 인그레스(Ingress) 리소스가 어떻게 단일 IP 주소로 여러 서비스를 노출하고, L7 레벨의 정교한 라우팅 규칙을 정의하며, SSL/TLS 종료와 같은 중요한 기능을 수행하는지 살펴보았습니다. 하지만 여기서 한 가지 중요한 질문이 생깁니다. 우리가 YAML 파일로 정의한 이 인그레스 리소스의 ‘규칙’들은 과연 누가, 어떻게 실제로 해석하고 적용하여 외부 트래픽을 처리하는 걸까요? 인그레스 리소스 자체는 단지 우리가 “이러한 방식으로 트래픽을 처리해 달라”고 선언한 명세서일 뿐, 그 자체로 네트워크 트래픽을 직접 다루는 능력을 가지고 있지는 않습니다.
바로 이 지점에서 인그레스 컨트롤러(Ingress Controller)라는 핵심적인 컴포넌트가 등장합니다. 인그레스 컨트롤러는 쿠버네티스 클러스터 내에서 실행되면서, API 서버를 통해 인그레스 리소스의 변경 사항을 지속적으로 감시하고, 이 리소스에 정의된 규칙들을 실제 네트워크 트래픽 라우팅 로직으로 변환하여 적용하는 역할을 수행하는 일종의 ‘실행 엔진’ 또는 ‘번역가’와 같습니다. 마치 우리가 작성한 건축 설계도(인그레스 리소스)를 보고 실제로 건물을 짓는 시공사(인그레스 컨트롤러)와 같은 존재라고 생각할 수 있습니다.
인그레스 컨트롤러가 없다면, 우리가 아무리 멋진 인그레스 리소스를 만들어도 그것은 단지 종이 위의 계획에 불과하며, 외부 트래픽은 클러스터 내부의 서비스로 전달될 수 없습니다. 따라서 쿠버네티스 클러스터에서 인그레스를 사용하기 위해서는 반드시 하나 이상의 인그레스 컨트롤러가 설치되고 실행 중이어야 합니다.

8.3.2.1 인그레스 리소스 규칙을 실제 적용하는 컴포넌트
쿠버네티스에서 인그레스(Ingress) 리소스는 클러스터 외부에서 들어오는 HTTP(S) 트래픽을 내부 서비스로 어떻게 라우팅할지에 대한 ‘규칙의 집합’ 또는 ‘선언적 명세’를 정의합니다. 하지만 이 인그레스 리소스 자체는 마치 잘 작성된 건축 설계도와 같아서, 그 자체만으로는 실제 건물을 지을 수 없습니다. 이 설계도에 담긴 지시사항들을 실제로 해석하고, 그에 따라 벽돌을 쌓고 창문을 다는 등의 구체적인 작업을 수행하여 건물을 완성하는 시공사가 필요한 것처럼, 인그레스 리소스에 정의된 라우팅 규칙들을 실제로 네트워크 트래픽에 적용하여 원하는 동작을 구현하는 핵심적인 실행 주체가 바로 인그레스 컨트롤러(Ingress Controller)입니다.
인그레스 컨트롤러의 가장 기본적인 역할은 한마디로 쿠버네티스 API 서버를 통해 인그레스 리소스의 생성, 변경, 삭제와 같은 모든 이벤트를 지속적으로 감시(watch)하고, 이 리소스에 명시된 라우팅 규칙, 호스트 이름 설정, TLS(SSL) 구성 정보 등을 자신이 내부적으로 관리하는 실제 로드 밸런서(예: Nginx, HAProxy, Envoy 등)의 설정으로 동적으로 변환하여 적용함으로써, 외부 트래픽이 선언된 규칙에 따라 올바르게 처리되도록 보장하는 것입니다. 이 일련의 과정은 일반적으로 다음과 같은 정교한 단계들을 거쳐 이루어집니다.
- **인그레스 리소스의 지속적인 감시 (Watching Ingress Resources):**인그레스 컨트롤러는 쿠버네티스 클러스터 내에서 데몬셋(DaemonSet)이나 디플로이먼트(Deployment) 형태로 실행되는 하나 이상의 파드(Pod)로 구성됩니다. 이 컨트롤러 파드들은 시작과 동시에 쿠버네티스 API 서버에 접속하여, 클러스터 내에 생성되거나, 내용이 수정되거나, 또는 삭제되는 모든 인그레스 리소스 객체들의 변경 사항을 실시간으로 감시하는 ‘워치(watch)’ 메커니즘을 설정합니다. 이를 통해 인그레스 컨트롤러는 항상 최신의 인그레스 규칙 정보를 유지할 수 있습니다.최신 쿠버네티스 버전에서는 spec.ingressClassName 필드를 사용하여 특정 인그레스 컨트롤러가 어떤 인그레스 리소스들을 담당할지 명시적으로 지정할 수 있습니다. 이를 통해 클러스터 내에 여러 종류의 인그레스 컨트롤러가 공존하면서 각자의 역할을 수행하는 것도 가능해집니다. 인그레스 컨트롤러는 자신이 담당해야 할 ingressClassName을 가진 인그레스 리소스만을 선택적으로 처리하게 됩니다.
- **라우팅 규칙 및 TLS 구성의 정밀한 해석 (Parsing Rules and TLS Configuration):**새로운 인그레스 리소스가 API 서버를 통해 감지되거나 기존 리소스의 내용이 변경되면, 인그레스 컨트롤러는 해당 리소스의 spec 필드에 정의된 복잡한 규칙들을 상세하게 파싱하고 해석합니다. 여기에는 다음과 같은 정보들이 포함될 수 있습니다.
- 호스트 기반 라우팅 규칙: 특정 호스트 이름(예: api.example.com, blog.example.com)으로 들어오는 요청을 처리할 규칙들.
- 경로 기반 라우팅 규칙: 특정 URL 경로(예: /api/v1, /images, /admin)로 들어오는 요청을 어떤 백엔드 서비스의 어떤 포트로 전달할지에 대한 정의. 경로 매칭 방식(예: 정확히 일치, 접두사 일치, 정규 표현식 사용 등)도 함께 고려됩니다.
- 기본 백엔드 (Default Backend): 어떤 규칙에도 해당하지 않는 요청을 처리할 기본 서비스 지정.
- TLS(SSL) 설정: spec.tls 필드에 정의된 호스트 이름들과 해당 호스트에 사용할 TLS 인증서 및 개인키가 저장된 쿠버네티스 시크릿(Secret)의 이름. 이를 통해 HTTPS 통신을 활성화하고 SSL/TLS 종료를 수행할 수 있습니다.
- 어노테이션(Annotations): 인그레스 리소스의 metadata.annotations 필드를 통해 인그레스 컨트롤러 종류별로 특화된 추가적인 설정(예: 리다이렉션 규칙, 인증 방식, 요청/응답 헤더 조작, 타임아웃 설정, CORS 설정 등)을 지시할 수 있습니다. 인그레스 컨트롤러는 이러한 어노테이션도 함께 해석하여 로드 밸런서 설정에 반영합니다.
- **내부 백엔드 로드 밸런서 구성의 동적 업데이트 (Dynamically Updating Backend Load Balancer Configuration):**해석된 라우팅 규칙과 TLS 구성, 그리고 어노테이션 정보들을 바탕으로, 인그레스 컨트롤러는 자신이 내부적으로 사용하는 실제 로드 밸런싱 엔진(소프트웨어)의 설정을 동적으로 업데이트합니다. 예를 들어, 가장 널리 사용되는 Nginx Ingress Controller의 경우, 이 정보들을 기반으로 새로운 Nginx 설정 파일(nginx.conf)을 생성하거나 기존 파일을 수정합니다. 이 설정 파일에는 특정 server 블록(가상 호스트 정의), location 블록(경로 매칭 규칙), upstream 블록(백엔드 서비스의 파드 IP와 포트 목록), SSL/TLS 인증서 경로 지정 등이 포함됩니다. 설정 파일이 변경되면, Nginx Ingress Controller는 Nginx 마스터 프로세스에 시그널을 보내어 설정을 안전하게 다시 로드(graceful reload)하도록 하여, 서비스 중단 없이 새로운 라우팅 규칙이 적용되도록 합니다. 다른 인그레스 컨트롤러들(예: Traefik, HAProxy Ingress, Envoy 기반 컨트롤러)도 각자의 방식(API 호출, 내부 설정 관리 메커니즘 등)으로 이러한 동적 구성 업데이트를 수행합니다.
- **수신된 외부 트래픽의 지능적인 처리 및 라우팅 (Processing and Routing External Traffic):**이제 인그레스 컨트롤러 파드(이 파드 자체는 보통 LoadBalancer 타입의 서비스나 NodePort 서비스를 통해 외부 로드 밸런서와 연결되어 외부 트래픽을 수신합니다)로 들어오는 실제 외부 HTTP(S) 요청은, 새롭게 업데이트된 내부 로드 밸런서의 설정을 통해 인그레스 리소스에 정의된 규칙에 따라 적절한 내부 쿠버네티스 서비스로 정확하게 라우팅됩니다. 만약 특정 호스트와 경로에 대해 SSL/TLS 종료가 설정되어 있다면, 인그레스 컨트롤러는 이 단계에서 클라이언트와의 TLS 핸드셰이크를 수행하고, 암호화된 트래픽을 복호화하여 내부 서비스로 전달하며, 내부 서비스로부터의 응답은 다시 암호화하여 클라이언트에게 전송하는 작업을 담당합니다.
- **백엔드 엔드포인트의 지속적인 추적 및 상태 관리 (Tracking and Managing Backend Endpoints):**인그레스 컨트롤러는 단순히 인그레스 리소스에 정의된 정적인 규칙만을 따르는 것이 아닙니다. 인그레스 규칙에서 참조하는 각 백엔드 쿠버네티스 서비스(Service)와, 해당 서비스가 실제로 가리키고 있는 파드들의 IP 주소와 포트 정보가 담긴 엔드포인트(Endpoints) 오브젝트 또는 더 최신 방식인 엔드포인트 슬라이스(EndpointSlices) 오브젝트의 변경 사항도 지속적으로 감시합니다. 만약 특정 서비스의 백엔드 파드 수가 스케일 아웃/인에 따라 변경되거나, 파드가 재시작되어 IP 주소가 바뀌거나, 또는 파드가 비정상 상태가 되어 서비스 엔드포인트 목록에서 제외되면, 인그레스 컨트롤러는 이러한 변화를 즉시 인지하고 자신이 관리하는 내부 로드 밸런서의 업스트림(백엔드) 서버 목록을 실시간으로 업데이트합니다. 이를 통해 항상 건강하고 준비된(Ready) 상태의 파드들로만 트래픽이 지능적으로 전달되도록 보장하며, 서비스의 가용성과 응답성을 높이는 데 기여합니다.
이처럼 인그레스 컨트롤러는 쿠버네티스의 선언적 API 모델(사용자는 “원하는 상태”인 인그레스 리소스를 정의)과 실제 네트워크 트래픽 처리(내부 로드 밸런서가 실제 요청을 라우팅) 사이의 매우 중요한 가교 역할을 수행합니다. 사용자는 복잡한 로드 밸런서 설정이나 동적인 파드 IP 관리에 대해 직접 신경 쓸 필요 없이, 인그레스 리소스를 통해 원하는 L7 라우팅 정책을 선언하기만 하면, 인그레스 컨트롤러가 그 상태를 달성하고 지속적으로 유지하기 위해 필요한 모든 복잡한 작업을 내부적으로 자동화하여 처리해주는 것입니다.
여기서 반드시 기억해야 할 중요한 점은, 쿠버네티스 자체는 기본적으로 어떤 특정한 인그레스 컨트롤러 구현체를 제공하지 않는다는 것입니다. (물론, 일부 관리형 쿠버네티스 서비스나 특정 쿠버네티스 배포판들은 편의를 위해 기본 인그레스 컨트롤러를 사전 설치하여 제공할 수도 있습니다.) 따라서 대부분의 경우, 사용자는 자신의 클러스터 환경, 애플리케이션 요구사항, 성능 목표, 그리고 운영 선호도 등을 고려하여 적합한 인그레스 컨트롤러(예: Nginx Ingress, Traefik, HAProxy Ingress, Contour 등)를 직접 선택하여 클러스터에 설치하고 구성해야 합니다. 만약 클러스터에 어떠한 인그레스 컨트롤러도 설치되어 있지 않다면, 사용자가 인그레스 리소스를 생성하더라도 이는 단지 API 서버에 저장된 선언적인 객체일 뿐, 실제로 외부 트래픽을 처리하거나 라우팅 규칙을 적용하는 어떠한 효과도 발생하지 않습니다. 따라서 인그레스 기능을 활용하기 위한 첫 번째 단계는 바로 적절한 인그레스 컨트롤러를 클러스터에 배포하는 것입니다.
8.3.2.2 대표적인 인그레스 컨트롤러 (Nginx Ingress, Traefik 등)
쿠버네티스 생태계에는 다양한 종류의 인그레스 컨트롤러 구현체들이 존재하며, 각각 서로 다른 기반 기술, 특징, 그리고 장단점을 가지고 있습니다. 어떤 인그레스 컨트롤러를 선택하느냐에 따라 인그레스 기능의 범위, 성능, 설정 방법 등이 크게 달라질 수 있으므로, 신중한 선택이 필요합니다. 대표적으로 널리 사용되거나 주목받는 인그레스 컨트롤러들은 다음과 같습니다.
- Nginx Ingress Controller (NGINX 인그레스 컨트롤러):
- 아마도 가장 널리 사용되고 커뮤니티 지원이 활발한 인그레스 컨트롤러 중 하나일 것입니다. 쿠버네티스 커뮤니티에서 유지보수하는 버전(kubernetes/ingress-nginx)과 NGINX, Inc.에서 직접 지원하는 버전(nginxinc/kubernetes-ingress) 두 가지 주요 구현체가 있습니다. (기능과 설정 방식에 약간의 차이가 있을 수 있습니다.)
- 매우 안정적이고 성능이 뛰어난 웹 서버 및 리버스 프록시 소프트웨어인 NGINX를 기반으로 동작합니다. 인그레스 리소스의 규칙을 NGINX 설정으로 변환하고, NGINX를 통해 실제 트래픽 라우팅 및 로드 밸런싱을 수행합니다.
- 호스트 기반 라우팅, 경로 기반 라우팅, SSL/TLS 종료, 리다이렉션, 헤더 조작, 인증, 요청 속도 제한 등 NGINX가 제공하는 풍부하고 강력한 기능들을 대부분 활용할 수 있습니다. 커스텀 설정을 위한 어노테이션(annotation)도 다양하게 지원합니다.
- 오랜 기간 검증된 안정성과 풍부한 기능, 방대한 사용자 커뮤니티 덕분에 많은 프로덕션 환경에서 선호되는 선택지입니다.
- Traefik Proxy (트래픽 프록시, 이전 Traefik Ingress Controller):
- Traefik Labs에서 개발한 현대적인 HTTP 리버스 프록시 및 로드 밸런서로, 특히 클라우드 네이티브 환경과 마이크로서비스 아키텍처를 위해 설계되었습니다. 쿠버네티스 인그레스 컨트롤러 기능도 강력하게 지원합니다.
- 자동 서비스 디스커버리 기능이 매우 뛰어납니다. 쿠버네티스 API 서버와 직접 통합되어 인그레스 리소스뿐만 아니라 서비스, 엔드포인트 등의 변경 사항을 실시간으로 감지하고 자신의 라우팅 설정을 동적으로 업데이트합니다. 이 과정에서 별도의 설정 파일 리로드가 거의 필요 없어 매우 빠르고 유연하게 동작합니다.
- Let’s Encrypt를 통한 자동 SSL/TLS 인증서 발급 및 갱신 기능을 내장하고 있어 HTTPS 설정이 매우 간편합니다. 또한, 다양한 미들웨어(예: 인증, 헤더 조작, 압축 등)를 지원하며, 웹 기반 대시보드를 통해 현재 라우팅 상태와 통계 정보를 쉽게 확인할 수 있습니다.
- 설정이 비교적 간편하고, 동적인 환경 변화에 빠르게 대응할 수 있으며, 다양한 편의 기능을 제공하여 개발자 친화적인 인그레스 컨트롤러로 평가받습니다.
- HAProxy Ingress Controller (HAProxy 인그레스 컨트롤러):
- 고성능 TCP/HTTP 로드 밸런서로 잘 알려진 HAProxy를 기반으로 하는 인그레스 컨트롤러입니다.
- HAProxy 자체의 뛰어난 성능과 안정성, 그리고 다양한 로드 밸런싱 알고리즘 및 세션 지속성(session persistence) 기능 등을 활용할 수 있습니다. TCP 레벨의 로드 밸런싱이 필요한 경우에도 유용하게 사용될 수 있습니다.
- NGINX Ingress Controller와 유사하게 인그레스 규칙을 HAProxy 설정으로 변환하여 적용하며, 대규모 트래픽 처리나 특정 고급 로드 밸런싱 요구사항이 있는 환경에서 고려될 수 있습니다.
- Envoy 기반 인그레스 컨트롤러 (예: Contour, Ambassador/Emissary-ingress, Istio Ingress Gateway):
- Lyft에서 개발하고 CNCF의 Graduated 프로젝트가 된 Envoy Proxy는 고성능, 고확장성의 L7 프록시 및 통신 버스입니다. Istio, App Mesh와 같은 서비스 메쉬의 데이터 플레인으로 널리 사용되며, 이를 기반으로 하는 여러 인그레스 컨트롤러 구현체들이 있습니다.
- Contour: VMware에서 주도하는 Envoy 기반 인그레스 컨트롤러로, 동적 구성 업데이트와 안전한 멀티테넌시 지원에 중점을 둡니다.
- Ambassador (현 Emissary-ingress): Datawire에서 개발한 API 게이트웨이 기능을 겸하는 Envoy 기반 인그레스 컨트롤러로, 개발자 친화적인 설정 방식과 다양한 인증/인가, 요청 변환 기능 등을 제공합니다.
- Istio Ingress Gateway: 서비스 메쉬인 Istio의 일부로 제공되는 Envoy 기반 게이트웨이로, Istio의 강력한 트래픽 관리, 보안, 관찰성 기능을 클러스터 외부 트래픽에 대해서도 동일하게 적용할 수 있게 해줍니다.
- Envoy 기반 컨트롤러들은 일반적으로 매우 유연하고 확장 가능하며, 복잡한 트래픽 관리 시나리오나 서비스 메쉬와의 긴밀한 통합이 필요한 경우에 강력한 선택이 될 수 있습니다.
- Lyft에서 개발하고 CNCF의 Graduated 프로젝트가 된 Envoy Proxy는 고성능, 고확장성의 L7 프록시 및 통신 버스입니다. Istio, App Mesh와 같은 서비스 메쉬의 데이터 플레인으로 널리 사용되며, 이를 기반으로 하는 여러 인그레스 컨트롤러 구현체들이 있습니다.
이 외에도 Kong Ingress Controller (API 게이트웨이 기능 통합), Skipper (HTTP 라우터 및 리버스 프록시) 등 다양한 인그레스 컨트롤러들이 있으며, 각 클라우드 제공업체들도 자사 로드 밸런서 서비스와 통합된 관리형 인그레스 컨트롤러(예: AWS ALB Ingress Controller, Azure Application Gateway Ingress Controller, Google Cloud GKE Ingress)를 제공하기도 합니다.
대표적인 인그레스 컨트롤러 비교
| 기능/특징 | Nginx Ingress Controller | Traefik Proxy | HAProxy Ingress Controller | Envoy 기반 컨트롤러 (Contour, Emissary, Istio 등) |
|---|---|---|---|---|
| 기반 기술 | NGINX (웹 서버/리버스 프록시) | 자체 개발 (Go 언어 기반) | HAProxy (TCP/HTTP 로드 밸런서) | Envoy Proxy (L7 프록시/통신 버스) |
| 주요 개발/유지보수 | 쿠버네티스 커뮤니티, NGINX, Inc. | Traefik Labs | HAProxy Technologies, 커뮤니티 | VMware (Contour), Datawire (Emissary), Istio 커뮤니티 등 |
| 설정 방식 | NGINX 설정 파일 동적 생성/리로드 (어노테이션 활용) | 쿠버네티스 API 직접 감시, 동적 구성 (리로드가 거의 불필요) | HAProxy 설정 파일 동적 생성/리로드 (어노테이션 활용) | xDS API를 통한 동적 구성 (Envoy의 표준 방식) |
| 성능 | 매우 높음 (NGINX 자체 성능 우수) | 높음 (경량, 동적 구성에 최적화) | 매우 높음 (HAProxy 자체 성능 우수) | 매우 높음 (Envoy 자체 성능 및 확장성 우수) |
| 유연성/확장성 | 높음 (NGINX 모듈, Lua 스크립팅 등 활용 가능) | 중간 ~ 높음 (미들웨어 시스템) | 높음 (HAProxy의 다양한 기능 활용) | 매우 높음 (Envoy의 풍부한 필터 체인, xDS API) |
| 자동 SSL/TLS (Let’s Encrypt) | cert-manager와 통합 필요 | 내장 기능으로 강력 지원 | cert-manager와 통합 필요 | cert-manager와 통합 또는 자체 기능 (컨트롤러별 상이) |
| 웹 대시보드 | 제한적 (NGINX Plus 버전은 제공) | 제공 (라우팅, 서비스, 미들웨어 등 시각화) | 제한적 | 일부 제공 (컨트롤러별 상이, 예: Istio의 Kiali) |
| 주요 장점 | 검증된 안정성, 풍부한 기능, 방대한 커뮤니티, 고성능 | 사용 편의성, 자동 서비스 디스커버리, 자동 SSL, 동적 구성 | 고성능 TCP/HTTP 로드 밸런싱, 다양한 LB 알고리즘, 세션 지속성 | 고성능, 고확장성, 서비스 메쉬와 긴밀한 통합, 고급 트래픽 관리 |
| 고려 사항 | 설정 리로드 빈번 시 성능 영향 가능성, 커뮤니티/상용 버전 차이 | NGINX/HAProxy만큼의 광범위한 고급 기능은 부족할 수 있음 | Traefik만큼의 자동화/편의 기능은 부족할 수 있음 | 상대적으로 높은 학습 곡선, Envoy 및 xDS 이해 필요 |
| 주요 사용 사례 | 일반적인 웹 서비스, 프로덕션 환경, NGINX 경험이 있는 팀 | 개발/테스트, 마이크로서비스, 자동화된 SSL/TLS 요구 환경 | 대규모 트래픽, TCP 로드 밸런싱 요구, HAProxy 경험 팀 | 복잡한 트래픽 관리, 서비스 메쉬 환경, API 게이트웨이 요구 |
| 커뮤니티 활성도 | 매우 높음 | 높음 | 중간 | 높음 (Envoy 및 각 컨트롤러 프로젝트 활발) |
| K3s 기본 컨트롤러 | 아니요 (설치 가능) | 예 (주로 사용됨) | 아니요 (설치 가능) | 아니요 (설치 가능) |
표 읽는 방법 및 추가 설명:
- 성능은 매우 다양한 요소(하드웨어, 네트워크, 설정, 워크로드 특성 등)에 따라 달라지므로, 위 표는 일반적인 경향을 나타냅니다. 실제 환경에서는 반드시 벤치마킹을 통해 검증해야 합니다.
- Envoy 기반 컨트롤러는 단일 제품이라기보다는 Envoy Proxy를 기반으로 하는 여러 구현체들의 특징을 포괄적으로 설명한 것입니다. 각 개별 컨트롤러(Contour, Emissary-ingress, Istio Ingress Gateway 등)는 세부적인 기능과 설정 방식, 그리고 지향하는 목표에서 차이가 있을 수 있습니다.
- cert-manager는 쿠버네티스에서 Let’s Encrypt와 같은 인증 기관으로부터 SSL/TLS 인증서를 자동으로 발급받고 갱신해주는 매우 유용한 도구입니다. 대부분의 인그레스 컨트롤러는 cert-manager와 잘 통합되어 동작합니다.
- 어노테이션(Annotations): 대부분의 인그레스 컨트롤러는 쿠버네티스 인그레스 리소스의 metadata.annotations 필드를 통해 컨트롤러별 특화된 설정을 적용할 수 있도록 지원합니다. 이를 통해 기본 인그레스 명세만으로는 표현하기 어려운 다양한 고급 기능(예: 리다이렉션, 인증, CORS, 요청/응답 헤더 조작, 타임아웃 설정 등)을 활용할 수 있습니다.
어떤 컨트롤러를 선택할지는 클러스터의 규모, 성능 요구사항, 필요한 기능(예: 특정 인증 방식, 고급 라우팅 규칙, 서비스 메쉬 통합), 운영 편의성, 그리고 팀의 기술적 선호도 등을 종합적으로 고려하여 결정해야 합니다.
8.3.2.3 K3s/Rancher Desktop의 내장 컨트롤러 (Traefik 등) 활용
쿠버네티스를 처음 접하는 사용자나 로컬에서 빠르게 개발 및 테스트 환경을 구축하고자 하는 개발자에게 있어, CNI 플러그인 선택 및 설치, 스토리지 클래스 설정, 그리고 인그레스 컨트롤러 배포와 같은 초기 클러스터 구성 작업은 상당한 시간과 노력을 요구하는 진입 장벽으로 작용할 수 있습니다. 이러한 사용자의 불편함을 해소하고 쿠버네티스 경험을 훨씬 더 간편하고 직관적으로 만들기 위해 등장한 것이 바로 K3s나 Rancher Desktop과 같은 경량 쿠버네티스 배포판입니다. 이들 도구는 “배터리 포함(batteries-included)” 철학을 바탕으로, 쿠버네티스 클러스터 운영에 필요한 핵심 구성 요소들을 기본적으로 내장하거나 손쉽게 활성화할 수 있도록 지원합니다. 인그레스 컨트롤러 역시 이러한 핵심 구성 요소 중 하나로, 대부분의 경량 배포판에서는 사용자가 별도의 설치나 복잡한 구성 과정 없이도 즉시 인그레스 리소스를 생성하고 강력한 L7 라우팅 기능을 활용할 수 있도록 기본 제공되는 경우가 많습니다.
이러한 내장 인그레스 컨트롤러의 존재는 개발자들이 쿠버네티스 인프라의 세부적인 설정에 얽매이지 않고, 애플리케이션 로직 개발과 쿠버네티스의 핵심 개념 학습에 더 집중할 수 있도록 돕는다는 점에서 매우 큰 의미를 가집니다. 이제 대표적인 경량 쿠버네티스 배포판인 K3s와 Rancher Desktop이 어떤 방식으로 인그레스 컨트롤러를 제공하고 활용하는지 자세히 살펴보겠습니다.
- K3s (케이쓰리에스):
- SUSE(이전 Rancher Labs)에서 개발한 K3s는 CNCF 인증을 받은 경량 쿠버네티스 배포판으로, 단일 바이너리 형태로 제공되어 설치가 매우 간편하고 리소스 사용량이 현저히 적다는 장점을 가지고 있습니다. 이러한 특성 덕분에 엣지 컴퓨팅 환경, IoT 디바이스, CI/CD 파이프라인 내 임시 클러스터, 그리고 개발자의 로컬 머신 등 다양한 시나리오에서 널리 활용되고 있습니다.
- K3s는 기본적으로 Traefik Proxy (트래픽 프록시)를 내장 인그레스 컨트롤러로 채택하여 제공합니다. 사용자가 K3s 서버(마스터 노드 역할을 하는 컴포넌트)를 시작하면, 별도의 추가 작업 없이도 Traefik 파드가 클러스터 내의 kube-system 네임스페이스(또는 K3s 버전에 따라 다른 지정된 네임스페이스)에 자동으로 배포되고 실행됩니다. 이렇게 실행된 Traefik 인그레스 컨트롤러는 즉시 클러스터 내에 생성되는 인그레스 리소스들을 감시하고, 해당 리소스에 정의된 라우팅 규칙들을 처리할 준비를 갖추게 됩니다.
- Traefik Proxy는 현대적인 클라우드 네이티브 환경을 위해 설계된 HTTP 리버스 프록시 및 로드 밸런서로, 다음과 같은 개발자 친화적인 특징들을 가지고 있어 K3s의 간편함과 매우 잘 어울립니다:
- 동적 구성 업데이트: 쿠버네티스 API 서버와 긴밀하게 통합되어 인그레스, 서비스, 엔드포인트 등의 변경 사항을 실시간으로 감지하고 자신의 라우팅 설정을 매우 빠르게 동적으로 업데이트합니다. 이 과정에서 전통적인 웹 서버처럼 설정 파일을 다시 로드(reload)하는 과정이 거의 필요 없어, 변경 사항이 즉각적으로 반영되고 서비스 중단 가능성이 최소화됩니다.
- 자동 SSL/TLS 인증서 관리: Let’s Encrypt와 같은 ACME(Automatic Certificate Management Environment) 프로토콜을 지원하는 인증 기관과의 통합을 통해, HTTPS 통신에 필요한 SSL/TLS 인증서를 자동으로 발급받고 주기적으로 갱신하는 기능을 내장하고 있습니다. 이는 HTTPS 설정을 매우 간편하게 만들어줍니다.
- 사용하기 쉬운 웹 대시보드: 현재 적용된 라우팅 규칙, 서비스 상태, 미들웨어 구성 등을 시각적으로 확인할 수 있는 웹 기반 대시보드를 제공하여 운영 편의성을 높입니다.
- K3s 사용자는 단순히 인그레스 리소스 YAML 파일을 작성하여 클러스터에 적용하는 것만으로도 즉시 호스트 기반 라우팅이나 경로 기반 라우팅과 같은 L7 로드 밸런싱 기능을 테스트하고 활용할 수 있습니다. 물론, K3s는 유연성을 제공하여 사용자가 원한다면 –no-deploy traefik (또는 유사한) 옵션을 사용하여 기본 Traefik 인그레스 컨트롤러의 배포를 비활성화하고, 이후에 자신이 선호하는 다른 인그레스 컨트롤러(예: Nginx Ingress Controller, Contour 등)를 직접 설치하여 사용할 수도 있습니다. 하지만 대부분의 로컬 개발, 학습, 테스트, 또는 리소스가 제한적인 소규모 엣지 환경에서는 K3s에 내장된 Traefik만으로도 충분히 만족스러운 인그레스 기능을 경험할 수 있습니다.
- Rancher Desktop (랜처 데스크탑):
- 마찬가지로 SUSE에서 제공하는 오픈소스 데스크탑 애플리케이션인 Rancher Desktop은 Windows, macOS, Linux 운영체제에서 사용자가 클릭 몇 번만으로 손쉽게 로컬 쿠버네티스 개발 환경과 컨테이너 관리 도구를 구축하고 사용할 수 있도록 지원하는 매우 유용한 도구입니다. Docker Desktop의 훌륭한 대안으로 주목받고 있으며, 특히 사용자가 원하는 쿠버네티스 버전을 유연하게 선택할 수 있고, 다양한 컨테이너 런타임(containerd 또는 dockerd/moby)을 지원한다는 점이 큰 장점입니다.
- Rancher Desktop은 내부적으로 쿠버네티스를 실행하기 위한 백엔드 엔진으로 k3s 엔진 또는 moby/dockerd 엔진 중 하나를 사용자가 선택할 수 있도록 합니다. 이 선택에 따라 내부적으로 사용되는 인그레스 컨트롤러의 종류나 기본 포함 여부가 달라질 수 있습니다.
- 만약 사용자가 Rancher Desktop에서 k3s 엔진을 사용하도록 설정했다면, 그 동작 방식은 앞서 설명한 K3s와 매우 유사합니다. 즉, Rancher Desktop은 내부적으로 경량 리눅스 가상 머신(VM)을 실행하고, 이 VM 내에서 K3s를 구동하여 쿠버네티스 클러스터를 제공합니다. 따라서 이 경우, K3s의 기본 동작과 마찬가지로 Traefik Proxy가 기본 인그레스 컨트롤러로 포함되어 실행될 가능성이 매우 높습니다. 이를 통해 사용자는 Rancher Desktop에서 쿠버네티스 클러스터를 시작하자마자 별도의 인그레스 컨트롤러 설치나 복잡한 설정 과정 없이 인그레스 리소스를 생성하고 L7 라우팅 기능을 바로 테스트해 볼 수 있습니다.
- 만약 Rancher Desktop이 moby/dockerd 엔진을 사용하도록 설정되어 있다면 (이는 과거 Docker Desktop이 쿠버네티스를 지원하던 방식과 유사할 수 있습니다), 인그레스 컨트롤러의 종류나 기본 포함 여부는 Rancher Desktop의 구체적인 버전 및 사용자의 추가 설정에 따라 달라질 수 있습니다. 일부 경우에는 Docker Desktop이 과거에 제공했던 자체적인 CNI 및 인그레스 솔루션과 유사한 방식이 사용될 수도 있고, 또는 사용자가 직접 선호하는 인그레스 컨트롤러(예: Nginx Ingress Controller, Traefik 등)를 로컬 클러스터에 추가로 설치해야 할 수도 있습니다. Rancher Desktop은 지속적으로 발전하고 있는 프로젝트이므로, 특정 버전에 대한 정확한 기본 인그레스 컨트롤러 정보는 공식 문서나 릴리스 노트를 통해 확인하는 것이 가장 확실한 방법입니다.
- Rancher Desktop이 추구하는 핵심 가치는 개발자 편의성 극대화입니다. 어떤 내부 엔진이나 CNI, 인그레스 컨트롤러를 사용하든 간에, 최종 사용자에게는 가능한 한 인프라 구성의 복잡함을 감추고 “바로 사용 가능한(out-of-the-box)” 로컬 쿠버네티스 개발 환경을 제공하는 데 초점을 맞추고 있습니다. 이를 통해 개발자들은 자신의 애플리케이션을 컨테이너화하고, 쿠버네티스 환경에서 배포 및 테스트하는 데 필요한 시간과 노력을 크게 줄일 수 있으며, 쿠버네티스의 다양한 기능들을 보다 쉽고 빠르게 학습하고 경험할 수 있습니다.
결론적으로, K3s나 Rancher Desktop과 같은 사용자 친화적인 경량 쿠버네티스 배포판들은 인그레스 컨트롤러를 기본적으로 내장하거나 매우 손쉽게 활성화할 수 있도록 지원함으로써, 사용자들이 쿠버네티스의 강력한 L7 라우팅 기능을 보다 낮은 진입 장벽으로 접하고 활용할 수 있도록 돕습니다. 이는 특히 쿠버네티스를 처음 배우는 입문자나, 로컬 환경에서 빠르게 프로토타입을 개발하고 테스트해야 하는 개발자들에게 매우 유용한 환경을 제공합니다. 물론, 실제 프로덕션 환경으로 나아가거나 매우 특수한 네트워크 요구사항(예: 초고성능, 특정 보안 규정 준수, 복잡한 트래픽 쉐이핑 등)이 있는 경우에는, 해당 환경의 특성과 요구사항에 맞춰 보다 신중하게 인그레스 컨트롤러를 선택하고, 성능 튜닝 및 보안 강화를 위한 전문적인 구성 작업을 수행하는 과정이 반드시 필요할 것입니다.
8.3.3 인그레스 리소스 정의
앞서 우리는 인그레스 컨트롤러가 어떻게 인그레스 리소스에 정의된 규칙들을 실제 네트워크 트래픽 라우팅으로 변환하는지 알아보았습니다. 그렇다면 이 ‘인그레스 리소스’는 과연 어떻게 생겼고, 어떤 정보들을 담고 있을까요? 인그레스 리소스는 쿠버네티스의 다른 오브젝트들과 마찬가지로 YAML(또는 JSON) 형식의 매니페스트 파일을 통해 선언적으로 정의됩니다. 이 파일에는 인그레스의 이름, 레이블과 같은 메타데이터와 함께, 인그레스의 핵심 동작을 규정하는 spec 필드가 포함됩니다.
마치 우리가 여행 계획을 세울 때, 어떤 교통수단을 이용하고(프로토콜), 어떤 도시를 방문하며(호스트), 각 도시에서 어떤 명소를 둘러볼지(경로), 그리고 각 명소에서 무엇을 할지(백엔드 서비스)를 상세히 기록하는 것과 같습니다. 인그레스 리소스는 쿠버네티스에게 “외부에서 이렇게 생긴 HTTP(S) 요청이 들어오면, 저렇게 처리해서 내부의 이 서비스로 보내줘” 라고 구체적인 지시를 내리는 명세서인 셈입니다.
이번 절에서는 인그레스 리소스의 spec 필드에 포함되는 핵심적인 구성 요소들, 특히 트래픽 라우팅 규칙을 정의하는 rules 필드와 HTTPS 통신을 위한 tls 필드에 대해 자세히 살펴보겠습니다. 이를 통해 독자 여러분은 실제로 인그레스 규칙을 어떻게 작성하고 원하는 L7 라우팅 동작을 구현할 수 있는지 이해하게 될 것입니다.
8.3.3.1 규칙 (Rules): 호스트, 경로, 백엔드 서비스 정의
인그레스 리소스의 가장 핵심적인 부분은 바로 spec.rules 필드입니다. 이 필드는 하나 이상의 라우팅 규칙들의 목록을 정의하며, 각 규칙은 특정 조건(예: 호스트 이름, URL 경로)을 만족하는 HTTP(S) 요청을 어떤 내부 쿠버네티스 서비스로 전달할지를 명시합니다. 마치 복잡한 교차로에서 교통 경찰이 각 차량의 목적지를 확인하고 적절한 차선으로 안내하는 것과 같습니다.
각 규칙(rule)은 주로 다음과 같은 하위 필드들로 구성됩니다.
- **host 필드 (선택 사항):**이 필드는 해당 규칙이 적용될 호스트 이름(도메인 이름)을 지정합니다. 만약 이 필드가 설정되어 있다면, 인그레스 컨트롤러는 들어오는 HTTP 요청의 Host 헤더 값을 확인하여 이 값과 일치하는 경우에만 해당 규칙을 적용합니다.
- 예를 들어, host: foo.bar.com으로 설정하면, foo.bar.com이라는 도메인으로 들어오는 요청에 대해서만 이 규칙이 적용됩니다.
- 와일드카드 호스트 이름(예: *.foo.com)도 일부 인그레스 컨트롤러에서 지원될 수 있지만, 이는 컨트롤러의 구현에 따라 다릅니다. (표준 인그레스 명세 자체는 와일드카드를 명시적으로 지원하지 않지만, Nginx Ingress Controller 등은 이를 해석하여 지원합니다.)
- 만약 host 필드가 생략되거나 * (모든 호스트를 의미하는 특수 값, 일부 컨트롤러에서 지원)로 설정되면, 해당 규칙은 IP 주소로 직접 들어오거나 어떤 호스트 이름으로 들어오든 모든 요청에 대해 적용될 수 있습니다 (단, 다른 host가 명시된 규칙이 우선적으로 매칭될 수 있습니다).
- **http 필드:**host 필드와 함께 사용되며 (또는 host 없이 단독으로 사용될 수도 있음), 해당 호스트(또는 모든 호스트)에 대한 HTTP 기반의 라우팅 규칙들을 정의합니다. 이 필드 아래에는 paths라는 배열이 위치하며, 각 path 항목은 특정 URL 경로에 대한 라우팅 동작을 기술합니다.
- paths 배열: 하나 이상의 경로 기반 라우팅 규칙을 포함합니다. 각 규칙은 다음 필드들로 구성됩니다.
- path 필드 (필수): 요청 URL의 경로 부분을 지정합니다. 이 경로와 일치하는 요청이 해당 규칙의 대상이 됩니다. 경로 매칭 방식은 아래 pathType 필드에 의해 결정됩니다.
- 예를 들어, /testpath, /app1/api, / (루트 경로) 등을 지정할 수 있습니다.
- pathType 필드 (필수, 쿠버네티스 v1.18+): path 필드가 어떻게 해석되고 매칭될지를 정의합니다. 주요 값은 다음과 같습니다.
- Exact: path 필드에 지정된 문자열과 요청 URL 경로가 정확히 일치해야 합니다. 대소문자를 구분합니다. 예를 들어, path: /foo, pathType: Exact는 /foo 요청에만 매칭되고, /foo/나 /foo/bar에는 매칭되지 않습니다.
- Prefix: path 필드에 지정된 문자열이 요청 URL 경로의 접두사(prefix)와 일치해야 합니다. 경로 구분자 /를 기준으로 매칭되며, 대소문자를 구분합니다. 예를 들어, path: /foo, pathType: Prefix는 /foo, /foo/, /foo/bar 요청에 모두 매칭됩니다. 가장 일반적으로 사용되는 타입입니다.
- ImplementationSpecific (기본값, 이전 버전 호환성): 이 타입을 사용하면 경로 매칭 방식은 전적으로 인그레스 컨트롤러의 구현에 따라 달라집니다. 따라서 예측 가능한 동작을 위해서는 Exact나 Prefix를 명시적으로 사용하는 것이 권장됩니다. 이전 버전의 인그레스 리소스에서는 이 타입이 기본이었으며, Nginx Ingress Controller 등은 이를 정규 표현식 또는 접두사 매칭으로 해석하기도 했습니다.
- backend 필드 (필수): 해당 경로로 들어온 요청을 어떤 내부 쿠버네티스 서비스로 전달할지를 정의합니다. 이 필드 아래에는 다음과 같은 하위 필드들이 위치합니다.
- service 필드 (필수, 쿠버네티스 v1.20+ 에서는 service.name과 service.port 대신 resource 필드를 사용할 수도 있음):
- name 필드 (필수): 트래픽을 전달할 대상 쿠버네티스 서비스의 이름을 지정합니다.
- port 필드 (필수): 대상 서비스가 노출하는 포트 중 어떤 포트로 트래픽을 전달할지를 지정합니다. 이 포트는 서비스 명세에 정의된 포트 이름(name) 또는 포트 번호(number)일 수 있습니다.
- name: 서비스 포트의 이름을 지정합니다 (예: http, web).
- number: 서비스 포트의 번호를 직접 지정합니다 (예: 80, 8080).
- service 필드 (필수, 쿠버네티스 v1.20+ 에서는 service.name과 service.port 대신 resource 필드를 사용할 수도 있음):
- path 필드 (필수): 요청 URL의 경로 부분을 지정합니다. 이 경로와 일치하는 요청이 해당 규칙의 대상이 됩니다. 경로 매칭 방식은 아래 pathType 필드에 의해 결정됩니다.
- paths 배열: 하나 이상의 경로 기반 라우팅 규칙을 포함합니다. 각 규칙은 다음 필드들로 구성됩니다.
인그레스 규칙 예시:
다음은 호스트 기반 라우팅과 경로 기반 라우팅을 함께 사용하는 인그레스 리소스의 rules 필드 예시입니다.
클립보드에 복사
Syntax Highlighter
spec:
rules:
-
host: “foo.example.com” # foo.example.com 호스트에 대한 규칙
http:
paths:
-
path: “/foo-path”
pathType: Prefix
backend:
service:
name: foo-service # foo.example.com/foo-path 요청은 foo-service의 80번 포트로 port: number: 80 -
path: “/bar-path”
pathType: Exact
backend:
service:
name: bar-service # foo.example.com/bar-path (정확히 일치) 요청은 bar-service의 http 포트로 port: name: http
-
-
host: “bar.example.com” # bar.example.com 호스트에 대한 규칙
http:
paths:
-
path: ”/” # bar.example.com/ 이하 모든 경로 요청은 (다른 경로 규칙이 없다면)
pathType: Prefix
backend:
service:
name: main-service # main-service의 8080번 포트로 port: number: 8080
-
host 필드가 없는 규칙 (모든 호스트 또는 IP 직접 접근 시)
-
http:
paths:
-
path: “/healthz”
pathType: Exact
backend:
service:
name: health-check-service port: number: 8888
-
이처럼 rules 필드를 통해 매우 유연하고 정교하게 HTTP(S) 트래픽을 원하는 내부 서비스로 라우팅할 수 있습니다. 인그레스 컨트롤러는 이러한 규칙들을 순서대로 평가하여 가장 먼저 매칭되는 규칙에 따라 트래픽을 처리합니다. (매칭 우선순위는 인그레스 컨트롤러 구현에 따라 다를 수 있지만, 일반적으로 더 구체적인 경로가 우선합니다.)
8.3.3.2 TLS 설정 (tls 필드)
오늘날 대부분의 웹 서비스는 HTTPS를 통해 암호화된 통신을 제공하여 사용자의 데이터를 보호하고 서비스의 신뢰도를 높입니다. 인그레스 리소스는 spec.tls 필드를 통해 이러한 HTTPS 통신을 설정하고 관리하는 기능을 제공합니다. 이 필드를 사용하면 특정 호스트 이름에 대해 SSL/TLS 인증서를 적용하고, 인그레스 컨트롤러에서 SSL/TLS 종료(Termination)를 수행하도록 지시할 수 있습니다.
spec.tls 필드는 TLS 설정 객체들의 배열로 구성되며, 각 객체는 다음과 같은 하위 필드들을 가집니다.
- **hosts 배열 (선택 사항):**이 TLS 설정을 적용할 호스트 이름(도메인 이름)들의 목록을 지정합니다. 여기에 명시된 호스트 이름으로 HTTPS 요청이 들어올 때, 아래 secretName에 지정된 인증서가 사용됩니다.
- 예를 들어, hosts: [“secure.example.com“, “another.example.org“] 와 같이 여러 호스트를 지정할 수 있습니다.
- 만약 hosts 필드가 생략되면, 이 TLS 설정은 인그레스 규칙에 정의된 모든 호스트에 적용될 수 있거나 (인그레스 컨트롤러 구현에 따라), 또는 기본 TLS 인증서(default TLS certificate)로 사용될 수 있습니다. (SNI – Server Name Indication를 통해 클라이언트가 요청한 호스트 이름에 맞는 인증서를 선택하여 제공합니다.)
- **secretName 필드 (필수):**해당 호스트(들)에 사용할 SSL/TLS 인증서와 개인키가 저장된 쿠버네티스 시크릿(Secret) 오브젝트의 이름을 지정합니다.
- 이 시크릿은 반드시 kubernetes.io/tls 타입이어야 하며, tls.crt (인증서 파일 내용)와 tls.key (개인키 파일 내용)라는 두 개의 키를 가져야 합니다.
- 인증서는 공인된 인증 기관(CA)으로부터 발급받거나, Let’s Encrypt와 같은 무료 자동화 CA를 통해 발급받을 수 있습니다 (cert-manager와 같은 도구를 사용하면 이 과정을 자동화할 수 있습니다). 또는 자체 서명된 인증서를 테스트 목적으로 사용할 수도 있습니다.
- 이 시크릿은 인그레스 리소스와 동일한 네임스페이스에 존재해야 합니다.
TLS 설정 예시:
다음은 secure.example.com 호스트에 대해 my-tls-secret이라는 이름의 시크릿에 저장된 TLS 인증서를 사용하도록 설정하는 인그레스 리소스의 tls 필드 예시입니다.
클립보드에 복사
Syntax Highlighter
spec:
tls:
-
hosts:
- “secure.example.com” # 이 호스트에 대해 TLS 적용
secretName: “my-tls-secret” # ‘my-tls-secret’ 시크릿에 저장된 인증서 사용
rules:
-
host: “secure.example.com”
http:
paths:
-
path: ”/”
pathType: Prefix
backend:
service:
name: my-secure-service port: number: 80 # 인그레스 컨트롤러와 백엔드 서비스 간에는 HTTP 통신 가능 (TLS 종료)
-
이렇게 tls 필드를 설정하면, 외부 클라이언트가 https://secure.example.com으로 접속할 때 인그레스 컨트롤러는 my-tls-secret에 저장된 인증서를 사용하여 TLS 핸드셰이크를 수행하고 암호화된 통신을 제공합니다. 그리고 인그레스 컨트롤러는 이 암호화된 요청을 복호화하여, 내부의 my-secure-service (보통 HTTP로 리스닝하는)로 전달하게 됩니다. 이것이 바로 “SSL/TLS 종료”입니다.
만약 특정 호스트에 대해 tls 필드가 설정되어 있고, 해당 호스트로 HTTP 요청이 들어오면, 많은 인그레스 컨트롤러는 이를 자동으로 HTTPS로 리다이렉션하는 기능을 제공하기도 합니다 (어노테이션을 통해 제어 가능).
이처럼 인그레스 리소스의 rules 필드와 tls 필드를 적절히 활용하면, 복잡한 웹 애플리케이션의 라우팅 요구사항과 보안 요구사항을 쿠버네티스 환경에서 선언적이고 효율적인 방식으로 관리할 수 있게 됩니다. 이는 클라우드 네이티브 애플리케이션을 위한 강력한 트래픽 관리 솔루션을 제공하는 인그레스의 핵심적인 능력이라고 할 수 있습니다.
8.3.4 [실습] 인그레스를 이용한 경로 기반 라우팅 설정
이론으로 인그레스(Ingress)가 어떻게 HTTP(S) 트래픽을 관리하고, 인그레스 컨트롤러가 어떤 역할을 하며, 인그레스 리소스 규칙을 어떻게 정의하는지 자세히 살펴보았습니다. 이제는 직접 인그레스 리소스를 생성하고, 그 강력한 L7 라우팅 기능을 실제로 경험해 볼 시간입니다! 이번 실습에서는 두 개의 서로 다른 간단한 웹 애플리케이션을 배포하고, 인그레스를 사용하여 URL 경로에 따라 각기 다른 애플리케이션으로 요청을 라우팅하는 경로 기반 라우팅(Path-based Routing)을 설정해 볼 것입니다.
본 실습을 위해서는 이전 실습들과 마찬가지로 쿠버네티스 클러스터와 kubectl CLI가 준비되어 있어야 합니다. 또한, 클러스터 내에 인그레스 컨트롤러가 반드시 설치되고 실행 중이어야 합니다. 만약 K3s나 Rancher Desktop(k3s 엔진 사용 시)과 같이 Traefik이 기본 내장된 환경을 사용하신다면 별도의 설치가 필요 없을 수 있습니다. 그렇지 않은 경우(예: Minikube, kind, 또는 직접 구성한 클러스터)에는 Nginx Ingress Controller나 다른 인그레스 컨트롤러를 먼저 설치해야 합니다. (Minikube의 경우 minikube addons enable ingress 명령으로 Nginx Ingress Controller를 쉽게 활성화할 수 있습니다.)
자, 그럼 쿠버네티스 인그레스의 스마트한 트래픽 분배 능력을 직접 확인하러 떠나볼까요?
8.3.4.1 두 개의 다른 디플로이먼트/서비스 생성 (예: app-a, app-b)
가장 먼저, 인그레스 라우팅 규칙의 대상이 될 두 개의 서로 다른 간단한 웹 애플리케이션을 배포해야 합니다. 각 애플리케이션은 자신만의 고유한 환영 메시지를 보여주도록 하여, 나중에 라우팅이 올바르게 되었는지 쉽게 확인할 수 있도록 할 것입니다. 이번 실습에서는 간단하게 Nginx 이미지를 사용하되, 각 애플리케이션(디플로이먼트)마다 다른 index.html 내용을 가지도록 ConfigMap을 사용하여 구성해 보겠습니다.
1단계: ConfigMap 생성 (각 앱의 index.html 내용 정의)
먼저, 각 애플리케이션이 사용할 index.html 파일의 내용을 담은 ConfigMap 두 개를 생성합니다.
app-a-html-configmap.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: v1
kind: ConfigMap
metadata:
name: app-a-html
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to App A</title>
</head>
<body>
<h1>Hello from Application A!</h1>
<p>This is the main page of App A.</p>
</body>
</html>
app-b-html-configmap.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: v1
kind: ConfigMap
metadata:
name: app-b-html
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to App B</title>
</head>
<body>
<h1>Greetings from Application B!</h1>
<p>You have reached App B.</p>
</body>
</html>
터미널에서 다음 명령어로 ConfigMap들을 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f app-a-html-configmap.yaml
kubectl apply -f app-b-html-configmap.yaml
2단계: App A 디플로이먼트 및 서비스 생성
이제 app-a-html ConfigMap을 사용하여 index.html을 제공하는 Nginx 기반의 ‘App A’ 디플로이먼트와, 이 디플로이먼트를 위한 ClusterIP 타입의 서비스를 생성합니다.
app-a-deployment-service.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-a-deployment
spec:
replicas: 1
selector:
matchLabels:
app: app-a
template:
metadata:
labels:
app: app-a # 이 레이블이 서비스의 selector와 인그레스 규칙에서 사용됩니다.
spec:
containers:
- name: app-a-container
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts: # ConfigMap을 볼륨으로 마운트하여 index.html을 교체
- name: html-volume
mountPath: /usr/share/nginx/html/index.html
subPath: index.html # ConfigMap의 index.html 키를 파일로 마운트
volumes:
- name: html-volume
configMap:
name: app-a-html # app-a-html ConfigMap 사용
apiVersion: v1
kind: Service
metadata:
name: app-a-service # App A를 위한 서비스
spec:
type: ClusterIP
selector:
app: app-a # app: app-a 레이블을 가진 파드를 대상으로 합니다.
ports:
-
protocol: TCP
port: 80 # 서비스가 노출할 포트
targetPort: 80 # 파드 컨테이너가 리스닝하는 포트
터미널에서 다음 명령어로 App A 관련 리소스들을 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f app-a-deployment-service.yaml
3단계: App B 디플로이먼트 및 서비스 생성
마찬가지로 app-b-html ConfigMap을 사용하는 ‘App B’ 디플로이먼트와 서비스를 생성합니다.
app-b-deployment-service.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-b-deployment
spec:
replicas: 1
selector:
matchLabels:
app: app-b
template:
metadata:
labels:
app: app-b # 이 레이블이 서비스의 selector와 인그레스 규칙에서 사용됩니다.
spec:
containers:
- name: app-b-container
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: html-volume
configMap:
name: app-b-html # app-b-html ConfigMap 사용
apiVersion: v1
kind: Service
metadata:
name: app-b-service # App B를 위한 서비스
spec:
type: ClusterIP
selector:
app: app-b # app: app-b 레이블을 가진 파드를 대상으로 합니다.
ports:
-
protocol: TCP
port: 80
targetPort: 80
터미널에서 다음 명령어로 App B 관련 리소스들을 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f app-b-deployment-service.yaml
이제 kubectl get deployments와 kubectl get services 명령으로 app-a-deployment, app-b-deployment, app-a-service, app-b-service가 모두 정상적으로 생성되었는지 확인합니다. 또한, kubectl get pods -l app=app-a 와 kubectl get pods -l app=app-b 명령으로 각 앱의 파드가 Running 상태인지 확인합니다.
이로써 우리는 인그레스 라우팅의 대상이 될 두 개의 서로 다른 내부 서비스를 준비했습니다.
8.3.4.2 페이지 라우팅하는 인그레스 생성
이제 드디어 인그레스 리소스를 생성하여, 특정 URL 경로로 들어오는 요청을 앞서 만든 각기 다른 서비스로 전달하도록 설정할 차례입니다. 이번 실습에서는 호스트 이름을 특정하지 않고, 모든 호스트로 들어오는 요청에 대해 /a 경로는 app-a-service로, /b 경로는 app-b-service로 라우팅하는 간단한 경로 기반 라우팅 규칙을 만들 것입니다.
아래 내용을 my-path-ingress.yaml 파일로 저장해 주세요.
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1 # 인그레스는 networking.k8s.io API 그룹의 v1 버전을 사용합니다.
kind: Ingress
metadata:
name: path-based-routing-ingress
annotations:
# 사용할 인그레스 컨트롤러에 따라 필요한 어노테이션이 있을 수 있습니다.
# 예: Nginx Ingress Controller의 경우 Rewrite Target 설정 (최신 버전에서는 불필요할 수 있음)
# nginx.ingress.kubernetes.io/rewrite-target: /
# Traefik의 경우 보통 어노테이션 없이도 잘 동작합니다.
# 사용 중인 인그레스 컨트롤러의 문서를 확인하세요.
spec:
ingressClassName: <사용할_인그레스_컨트롤러_클래스명> # 클러스터에 여러 인그레스 컨트롤러가 있거나 특정 컨트롤러를 지정하고 싶을 때 사용
rules:
-
http: # host 필드가 없으므로 모든 호스트에 적용됩니다.
paths:
-
path: /a # /a 경로로 시작하는 요청
pathType: Prefix # 접두사 일치
backend:
service:
name: app-a-service # app-a-service로 전달 port: number: 80 # app-a-service의 80번 포트로 -
path: /b # /b 경로로 시작하는 요청
pathType: Prefix # 접두사 일치
backend:
service:
name: app-b-service # app-b-service로 전달 port: number: 80 # app-b-service의 80번 포트로
-
이 YAML 파일의 주요 부분을 살펴보겠습니다.
- apiVersion: networking.k8s.io/v1과 kind: Ingress: 이 명세가 인그레스 리소스를 정의함을 나타냅니다.
- metadata.name: path-based-routing-ingress: 인그레스 리소스의 이름을 지정합니다.
- metadata.annotations: 이 부분은 사용하는 인그레스 컨트롤러에 따라 매우 중요할 수 있습니다. 예를 들어, 예전 버전의 Nginx Ingress Controller를 사용할 경우, 경로 재작성(rewrite)을 위해 nginx.ingress.kubernetes.io/rewrite-target: /$2 와 같은 어노테이션이 필요할 수 있습니다 (최신 버전에서는 pathType: Prefix 사용 시 자동으로 처리되기도 합니다). Traefik과 같은 컨트롤러는 이러한 어노테이션 없이도 경로 기반 라우팅이 잘 동작하는 경우가 많습니다. 실습하는 환경의 인그레스 컨트롤러 문서를 반드시 확인하여 필요한 어노테이션이 있는지 확인하고 추가해야 합니다.
- spec.ingressClassName: 만약 클러스터에 여러 종류의 인그레스 컨트롤러가 설치되어 있거나, 기본값 외의 특정 컨트롤러를 사용하고 싶다면 이 필드에 해당 인그레스 컨트롤러의 클래스 이름을 명시해야 합니다. (예: nginx, traefik). 이 필드를 생략하면 기본으로 설정된 인그레스 컨트롤러가 이 인그레스 리소스를 처리하게 됩니다.
- spec.rules: 라우팅 규칙을 정의합니다.
- host 필드가 없으므로, 이 규칙은 어떤 호스트 이름으로 요청이 들어오든 적용됩니다.
- paths 배열에는 두 개의 경로 규칙이 정의되어 있습니다.
- 첫 번째 규칙: path: /a, pathType: Prefix로 설정되어, /a로 시작하는 모든 경로(예: /a, /a/, /a/anything)의 요청을 app-a-service의 80번 포트로 전달합니다.
- 두 번째 규칙: path: /b, pathType: Prefix로 설정되어, /b로 시작하는 모든 경로의 요청을 app-b-service의 80번 포트로 전달합니다.
이제 터미널에서 다음 명령어로 인그레스 리소스를 생성합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f my-path-ingress.yaml
“ingress.networking.k8s.io/path-based-routing-ingress created” 메시지가 출력됩니다. 생성된 인그레스의 상태를 확인합니다.
클립보드에 복사
Syntax Highlighter
kubectl get ingress path-based-routing-ingress
또는 kubectl get ing path-based-routing-ingress
출력 결과를 보면 CLASS, HOSTS, ADDRESS, PORTS, AGE 등의 정보가 표시됩니다. HOSTS는 * (모든 호스트)로, ADDRESS에는 인그레스 컨트롤러가 외부로 노출되는 IP 주소 또는 호스트 이름이 표시될 것입니다. (이 주소가 실제로 외부에서 접근 가능한 주소입니다. Minikube에서는 minikube ip로 얻은 주소이거나, minikube tunnel 실행 후 할당되는 주소일 수 있습니다. Docker Desktop은 localhost일 수 있습니다.) 이 ADDRESS를 잘 기억해 두세요.
8.3.4.3 외부에서 접속하여 라우팅 확인
이제 모든 준비가 끝났습니다! 외부에서 인그레스 컨트롤러의 주소로 접속하여, 우리가 정의한 경로 기반 라우팅 규칙이 올바르게 동작하는지 확인해 볼 차례입니다.
앞서 kubectl get ingress 명령으로 확인한 인그레스 컨트롤러의 ADDRESS (예: 192.168.49.2 또는 localhost)를 사용합니다.
1. /a 경로로 접속하여 App A 응답 확인:
웹 브라우저 주소창에 http://<인그레스_ADDRESS>/a (예: http://192.168.49.2/a 또는 http://localhost/a)를 입력하거나, 터미널에서 curl 명령을 실행합니다.
클립보드에 복사
Syntax Highlighter
curl http://<인그레스_ADDRESS>/a
예시: curl http://192.168.49.2/a
“Hello from Application A!” 라는 메시지가 포함된 HTML 응답을 받아야 합니다. 이는 /a 경로로 들어온 요청이 성공적으로 app-a-service로 라우팅되었음을 의미합니다. /a/test 와 같이 하위 경로로 접속해도 동일하게 App A로 라우팅되어야 합니다 (pathType: Prefix 때문).
2. /b 경로로 접속하여 App B 응답 확인:
이번에는 웹 브라우저 주소창에 http://<인그레스_ADDRESS>/b (예: http://192.168.49.2/b 또는 http://localhost/b)를 입력하거나, 터미널에서 curl 명령을 실행합니다.
클립보드에 복사
Syntax Highlighter
curl http://<인그레스_ADDRESS>/b
예시: curl http://192.168.49.2/b
“Greetings from Application B!” 라는 메시지가 포함된 HTML 응답을 받아야 합니다. 이는 /b 경로로 들어온 요청이 성공적으로 app-b-service로 라우팅되었음을 의미합니다.
만약 라우팅이 제대로 동작하지 않는다면:
- 인그레스 컨트롤러가 정상적으로 실행 중인지 확인하세요 (kubectl get pods -n <인그레스_컨트롤러_네임스페이스>).
- 인그레스 리소스의 ADDRESS가 올바르게 할당되었는지, 그리고 해당 주소로 실제로 접근 가능한지 확인하세요.
- 인그레스 리소스의 annotations가 사용 중인 인그레스 컨트롤러에 맞게 올바르게 설정되었는지 다시 한번 확인하세요. (특히 Nginx Ingress Controller의 rewrite-target 관련 어노테이션은 자주 문제를 일으키는 부분입니다.)
- 인그레스 컨트롤러의 로그를 확인하여 오류 메시지가 있는지 살펴보세요 (kubectl logs -n <인그레스_컨트롤러_네임스페이스> <인그레스_컨트롤러_파드이름>).
이 실습을 통해 우리는 두 개의 서로 다른 애플리케이션을 배포하고, 인그레스 리소스를 사용하여 URL 경로에 따라 각기 다른 애플리케이션으로 요청을 성공적으로 라우팅하는 것을 직접 확인했습니다. 인그레스는 이처럼 복잡한 L7 라우팅 요구사항을 쿠버네티스 환경에서 매우 유연하고 강력하게 지원하며, 마이크로서비스 아키텍처의 외부 노출을 관리하는 데 핵심적인 역할을 수행합니다. 호스트 기반 라우팅이나 SSL/TLS 종료와 같은 다른 기능들도 유사한 방식으로 설정하고 테스트해 볼 수 있을 것입니다.
8.4 네트워크 폴리시 (Network Policy): 파드 간 통신 제어
지금까지 우리는 쿠버네티스에서 파드들이 서로 어떻게 통신하고(파드 IP, 서비스), 외부에서 어떻게 클러스터 내부의 서비스에 접근하는지(서비스 타입, 인그레스)에 대해 살펴보았습니다. 쿠버네티스는 기본적으로 클러스터 내의 모든 파드가 서로 자유롭게 통신할 수 있도록 하는 개방적인(open) 네트워크 모델을 가지고 있습니다. 이는 개발 초기 단계나 신뢰할 수 있는 내부 네트워크 환경에서는 편리할 수 있지만, 보안이 중요한 프로덕션 환경이나 멀티테넌트(multi-tenant) 환경에서는 심각한 보안 취약점이 될 수 있습니다. 마치 모든 집의 문이 항상 열려 있어서 누구나 자유롭게 드나들 수 있는 마을과 같습니다. 편리할 수는 있지만 안전하지는 않겠죠.
애플리케이션의 보안을 강화하고, “최소 권한의 원칙(principle of least privilege)”을 네트워크 레벨에서 구현하기 위해서는, 파드 간의 통신 흐름을 세밀하게 제어할 필요가 있습니다. 즉, 특정 파드는 오직 허용된 다른 파드나 서비스하고만 통신할 수 있도록 하고, 그 외의 모든 불필요한 통신은 차단해야 합니다. 바로 이러한 요구사항을 충족시키기 위해 쿠버네티스가 제공하는 기능이 네트워크 폴리시(Network Policy)입니다.
네트워크 폴리시는 마치 파드들 사이에 방화벽 규칙을 설정하는 것과 유사하게, 어떤 파드가 어떤 다른 파드(또는 IP 주소 범위)와 어떤 포트를 통해 통신할 수 있는지(또는 없는지)를 선언적으로 정의하는 쿠버네티스 리소스입니다. 이를 통해 우리는 마이크로서비스 환경에서 각 서비스 간의 통신 경로를 명확히 하고, 잠재적인 공격 표면을 줄이며, 보안 사고 발생 시 피해 범위를 최소화할 수 있습니다.
이번 8.4장에서는 쿠버네티스 클러스터의 네트워크 보안을 한층 강화해주는 네트워크 폴리시에 대해 자세히 알아볼 것입니다.
- 먼저 8.4.1 네트워크 폴리시의 필요성 (기본: 모든 파드 간 통신 허용)에서는 왜 쿠버네티스의 기본 개방형 네트워크 모델만으로는 부족하며, 어떤 상황에서 네트워크 폴리시를 통한 통신 제어가 반드시 필요한지 그 배경과 중요성을 살펴볼 것입니다. 제로 트러스트(Zero Trust) 네트워크 보안 모델과 관련하여 네트워크 폴리시가 어떤 역할을 하는지에 대해서도 이해하게 될 것입니다.
- 다음으로 8.4.2 네트워크 폴리시 동작 원리 (CNI 플러그인 지원 필요)에서는 네트워크 폴리시 리소스에 정의된 규칙들이 실제로 어떻게 적용되어 파드 간 통신을 차단하거나 허용하는지 그 내부 메커니즘을 알아볼 것입니다. 특히, 모든 CNI(Container Network Interface) 플러그인이 네트워크 폴리시를 지원하는 것은 아니며, Calico, Cilium, Weave Net과 같이 네트워크 폴리시 기능을 지원하는 CNI 플러그인이 클러스터에 반드시 설치되어 있어야 한다는 중요한 전제 조건을 강조할 것입니다. K3s나 Rancher Desktop과 같은 환경에서 사용되는 기본 CNI(예: Flannel)가 네트워크 폴리시를 지원하는지, 만약 지원하지 않는다면 어떻게 다른 CNI로 교체하거나 추가 기능을 활성화할 수 있는지도 간략히 언급할 것입니다.
- 이어서 8.4.3 폴리시 타입 (Policy Types): Ingress, Egress에서는 네트워크 폴리시가 어떤 방향의 트래픽을 제어할 수 있는지를 정의하는 policyTypes 필드에 대해 설명합니다. 파드로 들어오는 트래픽(인바운드)을 제어하는 Ingress 타입과, 파드에서 나가는 트래픽(아웃바운드)을 제어하는 Egress 타입이 있으며, 이들을 어떻게 조합하여 원하는 통제 수준을 달성할 수 있는지 알아볼 것입니다.
- 그리고 8.4.4 규칙 정의 (Ingress/Egress Rules)에서는 실제 네트워크 폴리시 YAML 파일에서 어떻게 구체적인 허용/차단 규칙을 정의하는지 그 명세 구조를 자세히 살펴볼 것입니다. 특정 레이블을 가진 파드들만 선택하는 podSelector, 특정 네임스페이스 전체를 대상으로 하는 namespaceSelector, 그리고 특정 IP 주소 범위(CIDR)를 지정하는 ipBlock을 활용하여 트래픽의 출발지(Egress 규칙의 경우 목적지)를 어떻게 지정하는지, 그리고 특정 프로토콜과 포트 번호를 어떻게 명시하는지 등을 배우게 됩니다.
- 마지막으로 8.4.5 [실습] 특정 네임스페이스/파드 간의 통신 제한 설정에서는 직접 네트워크 폴리시를 생성하여 파드 간 통신을 제어하는 과정을 경험해 볼 것입니다. 먼저 기본적으로 모든 트래픽을 차단하는 “기본 Deny” 정책을 설정한 후, 특정 레이블을 가진 파드에서만 다른 특정 파드로의 접근을 선택적으로 허용하는 정책을 추가로 생성할 것입니다. 그리고 실제로 통신 테스트를 통해 정의한 네트워크 폴리시가 올바르게 적용되어 의도한 대로 통신이 차단되거나 허용되는지를 눈으로 직접 확인할 것입니다. 이 실습을 통해 네트워크 폴리시의 강력함과 유연성을 체감하고, 실제 운영 환경에서 보안 정책을 수립하는 데 필요한 기초를 다질 수 있을 겁니다.
네트워크 폴리시는 쿠버네티스 클러스터의 보안을 위한 매우 중요한 도구이지만, 잘못 설정하면 정상적인 서비스 통신까지 차단할 수 있으므로 신중한 계획과 테스트가 필요합니다. 이 장을 통해 네트워크 폴리시의 개념과 사용법을 정확히 익히신다면, 더욱 안전하고 신뢰할 수 있는 클라우드 네이티브 애플리케이션 환경을 구축하는 데 큰 도움이 될 것입니다.
8.4.1 네트워크 폴리시의 필요성 (기본: 모든 파드 간 통신 허용)
쿠버네티스는 애플리케이션을 컨테이너화하고, 이를 파드(Pod)라는 단위로 묶어 클러스터 환경에서 효율적으로 배포하고 관리할 수 있도록 해주는 강력한 오케스트레이션 플랫폼입니다. 이러한 쿠버네티스가 제공하는 네트워킹 환경의 가장 기본적인 특징 중 하나는, 특별한 설정을 하지 않는 한 클러스터 내의 모든 파드가 서로 자유롭게 네트워크 통신을 할 수 있도록 허용한다는 점입니다. 즉, 네임스페이스(Namespace)가 다르더라도, 서로 다른 노드(Node)에 배포되어 있더라도, 기본적으로는 모든 파드가 다른 모든 파드의 IP 주소로 직접 네트워크 요청을 보내고 응답을 받을 수 있는 개방형(open) 또는 평평한(flat) 네트워크 모델을 가지고 있습니다.
이는 마치 새로 지어진 아파트 단지에서 각 집(파드)들이 서로의 현관문을 잠그지 않고 열어두어, 이웃들이 자유롭게 드나들며 소통할 수 있도록 하는 것과 유사합니다. 개발 초기 단계나, 모든 구성원이 서로를 완전히 신뢰할 수 있는 매우 작은 규모의 내부 시스템에서는 이러한 개방성이 애플리케이션 간의 연동을 단순화하고 개발 속도를 높이는 데 도움이 될 수 있습니다. 개발자들은 복잡한 방화벽 규칙이나 네트워크 격리 설정을 신경 쓰지 않고도 각 마이크로서비스들이 서로 쉽게 통신하도록 구현할 수 있기 때문입니다.
하지만, 이러한 “기본적으로 모든 통신을 허용하는” 정책은 애플리케이션의 규모가 커지고, 다양한 종류의 서비스들이 함께 운영되며, 특히 보안이 중요한 데이터를 다루거나 외부의 공격 위협에 노출될 가능성이 있는 프로덕션 환경에서는 심각한 보안 취약점으로 작용할 수 있습니다. 상상해 보십시오. 만약 아파트 단지의 한 집(파드)에 도둑(악성 코드 또는 공격자)이 침입하는 데 성공했다면, 그 도둑은 다른 모든 집들의 문도 열려있기 때문에 단지 내의 다른 집들로 쉽게 이동하며 추가적인 피해를 입힐 수 있을 것입니다.
구체적으로, 쿠버네티스의 기본 개방형 네트워크 모델 하에서 발생할 수 있는 보안상의 문제점과 네트워크 폴리시가 필요한 이유는 다음과 같습니다.
- 공격 확산(Lateral Movement)의 용이성: 만약 클러스터 내의 특정 파드가 보안 취약점으로 인해 외부 공격자에게 침해당하거나 악성코드에 감염되었을 경우, 이 파드는 마치 교두보처럼 활용되어 클러스터 내의 다른 중요한 파드(예: 데이터베이스 파드, 인증 서비스 파드)로 쉽게 접근하여 추가적인 공격을 시도하거나 민감한 정보를 탈취할 수 있습니다. 모든 파드 간 통신이 기본적으로 허용되어 있기 때문에, 공격자는 내부 네트워크를 자유롭게 탐색하며 공격 범위를 넓혀갈 수 있습니다.
- 최소 권한 원칙(Principle of Least Privilege) 위배: 보안의 가장 기본적인 원칙 중 하나는 각 구성 요소가 자신의 작업을 수행하는 데 필요한 최소한의 권한과 접근만을 가져야 한다는 것입니다. 하지만 모든 파드가 서로 통신할 수 있다면, 프론트엔드 웹 서버 파드가 굳이 직접 통신할 필요가 없는 내부 결제 처리 파드나 사용자 개인 정보 데이터베이스 파드와도 네트워크적으로 연결될 수 있게 됩니다. 이는 불필요한 접근 경로를 열어두어 잠재적인 보안 위험을 증가시킵니다.
- 멀티테넌시(Multi-tenancy) 환경에서의 격리 부족: 하나의 쿠버네티스 클러스터에서 여러 팀, 여러 프로젝트, 또는 여러 고객의 애플리케이션을 함께 운영하는 멀티테넌트 환경에서는 각 테넌트(tenant) 간의 네트워크 격리가 매우 중요합니다. 만약 A팀의 애플리케이션 파드가 B팀의 애플리케이션 파드와 아무런 제약 없이 통신할 수 있다면, 한 테넌트의 보안 문제가 다른 테넌트에게 영향을 미치거나, 서로의 데이터를 의도치 않게 열람할 가능성이 생깁니다.
- 컴플라이언스 및 규제 준수 요구사항: 특정 산업(예: 금융, 의료)이나 데이터 보호 규정(예: GDPR, HIPAA, PCI DSS)에서는 민감한 데이터를 처리하는 시스템에 대해 엄격한 네트워크 접근 제어 및 격리 조치를 요구합니다. 쿠버네티스의 기본 네트워크 모델만으로는 이러한 규제 요구사항을 충족시키기 어려울 수 있습니다.
바로 이러한 배경에서 네트워크 폴리시(Network Policy)의 필요성이 강력하게 대두됩니다. 네트워크 폴리시는 쿠버네티스 클러스터 관리자나 애플리케이션 개발자가 선언적인 방식(declarative way)으로 특정 파드 그룹(보통 레이블 셀렉터를 통해 선택됨)에 대해 어떤 다른 파드나 네트워크로부터 들어오는 트래픽(Ingress)을 허용하고, 어떤 다른 파드나 네트워크로 나가는 트래픽(Egress)을 허용할지를 세밀하게 정의할 수 있도록 해주는 쿠버네티스 API 리소스입니다.
네트워크 폴리시를 적용하면, 기본적으로 “모든 것을 허용”하는 정책에서 “기본적으로 모든 것을 차단하고, 명시적으로 허용된 통신만 허용”하는 “기본 거부(default deny)” 또는 “화이트리스트(whitelist)” 기반의 보안 모델로 전환할 수 있게 됩니다. 이는 마치 아파트 단지의 모든 집들이 평소에는 문을 잠가두고, 오직 허가된 방문객(정의된 네트워크 폴리시 규칙에 맞는 통신)에게만 문을 열어주는 것과 같습니다.
이러한 네트워크 폴리시를 통해 우리는 다음과 같은 중요한 보안 목표를 달성할 수 있습니다.
- 마이크로서비스 간 통신 격리: 각 마이크로서비스는 오직 자신이 직접 통신해야 하는 다른 특정 서비스하고만 네트워크 연결을 맺도록 제한하여, 불필요한 상호작용을 차단하고 각 서비스의 독립성을 높일 수 있습니다.
- 네임스페이스 기반 격리: 서로 다른 네임스페이스에 배포된 애플리케이션들 간의 통신을 기본적으로 차단하고, 필요한 경우에만 명시적으로 허용함으로써 멀티테넌시 환경에서의 보안 경계를 강화할 수 있습니다.
- 계층형 보안(Defense in Depth) 강화: 애플리케이션 레벨의 보안 조치와 더불어 네트워크 레벨에서의 접근 제어를 추가함으로써, 여러 계층에 걸쳐 보안을 강화하는 심층 방어 전략을 구현할 수 있습니다.
- 제로 트러스트(Zero Trust) 네트워크 원칙 적용: “아무것도 신뢰하지 않고, 모든 것을 검증한다”는 제로 트러스트 보안 모델의 핵심 원칙을 쿠버네티스 내부 네트워크에 적용하는 데 중요한 역할을 합니다. 즉, 파드 간 통신이라 할지라도 기본적으로는 신뢰하지 않고, 반드시 필요한 통신 경로만을 명시적으로 허용하도록 정책을 수립하는 것입니다.
결론적으로, 쿠버네티스의 기본 네트워크 모델은 개발 편의성을 제공하지만, 실제 운영 환경, 특히 보안이 중요한 시스템에서는 반드시 네트워크 폴리시를 통한 세밀한 접근 제어가 필요합니다. 네트워크 폴리시는 클러스터 내부 네트워크의 보안을 강화하고, 애플리케이션을 잠재적인 위협으로부터 보호하며, 다양한 규제 요구사항을 만족시키는 데 있어 필수적인 도구라고 할 수 있습니다. 따라서 클라우드 네이티브 애플리케이션을 설계하고 운영하는 모든 개발자와 운영자는 네트워크 폴리시의 개념과 필요성을 정확히 이해하고, 자신의 환경에 맞는 적절한 보안 정책을 수립하고 적용하는 능력을 갖추어야 합니다.
8.4.2 네트워크 폴리시 동작 원리 (CNI 플러그인 지원 필요)
앞서 우리는 쿠버네티스 클러스터의 기본 네트워크 모델이 모든 파드 간 통신을 허용하며, 이것이 보안상 취약점이 될 수 있어 네트워크 폴리시(Network Policy)를 통해 파드 간 통신을 세밀하게 제어해야 한다고 배웠습니다. 그렇다면 우리가 YAML 파일로 정의한 이 네트워크 폴리시 규칙들은 과연 어떻게 실제 네트워크 트래픽에 적용되어 특정 통신을 허용하거나 차단하는 걸까요? 네트워크 폴리시 리소스 자체는 단지 ‘원하는 통신 제어 상태’를 선언한 명세일 뿐, 그 자체로 방화벽처럼 동작하지는 않습니다.
네트워크 폴리시가 실제로 동작하기 위해서는 쿠버네티스 클러스터에 설치된 CNI(Container Network Interface) 플러그인이 네트워크 폴리시 기능을 지원하고, 이를 해석하여 실제 네트워크 트래픽 필터링 규칙(예: iptables 규칙, eBPF 프로그램 등)으로 변환하여 적용하는 과정이 반드시 필요합니다. 즉, 네트워크 폴리시는 쿠버네티스 API 레벨에서의 ‘선언’이고, 이 선언을 실제 ‘행동’으로 옮기는 것은 바로 CNI 플러그인의 역할입니다. 마치 우리가 법률(네트워크 폴리시)을 만들어도, 그 법을 집행하는 경찰(CNI 플러그인)이 있어야 실제 효력이 발생하는 것과 같습니다.
이번 절에서는 네트워크 폴리시가 어떤 원리로 동작하며, 왜 네트워크 폴리시를 지원하는 CNI 플러그인이 필수적인지, 그리고 우리가 흔히 사용하는 K3s나 Rancher Desktop과 같은 환경에서는 이 부분을 어떻게 확인해야 하는지 자세히 살펴보겠습니다.
8.4.2.1 네트워크 폴리시(NetworkPolicy)를 지원하는 대표적인 CNI 플러그인 비교
네트워크 폴리시의 동작 원리를 이해하기 위한 가장 중요한 전제 조건은, 모든 CNI 플러그인이 네트워크 폴리시 기능을 기본적으로 지원하는 것은 아니다라는 점입니다. CNI 명세 자체는 파드 네트워킹의 기본적인 부분(IP 할당, 인터페이스 생성 등)만을 표준화하고 있으며, 네트워크 폴리시와 같은 고급 네트워킹 기능은 각 CNI 플러그인이 선택적으로 구현하여 제공합니다.
따라서, 우리가 쿠버네티스 클러스터에 네트워크 폴리시 리소스를 생성하더라도, 만약 현재 클러스터에 설치된 CNI 플러그인이 네트워크 폴리시를 지원하지 않는다면, 해당 네트워크 폴리시 규칙은 아무런 효과 없이 무시됩니다. 즉, 파드 간 통신은 여전히 기본 정책(모든 통신 허용)을 따르게 됩니다. 이는 마치 방화벽 설정을 위한 소프트웨어는 설치했지만, 실제로 방화벽 엔진 자체가 없거나 비활성화되어 있는 것과 같습니다.
네트워크 폴리시 기능을 지원하는 대표적인 CNI 플러그인들은 다음과 같습니다.
- Calico (칼리코): 앞서 CNI 플러그인 종류에서 설명했듯이, Calico는 강력한 네트워크 폴리시 기능을 핵심 기능으로 제공합니다. iptables 또는 eBPF를 사용하여 매우 유연하고 성능 좋은 방식으로 네트워크 폴리시 규칙을 적용하며, L3/L4 레벨뿐만 아니라 일부 L7 정보(eBPF 사용 시)를 기반으로 한 정책 설정도 지원합니다. Calico는 네트워크 폴리시 구현에 있어 가장 널리 알려지고 많이 사용되는 CNI 플러그인 중 하나입니다.
- Cilium (실리움): eBPF 기술을 적극적으로 활용하는 Cilium 역시 매우 강력하고 현대적인 네트워크 폴리시 기능을 제공합니다. eBPF를 통해 커널 수준에서 효율적으로 트래픽을 필터링하고 정책을 적용하므로, 기존 iptables 기반 방식보다 성능이 우수하고 더 세밀한 제어(예: API 호출 레벨의 정책)가 가능합니다.
- Weave Net (위브넷): Weave Net도 자체적인 네트워크 폴리시 구현을 제공합니다. iptables를 사용하여 기본적인 Ingress/Egress 규칙을 지원합니다.
- Antrea (안트레아): VMware에서 주도하는 CNI 플러그인으로, Open vSwitch(OVS)를 기반으로 하며 네트워크 폴리시 기능을 지원합니다.
반면, Flannel (플란넬)과 같이 단순성과 경량성에 초점을 맞춘 일부 CNI 플러그인들은 자체적으로 네트워크 폴리시 기능을 제공하지 않습니다. Flannel은 주로 L2/L3 오버레이 네트워크를 통한 파드 간 통신 연결성에만 집중합니다. 따라서 만약 클러스터에 Flannel만 CNI 플러그인으로 설치되어 있다면, 네트워크 폴리시 리소스를 생성해도 아무런 통신 제어 효과를 볼 수 없습니다.
네트워크 폴리시(NetworkPolicy)를 지원하는 대표적인 CNI 플러그인 간의 기능 차이를 정리한 표입니다.
| 항목 | Flannel | Calico | Cilium | Weave Net | Antrea |
|---|---|---|---|---|---|
| 기본 네트워크 모드 | Overlay (VXLAN 등) | L3 라우팅 | eBPF 기반 L3/L4/L7 | Overlay (VXLAN) | OVS 기반 |
| 네트워크 폴리시 지원 | ❌ (기본 미지원, Calico 연동 필요) | ✅ Kubernetes, 자체 정책 모두 지원 | ✅ Kubernetes, Cilium 확장 정책 지원 | ❌ (정책 자체 미지원, Calico 등 연동 필요) | ✅ Kubernetes 네트워크 폴리시 및 자체 확장 |
| 고급 기능 | 단순 IPAM 및 네트워킹 | IDS/IPS, DNS 정책, Global Policy 등 | L7 정책, eBPF 기반 성능 최적화 | 간단한 연결, 암호화 지원 | 트래픽 미러링, Traceflow, 정책 시각화 등 |
| 성능 | 보통 (Overlay로 인한 오버헤드 있음) | 높음 (라우팅 기반) | 매우 높음 (eBPF 사용) | 보통 | 높음 (OVS 최적화 가능) |
| 운영 복잡성 | 매우 낮음 (설정 간단) | 중간 (기능이 많아 설정 복잡할 수 있음) | 중간 이상 (eBPF 이해 필요) | 낮음 | 중간 (기능은 많지만 설치 간단) |
| eBPF 지원 | ❌ | ❌ (일부 기능에서 가능) | ✅ 주력 기술 | ❌ | ❌ (단, OVS로 대체 구현) |
| 암호화 지원 | 제한적 (WireGuard 등 별도 설정 필요) | ✅ (IPSec/WireGuard) | ✅ (XDP/eBPF 기반 암호화) | ✅ | ✅ |
| 멀티클러스터 | ❌ | ✅ (Calico Enterprise 포함) | ✅ (ClusterMesh) | ❌ | ✅ (Antrea Multi-cluster) |
| K3s/Rancher Desktop 호환성 | ✅ 기본 CNI로 자주 사용 | ✅ (RKE/K3s에서 사용 가능) | ✅ (K3s에서도 가능하나 커널 의존성 있음) | ✅ (간단히 구성 가능) | ✅ (K3s에서 실험적 지원) |
요약
- Flannel: 단순하고 가볍지만 네트워크 폴리시는 미지원. Rancher Desktop의 기본 CNI로 자주 사용됨.
- Calico: 가장 널리 쓰이는 CNI. 정책 기능과 운영 안정성, 퍼포먼스 모두 우수. 정책이 중요한 환경에 적합.
- Cilium: eBPF 기반으로 L7 정책, 성능, 보안에서 매우 강력. 복잡한 정책과 고성능 환경에 적합.
- Weave Net: 단순한 클러스터에 유용하며 암호화 지원. 정책은 미지원.
- Antrea: VMware 주도 개발, OVS 기반으로 기업 환경에서 인기. Traceflow, UI 시각화 등 강력한 운영 기능이 장점.
8.4.2.2 Calico 등 네트워크 폴리시 지원 CNI 필요 (K3s/Rancher Desktop 확인)
그렇다면 우리가 로컬 개발이나 테스트 목적으로 자주 사용하는 K3s나 Rancher Desktop 환경에서는 네트워크 폴리시를 사용할 수 있을까요? 이는 해당 배포판이 기본적으로 어떤 CNI 플러그인을 사용하고, 그 CNI 플러그인이 네트워크 폴리시를 지원하는지에 따라 달라집니다.
K3s 및 Rancher Desktop 환경에서의 확인:
- K3s (케이쓰리에스):
- 앞서 언급했듯이, K3s는 기본적으로 Flannel CNI 플러그인을 내장하여 사용합니다. 따라서 K3s를 기본 설정으로 설치한 경우, 네트워크 폴리시 리소스를 생성하더라도 실제 통신 제어는 이루어지지 않습니다.
- 하지만 K3s는 매우 유연하여, 사용자가 원한다면 기본 Flannel 대신 Calico와 같은 네트워크 폴리시 지원 CNI를 사용하도록 설치 시 옵션을 지정하거나, 설치 후에 변경할 수 있습니다. 예를 들어, K3s 서버 설치 시 –flannel-backend=none 옵션과 함께 Calico를 직접 설치하는 매니페스트를 적용하는 방식으로 Calico CNI를 사용할 수 있습니다. 이렇게 Calico가 CNI로 동작하도록 설정된 K3s 클러스터에서는 네트워크 폴리시가 정상적으로 작동합니다.
- 또한, K3s의 일부 최신 버전이나 특정 설정에서는 Flannel과 함께 Canal (Flannel의 네트워킹과 Calico의 폴리시 엔진을 결합한 프로젝트)과 유사한 방식으로 네트워크 폴리시 기능을 부분적으로 지원하려는 시도나 옵션이 있을 수도 있으므로, 사용하는 K3s 버전에 대한 공식 문서를 확인하는 것이 중요합니다.
- Rancher Desktop (랜처 데스크탑):
- Rancher Desktop이 내부적으로 k3s 엔진을 사용하고, 이 k3s 엔진이 기본 Flannel CNI를 사용한다면, 위 K3s의 경우와 마찬가지로 네트워크 폴리시는 기본적으로 동작하지 않을 가능성이 높습니다.
- Rancher Desktop의 설정 옵션에서 CNI 플러그인을 변경하거나 네트워크 폴리시를 지원하는 다른 쿠버네티스 배포 옵션(예: Calico를 포함한 배포)을 선택할 수 있는지 확인해 보아야 합니다. Rancher Desktop은 사용자 편의성을 중시하므로, 향후 버전에서는 네트워크 폴리시 지원이 더 강화되거나 쉽게 활성화할 수 있는 옵션이 제공될 수도 있습니다.
- 만약 Rancher Desktop이 내부적으로 moby/dockerd 엔진을 사용하고, Docker Desktop과 유사한 네트워킹 스택을 따른다면, 해당 스택이 네트워크 폴리시를 지원하는지 여부를 확인해야 합니다. (Docker Desktop 자체는 특정 버전부터 Calico를 통한 네트워크 폴리시를 지원하기도 했습니다.
네트워크 폴리시 동작 원리 개요:
네트워크 폴리시를 지원하는 CNI 플러그인(예: Calico)이 설치되어 있다고 가정했을 때, 네트워크 폴리시의 일반적인 동작 원리는 다음과 같습니다.
- 네트워크 폴리시 리소스 생성: 사용자는 YAML 파일을 통해 특정 파드 그룹에 대한 Ingress(수신) 또는 Egress(송신) 규칙을 정의하는 네트워크 폴리시 리소스를 쿠버네티스 API 서버에 생성합니다.
- CNI 컨트롤러의 감시: CNI 플러그인에 포함된 컨트롤러(또는 에이전트)는 API 서버를 통해 네트워크 폴리시 리소스의 변경 사항과 영향을 받는 파드들의 정보(레이블, IP 주소 등)를 지속적으로 감시합니다.
- 규칙 변환 및 적용: 새로운 네트워크 폴리시가 감지되거나 기존 폴리시가 변경되면, CNI 컨트롤러는 이 폴리시 규칙들을 해석하여 각 노드에서 실제 네트워크 트래픽을 필터링할 수 있는 저수준의 규칙으로 변환합니다.
- iptables 기반: 많은 CNI 플러그인(예: Calico의 초기 버전, Weave Net)은 리눅스 커널의 iptables 방화벽 규칙을 사용하여 네트워크 폴리시를 구현합니다. 즉, 폴리시 규칙에 따라 특정 IP 주소나 포트로의 연결을 허용(ACCEPT)하거나 차단(DROP)하는 iptables 체인과 규칙들을 각 노드에 동적으로 생성하고 관리합니다.
- eBPF 기반: 최신 CNI 플러그인(예: Cilium, Calico의 eBPF 모드)은 eBPF(extended Berkeley Packet Filter) 기술을 사용하여 네트워크 폴리시를 구현합니다. eBPF는 커널 코드 수정 없이도 커널 수준에서 네트워크 패킷을 직접 프로그래밍 방식으로 처리하고 필터링할 수 있게 해주므로, iptables보다 훨씬 더 높은 성능과 유연성, 그리고 세밀한 제어(예: L7 정책)를 제공할 수 있습니다. CNI 컨트롤러는 eBPF 프로그램을 각 노드의 네트워크 인터페이스나 특정 훅(hook) 지점에 로드하여 트래픽을 검사하고 정책을 적용합니다.
- 트래픽 필터링: 이제 파드 간 또는 파드와 외부 간의 네트워크 트래픽이 발생하면, 각 노드에 적용된 iptables 규칙 또는 eBPF 프로그램이 해당 트래픽을 검사하여, 정의된 네트워크 폴리시 규칙에 따라 허용되거나 차단됩니다.
결론적으로, 네트워크 폴리시는 쿠버네티스 API 레벨에서 선언된 ‘의도’이며, 이 의도를 실제 네트워크 트래픽 제어라는 ‘행동’으로 옮기는 것은 네트워크 폴리시 기능을 지원하는 CNI 플러그인의 핵심적인 책임입니다. 따라서 네트워크 폴리시를 사용하고자 한다면, 가장 먼저 자신의 클러스터에 설치된 CNI 플러그인이 해당 기능을 지원하는지 확인하고, 만약 지원하지 않는다면 적절한 CNI 플러그인으로 교체하거나 추가하는 작업을 선행해야 합니다. 이것이 바로 쿠버네티스에서 효과적인 네트워크 보안을 구현하기 위한 첫걸음입니다.
8.4.3 폴리시 타입 (Policy Types): Ingress, Egress
쿠버네티스 네트워크 폴리시(Network Policy)는 파드(Pod)를 기준으로 네트워크 트래픽의 흐름을 제어하는 강력한 도구입니다. 우리가 특정 파드 그룹에 대한 통신 규칙을 정의하고자 할 때, 이 규칙이 어떤 방향의 트래픽에 적용될 것인지를 명확히 지정해야 합니다. 마치 건물의 출입 관리를 할 때, 건물로 들어오는 사람(Ingress)에 대한 규칙과 건물에서 나가는 사람(Egress)에 대한 규칙을 별도로 설정하는 것과 같습니다.
네트워크 폴리시 명세(spec) 내의 policyTypes 필드는 바로 이러한 트래픽의 방향, 즉 해당 폴리시가 어떤 종류의 트래픽 흐름을 제어할 것인지를 정의하는 역할을 합니다. 이 필드는 선택 사항이며, 만약 생략될 경우의 기본 동작은 폴리시에 ingress 규칙이 정의되어 있으면 Ingress 타입을, egress 규칙이 정의되어 있으면 Egress 타입을, 둘 다 정의되어 있으면 두 타입 모두를 의미하는 것으로 해석될 수 있습니다 (단, 명확성을 위해 명시적으로 지정하는 것이 좋습니다).
policyTypes 필드에는 다음 두 가지 주요 값을 포함하는 배열을 지정할 수 있습니다.
- Ingress (인그레스, 수신 트래픽): 이 타입을 지정하면, 해당 네트워크 폴리시는 **선택된 파드(들)로 들어오는 트래픽(inbound traffic)**에 대한 규칙을 정의하고 적용합니다. 즉, 어떤 다른 파드나 네트워크 소스에서 이 파드로 연결을 시도할 수 있는지, 그리고 어떤 포트를 통해 연결할 수 있는지를 제어합니다.
- Egress (이그레스, 송신 트래픽): 이 타입을 지정하면, 해당 네트워크 폴리시는 **선택된 파드(들)에서 나가는 트래픽(outbound traffic)**에 대한 규칙을 정의하고 적용합니다. 즉, 이 파드가 어떤 다른 파드나 외부 네트워크 대상으로 연결을 시도할 수 있는지, 그리고 어떤 포트를 통해 연결할 수 있는지를 제어합니다.
하나의 네트워크 폴리시 리소스 내에서 Ingress 규칙과 Egress 규칙을 함께 정의하여 양방향 트래픽을 모두 제어할 수도 있습니다. 이 경우 policyTypes 필드에는 [“Ingress”, “Egress”] 와 같이 두 가지 타입을 모두 명시합니다.
이제 각 폴리시 타입이 구체적으로 어떤 의미를 가지며, 어떻게 사용되는지 예시와 함께 자세히 살펴보겠습니다.
8.4.3.1 Ingress (인그레스) 폴리시 타입: 파드로 들어오는 문 지키기
Ingress 폴리시 타입은 네트워크 폴리시가 적용되는 대상 파드(들) (보통 spec.podSelector로 선택됨)로 향하는 모든 수신 네트워크 연결을 제어합니다. 기본적으로 쿠버네티스 클러스터 내의 모든 파드는 서로 자유롭게 통신할 수 있지만, 특정 파드 그룹에 Ingress 타입의 네트워크 폴리시가 적용되면 상황이 달라집니다. 만약 이 폴리시 내에 어떠한 명시적인 ingress 허용 규칙도 정의되어 있지 않거나, 정의된 ingress 규칙과 일치하는 수신 트래픽이 없다면, 해당 파드 그룹으로의 모든 다른 수신 트래픽은 기본적으로 차단(deny)됩니다. 이는 매우 중요한 특징으로, “기본 거부(default deny)” 원칙을 수신 트래픽에 대해 구현하는 핵심적인 방법입니다. 즉, 초대받지 않은 손님은 문 앞에서 돌려보내는 것과 같습니다.
Ingress 규칙은 다음과 같은 핵심적인 질문에 대한 답을 정의합니다.
- “어떤 소스(Source)에서 오는 트래픽을 이 파드(들)에게 허용할 것인가?”
- 이 소스는 레이블 셀렉터(podSelector)를 사용하여 특정 레이블을 가진 다른 파드들을 지정할 수도 있고, 네임스페이스 셀렉터(namespaceSelector)를 사용하여 특정 네임스페이스에 속한 모든 파드를 지정할 수도 있습니다. 또한, 특정 IP 주소 범위(ipBlock의 cidr)를 지정하여 클러스터 내부 또는 외부의 특정 네트워크 대역으로부터의 접근을 허용할 수도 있습니다. 이러한 조건들은 from 필드 아래에 배열 형태로 여러 개 정의될 수 있으며, 이 중 하나라도 만족하면 해당 소스로부터의 트래픽이 고려 대상이 됩니다.
- “허용한다면, 어떤 프로토콜(TCP, UDP, SCTP)과 어떤 대상 포트 번호(또는 명명된 포트)를 통해 들어오는 연결을 허용할 것인가?”
- 이는 ports 필드 아래에 정의되며, 특정 프로토콜과 포트 번호를 명시하여 오직 해당 서비스 포트로의 연결만을 선택적으로 허용할 수 있습니다. 예를 들어, 웹 서버 파드라면 TCP 80번 또는 443번 포트로의 접근만 허용하고 다른 포트로의 접근은 차단할 수 있습니다.
예시: 특정 프론트엔드 파드에서만 백엔드 API 파드로의 HTTP 접근 허용
다음은 app: my-backend-api 레이블을 가진 파드 그룹(백엔드 API)에 대해, 오직 role: frontend-web 레이블을 가진 파드들(프론트엔드 웹 서버)로부터의 TCP 80번 포트로의 수신 트래픽만을 허용하는 Ingress 네트워크 폴리시 예시입니다.
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-api-allow-frontend
namespace: my-production-ns # 이 폴리시는 ‘my-production-ns’ 네임스페이스에 적용됩니다.
spec:
podSelector:
matchLabels:
app: my-backend-api # 이 레이블을 가진 파드들이 이 정책의 대상입니다.
policyTypes:
- Ingress # 수신 트래픽에 대한 정책임을 명시합니다.
ingress:
-
from:
-
podSelector: # 어떤 파드로부터 오는 트래픽을 허용할지 정의합니다.
matchLabels:
role: frontend-web # 'role: frontend-web' 레이블을 가진 파드들로부터의 트래픽
ports: # 어떤 포트로의 트래픽을 허용할지 정의합니다.
-
protocol: TCP
port: 80 # 80번 TCP 포트로의 접근을 허용합니다.
-
이 정책이 적용되면, my-backend-api 파드들은 role: frontend-web 레이블을 가진 파드들로부터 80번 포트를 통해 들어오는 요청은 수신할 수 있지만, 그 외의 다른 모든 소스(다른 레이블의 파드, 다른 네임스페이스의 파드, IP 블록 등)로부터의 모든 수신 연결은 차단됩니다. 이는 백엔드 API를 오직 허가된 프론트엔드 애플리케이션만이 사용할 수 있도록 보호하는 효과적인 방법입니다.
8.4.3.2 Egress (이그레스) 폴리시 타입: 파드에서 나가는 문 지키기
Egress 폴리시 타입은 네트워크 폴리시가 적용되는 대상 파드(들)에서 외부로 향하는 모든 송신 네트워크 연결을 제어합니다. Ingress와 마찬가지로, 기본적으로 쿠버네티스 파드는 클러스터 내부의 다른 파드나 외부 네트워크로 자유롭게 연결을 시도할 수 있습니다. 하지만 특정 파드 그룹에 Egress 타입의 네트워크 폴리시가 적용되고, 이 폴리시 내에 어떠한 명시적인 egress 허용 규칙도 정의되어 있지 않거나, 정의된 egress 규칙과 일치하는 송신 트래픽이 없다면, 해당 파드 그룹에서의 모든 다른 목적지로의 송신 트래픽은 기본적으로 차단(deny)됩니다. 이는 파드가 의도치 않거나 악의적인 외부 연결을 맺는 것을 방지하는 데 매우 중요합니다. 마치 자녀에게 “이 가게들만 가고, 다른 곳은 가면 안 돼!”라고 허용된 목적지만을 정해주는 것과 같습니다.
Egress 규칙은 다음과 같은 핵심적인 질문에 대한 답을 정의합니다.
- “이 파드(들)가 어떤 목적지(Destination)로 트래픽을 보내는 것을 허용할 것인가?”
- Ingress 규칙의 from 필드와 유사하게, Egress 규칙의 to 필드 아래에는 허용할 목적지를 정의합니다. 목적지는 레이블 셀렉터(podSelector)를 사용하여 특정 레이블을 가진 다른 파드들을 지정하거나, 네임스페이스 셀렉터(namespaceSelector)를 사용하여 특정 네임스페이스에 속한 모든 파드를 지정할 수 있습니다. 또한, 특정 IP 주소 범위(ipBlock의 cidr)를 지정하여 클러스터 외부의 특정 서비스(예: 외부 데이터베이스, 공용 API 엔드포인트)나 내부의 특정 네트워크 대역으로의 연결을 허용할 수 있습니다.
- “허용한다면, 어떤 프로토콜(TCP, UDP, SCTP)과 어떤 목적지 포트 번호(또는 명명된 포트)를 통해 나가는 연결을 허용할 것인가?”
- ports 필드를 사용하여, 특정 프로토콜과 목적지 포트 번호를 명시하여 오직 해당 서비스 포트로의 송신 연결만을 선택적으로 허용할 수 있습니다. 예를 들어, 애플리케이션 파드가 외부 데이터베이스에 접속해야 한다면, 해당 데이터베이스의 IP 주소와 포트로의 TCP 연결만 허용하고 다른 모든 외부 연결은 차단할 수 있습니다.
예시: 애플리케이션 파드가 특정 외부 IP 대역의 HTTPS 포트로만 송신 허용
다음은 app: my-application 레이블을 가진 파드 그룹(애플리케이션)이 오직 10.0.0.0/24 IP 주소 블록에 있는 서버들의 TCP 443번 포트(일반적으로 HTTPS)로만 송신 트래픽을 보낼 수 있도록 제한하는 Egress 네트워크 폴리시 예시입니다. (또한, DNS 조회를 위해 클러스터 내부 DNS 서비스로의 통신도 허용해야 합니다.)
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-app-allow-external-https-and-dns
namespace: my-secure-app-ns
spec:
podSelector:
matchLabels:
app: my-application # 이 레이블을 가진 파드들이 이 정책의 대상입니다.
policyTypes:
- Egress # 송신 트래픽에 대한 정책임을 명시합니다.
egress:
-
to: # 어떤 목적지로의 트래픽을 허용할지 정의합니다.
-
ipBlock: # 특정 IP 블록으로의 트래픽
cidr: 10.0.0.0/24 # 10.0.0.0/24 대역의 IP 주소들
ports: # 어떤 포트로의 트래픽을 허용할지 정의합니다.
-
protocol: TCP
port: 443 # 443번 TCP 포트 (HTTPS)로의 접근을 허용합니다.
-
-
to: # 클러스터 내부 DNS 서비스로의 통신 허용 (대부분의 경우 필요)
Kubernetes DNS 서비스는 보통 kube-system 네임스페이스에 k8s-app=kube-dns 레이블을 가집니다.
또는 namespaceSelector 와 podSelector 를 함께 사용하여 더 정확히 지정할 수 있습니다.
여기서는 간단히 모든 네임스페이스의 kube-dns 레이블을 가진 파드를 대상으로 가정합니다.
실제 환경에서는 CoreDNS 서비스의 정확한 셀렉터를 확인해야 합니다.
-
namespaceSelector: {} # 모든 네임스페이스를 대상으로 할 수 있지만, 더 구체적으로 지정하는 것이 좋습니다.
podSelector:
matchLabels:
k8s-app: kube-dns # CoreDNS 파드에 주로 사용되는 레이블 (확인 필요)
ports:
-
protocol: UDP
port: 53
-
protocol: TCP # TCP도 지원하는 경우가 있음
port: 53
-
이 정책이 적용되면, app: my-application 파드들은 10.0.0.0/24 대역의 443번 포트와 클러스터 내부 DNS 서비스의 53번 포트로만 나가는 연결을 맺을 수 있으며, 그 외의 다른 모든 목적지(예: 인터넷의 다른 웹사이트, 클러스터 내의 다른 허용되지 않은 파드)로의 송신 트래픽은 차단됩니다. 이는 애플리케이션 파드가 악성코드에 감염되어 외부로 데이터를 유출하거나 다른 시스템을 공격하는 것을 효과적으로 방지하는 데 도움을 줄 수 있습니다.
policyTypes 필드의 중요성 및 기본 동작 재확인:
앞서 간략히 언급했듯이, policyTypes 필드는 네트워크 폴리시의 의도를 명확히 하고 예측 가능한 동작을 보장하는 데 매우 중요합니다.
- policyTypes: [“Ingress”]: 오직 수신 트래픽만 이 폴리시의 규칙에 따라 제어됩니다. 송신 트래픽은 제한 없이 모두 허용됩니다.
- policyTypes: [“Egress”]: 오직 송신 트래픽만 이 폴리시의 규칙에 따라 제어됩니다. 수신 트래픽은 제한 없이 모두 허용됩니다.
- policyTypes: [“Ingress”, “Egress”]: 수신 및 송신 트래픽 모두 이 폴리시의 해당 규칙에 따라 제어됩니다.
- policyTypes 필드 생략 시:
- ingress 규칙만 정의되어 있으면: [“Ingress”]와 동일하게 동작 (Egress는 모두 허용).
- egress 규칙만 정의되어 있으면: [“Egress”]와 동일하게 동작 (Ingress는 모두 허용).
- ingress와 egress 규칙이 모두 정의되어 있으면: [“Ingress”, “Egress”]와 동일하게 동작.
- policyTypes: [] (빈 배열): 해당 podSelector에 매칭되는 파드는 모든 Ingress 및 Egress 트래픽이 차단됩니다 (단, 이 폴리시 외에 다른 허용 폴리시가 없다면). 이는 “기본 거부” 정책을 설정하는 강력한 방법입니다.
결론적으로, Ingress와 Egress라는 두 가지 폴리시 타입을 통해 우리는 파드를 중심으로 들어오고 나가는 네트워크 트래픽의 흐름을 마치 성벽의 문지기처럼 철저하게 통제할 수 있습니다. 이는 쿠버네티스 클러스터 내에서 “최소 권한의 원칙”을 네트워크 레벨에서 구현하고, 애플리케이션의 보안 경계를 명확히 하며, 전반적인 시스템의 보안 태세를 강화하는 데 있어 핵심적인 역할을 수행합니다.
8.4.4 규칙 정의 (Ingress/Egress Rules)
앞서 우리는 네트워크 폴리시(Network Policy)가 Ingress (수신)와 Egress (송신)라는 두 가지 주요 타입으로 나뉘어 파드(Pod)로 들어오거나 나가는 트래픽을 제어한다는 것을 배웠습니다. 이제 한 걸음 더 나아가, 이 Ingress 규칙과 Egress 규칙 내에서 구체적으로 어떤 조건의 트래픽을 허용할 것인지를 어떻게 정의하는지 자세히 살펴보겠습니다. 마치 우리가 방화벽 규칙을 설정할 때, “어떤 출발지에서 오는”, “어떤 목적지로 향하는”, “어떤 서비스 포트를 사용하는” 트래픽을 허용하거나 차단할지를 명시하는 것과 같습니다.
네트워크 폴리시 명세(spec) 내의 ingress (배열) 또는 egress (배열) 필드 아래에는 하나 이상의 규칙(rule) 객체들을 정의할 수 있습니다. 각 규칙 객체는 “이러한 조건을 만족하는 트래픽은 허용한다”는 의미를 가지며, 주로 트래픽의 출발지/목적지를 지정하는 부분(from 또는 to 필드)과 허용할 포트를 지정하는 부분(ports 필드)으로 구성됩니다. 만약 특정 파드에 대해 네트워크 폴리시가 적용되었는데, 들어오거나 나가는 트래픽이 이 규칙들 중 어느 하나에도 해당하지 않는다면, 그 트래픽은 기본적으로 차단됩니다 (단, policyTypes 설정에 따라 해당 방향의 트래픽이 제어 대상일 경우).
이제 이 규칙 정의의 핵심 요소들을 하나씩 자세히 알아보겠습니다.
8.4.4.1 podSelector, namespaceSelector, ipBlock 활용
네트워크 폴리시 규칙에서 가장 중요한 부분 중 하나는 **어떤 네트워크 엔드포인트로부터의 트래픽을 허용할지(ingress 규칙의 from 필드), 또는 어떤 네트워크 엔드포인트로의 트래픽을 허용할지(egress 규칙의 to 필드)**를 명확히 지정하는 것입니다. 쿠버네티스는 이를 위해 podSelector, namespaceSelector, 그리고 ipBlock이라는 세 가지 강력한 메커니즘을 제공합니다. 이들은 서로 조합하여 사용될 수도 있으며 (논리적 OR 관계로 동작), 매우 유연하고 정교한 방식으로 트래픽의 소스 또는 대상을 선택할 수 있게 해줍니다.
-
**podSelector (파드 셀렉터):**이 필드는 특정 레이블(label)을 가진 파드들을 트래픽의 소스(from 내부) 또는 대상(to 내부)으로 지정할 때 사용됩니다. 레이블 셀렉터 문법을 사용하여 matchLabels (정확히 일치하는 레이블) 또는 matchExpressions (더 복잡한 표현식)를 통해 원하는 파드 그룹을 선택할 수 있습니다.
- ingress 규칙의 from 필드 내 podSelector: 여기에 정의된 레이블 셀렉터와 일치하는 레이블을 가진 파드들로부터 들어오는 트래픽을 허용합니다. 예를 들어, from: [] 라고 정의하면, role: frontend 레이블을 가진 파드들로부터 오는 연결만 허용합니다. 이때, 이 podSelector는 해당 네트워크 폴리시가 정의된 동일한 네임스페이스 내의 파드들만을 대상으로 합니다. 다른 네임스페이스의 파드를 지정하려면 namespaceSelector와 함께 사용해야 합니다.
- egress 규칙의 to 필드 내 podSelector: 여기에 정의된 레이블 셀렉터와 일치하는 레이블을 가진 파드들로 나가는 트래픽을 허용합니다. 예를 들어, to: [] 라고 정의하면, app: database 레이블을 가진 파드들로의 연결만 허용합니다. 마찬가지로 동일 네임스페이스 내의 파드를 대상으로 합니다.
podSelector는 마이크로서비스 아키텍처에서 특정 서비스 간의 통신을 명시적으로 허용하는 데 매우 유용합니다. 예를 들어, “프론트엔드 파드는 백엔드 API 파드하고만 통신할 수 있다”와 같은 규칙을 쉽게 구현할 수 있습니다.
-
**namespaceSelector (네임스페이스 셀렉터):**이 필드는 특정 레이블을 가진 네임스페이스(Namespace)에 속한 모든 파드들을 트래픽의 소스(from 내부) 또는 대상(to 내부)으로 지정할 때 사용됩니다. podSelector와 마찬가지로 matchLabels 또는 matchExpressions를 사용하여 원하는 네임스페이스 그룹을 선택할 수 있습니다.
- ingress 규칙의 from 필드 내 namespaceSelector: 여기에 정의된 레이블 셀렉터와 일치하는 레이블을 가진 네임스페이스에 속한 모든 파드들로부터 들어오는 트래픽을 허용합니다. 예를 들어, from: [] 라고 정의하면, project: monitoring 레이블을 가진 네임스페이스 내의 모든 파드들로부터 오는 연결을 허용합니다.
- egress 규칙의 to 필드 내 namespaceSelector: 여기에 정의된 레이블 셀렉터와 일치하는 레이블을 가진 네임스페이스에 속한 모든 파드들로 나가는 트래픽을 허용합니다. 예를 들어, to: [] 라고 정의하면, environment: shared-services 레이블을 가진 네임스페이스 내의 모든 파드들로의 연결을 허용합니다.
namespaceSelector는 멀티테넌트 환경이나 여러 팀이 공동으로 사용하는 클러스터에서 네임스페이스 단위로 네트워크 격리 및 접근 제어를 설정하는 데 매우 효과적입니다. 예를 들어, “A팀의 네임스페이스는 B팀의 네임스페이스에 있는 특정 서비스에만 접근할 수 있다”와 같은 정책을 구현할 수 있습니다.
podSelector와 namespaceSelector의 조합:
from 또는 to 필드 내에서 podSelector와 namespaceSelector를 함께 사용할 수도 있습니다. 이 경우, 지정된 namespaceSelector와 일치하는 네임스페이스에 있으면서, 동시에 지정된 podSelector와 일치하는 레이블을 가진 파드들이 트래픽의 소스 또는 대상이 됩니다. 이는 더 세밀한 접근 제어를 가능하게 합니다. 예를 들어, “모니터링 네임스페이스(namespaceSelector)에 있는 프로메테우스 서버 파드(podSelector)로부터만” 트래픽을 허용하도록 설정할 수 있습니다.
-
**ipBlock (IP 블록):**이 필드는 특정 IP 주소 범위(CIDR – Classless Inter-Domain Routing 형식) 또는 특정 IP 주소를 트래픽의 소스(from 내부) 또는 대상(to 내부)으로 지정할 때 사용됩니다. 이는 주로 쿠버네티스 클러스터 외부의 네트워크(예: 온프레미스 데이터센터, 다른 VPC, 인터넷상의 특정 IP)와의 통신을 제어하거나, 클러스터 내부의 특정 노드 IP 또는 서비스 IP 대역과의 통신을 명시적으로 허용/차단할 때 유용합니다.
-
ingress 규칙의 from 필드 내 ipBlock: 여기에 정의된 CIDR 범위에서 오는 트래픽을 허용합니다.
클립보드에 복사
Syntax Highlighter
from:
-
ipBlock:
cidr: 192.168.1.0/24 # 192.168.1.0 ~ 192.168.1.255 범위의 IP로부터의 트래픽 허용
except: # 선택 사항: 해당 CIDR 범위 내에서 제외할 IP 주소 범위 목록
- 192.168.1.100/32 # 192.168.1.100 IP는 제외
-
-
egress 규칙의 to 필드 내 ipBlock: 여기에 정의된 CIDR 범위로 나가는 트래픽을 허용합니다.
클립보드에 복사
Syntax Highlighter
to:
-
ipBlock:
cidr: 0.0.0.0/0 # 모든 외부 IP 주소로의 트래픽을 의미 (인터넷 접근 허용)
except:
-
10.0.0.0/8 # 단, 10.x.x.x 대역의 사설 IP는 제외
-
172.16.0.0/12 # 172.16.x.x ~ 172.31.x.x 대역 제외
-
192.168.0.0/16 # 192.168.x.x 대역 제외
-
(위 0.0.0.0/0에 대한 except 설정은 “인터넷으로만 나가고, 클러스터 내부 사설망으로는 나가지 못하게” 하는 일반적인 패턴입니다. 단, 클러스터 내부 파드 IP 대역이나 서비스 IP 대역이 이 사설망 범위에 포함된다면 의도치 않게 내부 통신까지 차단될 수 있으므로 주의해야 합니다. 보통은 클러스터 내부 통신은 podSelector나 namespaceSelector로 명시적으로 허용하고, 외부 인터넷 접근은 ipBlock으로 별도 관리하는 것이 좋습니다.)
-
-
ipBlock을 사용하면, 예를 들어 “사무실 고정 IP에서만 관리자 파드에 접근 허용” 또는 “애플리케이션 파드가 특정 외부 결제 게이트웨이 API 서버 IP로만 HTTPS 요청 가능”과 같은 정책을 구현할 수 있습니다.
from 또는 to 필드의 동작:
ingress 규칙의 from 배열이나 egress 규칙의 to 배열 내에 여러 개의 항목(예: 여러 개의 podSelector, namespaceSelector, ipBlock 항목)이 정의되어 있다면, 이들은 논리적 OR 관계로 평가됩니다. 즉, 들어오거나 나가는 트래픽이 이 항목들 중 어느 하나라도 만족하면 해당 소스 또는 대상으로 간주되어 규칙의 다음 단계(포트 검사)로 진행됩니다.
만약 from (Ingress 규칙의 경우) 또는 to (Egress 규칙의 경우) 필드가 아예 생략되거나 빈 배열 []로 주어지면, 이는 모든 소스 또는 모든 대상을 의미합니다. 즉, 특정 소스나 대상을 제한하지 않겠다는 뜻입니다. (단, 이 경우에도 ports 필드에 의해 포트 제한은 적용될 수 있습니다.)
8.4.4.2 포트 지정
네트워크 폴리시 규칙에서 트래픽의 출발지/목적지를 지정하는 것만큼이나 중요한 것은 어떤 네트워크 포트를 통해 통신을 허용할 것인지를 명시하는 것입니다. 아무리 신뢰할 수 있는 소스로부터 온 요청이라 할지라도, 예상치 못한 포트로의 접근은 보안 위협이 될 수 있습니다. 따라서 네트워크 폴리시는 ports 필드를 통해 허용할 프로토콜과 포트 번호를 세밀하게 제어할 수 있도록 지원합니다.
ports 필드는 ingress 규칙 또는 egress 규칙의 각 항목(from 또는 to 항목과 같은 레벨) 내에 정의되며, 허용할 포트들의 목록을 배열 형태로 가집니다. 각 포트 항목은 다음과 같은 하위 필드들로 구성됩니다.
- **protocol 필드 (선택 사항):**허용할 네트워크 프로토콜을 지정합니다. 유효한 값은 TCP, UDP, SCTP 입니다. 만약 이 필드가 생략되면 기본값으로 TCP가 사용됩니다.
- **port 필드 (필수):**허용할 포트 번호를 지정합니다. 이 값은 정수형 포트 번호 (예: 80, 443, 5432) 또는 파드 명세에 정의된 **명명된 포트(named port)**의 이름 (예: http, mysql-port)일 수 있습니다.
- 정수형 포트 번호: 특정 포트 번호로의 트래픽만 명시적으로 허용합니다.
- 명명된 포트: 파드 템플릿의 컨테이너 포트 정의 시 name 필드를 사용하여 포트에 이름을 부여할 수 있습니다 (예: name: web-port, containerPort: 8080). 네트워크 폴리시에서 이 web-port라는 이름을 port 필드에 지정하면, 실제 포트 번호가 변경되더라도 폴리시를 수정할 필요 없이 유연하게 대응할 수 있다는 장점이 있습니다.
포트 지정 예시:
다음은 ports 필드를 사용하여 다양한 방식으로 포트를 지정하는 예시입니다.
클립보드에 복사
Syntax Highlighter
Ingress 규칙 내의 ports 필드 예시
ingress:
-
from:
… (소스 지정)
ports:
-
protocol: TCP
port: 80 # 80번 TCP 포트 허용
-
protocol: TCP
port: 443 # 443번 TCP 포트 허용
-
protocol: UDP
port: 53 # 53번 UDP 포트 허용 (예: DNS)
-
port: my-custom-app-port # ‘my-custom-app-port’라는 이름의 명명된 포트 허용
-
Egress 규칙 내의 ports 필드 예시
egress:
-
to:
… (목적지 지정)
ports:
-
protocol: TCP
port: 3306 # 3306번 TCP 포트 (MySQL 기본 포트)로의 송신 허용
-
ports 필드의 동작:
- 만약 ports 필드가 아예 생략되거나 빈 배열 []로 주어지면, 해당 from 또는 to 조건에 맞는 소스/대상과의 모든 포트 및 모든 프로토콜에 대한 통신이 허용됩니다. 이는 매우 관대한 설정이므로, 정말로 모든 포트를 열어두어야 하는 경우가 아니라면 필요한 포트만 명시적으로 지정하는 것이 보안상 권장됩니다.
- 만약 ports 필드가 정의되어 있지만, 들어오거나 나가는 트래픽이 해당 ports 목록에 명시된 프로토콜과 포트 조합과 일치하지 않는다면, 그 트래픽은 차단됩니다 (해당 from/to 조건은 만족했더라도).
이처럼 네트워크 폴리시 규칙 내에서 podSelector, namespaceSelector, ipBlock을 조합하여 트래픽의 소스/대상을 정교하게 선택하고, ports 필드를 통해 허용할 프로토콜과 포트를 명확히 지정함으로써, 우리는 쿠버네티스 클러스터 내에서 매우 세밀하고 강력한 네트워크 접근 제어 정책을 구현할 수 있습니다. 이는 “최소 권한의 원칙”을 네트워크 레벨에서 실현하고, 애플리케이션의 보안 경계를 효과적으로 방어하는 데 핵심적인 역할을 합니다.
8.4.5 [실습] 특정 네임스페이스/파드 간의 통신 제한 설정
이론으로 네트워크 폴리시(Network Policy)의 필요성과 동작 원리, 그리고 규칙을 정의하는 방법을 자세히 살펴보았습니다. 이제는 직접 네트워크 폴리시를 생성하고 적용하여, 파드(Pod) 간의 통신을 실제로 제어해보는 실습을 진행할 차례입니다! 이번 실습에서는 먼저 특정 네임스페이스에 “기본적으로 모든 트래픽을 차단하는(Default Deny)” 정책을 설정한 후, 이어서 특정 레이블을 가진 파드에서만 다른 특정 파드로의 접근을 선택적으로 허용하는 정책을 추가로 생성해 볼 것입니다. 그리고 마지막으로 간단한 통신 테스트를 통해 우리가 정의한 네트워크 폴리시가 의도한 대로 잘 적용되어 통신이 차단되거나 허용되는지를 눈으로 직접 확인할 것입니다.
본 실습을 진행하기 위해서는 이전 실습들과 마찬가지로 쿠버네티스 클러스터와 kubectl CLI가 준비되어 있어야 합니다. 가장 중요한 전제 조건은, 현재 사용 중인 쿠버네티스 클러스터에 네트워크 폴리시 기능을 지원하는 CNI(Container Network Interface) 플러그인(예: Calico, Cilium, Weave Net 등)이 반드시 설치되고 활성화되어 있어야 한다는 점입니다. 만약 K3s를 기본 설정으로 사용 중이라면 Flannel CNI가 설치되어 있어 네트워크 폴리시가 동작하지 않을 수 있으므로, Calico와 같은 CNI로 변경하거나 네트워크 폴리시를 지원하는 환경을 별도로 준비해야 합니다. (일부 K3s 버전이나 설정에서는 Flannel과 함께 Canal 등을 통해 제한적인 폴리시 지원이 있을 수도 있으나, 확실한 테스트를 위해서는 Calico 사용을 권장합니다.)
자, 그럼 쿠버네티스 네트워크의 문지기, 네트워크 폴리시를 직접 설정하여 우리 클러스터의 보안을 한층 강화해 볼까요?
8.4.5.1 기본 Deny 정책 설정
네트워크 보안의 가장 좋은 출발점 중 하나는 “최소 권한의 원칙”에 따라, “기본적으로 모든 것을 차단하고, 필요한 것만 명시적으로 허용”하는 것입니다. 네트워크 폴리시를 사용하여 이러한 “기본 거부(Default Deny)” 전략을 특정 네임스페이스에 적용해 보겠습니다.
1단계: 테스트용 네임스페이스 생성
먼저, 이번 실습을 진행할 격리된 테스트 환경을 만들기 위해 새로운 네임스페이스를 하나 생성하겠습니다.
클립보드에 복사
Syntax Highlighter
kubectl create namespace network-policy-test
2단계: 테스트용 애플리케이션 배포 (서버 및 클라이언트 역할)
이 네임스페이스 안에 간단한 웹 서버 역할을 할 Nginx 파드(서버 역할)와, 이 서버에 접속을 시도할 유틸리티 파드(클라이언트 역할, 예: busybox)를 각각 디플로이먼트를 통해 배포하겠습니다.
nginx-server-deployment.yaml (서버 역할):
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-server
namespace: network-policy-test
spec:
replicas: 1
selector:
matchLabels:
app: nginx-server # 서버 파드를 식별할 레이블
template:
metadata:
labels:
app: nginx-server
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
busybox-client-deployment.yaml (클라이언트 역할):
클립보드에 복사
Syntax Highlighter
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-client
namespace: network-policy-test
spec:
replicas: 1
selector:
matchLabels:
app: busybox-client # 클라이언트 파드를 식별할 레이블
template:
metadata:
labels:
app: busybox-client
spec:
containers:
- name: busybox
image: busybox:1.28
command: ["sleep", "3600"] # 계속 실행되도록 sleep 명령 사용
터미널에서 다음 명령어로 배포합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f nginx-server-deployment.yaml
kubectl apply -f busybox-client-deployment.yaml
잠시 후 kubectl get pods -n network-policy-test 명령으로 두 파드가 모두 Running 상태인지 확인합니다. 또한, nginx-server 파드의 IP 주소를 확인해 둡니다 (예: kubectl get pods -n network-policy-test -o wide).
3단계: 초기 통신 테스트 (네트워크 폴리시 적용 전)
아직 아무런 네트워크 폴리시도 적용하지 않았으므로, busybox-client 파드에서 nginx-server 파드로 자유롭게 접속할 수 있어야 합니다. kubectl exec를 사용하여 busybox-client 파드 내부에서 nginx-server 파드의 IP 주소로 wget 또는 curl을 시도해 봅시다. (아래
클립보드에 복사
Syntax Highlighter
busybox-client 파드의 실제 이름 확인 (예: busybox-client-xxxxxxxxxx-yyyyy)
kubectl get pods -n network-policy-test -l app=busybox-client
확인된 busybox-client 파드 내부에서 nginx-server 파드로 접속 시도
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O - http://
성공적으로 접속되어 Nginx의 환영 페이지 HTML 내용이 출력될 것입니다. 이는 현재 아무런 통신 제한이 없음을 의미합니다.
4단계: 기본 Deny 정책 YAML 파일 작성 및 적용
이제 network-policy-test 네임스페이스에 있는 모든 파드에 대해 들어오고(Ingress) 나가는(Egress) 모든 트래픽을 기본적으로 차단하는 네트워크 폴리시를 생성합니다.
default-deny-all.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: network-policy-test # 이 네임스페이스에만 적용됩니다.
spec:
podSelector: {} # 빈 podSelector는 네임스페이스 내의 모든 파드를 선택합니다.
policyTypes:
-
Ingress # 수신 트래픽을 제어 대상으로 합니다.
-
Egress # 송신 트래픽을 제어 대상으로 합니다.
ingress: [] # ingress 규칙을 비워두면 모든 수신 트래픽을 차단합니다 (명시적으로 추가해도 동일).
egress: [] # egress 규칙을 비워두면 모든 송신 트래픽을 차단합니다 (명시적으로 추가해도 동일).
이 정책은 podSelector: 를 사용하여 network-policy-test 네임스페이스 내의 모든 파드를 대상으로 합니다. policyTypes에 Ingress와 Egress를 모두 지정하고, 구체적인 ingress나 egress 허용 규칙을 정의하지 않았으므로, 이 네임스페이스 내의 모든 파드는 이제 어떠한 수신 또는 송신 트래픽도 허용하지 않게 됩니다.
터미널에서 다음 명령어로 이 정책을 적용합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f default-deny-all.yaml
5단계: 통신 재테스트 (기본 Deny 정책 적용 후)
이제 다시 busybox-client 파드에서 nginx-server 파드로 접속을 시도해 봅시다. (네트워크 폴리시가 적용되는 데 약간의 시간이 걸릴 수 있습니다.)
클립보드에 복사
Syntax Highlighter
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O - -T 5 http://
-T 5 옵션은 타임아웃을 5초로 설정합니다.
이번에는 접속이 실패하고 타임아웃이 발생하거나, 연결 거부(connection refused)와 유사한 오류가 발생해야 합니다. 이는 default-deny-all 정책에 의해 nginx-server 파드로의 모든 수신 트래픽이 차단되었고, 동시에 busybox-client 파드에서의 모든 송신 트래픽도 차단되었기 때문입니다 (DNS 조회조차 실패할 수 있습니다).
이처럼 “기본 Deny” 정책은 네트워크 보안의 출발점으로 매우 중요하며, 이후 필요한 통신만을 선택적으로 허용하는 “화이트리스트” 기반의 정책을 구축하는 기초가 됩니다.
8.4.5.2 특정 레이블을 가진 파드에서만 접근 허용 정책 생성
이제 “기본 Deny” 상태에서, 특정 조건을 만족하는 경우에만 통신을 허용하는 네트워크 폴리시를 추가로 생성해 보겠습니다. 이번에는 app: busybox-client 레이블을 가진 파드에서 app: nginx-server 레이블을 가진 파드의 80번 포트로만 접근을 허용하는 Ingress 정책을 nginx-server 파드에 적용해 보겠습니다.
allow-nginx-from-busybox.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-nginx-ingress-from-busybox
namespace: network-policy-test
spec:
podSelector:
matchLabels:
app: nginx-server # 이 정책은 'app: nginx-server' 레이블을 가진 파드에 적용됩니다.
policyTypes:
- Ingress # 수신 트래픽에 대한 정책입니다.
ingress:
-
from: # 어떤 소스로부터 오는 트래픽을 허용할지 정의합니다.
-
podSelector: # ‘app: busybox-client’ 레이블을 가진 파드로부터 오는 트래픽
matchLabels:
app: busybox-client
ports: # 어떤 포트로의 트래픽을 허용할지 정의합니다.
-
protocol: TCP
port: 80 # 80번 TCP 포트로의 접근을 허용합니다.
-
이 정책은 app: nginx-server 레이블을 가진 파드(우리의 Nginx 서버)를 대상으로 하며, ingress 규칙을 통해 app: busybox-client 레이블을 가진 파드(우리의 BusyBox 클라이언트)로부터 오는 80번 TCP 포트로의 수신 트래픽만을 명시적으로 허용합니다. “기본 Deny” 정책이 이미 적용되어 있는 상태에서 이 정책이 추가되면, 이 규칙에 해당하지 않는 다른 모든 수신 트래픽은 여전히 차단됩니다. (주의: 이 정책은 nginx-server로 들어오는 트래픽만 허용합니다. busybox-client가 nginx-server로 나가는 트래픽을 허용하는 Egress 정책은 아직 없으므로, 실제 통신을 위해서는 busybox-client에 대한 Egress 허용 정책도 필요할 수 있습니다. 하지만 우선은 Ingress 정책의 효과만 확인해 보겠습니다.)
터미널에서 다음 명령어로 이 정책을 적용합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f allow-nginx-from-busybox.yaml
8.4.5.3 통신 테스트를 통해 정책 적용 확인
이제 두 개의 네트워크 폴리시(“기본 Deny” 정책과 “특정 Ingress 허용” 정책)가 모두 적용된 상태입니다. 이 상태에서 다시 통신 테스트를 수행하여 정책이 의도한 대로 동작하는지 확인해 보겠습니다.
1단계: busybox-client에서 nginx-server로의 통신 확인 (Ingress 허용)
먼저, allow-nginx-ingress-from-busybox 정책에 의해 허용되어야 하는 통신, 즉 busybox-client 파드에서 nginx-server 파드의 80번 포트로의 접속을 시도합니다.
클립보드에 복사
Syntax Highlighter
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O - -T 5 http://
이론적으로는 nginx-server 입장에서 busybox-client로부터의 80번 포트 접근은 허용되었습니다. 하지만 여전히 접속이 실패하고 타임아웃이 발생할 가능성이 높습니다. 왜 그럴까요?
바로 “기본 Deny” 정책이 busybox-client 파드의 Egress(송신) 트래픽도 모두 차단하고 있기 때문입니다! nginx-server가 문을 열어주었지만, busybox-client가 집 밖으로 나갈 수 없는 상황인 것입니다.
2단계: busybox-client의 Egress 허용 정책 추가 (선택 사항, 완전한 테스트를 위해)
따라서 완전한 통신 테스트를 위해서는 busybox-client 파드가 nginx-server 파드의 80번 포트로 나가는 트래픽을 허용하는 Egress 정책도 추가해야 합니다.
allow-busybox-egress-to-nginx.yaml:
2단계: busybox-client의 Egress 허용 정책 추가 (선택 사항, 완전한 테스트를 위해)
따라서 완전한 통신 테스트를 위해서는 busybox-client 파드가 nginx-server 파드의 80번 포트로 나가는 트래픽을 허용하는 Egress 정책도 추가해야 합니다.
allow-busybox-egress-to-nginx.yaml:
클립보드에 복사
Syntax Highlighter
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-busybox-egress-to-nginx
namespace: network-policy-test
spec:
podSelector:
matchLabels:
app: busybox-client # 이 정책은 'app: busybox-client' 레이블을 가진 파드에 적용됩니다.
policyTypes:
- Egress # 송신 트래픽에 대한 정책입니다.
egress:
-
to: # 어떤 목적지로 가는 트래픽을 허용할지 정의합니다.
-
podSelector: # ‘app: nginx-server’ 레이블을 가진 파드로 가는 트래픽
matchLabels:
app: nginx-server
ports: # 어떤 포트로의 트래픽을 허용할지 정의합니다.
-
protocol: TCP
port: 80 # 80번 TCP 포트로의 접근을 허용합니다.
-
DNS 조회를 위한 Egress 허용도 추가하는 것이 좋습니다.
-
to:
-
namespaceSelector: {} # 모든 네임스페이스
podSelector:
matchLabels:
k8s-app: kube-dns # CoreDNS 파드 레이블 (실제 환경에 맞게 확인)
ports:
-
protocol: UDP
port: 53
-
protocol: TCP
port: 53
-
위 Egress 정책을 적용합니다.
클립보드에 복사
Syntax Highlighter
kubectl apply -f allow-busybox-egress-to-nginx.yaml
3단계: 최종 통신 테스트 (모든 허용 정책 적용 후)
이제 nginx-server로의 Ingress가 허용되었고, busybox-client에서 nginx-server로의 Egress도 허용되었으므로, 다시 통신을 시도합니다.
클립보드에 복사
Syntax Highlighter
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O - -T 5 http://
이번에는 성공적으로 Nginx의 환영 페이지 HTML 내용이 출력되어야 합니다!
4단계: 허용되지 않은 통신 시도 (다른 포트 또는 다른 파드)
만약 nginx-server 파드에 80번 포트가 아닌 다른 포트(예: 81번)로 접속을 시도하거나, busybox-client가 아닌 다른 파드(만약 있다면)에서 nginx-server로 접속을 시도하면, 해당 통신은 여전히 차단되어야 합니다. 이는 우리가 정의한 네트워크 폴리시가 정확히 의도한 대로 동작하고 있음을 보여줍니다.
예를 들어, busybox-client에서 nginx-server의 81번 포트로 접속 시도 (실패해야 함):
클립보드에 복사
Syntax Highlighter
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O - -T 5 http://
kubectl exec -n network-policy-test <busybox-client-파드이름> — wget -q -O – -T 5 http://
이 실습을 통해 우리는 먼저 “기본 Deny” 정책을 통해 모든 통신을 차단한 후, 필요한 통신만을 Ingress 및 Egress 규칙을 통해 선택적으로 허용하는 방식으로 네트워크 폴리시를 구성하고 테스트했습니다. 이러한 단계적인 접근 방식은 쿠버네티스 클러스터의 네트워크 보안을 체계적으로 강화하는 데 매우 효과적입니다. 실제 운영 환경에서는 애플리케이션의 통신 요구사항을 면밀히 분석하여, 각 파드 그룹에 대해 최소한의 필요한 통신만을 허용하는 정교한 네트워크 폴리시를 수립하는 것이 중요합니다.
작성일: 2025-11-06