최근 업데이트일 2024-11-03
쿠버네티스는 Pod과 직접 통신하는 방법 대신, 별도의 고정된 IP를 가진 서비스를 만들고 그 서비스를 통해 Pod에 접근하는 방식을 사용한다. 이러한 서비스에 대해서 알아보자.
쿠버네티스에서 Pod는 유동적인 IP를 가지므로, Pod의 재시작/재배포 시 IP가 바뀔 수 있다.
1. Service란?
- 동일한 서비스를 제공하는 Pod 그룹에 대해 Virtual IP를 만들어서 단일 진입점 제공한다.
- Kubernetes Cluster 내 어떤 Pod가 종료됐다가 다시 생성되면서 Pod IP가 바뀌었다면, 이를 다른 앱 내 Pod에게 알려준다.
- Pod 집합의 변경과 상관없이 항상 고정된 IP 가진다.
- 로드 밸런싱을 지원한다. (여러 Pod 간 트래픽 분산)
spec.selector를 통해 연결할 Pod 선택하며, Pod는 환경 변수나 DNS를 사용하여 서비스를 식별한다.
2. Service 주요 설정 필드
| 필드 | 설명 |
|---|
spec.clusterIP | 가상 IP(Virtual IP), 단일 진입점 IP |
spec.selector | 연결할 대상 Pod 지정 |
spec.ports.port | Service가 노출하는 포트 |
spec.ports.targetPort | 연결되는 Pod의 포트 |
spec.ports.nodePort | NodePort 타입일 때 노드에 노출되는 포트 |
spec.externalName | ExternalName 타입일 때 외부 DNS 이름 지정 |
3. Service 타입 및 종류
ClusterIP
클러스터 내부에서만 접근 가능한 IP를 제공한다.
- 기본 타입 (
spec.type 생략 시 적용)
- Pod 집합에 대한 단일 진입점 IP를 생성한다.
- Pod가 몇 개든지 간에 하나의 IP로 접근 가능하다.
- IP는
10.96.0.0/12 대역에서 자동 할당된다.
- 내부 DNS 등록:
<서비스이름>.<네임스페이스>.svc.cluster.local
- 클러스터 '내부'에서 로드밸런싱 기능을 포함한다.
- 내부 DNS 이름으로 Pod 간 통신이 가능하다.
NodePort
클러스터 외부에서 접근 가능한 포트를 모든 노드에 열어준다.
ClusterIP를 내부적으로 생성한 후, 노드의 포트도 노출시킨다.
- 외부에서
<Node IP>:<NodePort> 로 접근 가능하다.
30000~32767의 포트 범위 내에서 지정할 수 있다. 포트 확인: netstat -napt | grep <NodePort>
- 명시적으로 설정하지 않으면 자동으로 포트가 할당된다. (
service-node-port-range)
- 노드 IP + nodePort로 유입된 트래픽은 iptables의
KUBE-NODEPORTS Chain으로 전달된다.
- 이 체인은 다시 NodePort로 어떤 서비스에 대한 TCP Packet인지 판단하여 해당 서비스 Chain으로 전달한다.
- 클러스터 외부에서 접근 가능하다.
- 여러 노드가 있어도 어느 노드로든 접근 가능하다.
- 다만, 사용자가 살아있는 어떤 Node로 접근해야 하는지를 직접 알아야 한다.
자동으로 살아 있는 노드에 접근하기 위해 모든 노드를 바라보는 Load Balancer가 필요하다.
LoadBalancer
외부 LoadBalancer 인스턴스를 provisioning하여 서비스를 외부에 노출한다.
ClusterIP + NodePort가 자동으로 생성된다.
- 클라우드 플랫폼(AWS, GCP, Azure, OpenStack 등)에서 쿠버네티스가 API를 호출해서 사용한다.
- 퍼블릭 IP를 가진 로드밸런서가 생성되고, 그 IP를 통해 외부에서 접근 가능하다.
- 실제 트래픽은 Load Balancer → Node → NodePort → Pod로 전달된다.
- 외부 클라이언트 접근에 가장 이상적인 서비스 타입이다.
- 노드 수나 상태에 영향을 받지 않고, 자동으로 살아있는 노드 중 하나로 트래픽을 전달한다.
- 클라우드 인프라에서만 사용 가능하지만 온프레미스 환경에서도 MetalLB, Kube-VIP, OpenELB 등을 사용하여 구현할 수 있다.
ExternalName
클러스터 내부에서 외부 DNS 이름을 참조할 수 있게 해주는 서비스
- 클러스터 내부에서 마치 자체 서비스처럼 이름으로 접근하면, 실제로는 그 요청이 외부의 DNS 도메인으로 연결되게 만든다.
spec.externalName에 FQDN(Fully Qualified Domain Name)을 지정한다. (e.g. example.com)
- 클러스터 내부의 Pod들이 외부 시스템을 사용할 수 있게 해준다.
- Service의 이름으로 DNS 요청 시, 지정한 외부 도메인으로 redirect된다.
- 실제로
clusterIP가 할당되지 않으며 DNS level에서 처리된다.
- CNAME 응답을 받아서 이를 외부 DNS 주소로 직접 통신한다.
- 클러스터 내부 DNS 이름으로 외부 서비스를 참조할 수 있다.
- 요청이 네트워크 경로가 내부가 아닌 외부 DNS로 연결된다.
4. Headless Service
spec.clusterIP: None 으로 설정하여 로드밸런싱 없이 Pod에 개별 접근할 수 있다.
- 서비스 IP, 로드 밸런싱이 필요하지 않을 때 사용하며, 쿠버네티스 서비스 구현에 의존하지 않고 다른 Service Discovery Mechanism을 사용할 수 있도록 보장한다.
- CoreDNS는 Headless Service의 이름에 대해 A 레코드를 여러 개 등록하고 그 A 레코드는 각 Pod의 IP를 가리킨다.
<headless-service-name>.default.svc.cluster.local 같은 이름을 DNS 조회하면, 서비스 뒤에 연결된 각 Pod들의 IP 목록이 응답된다.
- 즉, Service와 연결된 Pod의 endpoint로 DNS 레코드(
<pod-name>.<namespace>.pod.cluster.local)가 생성돼서 coreDNS에 등록되어 DNS resolving Service 지원한다.
- StatefulSet 등 Pod 이름이 고정된 경우에 유용하다.
<pod-name>.<namespace>.pod.cluster.local로
cat /etc/resolv.conf
curl pod-ip.namespace.pod.cluster.local
※ Round-Robin DNS는 지원하지 않는다.
Round-Robin DNS: 서비스마다 DNS를 부여하고 백엔드 Pod들의 IP를 Record로 나열하여 Round-robin 방식으로 로드 밸런싱하는 방법
- 쿠버네티스는 DNS Round-Robin 로드밸런싱을 아래 이유로 공식적으로 지원하지 않는다.
- DNS의 TTL 설정을 고려하지 않는다.
- 클라이언트 측 DNS 캐싱이 불균형 발생할 수 있다.
- 트래픽이 특정 Pod으로 몰릴 수 있다.
대신, kube-proxy나 IPVS 등을 통해 실제 트래픽 분산 처리를 수행한다.
5. 백엔드 연결을 위한 kube-proxy: 기초
모든 노드에 존재하며 Service 요청을 적절한 Pod으로 전달하는 역할을 한다.
- 트래픽을 서비스 뒤의 Pod으로 전달하는 방식을 선택할 수 있으며, 사용 환경에 따라 다양한 프록시 모드를 제공한다.
- 서버와 클라이언트 사이에서 대리인의 역할을 하며 Node(Host)의 Interface(eth0)와 Pod의 Interface(veth0)를 중계한다.
- 서비스와 파드 연결(다른 노드간에도 가능) + 서비스 디스커버리(서비스 검색 후 IP와 포트로 트래픽 전달) + 로드 밸런싱(클라이언트 트래픽을 파드로 분산)
- Node Port와 같은 Service를 통해 외부에 port를 노출시켜야 할 때, 그 port를 Listen하는 역할도 담당한다.
proxy mode 종류
- userspace
- 초기 방식
- 클라이언트 트래픽을 수신해서 해당 서비스의 Pod로 전달하며, IPVS와 iptables 사용하지 않는다.
- 클라이언트 요청 → kube-proxy 수신 → Pod 전달
- iptables
- 기본 방식
- 쿠버네티스에서 Node와 Pod는 쉽게 교체되는 구조이기 때문에, netfilter와 iptables를 이용한다.
- Cluster IP가 생성되거나 Pod가 추가될 때, Endpoint 연결을 위해 자신이 동작하고 있는 Node의 iptables에 rule을 추가한다.
- clusterIP로 접속하면 해당 룰을 통해 Pod로 직접 연결된다.
- netfilter(리눅스 커널의 일부인 프레임워크)는 네트워크 트래픽을 패킷 필터링하거나, NAT 기능으로 패킷의 소스 또는 목적지 IP주소를 변경해서 실제 서비스의 파드 IP주소로 변환하며, 트래픽 포워딩을 한다.
- ipvs
- 리눅스 커널이 지원하는 L4 로드밸런싱 기술 이용하며, 별도의 ipvs 지원 모듈 설정 후 적용 가능하다.
- IP 주소가 Dummy NIC에 바인딩되어, LoadBalancer나 External IP의 트래픽 분산 성능 향상시킨다.
- 즉, 가상의 인터페이스에 IP를 할당하여 실제 NIC를 사용했을 때 충돌 위험성을 피하고 Virtual IP만 트래픽 분산용으로 사용하고, 실제 패킷 처리는 ipvs가 처리한다.
6. Session Affinity
Service에서 특정 클라이언트의 요청이 항상 동일한 Pod으로 가도록 설정한다.
sessionAffinity: ClientIP
iptables 기반 kube-proxy에서 recent 모듈을 사용해 구현한다.
- recent는 리눅스 커널 netfilter 기능 중 하나로, 최근에 접속했던 IP 주소를 기억하고, 조건에 따라 처리하는 기능을 제공한다.
- 특정 클라이언트의 Source IP 기준으로 이전에 접근한 Pod 기억한다.
- iptables에 IP–Pod 매핑을 저장하고, 이후 트래픽도 동일한 Pod으로 전달한다.
- 클라이언트가 원하지 않는데 해당 Pod로 갈 수 있기 때문에, 헤더나 쿠키, 세션 등을 잘 이용해야 한다.
7. Endpoint란?
Service와 연결된 Pod의 IP와 Port 정보 집합으로 IP/Port를 Endpoints 리소스로 관리한다.
- Service가 selector로 Pod를 찾아서 자동 생성되며,
kubectl get endpoints로 확인 가능하다.
spec.selector를 생략하는 경우 수동 정의해야 한다. 이때 Service의 metadata.name과 동일해야 하며, 지정한 subsets.addresses는 외부에 존재하는 IP가 될 수도 있다.
- 외부 서버를 내부에서 접근하고 싶을 때나 Pod가 아닌 고정된 IP/Port로 구성된 백엔드에 붙고 싶을 때 사용한다.