Pod는 생성될 때마다 IP가 바뀝니다. Service는 변하지 않는 단일 진입점을 제공하여 Pod 집합에 안정적으로 접근할 수 있게 합니다. 어떤 Service 타입을 선택하느냐에 따라 트래픽 경로, 비용, 보안 범위가 달라집니다.
핵심 요약
- ClusterIP: 클러스터 내부에서만 접근 가능한 가상 IP를 할당합니다. 마이크로서비스 간 내부 통신에 사용합니다.
- NodePort: 모든 Worker Node의 특정 포트(30000~32767)를 열어 외부 접근을 허용합니다. 개발/테스트 환경에서 주로 사용합니다.
- LoadBalancer: 클라우드 벤더의 로드 밸런서를 자동 프로비저닝하여 외부 트래픽을 받습니다. 프로덕션 외부 노출의 기본 선택입니다.
- Ingress: L7(HTTP/HTTPS) 수준에서 호스트/경로 기반 라우팅을 제공합니다. 여러 Service를 하나의 진입점으로 통합합니다.
- 실무에서는 대부분 Ingress + ClusterIP 조합을 사용합니다. LoadBalancer를 Service마다 생성하면 비용이 급증합니다.
1. 왜 Service가 필요한가
Deployment로 Pod 3개를 띄웠습니다. 각 Pod에 IP가 할당되지만, Pod가 재시작되면 IP가 바뀝니다. 다른 서비스에서 이 Pod들에 접근하려면 매번 새 IP를 알아내야 합니다.
이 문제를 해결하는 것이 Service입니다.
| 문제 | Service가 해결하는 방식 |
|---|---|
| Pod IP가 변경됨 | 고정된 Virtual IP(ClusterIP)를 제공 |
| 여러 Pod에 트래픽 분배 | 자동 로드 밸런싱 (kube-proxy가 처리) |
| Pod 추가/삭제 시 반영 | Label Selector로 동적 Endpoint 관리 |
| DNS 기반 접근 | <service-name>.<namespace>.svc.cluster.local 자동 등록 |
실무 시나리오: 프론트엔드 Pod가 백엔드 API Pod에 요청을 보내야 합니다. 백엔드 Pod가 3개이고 Rolling Update 중에 IP가 계속 바뀝니다. Service를 만들면 프론트엔드는 http://api-svc:8080이라는 고정 주소만 알면 됩니다. 어떤 Pod가 죽고 새로 생겨도 Service가 알아서 라우팅합니다.
2. Service 타입 전체 구조

Kubernetes Service는 계층적으로 동작합니다. NodePort는 ClusterIP를 포함하고, LoadBalancer는 NodePort를 포함합니다.
ClusterIP (기본)
└── NodePort (ClusterIP + 노드 포트 개방)
└── LoadBalancer (NodePort + 외부 LB 프로비저닝)
Ingress (별도 리소스, Service 위에서 L7 라우팅)
| 타입 | 접근 범위 | 외부 노출 | 비용 | 주요 용도 |
|---|---|---|---|---|
| ClusterIP | 클러스터 내부만 | ❌ | 없음 | 마이크로서비스 간 통신 |
| NodePort | 노드 IP:Port | ⚠️ 제한적 | 없음 | 개발/테스트, 온프레미스 |
| LoadBalancer | 외부 IP | ✅ | 클라우드 LB 비용 | 프로덕션 외부 노출 |
| Ingress | 도메인/경로 기반 | ✅ | Ingress Controller 비용 | L7 라우팅, TLS, 다중 서비스 |
3. ClusterIP: 클러스터 내부 통신의 기본
동작 원리
ClusterIP는 Service의 기본 타입입니다. 클러스터 내부에서만 접근 가능한 가상 IP를 할당합니다. 외부에서는 이 IP로 접근할 수 없습니다.

트래픽 흐름:
- 클라이언트 Pod가 Service의 ClusterIP(예: 10.96.0.10:80)로 요청을 보냅니다.
- kube-proxy가 설정한 iptables/nftables 규칙이 요청을 가로챕니다.
- DNAT(Destination NAT)로 실제 Pod IP:Port로 변환합니다.
- 여러 Pod가 있으면 랜덤 또는 라운드 로빈으로 분배합니다.
YAML 예시
apiVersion: v1
kind: Service
metadata:
name: api-svc
namespace: production
spec:
type: ClusterIP # 생략해도 기본값
selector:
app: api
version: v1
ports:
- name: http
port: 80 # Service가 노출하는 포트
targetPort: 8080 # Pod가 실제로 리스닝하는 포트
protocol: TCP
DNS 접근 방식
Service를 생성하면 CoreDNS가 자동으로 DNS 레코드를 등록합니다.
# 같은 네임스페이스에서 접근
curl http://api-svc:80
# 다른 네임스페이스에서 접근
curl http://api-svc.production.svc.cluster.local:80
언제 사용하는가
- 마이크로서비스 간 내부 통신 (프론트엔드 → 백엔드, 백엔드 → DB 프록시)
- 외부에 노출할 필요 없는 내부 API
- Ingress Controller 뒤에 배치되는 백엔드 Service
spec.clusterIP: None으로 설정하면 Headless Service가 됩니다. 가상 IP 없이 Pod IP를 직접 DNS로 반환합니다. StatefulSet(DB 클러스터 등)에서 특정 Pod에 직접 접근해야 할 때 사용합니다.4. NodePort: 노드 IP로 외부 접근 허용
동작 원리
NodePort는 ClusterIP에 추가로 모든 Worker Node의 특정 포트(30000~32767)를 열어 외부 트래픽을 받습니다. 어떤 노드의 IP:Port로 접근해도 해당 Service의 Pod로 라우팅됩니다.

트래픽 흐름:
- 외부 클라이언트가
<NodeIP>:30080으로 요청을 보냅니다. - 해당 노드의 kube-proxy가 요청을 받습니다.
- Service의 Endpoint 목록에서 Pod를 선택하여 전달합니다.
- Pod가 해당 노드에 없어도 다른 노드의 Pod로 전달됩니다 (cross-node 라우팅).
YAML 예시
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
type: NodePort
selector:
app: web
ports:
- name: http
port: 80 # ClusterIP에서 접근하는 포트
targetPort: 8080 # Pod 포트
nodePort: 30080 # 노드에서 열리는 포트 (생략하면 자동 할당)
protocol: TCP
제약사항과 trade-off
| 항목 | 설명 |
|---|---|
| 포트 범위 제한 | 30000~32767만 사용 가능. 80, 443 같은 표준 포트를 직접 사용할 수 없음 |
| 보안 노출 | 모든 노드에서 해당 포트가 열림. 특정 노드만 선택적으로 열 수 없음 |
| 로드 밸런싱 없음 | 클라이언트가 특정 노드 IP를 알아야 함. 노드 장애 시 수동 전환 필요 |
| 포트 충돌 | 클러스터 전체에서 NodePort가 고유해야 함. Service가 많아지면 관리 어려움 |
언제 사용하는가
- 개발/테스트 환경에서 빠르게 외부 접근을 확인할 때
- 온프레미스 환경에서 외부 로드 밸런서(F5, HAProxy)를 직접 연결할 때
- 클라우드 LB 비용을 쓸 수 없는 상황에서 임시 외부 노출
프로덕션에서 NodePort를 직접 외부에 노출하는 것은 권장하지 않습니다. 노드 IP가 변경되거나 노드가 교체되면 클라이언트 접근이 끊깁니다. 또한 노드의 보안 그룹/방화벽에서 30000~32767 범위를 열어야 하므로 공격 표면이 넓어집니다.
externalTrafficPolicy 설정
NodePort와 LoadBalancer에서 중요한 설정입니다.
spec:
externalTrafficPolicy: Local # 또는 Cluster (기본값)
| 정책 | 동작 | 장점 | 단점 |
|---|---|---|---|
| Cluster (기본) | 모든 노드가 트래픽을 받고, 다른 노드의 Pod로도 전달 | 균등 분배 | 추가 홉 발생, 클라이언트 IP 손실 |
| Local | 해당 노드에 Pod가 있을 때만 트래픽 수신 | 클라이언트 IP 보존, 지연 감소 | Pod가 없는 노드로 요청하면 실패 |
externalTrafficPolicy: Local은 클라이언트 IP를 보존해야 하는 경우(접근 로그, IP 기반 제한)에 사용합니다. 다만 Pod가 특정 노드에만 있으면 트래픽이 불균형해질 수 있습니다.
5. LoadBalancer: 클라우드 환경의 외부 노출 표준
동작 원리
LoadBalancer 타입은 클라우드 벤더(AWS, Azure, GCP)의 로드 밸런서를 자동으로 프로비저닝합니다. 외부 IP가 할당되고, 클라이언트는 이 IP로 접근합니다.

트래픽 흐름:
- 클라우드 LB가 External IP(예: 52.1.2.3)를 할당받습니다.
- 외부 클라이언트가 External IP:80으로 요청합니다.
- 클라우드 LB가 Worker Node의 NodePort로 트래픽을 전달합니다.
- kube-proxy가 실제 Pod로 라우팅합니다.
내부적으로 LoadBalancer = ClusterIP + NodePort + 클라우드 LB입니다.
YAML 예시
apiVersion: v1
kind: Service
metadata:
name: web-public
annotations:
# AWS NLB 사용 시
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
# 내부 LB로 제한 시
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
spec:
type: LoadBalancer
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
클라우드별 동작 차이
| 클라우드 | 기본 LB 타입 | 특징 |
|---|---|---|
| AWS | Classic LB → NLB (annotation으로 변경) | NLB는 고정 IP 지원, Target Group 직접 연결 가능 |
| Azure | Azure Load Balancer (L4) | Standard SKU 기본, Public/Internal 선택 |
| GCP | Network Load Balancer (L4) | Regional 기본, Global은 Ingress 사용 |
비용 문제: 왜 Service마다 LB를 만들면 안 되는가
실무 시나리오: 마이크로서비스 10개를 운영합니다. 각각 LoadBalancer Service를 만들면?
| 항목 | 계산 |
|---|---|
| AWS NLB 비용 | ~$16/월 × 10개 = $160/월 (고정 비용만) |
| 데이터 처리 비용 | 트래픽에 따라 추가 |
| IP 주소 | 10개의 Public IP 관리 필요 |
| TLS 인증서 | 10개 각각 설정 필요 |
대안: Ingress Controller 1개 + ClusterIP Service 10개 = LB 1개 비용만 발생. 이것이 실무에서 Ingress를 선호하는 핵심 이유입니다.
언제 사용하는가
- TCP/UDP 레벨(L4)에서 외부 노출이 필요한 경우 (데이터베이스, gRPC, 게임 서버)
- HTTP가 아닌 프로토콜을 외부에 노출해야 하는 경우
- 단일 서비스만 외부에 노출하는 단순한 구조
- Ingress Controller 자체를 외부에 노출할 때 (Ingress Controller의 Service가 LoadBalancer 타입)
AWS에서
service.beta.kubernetes.io/aws-load-balancer-type: "nlb-ip" annotation을 사용하면 NodePort를 거치지 않고 Pod IP로 직접 라우팅합니다. 추가 홉이 줄어들어 지연이 감소하고, externalTrafficPolicy 설정 없이도 클라이언트 IP를 보존할 수 있습니다.6. Ingress: L7 라우팅과 다중 서비스 통합
Ingress는 Service 타입이 아니다
Ingress는 Service의 type 필드가 아니라 별도의 Kubernetes 리소스입니다. Service 위에서 L7(HTTP/HTTPS) 수준의 라우팅 규칙을 정의합니다. 실제 트래픽 처리는 Ingress Controller(Nginx, AWS ALB, Traefik 등)가 담당합니다.

트래픽 흐름:
- 외부 클라이언트가
app.example.com으로 요청합니다. - DNS가 Ingress Controller의 External IP로 해석됩니다.
- Ingress Controller가 Ingress 규칙을 확인합니다.
- 경로/호스트에 따라 적절한 ClusterIP Service로 라우팅합니다.
- Service가 최종 Pod로 트래픽을 전달합니다.
YAML 예시
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
- path: /admin
pathType: Prefix
backend:
service:
name: admin-svc
port:
number: 80
Ingress Controller 선택
Ingress 리소스를 정의해도 Ingress Controller가 없으면 동작하지 않습니다. 클러스터에 별도로 설치해야 합니다.
| Controller | 특징 | 적합한 환경 |
|---|---|---|
| Nginx Ingress | 가장 널리 사용, 풍부한 annotation 지원 | 범용, 온프레미스/클라우드 모두 |
| AWS ALB Ingress (AWS LB Controller) | ALB를 직접 프로비저닝, AWS 네이티브 통합 | EKS 환경 |
| Traefik | 자동 TLS, 미들웨어 체인, 가벼움 | 소규모~중규모 |
| Istio Gateway | Service Mesh와 통합, 세밀한 트래픽 제어 | Istio 사용 환경 |
Ingress의 장점
| 기능 | 설명 |
|---|---|
| 호스트 기반 라우팅 | api.example.com → API Service, web.example.com → Web Service |
| 경로 기반 라우팅 | /api/* → API Service, / → Web Service |
| TLS 종료 | Ingress에서 HTTPS를 처리하고 백엔드는 HTTP로 통신 |
| 단일 진입점 | LB 1개로 여러 Service를 노출 → 비용 절감 |
| 인증/Rate Limiting | annotation으로 미들웨어 기능 추가 가능 |
언제 사용하는가
- HTTP/HTTPS 서비스를 외부에 노출할 때 (대부분의 웹 서비스)
- 여러 서비스를 하나의 도메인/IP로 통합할 때
- TLS 인증서를 중앙에서 관리하고 싶을 때
- 경로/호스트 기반으로 트래픽을 분배해야 할 때
Ingress는 HTTP/HTTPS(L7)만 지원합니다. TCP/UDP 프로토콜(데이터베이스, gRPC without HTTP/2, 게임 서버)을 외부에 노출해야 한다면 LoadBalancer Service를 사용해야 합니다. 일부 Ingress Controller(Nginx)는 TCP/UDP 프록시를 지원하지만 표준 Ingress 스펙 밖의 기능입니다.
7. 선택 기준: 어떤 타입을 언제 사용하는가

의사결정 기준 정리
| 질문 | 답변 | 선택 |
|---|---|---|
| 외부 트래픽을 받아야 하는가? | 아니오 | ClusterIP |
| L7 라우팅(경로/호스트)이 필요한가? | 예 | Ingress + ClusterIP |
| 클라우드 환경인가? | 예 | LoadBalancer |
| 개발/테스트 용도인가? | 예 | NodePort |
| 온프레미스 프로덕션인가? | 예 | NodePort + MetalLB 또는 외부 LB |
실무에서 가장 많이 쓰는 조합
1. Ingress + ClusterIP (가장 일반적)
외부 → Ingress Controller(LoadBalancer) → Ingress 규칙 → ClusterIP Service → Pod
- Ingress Controller 1개만 LoadBalancer로 노출
- 나머지 모든 Service는 ClusterIP
- 비용 효율적이고 L7 기능(TLS, 라우팅, Rate Limiting) 활용 가능
2. LoadBalancer 직접 사용 (L4 필요 시)
외부 → Cloud LB → NodePort → Pod
- TCP/UDP 프로토콜 노출 시
- gRPC, 데이터베이스 프록시, 게임 서버 등
3. ClusterIP + kubectl port-forward (개발 시)
# 로컬에서 클러스터 내부 Service에 접근
kubectl port-forward svc/api-svc 8080:80
# localhost:8080 → api-svc:80
- 개발 중 빠르게 내부 Service를 테스트할 때
- NodePort를 열지 않아도 됨
8. 실무 사용 사례
사례: 마이크로서비스 5개를 EKS에서 운영
전자상거래 서비스를 운영합니다. 프론트엔드(React), API Gateway, 상품 서비스, 주문 서비스, 결제 서비스로 구성됩니다.
설계 결정:
| 서비스 | Service 타입 | 이유 |
|---|---|---|
| 프론트엔드 | ClusterIP | Ingress 뒤에 배치, / 경로로 라우팅 |
| API Gateway | ClusterIP | Ingress 뒤에 배치, /api/* 경로로 라우팅 |
| 상품 서비스 | ClusterIP | API Gateway에서만 내부 호출 |
| 주문 서비스 | ClusterIP | API Gateway에서만 내부 호출 |
| 결제 서비스 | ClusterIP | 주문 서비스에서만 내부 호출 |
| Ingress Controller | LoadBalancer | 유일한 외부 진입점 |
Ingress 설정:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ecommerce-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- shop.example.com
secretName: shop-tls
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
이 설계의 trade-off:
- LB 1개 비용(~$16/월)으로 5개 서비스를 모두 노출할 수 있습니다.
- Ingress Controller가 단일 장애점(SPOF)이 될 수 있으므로 replica를 2개 이상 유지합니다.
- 내부 서비스 간 통신은 ClusterIP + DNS로 처리하여 외부 노출을 최소화합니다.
- 결제 서비스처럼 민감한 서비스는 NetworkPolicy로 접근을 주문 서비스에서만 허용합니다.
9. 보안 고려사항
Service 타입 선택은 곧 네트워크 노출 범위를 결정하는 것입니다. 불필요하게 넓은 범위로 노출하면 공격 표면이 증가합니다.
타입별 보안 위험
| 타입 | 위험 | 완화 방법 |
|---|---|---|
| ClusterIP | 클러스터 내부 Pod 간 무제한 접근 | NetworkPolicy로 Pod 간 통신 제한 |
| NodePort | 모든 노드에서 포트 개방 | Security Group/방화벽으로 소스 IP 제한 |
| LoadBalancer | 외부 IP 노출 | WAF 연동, IP 화이트리스트, TLS 필수 |
| Ingress | HTTP 레벨 공격 (XSS, SQL Injection) | Ingress annotation으로 Rate Limiting, ModSecurity 연동 |
최소 노출 원칙
# ❌ 나쁨: 모든 서비스를 LoadBalancer로 노출
apiVersion: v1
kind: Service
metadata:
name: internal-api
spec:
type: LoadBalancer # 내부 API인데 외부에 노출?
---
# ✅ 좋음: 내부 서비스는 ClusterIP, 외부 노출은 Ingress로 통합
apiVersion: v1
kind: Service
metadata:
name: internal-api
spec:
type: ClusterIP # 외부 접근 불가
Ingress 보안 설정 예시
metadata:
annotations:
# Rate Limiting
nginx.ingress.kubernetes.io/limit-rps: "10"
# IP 화이트리스트
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,172.16.0.0/12"
# HSTS 강제
nginx.ingress.kubernetes.io/hsts: "true"
# 요청 크기 제한
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
10. 비용/운영 고려사항
비용 비교
| 타입 | 고정 비용 | 변동 비용 | 비고 |
|---|---|---|---|
| ClusterIP | 없음 | 없음 | 클러스터 내부 리소스만 사용 |
| NodePort | 없음 | 없음 | 노드 포트만 사용, 추가 인프라 불필요 |
| LoadBalancer | ~$16~25/월 (클라우드 LB) | 데이터 처리량 기반 | Service마다 LB 1개 생성 |
| Ingress | LB 1개 비용 + Controller Pod 리소스 | 데이터 처리량 기반 | 여러 Service를 1개 LB로 통합 |
비용 최적화 전략
1. LoadBalancer 수 최소화
❌ Service 10개 × LoadBalancer = LB 10개 = ~$160~250/월
✅ Ingress Controller 1개(LoadBalancer) + ClusterIP 10개 = LB 1개 = ~$16~25/월
2. Internal LoadBalancer 활용
VPC 내부에서만 접근하는 서비스(관리자 도구, 내부 API)는 Internal LB를 사용합니다. Public IP가 할당되지 않아 보안과 비용 모두 유리합니다.
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
3. 불필요한 NodePort 정리
LoadBalancer Service를 삭제해도 클라우드 LB가 남아있는 경우가 있습니다. kubectl get svc로 EXTERNAL-IP가 할당된 Service를 주기적으로 확인합니다.
# 외부 IP가 할당된 Service 확인
kubectl get svc --all-namespaces -o wide | grep -i loadbalancer
11. 자주 하는 실수
1. 모든 Service를 LoadBalancer로 만듦
Service마다 LoadBalancer를 생성하면 비용이 선형으로 증가합니다. 대부분의 HTTP 서비스는 Ingress + ClusterIP 조합으로 충분합니다.
2. Service의 selector가 Pod label과 불일치
Service가 생성되었는데 Endpoint가 비어있으면 selector와 Pod label이 일치하지 않는 경우입니다.
# Endpoint 확인 — 비어있으면 selector 불일치
kubectl get endpoints api-svc
# NAME ENDPOINTS AGE
# api-svc <none> 5m ← Pod가 연결되지 않음
# Pod label 확인
kubectl get pods --show-labels
3. targetPort와 containerPort 불일치
Service의 targetPort는 Pod의 containerPort와 일치해야 합니다. 불일치하면 연결은 되지만 응답이 없습니다.
# Pod: containerPort: 8080
# Service: targetPort: 80 ← 불일치! 연결 실패
# Service: targetPort: 8080 ← 올바름
4. Ingress Controller를 설치하지 않고 Ingress 리소스만 생성
Ingress 리소스를 만들어도 Ingress Controller가 없으면 아무 일도 일어나지 않습니다. ADDRESS 필드가 비어있으면 Controller가 없거나 인식하지 못하는 상태입니다.
# Ingress 상태 확인
kubectl get ingress
# NAME CLASS HOSTS ADDRESS PORTS AGE
# app-ingress nginx app.example.com 80 5m
# ↑ 비어있음 = Controller 미설치 또는 미인식
5. NodePort를 프로덕션에서 직접 사용
NodePort는 노드 IP에 의존합니다. Auto Scaling으로 노드가 교체되면 IP가 바뀌고, 클라이언트 접근이 끊깁니다. 프로덕션에서는 LoadBalancer 또는 Ingress를 사용해야 합니다.
12. 정리
- ClusterIP는 클러스터 내부 통신의 기본입니다. 외부 노출이 필요 없는 모든 서비스에 사용합니다.
- NodePort는 개발/테스트 또는 온프레미스에서 외부 접근을 빠르게 확인할 때 사용합니다. 프로덕션 직접 노출은 피합니다.
- LoadBalancer는 클라우드 환경에서 L4 외부 노출이 필요할 때 사용합니다. 다만 Service마다 LB를 만들면 비용이 급증합니다.
- Ingress는 L7 라우팅이 필요한 HTTP/HTTPS 서비스의 표준 선택입니다. 여러 Service를 하나의 진입점으로 통합하여 비용과 관리 복잡도를 줄입니다.
- 실무에서 가장 일반적인 패턴은 Ingress Controller(LoadBalancer) + 백엔드 Service(ClusterIP) 조합입니다.
참고 문서
'Kubernetes' 카테고리의 다른 글
| Kubernetes HPA가 동작하지 않는 이유 (0) | 2026.06.07 |
|---|---|
| EKS vs AKS vs GKE: 매니지드 Kubernetes 비교 (0) | 2026.06.06 |
| Kubernetes RBAC란 무엇인가: Role, ClusterRole, Binding으로 권한 설계하기 (0) | 2026.06.01 |
| Kubernetes 아키텍처 기본 구조: Control Plane과 Worker Node (0) | 2026.05.31 |