본문 바로가기

Kubernetes

Kubernetes Service 종류: ClusterIP, NodePort, LoadBalancer, Ingress 비교

반응형

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 타입 전체 구조

 

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로 접근할 수 없습니다.

ClusterIP 트래픽 흐름

 

트래픽 흐름:

  1. 클라이언트 Pod가 Service의 ClusterIP(예: 10.96.0.10:80)로 요청을 보냅니다.
  2. kube-proxy가 설정한 iptables/nftables 규칙이 요청을 가로챕니다.
  3. DNAT(Destination NAT)로 실제 Pod IP:Port로 변환합니다.
  4. 여러 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
Tip
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로 라우팅됩니다.

NodePort 트래픽 흐름

 

트래픽 흐름:

  1. 외부 클라이언트가 <NodeIP>:30080으로 요청을 보냅니다.
  2. 해당 노드의 kube-proxy가 요청을 받습니다.
  3. Service의 Endpoint 목록에서 Pod를 선택하여 전달합니다.
  4. 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로 접근합니다.

LoadBalancer 트래픽 흐름

 

트래픽 흐름:

  1. 클라우드 LB가 External IP(예: 52.1.2.3)를 할당받습니다.
  2. 외부 클라이언트가 External IP:80으로 요청합니다.
  3. 클라우드 LB가 Worker Node의 NodePort로 트래픽을 전달합니다.
  4. 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 타입)
Tip
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 등)가 담당합니다.

Ingress 트래픽 흐름

 

트래픽 흐름:

  1. 외부 클라이언트가 app.example.com으로 요청합니다.
  2. DNS가 Ingress Controller의 External IP로 해석됩니다.
  3. Ingress Controller가 Ingress 규칙을 확인합니다.
  4. 경로/호스트에 따라 적절한 ClusterIP Service로 라우팅합니다.
  5. 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. 선택 기준: 어떤 타입을 언제 사용하는가

Service 타입 선택 의사결정 흐름

의사결정 기준 정리

질문 답변 선택
외부 트래픽을 받아야 하는가? 아니오 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. 보안 고려사항

Security Note
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) 조합입니다.

참고 문서

반응형