1. 개요

Service는 여러 Pod 앞에 두는 고정 주소다. 공식 문서는 “Pod 로 실행 중인 네트워크 애플리케이션을 노출하는 방법"이라고 정의한다.

Pod 는 죽고 다시 뜰 때마다 IP 가 바뀐다. 앱이 다른 앱의 IP 를 직접 알고 있으면 그 앱이 재시작하는 순간 연결이 끊긴다. Service 는 그 앞에 안 바뀌는 이름과 IP 를 하나 세워둔다.

오른쪽은 계속 바뀌는데, 왼쪽은 아무것도 안 바꿔도 된다frontend부르는 쪽http://backend:8080이 한 줄이 안 바뀐다Service: backend10.96.0.12 고정Pod 10.1.3.7 ✕ 죽음Pod 10.1.8.2 ✕ 죽음Pod 10.1.2.9 ● 지금이름으로재시작할 때마다 IP 가 새로 배정된다부르는 쪽은 뒤에서 Pod 가 몇 번 죽고 살아나든 모른다

앱은 http://backend:8080 으로 부르면 된다. 뒤에서 Pod 가 몇 개 뜨고 지든 모른다.

2. 타입별 도달 범위

네 종류가 있고, 앞의 셋은 위에 얹히는 구조다.

경로는 하나 — 타입은 그 앞에 입구를 몇 개 더 세울지 정한다ClusterIP10.96.0.12 · 항상 있다PodNodePort30000~32767LoadBalancer클라우드 공인 IP외부사용자클러스터 안 앱은 여기로 바로 들어온다type: ClusterIPtype: NodePorttype: LoadBalancer바깥 타입을 고르면 안쪽 것이 자동으로 함께 만들어진다ExternalName 만 이 경로에 없다 — 프록시 없이 DNS CNAME 만 돌려준다
타입어디서 닿나만들면 함께 생기는 것
ClusterIP (기본)클러스터 안에서만—
NodePort노드 IP + 고정 포트ClusterIP
LoadBalancer외부 IP (클라우드가 발급)NodePort + ClusterIP
ExternalName— (DNS CNAME 만 반환)프록시 없음

표를 위 그림과 겹쳐 읽으면 이렇게 된다. NodePort 를 만들면 ClusterIP 가 자동으로 함께 생기고, LoadBalancer 를 만들면 NodePort 와 ClusterIP 가 둘 다 함께 생긴다. 없애는 게 아니라 앞에 입구를 하나씩 더 세우는 것이다.

type: ClusterIP      →  [ClusterIP]
type: NodePort       →  [NodePort] → [ClusterIP]
type: LoadBalancer   →  [LB] → [NodePort] → [ClusterIP]
                          ↑ 새로 생기는 입구      ↑ 항상 있다

ExternalName 만 성격이 다르다. 트래픽을 넘기는 게 아니라 DNS 로 다른 이름을 알려줄 뿐이라 프록시도 ClusterIP 도 없다. 외부 DB 를 클러스터 안 이름처럼 부르고 싶을 때 쓴다.

3. 셀렉터와 EndpointSlice

Service 는 IP 목록을 직접 들고 있지 않다. 라벨 셀렉터로 조건만 적어두면 컨트롤러가 맞는 Pod 를 계속 찾아 채운다.

spec:
  selector:
    app: backend        # 이 라벨을 가진 Pod 전부
  ports:
  - port: 80            # Service 가 받는 포트
    targetPort: 9376    # Pod 가 듣는 포트

공식 문서의 설명대로 컨트롤러가 “셀렉터에 맞는 Pod 를 계속 스캔해 EndpointSlice 를 갱신"한다. Pod 가 늘거나 죽으면 그 목록이 자동으로 바뀐다.

Service 는 IP 목록을 갖고 있지 않다 — 조건만 적혀 있다내가 쓴 것Serviceselector: app=backend실제로 떠 있는 것app=backendapp=backendapp=other컨트롤러조건에 맞는 Pod 를 찾는다EndpointSlice10.1.3.7 · 10.1.8.2kube-proxy노드마다 라우팅 규칙① 조건을 읽고② 맞는 Pod 를 골라③ 목록을 채운다④ 읽는다Pod 가 늘거나 죽으면 ②③ 이 자동으로 다시 돈다조건을 적어두면 목록은 컨트롤러가 계속 맞춰 준다

port 와 targetPort 가 다를 수 있다는 점이 자주 걸린다. 앞은 부르는 쪽이 쓰는 번호, 뒤는 컨테이너가 실제로 듣는 번호다.

4. Headless Service

clusterIP: None 을 주면 가상 IP 를 만들지 않는다. DNS 를 물으면 Service IP 하나가 아니라 Pod IP 들이 그대로 돌아온다.

일반 Service      backend  →  10.96.0.12          (하나. 뒤에서 분배)
Headless Service  backend  →  10.1.3.7, 10.1.8.2  (전부. 직접 고른다)

부하 분산을 포기하는 대신 개별 Pod 를 지목할 수 있게 된다. StatefulSet 과 함께 쓰이는 이유다 — 데이터베이스 복제본에서 “0번이 리더"처럼 특정 Pod 를 찍어야 하는 경우가 있다.

5. 타입 선택

클러스터 안에서만 쓴다              → ClusterIP
개발 중 잠깐 밖에서 확인한다         → NodePort
운영 환경에서 외부에 연다            → LoadBalancer (또는 Ingress)
Pod 를 개별로 지목해야 한다          → Headless
외부 주소에 클러스터 안 이름을 붙인다  → ExternalName

HTTP 서비스를 여러 개 외부에 연다면 LoadBalancer 를 서비스마다 만드는 것보다 Ingress 가 낫다. LoadBalancer 는 클라우드에서 개당 과금되므로, 하나의 진입점에서 경로로 나누는 편이 저렴하고 관리하기 쉽다.

6. 관련 문서

  • Label / Selector — Service 가 Pod 를 고르는 방법 (예정)
  • StatefulSet — Headless Service 와 함께 쓰이는 워크로드 (예정)
  • Ingress — HTTP 경로 기반 외부 노출 (예정)

7. 출처

정의, 타입별 동작, 계층 관계, EndpointSlice 갱신은 공식 문서 “Service” 에 근거한다.

“In Kubernetes, a Service is a method for exposing a network application that is running as one or more Pods in your cluster.”

“NodePort: Exposes the Service on each Node’s IP at a static port (the NodePort). A ClusterIP Service, to which the NodePort Service routes, is automatically created.”

“LoadBalancer: Exposes the Service externally using a cloud provider’s load balancer. NodePort and ClusterIP Services, to which the external load balancer routes, are automatically created.”

“The controller for that Service continuously scans for Pods that match its selector, and then makes any necessary updates to the set of EndpointSlices for the Service.”