Pod는 다시 만들어질 때마다 IP가 바뀌므로, Kubernetes에서는 Service가 고정된 이름과 IP를 주고 뒤의 Pod로 요청을 나눠 보냅니다. 이 글에서는 Service의 동작과 타입, 클러스터 DNS, 그리고 HTTP 요청을 경로와 호스트 이름으로 나눠 보내는 Ingress와 그 후속인 Gateway API를 정리합니다.
컨테이너부터 Kubernetes까지 시리즈 8편입니다. 이전 글은 [Kubernetes] Pod, ReplicaSet, Deployment 정리이고, 전체 순서는 시리즈 목차에 있습니다.
1. Service가 필요한 이유
7편에서 본 것처럼 Pod는 지워지고 다시 만들어질 때마다 이름과 IP가 바뀌고, 롤링 업데이트를 하면 모든 Pod가 새 IP로 바뀝니다. 그래서 다른 앱이 Pod IP를 직접 적어 두고 접속할 수 없습니다.
Service는 라벨로 Pod를 골라 하나의 고정된 이름과 가상 IP(ClusterIP)로 묶습니다. 회사 대표 번호처럼, 담당자가 바뀌어도 대표 번호로 걸면 지금 자리에 있는 사람에게 연결됩니다. Service에 연결된 Pod 목록은 EndpointSlice라는 리소스에 기록되고, readinessProbe를 통과한 Pod만 들어갑니다. 각 노드의 kube-proxy가 이 목록을 보고 ClusterIP로 온 요청을 Pod 중 하나로 보냅니다.

NodePort는 ClusterIP 위에 노드 포트를, LoadBalancer는 그 위에 외부 로드밸런서를 더한 구조입니다
2. Service 타입
| 타입 | 접속 경로 | 용도 |
| ClusterIP | 클러스터 안에서만 쓰는 가상 IP와 DNS 이름 | 기본값, 클러스터 내부 통신 |
| NodePort | 모든 노드의 같은 포트(30000~32767)로 접속 ClusterIP도 함께 생김 |
테스트, 외부 로드밸런서를 직접 붙일 때 |
| LoadBalancer | 클라우드가 외부 로드밸런서와 공인 IP를 만들어 연결 | 클라우드에서 서비스를 외부에 공개 |
| ExternalName | 클러스터 밖 도메인을 가리키는 DNS 별칭(CNAME) | 외부 DB 등을 내부 이름으로 부를 때 |
Service는 클러스터 DNS(CoreDNS)에 이름이 등록됩니다. 같은 네임스페이스에서는 web처럼 Service 이름만으로, 다른 네임스페이스에서는 web.default처럼 네임스페이스를 붙여 접속하고, 전체 이름은 web.default.svc.cluster.local입니다. 4편에서 Docker의 사용자 정의 bridge가 컨테이너 이름을 IP로 바꿔 주던 것과 같은 역할입니다.
3. Ingress와 Gateway API
LoadBalancer Service는 서비스마다 로드밸런서와 IP를 하나씩 만들고, HTTP의 호스트 이름이나 경로는 보지 않습니다. 웹 서비스가 여러 개면 입구 하나에서 shop.example.com은 shop Service로, /api는 api Service로 나눠 보내는 L7 라우팅이 필요한데, 이를 정의하는 API가 Ingress와 Gateway API입니다. 둘 다 규칙만 정의하고, 실제로 요청을 받아 전달하는 것은 따로 설치하는 컨트롤러입니다.
Ingress는 오래 쓰여 온 방식이지만 Kubernetes 프로젝트는 Ingress API를 더 이상 발전시키지 않기로(frozen) 했고, 새로 구성할 때는 Gateway API를 권장합니다. Ingress API 자체는 GA라서 제거 계획은 없습니다. 가장 널리 쓰이던 컨트롤러인 ingress-nginx는 2026년 3월로 유지보수가 끝나 이후 보안 패치도 나오지 않으므로, 쓰고 있다면 Gateway API나 다른 컨트롤러로 옮겨야 합니다.

인프라 담당자는 GatewayClass와 Gateway를, 앱 담당자는 HTTPRoute를 관리하도록 역할이 나뉩니다
| 항목 | Ingress | Gateway API |
| 리소스 | Ingress 하나에 입구와 라우팅 규칙을 함께 정의 | GatewayClass, Gateway, HTTPRoute로 나눔 |
| 프로토콜 | HTTP, HTTPS | HTTP, HTTPS, gRPC 등(Route 종류별) |
| 고급 기능 | 컨트롤러마다 다른 어노테이션으로 설정 | 헤더 조건, 가중치 분배 등을 표준 필드로 정의 |
| 상태 | GA, 기능 추가 없음(frozen) | GA, 계속 발전 중 |
예를 들어 Gateway로 들어온 요청을 web Service로 보내는 HTTPRoute는 아래와 같습니다. parentRefs로 어느 Gateway에 붙을지, backendRefs로 어느 Service로 보낼지 정합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
spec:
parentRefs:
- name: web-gw
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web
port: 80
kind에서는 cloud-provider-kind라는 별도 프로그램을 호스트에서 실행하면 LoadBalancer Service와 Gateway API를 쓸 수 있습니다. 관리자 권한이 필요하고 OS마다 접속 방법이 달라서, 이 글의 실습은 Service까지만 하고 설정 방법은 kind 문서를 참고하면 됩니다.
4. 직접 해 보기
7편에서 만든 web Deployment 앞에 ClusterIP Service를 만듭니다.
kubectl expose deployment web --port 80
kubectl get svc web
kubectl get endpointslices -l kubernetes.io/service-name=web
expose는 Deployment의 라벨(app=web)을 그대로 selector로 쓰는 Service를 만듭니다. Service에는 10.96.으로 시작하는 CLUSTER-IP가 붙고, EndpointSlice에는 Pod IP 3개가 나옵니다. 이 IP로는 클러스터 밖에서 접속할 수 없으므로 임시 Pod를 띄워 안에서 확인합니다.
kubectl run tmp --rm -it --restart=Never --image=busybox:1.38 -- \
sh -c 'wget -qO- http://web | head -4; cat /etc/resolv.conf'
nginx 기본 페이지의 앞부분(Welcome to nginx!)이 나오면 web이라는 이름이 ClusterIP로 바뀌어 접속된 것입니다. resolv.conf의 nameserver는 CoreDNS의 Service IP(10.96.0.10)이고, search에 default.svc.cluster.local이 들어 있어서 web만 적어도 전체 이름으로 찾아집니다.
다음은 NodePort입니다. 6편에서 kind 설정에 30080 포트를 미리 열어 두었으므로 nodePort를 30080으로 고정합니다.
# web-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: web-nodeport
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
kubectl apply -f web-nodeport.yaml
curl localhost:30080
호스트의 30080 포트는 kind-control-plane 컨테이너의 30080으로 연결되고, 그 노드의 kube-proxy가 worker 노드에 있는 Pod로 요청을 넘깁니다. Pod가 없는 노드로 들어온 요청도 전달된다는 점이 NodePort의 특징입니다. 개발 중에 잠깐 확인할 때는 Service를 만들지 않고 kubectl port-forward svc/web 8080:80으로 내 PC의 포트를 연결하는 방법도 있습니다.
5. 운영 시 주의점
| 항목 | 조치 |
| selector 오타 | 라벨이 맞지 않으면 Service는 만들어지지만 EndpointSlice가 비어 접속 실패 접속이 안 되면 EndpointSlice부터 확인 |
| LoadBalancer 남발 | 클라우드에서는 Service마다 로드밸런서 비용과 공인 IP가 생김 웹 서비스는 Gateway 하나로 모아서 공개 |
| ingress-nginx | 2026년 3월 이후 보안 패치가 없음 Gateway API나 다른 컨트롤러로 이전(ingress2gateway 도구로 변환 가능) |
| externalIPs | v1.36부터 deprecated 외부 IP가 필요하면 LoadBalancer나 Gateway 사용 |
| NodePort 공개 | 모든 노드에 포트가 열림 운영에서 외부에 직접 열지 말고 로드밸런서 뒤에 둠 |
6. 정리
- Service는 라벨로 고른 Pod에 고정된 이름과 ClusterIP를 주고, 준비된 Pod에만 요청을 나눠 보냅니다.
- ClusterIP는 내부용이고, NodePort와 LoadBalancer는 그 위에 외부 입구를 하나씩 더한 타입입니다.
- HTTP 라우팅은 Ingress 대신 Gateway API로 구성하고, ingress-nginx를 쓰고 있다면 이전을 계획합니다.
다음 글 9편에서는 설정 값과 비밀번호를 이미지 밖으로 빼서 관리하는 ConfigMap과 Secret을 다룹니다.
참고
'DevOps > Kubernetes' 카테고리의 다른 글
| [Kubernetes] Volume, PV, PVC, StorageClass 정리 (0) | 2026.09.28 |
|---|---|
| [Kubernetes] ConfigMap과 Secret 정리 (0) | 2026.09.28 |
| [Kubernetes] Pod, ReplicaSet, Deployment 정리 (0) | 2026.09.28 |
| [Kubernetes] 클러스터 구조 정리 (0) | 2026.09.28 |
| [Kubernetes] 토큰 만료 후 워커 노드 추가 (0) | 2021.11.02 |