Post

Part 10/26: Service 타입과 Endpoint

Part 10/26: Service 타입과 Endpoint

Kubernetes에서 Pod는 일시적(ephemeral)인 존재이다. Pod가 재시작되면 IP 주소가 변경되고, Deployment로 관리되는 Pod들은 언제든지 새로운 Pod로 교체될 수 있다. 이러한 환경에서 안정적인 네트워크 통신을 위해 Service라는 추상화 계층이 필요하다.

원문 (kubernetes.io - Service): In Kubernetes, a Service is a method for exposing a network application that is running as one or more Pods in your cluster. The Service API abstracts the way you access the workload from the underlying deployment.

번역: Kubernetes에서 Service는 클러스터에서 하나 이상의 Pod로 실행되는 네트워크 애플리케이션을 노출하는 방법이다. Service API는 기본 배포에서 워크로드에 접근하는 방식을 추상화한다.

Service의 필요성

Pod IP의 한계

1
2
3
4
5
6
7
8
9
# Pod는 생성될 때마다 새로운 IP를 할당받는다
apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
  - name: nginx
    image: nginx:1.24

Pod IP의 문제점:

  • 일시적: Pod 재시작 시 IP 변경
  • 예측 불가: 어떤 IP가 할당될지 알 수 없음
  • 직접 노출 불가: 클러스터 외부에서 접근 불가
  • 로드밸런싱 없음: 여러 Pod에 트래픽 분산 불가

Service의 역할

Service는 다음을 제공한다:

  1. 안정적인 엔드포인트: 변하지 않는 ClusterIP와 DNS 이름
  2. 서비스 디스커버리: DNS를 통한 자동 검색
  3. 로드밸런싱: 여러 Pod에 트래픽 분산
  4. 외부 노출: NodePort, LoadBalancer를 통한 외부 접근

Service 기본 구조

Service와 Endpoints

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web        # 이 Label을 가진 Pod들을 선택
  ports:
  - port: 80        # Service 포트
    targetPort: 8080  # Pod의 컨테이너 포트
    protocol: TCP

Service를 생성하면 Kubernetes는 자동으로 Endpoints 오브젝트를 생성한다:

1
2
3
4
5
6
7
8
9
# Service 확인
kubectl get svc web-service
# NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
# web-service   ClusterIP   10.96.45.123   <none>        80/TCP    1m

# Endpoints 확인 - 실제 Pod IP 목록
kubectl get endpoints web-service
# NAME          ENDPOINTS                                AGE
# web-service   10.244.1.5:8080,10.244.2.6:8080,...     1m

핵심 원리: Service는 selector에 매칭되는 모든 Pod의 IP를 Endpoints에 등록하고, 트래픽을 이 Endpoints로 분산한다.

Selector 없는 Service

외부 서비스나 다른 Namespace의 서비스에 연결할 때 사용한다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Selector 없는 Service
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
  - port: 3306
---
# 수동으로 Endpoints 정의
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db  # Service와 이름이 같아야 함
subsets:
  - addresses:
    - ip: 192.168.1.100  # 외부 DB 서버 IP
    - ip: 192.168.1.101
    ports:
    - port: 3306

이 패턴은 다음 상황에서 유용하다:

  • 외부 데이터베이스 연결
  • 다른 클러스터의 서비스 연결
  • 레거시 시스템과의 통합

Service 타입

ClusterIP (기본값)

원문 (kubernetes.io - Service Types): Exposes the Service on a cluster-internal IP. Choosing this value makes the Service only reachable from within the cluster. This is the default that is used if you don’t explicitly specify a type for a Service.

번역: 클러스터 내부 IP에서 Service를 노출한다. 이 값을 선택하면 Service가 클러스터 내에서만 접근 가능하다. Service에 대한 type을 명시적으로 지정하지 않으면 사용되는 기본값이다.

클러스터 내부에서만 접근 가능한 가상 IP를 할당한다.

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP  # 생략해도 기본값
  selector:
    app: backend
  ports:
  - port: 80
    targetPort: 8080

특징:

  • 클러스터 내부 통신 전용
  • 안정적인 내부 DNS 제공
  • 가장 기본적이고 많이 사용되는 타입

접근 방법:

1
2
3
4
5
# 같은 Namespace에서
curl http://backend-service:80

# 다른 Namespace에서
curl http://backend-service.default.svc.cluster.local:80

NodePort

원문 (kubernetes.io - Service Types): Exposes the Service on each Node’s IP at a static port (the NodePort). To make the node port available, Kubernetes sets up a cluster IP address, the same as if you had requested a Service of type: ClusterIP.

번역: 각 노드의 IP에서 정적 포트(NodePort)로 Service를 노출한다. 노드 포트를 사용 가능하게 하기 위해 Kubernetes는 type: ClusterIP의 Service를 요청한 것과 동일하게 클러스터 IP 주소를 설정한다.

각 노드의 특정 포트를 열어 외부에서 접근할 수 있게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80          # Service 포트 (ClusterIP)
    targetPort: 8080  # Pod 포트
    nodePort: 30080   # 노드 포트 (30000-32767)

트래픽 흐름:

flowchart TB
    client["외부 클라이언트"]
    node["NodeIP:30080<br/>(어떤 노드든)"]
    cluster["ClusterIP:80"]
    pod["Pod:8080"]

    client --> node --> cluster --> pod

특징:

  • 포트 범위: 30000-32767 (기본값)
  • 모든 노드에서 해당 포트 오픈
  • nodePort 생략 시 자동 할당

주의사항:

  • 노드 IP가 변경되면 클라이언트도 업데이트 필요
  • 보안상 직접 노출보다는 LoadBalancer나 Ingress 권장
  • 노드가 다운되면 해당 노드로의 접근 불가

LoadBalancer

원문 (kubernetes.io - Service Types): Exposes the Service externally using an external load balancer. Kubernetes does not directly offer a load balancing component; you must provide one, or you can integrate your Kubernetes cluster with a cloud provider.

번역: 외부 로드 밸런서를 사용하여 Service를 외부로 노출한다. Kubernetes는 직접 로드 밸런싱 컴포넌트를 제공하지 않는다. 로드 밸런서를 제공하거나 Kubernetes 클러스터를 클라우드 공급자와 통합해야 한다.

클라우드 프로바이더의 외부 로드밸런서를 프로비저닝한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
  name: web-lb
  annotations:
    # AWS NLB 사용 예시
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080

트래픽 흐름:

flowchart TB
    internet["인터넷"]
    lb["Cloud Load Balancer<br/>(External IP)"]
    nodeport["NodePort<br/>(자동 생성)"]
    cluster["ClusterIP"]
    pod["Pod"]

    internet --> lb --> nodeport --> cluster --> pod

특징:

  • 클라우드 환경에서 자동으로 LB 생성
  • 외부 IP 주소 할당
  • NodePort를 자동으로 포함

클라우드별 어노테이션 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# AWS - Internal Load Balancer
metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"

# GCP - Static IP
metadata:
  annotations:
    networking.gke.io/load-balancer-type: "Internal"

# Azure - Internal Load Balancer
metadata:
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"

ExternalName

클러스터 내부에서 외부 DNS 이름을 사용할 수 있게 해주는 특수한 Service 타입이다.

1
2
3
4
5
6
7
apiVersion: v1
kind: Service
metadata:
  name: external-api
spec:
  type: ExternalName
  externalName: api.external-service.com

특징:

  • ClusterIP 할당 없음
  • CNAME 레코드 생성
  • 외부 서비스를 내부 DNS로 접근 가능

사용 예:

1
2
3
# 클러스터 내부에서
curl http://external-api.default.svc.cluster.local
# → api.external-service.com 으로 리다이렉트

Headless Service

ClusterIP가 없는 특수한 Service로, 개별 Pod에 직접 접근해야 할 때 사용한다.

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
  name: db-headless
spec:
  clusterIP: None  # Headless 선언
  selector:
    app: database
  ports:
  - port: 5432

일반 Service vs Headless Service:

구분일반 ServiceHeadless Service
ClusterIP있음None
DNS 응답ClusterIP 반환Pod IP 목록 반환
로드밸런싱kube-proxy가 수행클라이언트가 선택
용도일반적인 서비스StatefulSet, 클라이언트 사이드 LB

DNS 조회 결과:

1
2
3
4
5
6
7
# 일반 Service
nslookup web-service.default.svc.cluster.local
# → 10.96.45.123 (ClusterIP)

# Headless Service
nslookup db-headless.default.svc.cluster.local
# → 10.244.1.5, 10.244.2.6, 10.244.3.7 (모든 Pod IP)

StatefulSet과 함께 사용:

1
2
3
4
5
6
# StatefulSet의 각 Pod에 고유 DNS로 접근
nslookup mysql-0.db-headless.default.svc.cluster.local
# → mysql-0 Pod의 IP

nslookup mysql-1.db-headless.default.svc.cluster.local
# → mysql-1 Pod의 IP

Service Discovery

DNS 기반 디스커버리

Kubernetes는 CoreDNS를 통해 서비스 디스커버리를 제공한다.

DNS 이름 규칙:

1
<service-name>.<namespace>.svc.cluster.local

DNS 레코드 종류:

  • A 레코드: Service → ClusterIP
  • SRV 레코드: 포트 정보 포함
  • Pod DNS: <pod-ip-dashed>.<namespace>.pod.cluster.local
1
2
3
4
5
# Service DNS 조회
nslookup web-service.production.svc.cluster.local

# SRV 레코드 조회 (포트 정보 포함)
nslookup -type=SRV _http._tcp.web-service.production.svc.cluster.local

같은 Namespace에서 접근:

1
2
3
curl http://web-service        # 축약형
curl http://web-service.default  # namespace 포함
curl http://web-service.default.svc.cluster.local  # FQDN

환경 변수 기반 디스커버리

Pod가 생성될 때 같은 Namespace의 Service 정보가 환경 변수로 주입된다.

1
2
3
4
5
# Pod 내부에서 확인
env | grep WEB_SERVICE
# WEB_SERVICE_SERVICE_HOST=10.96.45.123
# WEB_SERVICE_SERVICE_PORT=80
# WEB_SERVICE_PORT=tcp://10.96.45.123:80

주의: Pod보다 나중에 생성된 Service는 환경 변수에 포함되지 않는다. DNS 방식을 권장한다.

Session Affinity

동일한 클라이언트의 요청을 같은 Pod로 라우팅하고 싶을 때 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
  name: sticky-service
spec:
  selector:
    app: web
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600  # 1시간 (기본값: 10800초/3시간)
  ports:
  - port: 80
    targetPort: 8080

sessionAffinity 옵션:

  • None: 기본값, 라운드로빈
  • ClientIP: 클라이언트 IP 기준 고정

참고: Cookie 기반 세션 어피니티는 Service에서 지원하지 않는다. 필요하다면 Ingress 사용.

포트 설정

Multi-Port Service

하나의 Service에서 여러 포트를 노출할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Service
metadata:
  name: multi-port-service
spec:
  selector:
    app: web
  ports:
  - name: http      # 다중 포트 시 name 필수
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8443
  - name: metrics
    port: 9090
    targetPort: 9090

Named Port 참조

Pod에서 정의한 포트 이름을 참조할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Pod 정의
apiVersion: v1
kind: Pod
metadata:
  name: web-pod
  labels:
    app: web
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - name: http-port
      containerPort: 80
    - name: https-port
      containerPort: 443
---
# Service에서 이름으로 참조
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  ports:
  - name: http
    port: 80
    targetPort: http-port  # 포트 이름 참조
  - name: https
    port: 443
    targetPort: https-port

장점: Pod의 포트 번호가 변경되어도 Service 수정 불필요.

External Traffic Policy

원문 (kubernetes.io - Service External Traffic Policy): Services with type LoadBalancer or NodePort have a field spec.externalTrafficPolicy which denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. There are two available options: Cluster (default) and Local. Cluster obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. Local preserves the client source IP and avoids a second hop for LoadBalancer and NodePort type Services, but risks potentially imbalanced traffic spreading.

번역: LoadBalancer 또는 NodePort 타입의 Service에는 외부 트래픽을 노드 로컬 엔드포인트로 라우팅할지 클러스터 전체 엔드포인트로 라우팅할지를 나타내는 spec.externalTrafficPolicy 필드가 있다. Cluster(기본값)와 Local의 두 가지 옵션이 있다. Cluster는 클라이언트 소스 IP를 숨기고 다른 노드로의 두 번째 홉이 발생할 수 있지만 전체적으로 좋은 로드 분산을 제공한다. Local은 클라이언트 소스 IP를 보존하고 LoadBalancer 및 NodePort 타입 Service의 두 번째 홉을 방지하지만 잠재적으로 불균형한 트래픽 분산이 발생할 위험이 있다.

외부 트래픽(NodePort, LoadBalancer)의 라우팅 방식을 제어한다.

Cluster (기본값)

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: NodePort
  externalTrafficPolicy: Cluster
  selector:
    app: web
  ports:
  - port: 80
    nodePort: 30080

동작: 모든 노드가 트래픽을 받고, 클러스터 전체 Pod로 분산.

장점: 균등한 로드밸런싱 단점:

  • 추가 네트워크 홉 발생 (다른 노드의 Pod로 전달 시)
  • 클라이언트 IP 보존 안 됨 (SNAT)

Local

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: NodePort
  externalTrafficPolicy: Local
  selector:
    app: web
  ports:
  - port: 80
    nodePort: 30080

동작: 해당 노드에 있는 Pod로만 트래픽 전달.

장점:

  • 네트워크 홉 감소
  • 클라이언트 IP 보존

단점:

  • Pod가 없는 노드에서는 연결 실패
  • 불균등한 로드밸런싱 가능
flowchart TB
    subgraph clusterPolicy["externalTrafficPolicy: Cluster (Client IP SNAT됨)"]
        cA["Node A (request)"]
        cB["Node B (forward)"]
        cPod["Pod (Node C)"]
        cA --> cB --> cPod
    end

    subgraph localPolicy["externalTrafficPolicy: Local (Client IP 보존)"]
        lA["Node A (request)"]
        lPod["Pod (Node A)"]
        lA --> lPod
    end

Internal Traffic Policy

클러스터 내부 트래픽의 라우팅 방식을 제어한다. Kubernetes 1.21+에서 지원.

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
  name: local-service
spec:
  selector:
    app: web
  internalTrafficPolicy: Local  # 같은 노드의 Pod로만
  ports:
  - port: 80

옵션:

  • Cluster: 기본값, 클러스터 전체 Pod로 분산
  • Local: 같은 노드의 Pod로만 라우팅

kube-proxy와 Service 구현

Service의 실제 네트워크 규칙은 kube-proxy가 각 노드에서 구현한다.

kube-proxy 모드

iptables 모드 (기본값):

1
2
# iptables 규칙 확인
iptables -t nat -L -n | grep web-service
  • 연결 기반 라우팅
  • 낮은 오버헤드
  • 백엔드 선택 후 변경 불가

IPVS 모드:

1
2
# IPVS 규칙 확인
ipvsadm -Ln
  • 고성능 (많은 Service 처리에 유리)
  • 다양한 로드밸런싱 알고리즘 지원
    • rr (round-robin)
    • lc (least connection)
    • dh (destination hashing)
    • sh (source hashing)
    • sed (shortest expected delay)
    • nq (never queue)

모드 변경 (kube-proxy ConfigMap):

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-proxy
  namespace: kube-system
data:
  config.conf: |
    mode: "ipvs"
    ipvs:
      scheduler: "lc"  # least connection

트러블슈팅

Service 연결 문제 진단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. Service 상태 확인
kubectl get svc web-service -o wide

# 2. Endpoints 확인 (Pod가 연결되어 있는지)
kubectl get endpoints web-service
# ENDPOINTS가 비어있다면 selector 미스매치

# 3. Pod Label 확인
kubectl get pods --show-labels

# 4. Selector 매칭 테스트
kubectl get pods -l app=web

# 5. Pod 상태 확인 (Ready 상태여야 Endpoints에 등록)
kubectl get pods -l app=web -o wide

# 6. DNS 테스트
kubectl run test --rm -it --image=busybox -- nslookup web-service

# 7. 연결 테스트
kubectl run test --rm -it --image=curlimages/curl -- curl web-service:80

일반적인 문제와 해결

Endpoints가 비어있는 경우:

  1. Selector가 Pod Label과 일치하는지 확인
  2. Pod가 Running 상태인지 확인
  3. Pod가 Ready 상태인지 확인 (Readiness Probe 통과)

연결은 되지만 응답이 없는 경우:

  1. targetPort가 컨테이너 포트와 일치하는지 확인
  2. 컨테이너 내부에서 서비스가 실행 중인지 확인
  3. NetworkPolicy가 트래픽을 차단하는지 확인

외부에서 접근이 안 되는 경우 (NodePort/LoadBalancer):

  1. 방화벽 규칙 확인
  2. 보안 그룹(클라우드) 확인
  3. externalTrafficPolicy 설정 확인

기술 면접 대비 (8년차 DevOps TL 관점)

Q1: ClusterIP, NodePort, LoadBalancer의 차이점은?

A: ClusterIP는 클러스터 내부 통신 전용으로 가상 IP를 할당한다. NodePort는 각 노드의 특정 포트(30000-32767)를 열어 외부 접근을 허용하며, ClusterIP를 포함한다. LoadBalancer는 클라우드 환경에서 외부 로드밸런서를 프로비저닝하고, NodePort와 ClusterIP를 모두 포함한다. 계층적 구조로 LoadBalancer ⊃ NodePort ⊃ ClusterIP 관계이다.

꼬리질문 1-1: NodePort 포트 범위가 30000-32767인 이유는? 변경할 수 있는가?

A: 이 범위는 well-known ports(0-1023)와 registered ports(1024-49151)와의 충돌을 피하기 위함이다. kube-apiserver의 --service-node-port-range 플래그로 변경 가능하지만, 다른 서비스와 충돌하지 않도록 주의해야 한다.

꼬리질문 1-2: 프로덕션에서 NodePort를 직접 사용하면 안 되는 이유는?

A: 보안상 노드 IP가 직접 노출되고, 노드 장애 시 해당 엔드포인트로의 접근이 불가능해진다. 또한 포트 관리가 복잡해지고, SSL 종료나 고급 라우팅 기능이 없다. 대신 LoadBalancer나 Ingress를 통해 접근하는 것이 Best Practice이다.


Q2: Headless Service는 무엇이고 언제 사용하는가?

A: ClusterIP가 None인 Service로, DNS 조회 시 ClusterIP 대신 Pod IP 목록을 직접 반환한다. StatefulSet과 함께 개별 Pod에 직접 접근해야 할 때, 또는 클라이언트 사이드 로드밸런싱이 필요할 때 사용한다.

꼬리질문 2-1: Headless Service에서 DNS A 레코드와 SRV 레코드의 차이는?

A: A 레코드는 service-name.namespace.svc.cluster.local로 조회하면 모든 Pod IP를 반환한다. SRV 레코드는 포트 정보까지 포함하여 _port-name._protocol.service-name.namespace.svc.cluster.local로 조회한다. Kafka나 ZooKeeper같은 애플리케이션이 SRV 레코드를 사용하여 클러스터 멤버를 자동 발견한다.

꼬리질문 2-2: 그럼 StatefulSet 없이 일반 Deployment에서 Headless Service를 사용하면 어떻게 되는가?

A: 동작은 하지만, Pod 이름이 랜덤하므로 특정 Pod에 DNS로 접근할 수 없다. StatefulSet은 pod-name.service-name.namespace.svc.cluster.local 형태의 예측 가능한 DNS를 제공하지만, Deployment의 Pod는 개별 DNS 엔트리가 없다.


Q3: Service는 어떻게 Pod를 선택하고 트래픽을 분산하는가?

A: Service의 selector와 일치하는 Label을 가진 Pod들이 자동으로 Endpoints에 등록된다. kube-proxy가 각 노드에서 iptables/IPVS 규칙을 설정하여 실제 트래픽 라우팅을 수행한다.

꼬리질문 3-1: iptables 모드와 IPVS 모드의 차이는? 대규모 클러스터에서 어떤 것을 선택해야 하는가?

A: iptables 모드는 구현이 단순하고 널리 사용되지만, Service/Endpoints가 많아지면 규칙 수가 O(n)으로 증가하여 성능 저하가 발생한다. IPVS 모드는 해시 테이블 기반으로 O(1) 조회가 가능하고, round-robin 외에 least-connection, source-hashing 등 다양한 알고리즘을 지원한다. 1000개 이상의 Service가 있다면 IPVS를 권장한다.

꼬리질문 3-2: Endpoints가 아닌 EndpointSlice가 도입된 이유는?

A: 대규모 클러스터에서 수천 개의 Pod가 있으면 단일 Endpoints 객체가 너무 커져서 etcd와 API Server에 부담이 된다. EndpointSlice는 최대 100개씩 분할하여 변경 시 전체가 아닌 일부만 업데이트되도록 한다. Kubernetes 1.21부터 기본 활성화되었다.


Q4: externalTrafficPolicy: Local의 장단점은?

A: 장점은 클라이언트 IP 보존과 네트워크 홉 감소이다. 단점은 해당 노드에 Pod가 없으면 연결이 실패하고, Pod 분포에 따라 불균등한 로드밸런싱이 발생할 수 있다.

꼬리질문 4-1: Local 정책 사용 시 불균등 로드밸런싱 문제를 어떻게 해결하는가?

A: Pod Anti-Affinity로 Pod를 노드에 균등 분포시키거나, Topology Spread Constraints를 사용한다. 또는 클라우드 LB의 헬스체크와 연동하여 Pod가 없는 노드는 LB 백엔드에서 제외한다. AWS NLB는 이 기능을 자동으로 지원한다.

꼬리질문 4-2: internalTrafficPolicy도 있던데, 이건 언제 사용하는가?

A: Kubernetes 1.22에 도입된 기능으로, 클러스터 내부 트래픽에 대해 동일한 노드의 Pod만 선택하도록 설정한다. 노드 간 네트워크 트래픽을 줄이고 지연을 최소화할 때 유용하다. internalTrafficPolicy: Local 설정 시 같은 노드의 Endpoints만 사용한다.


Q5: Pod가 Ready 상태가 아니면 Service에서 어떻게 처리되는가?

A: Pod가 Ready 상태가 아니면(Readiness Probe 실패) Endpoints에서 자동으로 제거된다.

꼬리질문 5-1: Readiness Probe가 실패하는 동안에도 트래픽을 받고 싶다면?

A: publishNotReadyAddresses: true 설정을 사용한다. StatefulSet의 Headless Service에서 주로 사용하며, 클러스터 부트스트랩 시 모든 멤버를 알아야 할 때 유용하다.

꼬리질문 5-2: Terminating 상태의 Pod는 Endpoints에서 어떻게 처리되는가?

A: 기본적으로 Terminating Pod도 Endpoints에서 제거된다. 하지만 연결 드레이닝을 위해 preStop 훅과 terminationGracePeriodSeconds를 활용하여 기존 연결을 처리할 시간을 확보할 수 있다. 롤링 업데이트 시 5xx 에러를 방지하려면 이 설정이 중요하다.

CKA 시험 대비 필수 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Service 생성 (명령형)
kubectl create service clusterip web-svc --tcp=80:8080
kubectl create service nodeport web-svc --tcp=80:8080 --node-port=30080
kubectl create service loadbalancer web-svc --tcp=80:8080

# Deployment 노출 (권장 방법)
kubectl expose deployment web --port=80 --target-port=8080 --type=ClusterIP
kubectl expose deployment web --port=80 --target-port=8080 --type=NodePort
kubectl expose deployment web --port=80 --target-port=8080 --type=LoadBalancer

# Pod 직접 노출
kubectl expose pod nginx --port=80 --name=nginx-svc

# Service 조회
kubectl get svc -o wide
kubectl describe svc web-service

# Endpoints 조회
kubectl get endpoints web-service

# DNS 테스트
kubectl run test --rm -it --image=busybox --restart=Never -- nslookup web-service

# 연결 테스트
kubectl run test --rm -it --image=curlimages/curl --restart=Never -- curl -s web-service:80

# Service 수정
kubectl edit svc web-service
kubectl patch svc web-service -p '{"spec":{"type":"NodePort"}}'

# Service 삭제
kubectl delete svc web-service

CKA 빈출 시나리오

1
2
3
4
5
6
7
8
9
10
11
# 시나리오 1: 특정 Deployment를 NodePort로 노출
kubectl expose deployment frontend --port=80 --target-port=8080 --type=NodePort --name=frontend-svc

# 시나리오 2: 특정 NodePort 지정
kubectl create service nodeport web --tcp=80:8080 --node-port=30100

# 시나리오 3: Service의 selector 수정
kubectl patch svc web-service -p '{"spec":{"selector":{"app":"web-v2"}}}'

# 시나리오 4: 외부 서비스용 Service 생성
kubectl create service externalname ext-api --external-name=api.external.com

실전 예제

완전한 애플리케이션 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.24
        ports:
        - containerPort: 80
---
# Frontend Service (외부 노출)
apiVersion: v1
kind: Service
metadata:
  name: frontend-svc
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80
---
# Backend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: api
        image: myapp/api:1.0
        ports:
        - containerPort: 8080
---
# Backend Service (내부 통신)
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
  - port: 80
    targetPort: 8080
---
# Database StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
---
# Database Headless Service
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306
---
# Database ClusterIP Service (일반 접근용)
apiVersion: v1
kind: Service
metadata:
  name: mysql-svc
spec:
  type: ClusterIP
  selector:
    app: mysql
  ports:
  - port: 3306

참고 자료

다음 단계

This post is licensed under CC BY 4.0 by the author.