iBetter Books
교안 슬라이드
수정

Service 타입 정리 (ClusterIP · NodePort · LoadBalancer)

Pod는 재시작될 때마다 IP가 바뀝니다. 특정 Pod의 IP를 직접 기록해두는 방법은 실전에서 통하지 않습니다. Service는 이 문제를 해결합니다. 라벨 셀렉터로 Pod 집합을 선택하고, 항상 같은 주소로 트래픽을 전달하는 안정적인 엔드포인트를 제공합니다.

Service 타입은 네 가지입니다. 각 타입은 "어디서 접근할 수 있는가"를 결정합니다.

Service 타입 비교

타입 접근 범위 클라우드 비용 주요 용도
ClusterIP 클러스터 내부만 없음 Pod 간 통신, 내부 서비스
NodePort 노드 IP:포트 없음 개발/테스트, 간단한 외부 노출
LoadBalancer 외부 IP (클라우드) 로드밸런서 1개당 과금 프로덕션 단일 서비스 노출
ExternalName 외부 DNS 이름 없음 클러스터 외부 서비스 추상화

ClusterIP

가장 기본적인 타입입니다. 클러스터 내부에서만 사용할 수 있는 가상 IP를 할당합니다. 타입을 명시하지 않으면 ClusterIP가 기본값입니다.

apiVersion: v1kind: Servicemetadata:  name: api-service  namespace: defaultspec:  type: ClusterIP          # 생략해도 동일  selector:    app: api-server        # 이 라벨을 가진 Pod에게 트래픽 전달  ports:    - name: http      port: 80             # Service가 수신하는 포트      targetPort: 8080     # Pod 컨테이너의 포트
# ClusterIP 확인kubectl get svc api-service# NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE# api-service   ClusterIP   10.96.45.123   <none>        80/TCP    5m# 클러스터 내부 다른 Pod에서 접근curl http://api-service.default.svc.cluster.local/healthcurl http://api-service/health   # 같은 네임스페이스라면 단축 가능

spec.clusterIP: None을 지정하면 Headless Service가 됩니다. 가상 IP 없이 Pod IP를 직접 DNS로 반환합니다. StatefulSet에서 각 Pod에 고정 DNS를 부여할 때 사용합니다.

apiVersion: v1kind: Servicemetadata:  name: mysql-headlessspec:  clusterIP: None    # Headless  selector:    app: mysql  ports:    - port: 3306      targetPort: 3306

NodePort

각 노드의 특정 포트(30000~32767)를 열어 외부에서 노드IP:포트로 접근할 수 있게 합니다. ClusterIP를 포함하므로 내부 통신도 됩니다.

apiVersion: v1kind: Servicemetadata:  name: web-nodeportspec:  type: NodePort  selector:    app: web-frontend  ports:    - name: http      port: 80             # 클러스터 내부 접근 포트      targetPort: 3000     # Pod 포트      nodePort: 30080      # 외부 접근 포트 (30000~32767, 생략 시 자동 할당)
# NodePort 서비스 생성 및 확인kubectl apply -f web-nodeport.yamlkubectl get svc web-nodeport# NAME           TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE# web-nodeport   NodePort   10.96.77.200    <none>        80:30080/TCP   2m# kind 환경에서 노드 IP 확인kubectl get nodes -o wide# 접근: curl http://<NODE_IP>:30080# minikube 환경에서 바로 접근minikube service web-nodeport --url

NodePort는 포트 범위가 제한적이고, 노드 IP가 바뀌면 접근 주소도 바뀝니다. 프로덕션보다는 개발·테스트 환경에 적합합니다.

LoadBalancer

클라우드 공급자(AWS, GCP, Azure)의 로드밸런서를 프로비저닝해서 고정 외부 IP를 부여합니다. NodePort를 포함하므로 내부/외부 통신을 모두 지원합니다.

apiVersion: v1kind: Servicemetadata:  name: api-lb  annotations:    # AWS EKS에서 NLB 사용    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"    # 내부 로드밸런서로 설정 (선택)    service.beta.kubernetes.io/aws-load-balancer-internal: "true"spec:  type: LoadBalancer  selector:    app: api-server  ports:    - name: http      port: 80      targetPort: 8080    - name: https      port: 443      targetPort: 8443  # 특정 IP에서만 접근 허용 (선택)  loadBalancerSourceRanges:    - "203.0.113.0/24"
# LoadBalancer 프로비저닝 대기 (클라우드에서 수 분 소요)kubectl get svc api-lb --watch# NAME     TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE# api-lb   LoadBalancer   10.96.10.200   203.0.113.45     80:31000/TCP   3m# EXTERNAL-IP로 직접 접근curl http://203.0.113.45/health

로컬 환경(kind, minikube)에서는 클라우드 프로바이더가 없어 EXTERNAL-IP가 <pending> 상태로 남습니다. 로컬에서 LoadBalancer를 테스트하려면 MetalLB를 설치하거나, Ingress를 사용하는 것이 현실적입니다.

# MetalLB 설치 (kind/bare-metal 환경)kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml# IP 주소 풀 설정cat <<EOF | kubectl apply -f -apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata:  name: first-pool  namespace: metallb-systemspec:  addresses:    - 172.20.0.200-172.20.0.250---apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata:  name: l2adv  namespace: metallb-systemEOF

ExternalName

클러스터 내부의 Service 이름을 외부 DNS 이름으로 매핑합니다. 실제로 트래픽을 중계하지 않고, DNS CNAME 레코드를 반환합니다.

apiVersion: v1kind: Servicemetadata:  name: external-db  namespace: productionspec:  type: ExternalName  externalName: prod-db.us-east-1.rds.amazonaws.com
# 클러스터 내부에서 external-db로 접근하면 RDS 엔드포인트로 해석됨# Pod 내부에서:# mysql -h external-db.production.svc.cluster.local -u admin -p

데이터베이스나 외부 API처럼 클러스터 외부에 있는 서비스를 클러스터 내부 이름으로 추상화할 때 유용합니다. 나중에 DB를 클러스터 안으로 이전해도 애플리케이션 코드를 수정할 필요가 없습니다.

언제 어떤 타입을 선택하는가

서비스 타입 선택 기준을 정리하면 다음과 같습니다.

ClusterIP를 선택합니다 - Pod끼리만 통신하는 백엔드 서비스(API 서버, DB, 캐시)에 사용합니다. 외부 노출이 필요 없고, 보안 측면에서 가장 안전합니다.

NodePort를 선택합니다 - 클라우드 없는 로컬이나 온프레미스 환경에서 간단하게 외부 접근이 필요할 때 사용합니다. 포트가 고정적이고 노드 수가 적은 환경에 적합합니다.

LoadBalancer를 선택합니다 - 클라우드 환경에서 단일 서비스를 외부에 안정적으로 노출할 때 사용합니다. 단, 서비스마다 로드밸런서 하나씩 과금되므로, 여러 서비스를 노출해야 한다면 Ingress가 경제적입니다.

ExternalName을 선택합니다 - 클러스터 외부 엔드포인트(RDS, 외부 API)를 클러스터 내부 이름으로 추상화할 때 사용합니다.

# 서비스 상태 한눈에 보기kubectl get svc -Akubectl describe svc <service-name># Endpoints 확인 (셀렉터가 매칭되는 Pod IP 목록)kubectl get endpoints <service-name># 특정 서비스로 트래픽이 실제로 가는지 확인kubectl run debug --image=curlimages/curl --rm -it --restart=Never -- \  curl http://api-service.default.svc.cluster.local/health