[쿠버네티스 패턴] 13장 Service Discovery

bocopile·2025년 10월 23일

쿠버네티스 패턴

목록 보기
11/28

1. Service Discovery란?

서비스 디스커버리(Service Discovery)는 분산 시스템의 핵심 동작 원리 중 하나로,
서로 다른 서비스 간의 위치(IP, 포트, 엔드포인트)를 자동으로 탐색하는 메커니즘을 말합니다.

예를 들어, 마이크로서비스 환경에서 frontendbackend에 요청을 보내려면backend의 위치 정보를 알아야 합니다.

하지만 다음과 같은 문제가 발생합니다.

  • 컨테이너가 재시작될 때마다 새로운 IP가 할당됨
  • Pod는 스케줄러에 의해 다른 노드로 이동할 수 있음
  • 수백~수천 개의 인스턴스가 동적으로 생성 및 삭제됨

따라서 고정된 IP에 접근하는 방식은 불가능합니다.

이 문제를 해결하기 위해 자동으로 서비스 위치를 탐색하는 시스템, 즉 서비스 디스커버리가 필요합니다.

2. Service Discovery의 일반적 구조

전통적인 시스템에서 서비스 디스커버리는 보통 두 가지 방식 중 하나로 구현됩니다.

1) Client-side Discovery

Kubernetes에서는 클라이언트가 Service Registry 역할을 하는 kube-apiserver (즉, Kubernetes API) 를 직접 조회하여 Pod IP를 얻는 구조로 볼 수 있습니다.

예를 들어, 클라이언트 애플리케이션이 Endpoints 리소스를 조회해 직접 대상 Pod를 선택하거나, 자체 로드밸런싱 로직을 수행할 수도 있습니다.

  • 예시: Custom Client Discovery, Service Mesh Sidecar (Envoy가 직접 서비스 목록 조회)

  • 특징

    • 클라이언트가 직접 Endpoints 또는 Service 정보를 조회하여 대상 Pod를 선택
    • 클라이언트 코드나 라이브러리에서 로드 밸런싱 로직을 수행
  • 장점

    • 세밀한 트래픽 제어 및 커스터마이징 가능 (예: 특정 Pod로 직접 트래픽 전송)
    • Service Mesh 또는 자체 Discovery 로직과 유연하게 결합 가능
  • 단점

    • 각 클라이언트 애플리케이션이 복잡해짐
    • Kubernetes API 접근 권한이 필요하고, 네트워크 정책 관리가 어려움

2) Server-side Discovery

Kubernetes에서는 Service 리소스가 대표적인 Server-side Discovery 방식입니다.
클라이언트는 ClusterIP나 Service DNS (예: myapp.default.svc.cluster.local)로 요청을 보내면, kube-proxy가 해당 요청을 자동으로 적절한 Pod로 라우팅합니다.

  • 예시: Kubernetes Service (ClusterIP, NodePort, LoadBalancer), Ingress, Istio Gateway

  • 특징

    • 클라이언트는 단순히 Service의 고정된 DNS 이름으로 접근
    • 내부적으로 kube-proxy 또는 Envoy (Service Mesh)가 로드 밸런싱 수행
  • 장점

    • 클라이언트는 단순하고 인프라 변경과 무관
    • Kubernetes Service, Ingress, Istio 등 다양한 컴포넌트가 자동으로 관리
  • 단점

    • kube-proxy, Ingress Controller, Service Mesh 등 인프라 구성요소에 의존
    • 세밀한 트래픽 제어는 한계가 있음

3) Kubernetes의 접근 방식 정리

Kubernetes는 위 두 가지 방식을 통합한 형태로 제공합니다.

서비스 디스커버리는 Service 리소스와 kube-dns(CoreDNS)를 통해 이루어집니다.

  • Server-side Discovery: Service가 Pod 뒤의 로드 밸런서 역할 수행
  • Registry 역할: etcd가 모든 Service → Pod 매핑 정보를 저장
  • Client-side Resolution: DNS가 서비스 이름을 ClusterIP로 변환

Kubernetes의 전체 흐름은 다음과 같습니다.

[Client Pod] → [DNS 조회] → [ClusterIP(Service)] → [kube-proxy(iptables/IPVS)] → [Pod Endpoint]

3. Kubernetes Service 리소스와 Discovery 메커니즘

1) Service 리소스

Service는 Pod의 동적 IP를 추상화하기 위한 가상 IP(ClusterIP)를 제공합니다.

이는 단순한 가상 엔드포인트가 아니라,

kube-proxy가 iptables 또는 IPVS 규칙을 생성하여 커널 레벨에서 트래픽을 라우팅하는 구조입니다.

apiVersion: v1
kind: Service
metadata:
  name: random-generator
spec:
  selector:
    app: random-generator
  ports:
    - port: 80
      targetPort: 8080
  • selector: 특정 라벨(app=random-generator)을 가진 Pod 집합을 선택
  • ClusterIP: Pod 집합을 대표하는 고정 가상 IP (내부 접근 포인트)
  • kube-proxy: 각 노드에 iptables/IPVS 규칙을 설정해 Service → Pod 트래픽을 라우팅

Pod가 재시작되더라도 Service의 IP는 변하지 않으므로 클라이언트는 항상 동일한 주소로 접근할 수 있습니다.

Service 리소스는 단순한 라우팅 객체가 아니라,

Kubernetes 네트워크 컨트롤 플레인(kube-proxy + CoreDNS + API Server + etcd)이

동적으로 IP 매핑을 유지하는 추상화 계층입니다.

2) 환경 변수 기반 디스커버리 (초기 버전)

Kubernetes의 초기 버전에서는 Pod가 생성될 때 kubelet이 해당 네임스페이스 내 모든 Service 정보를 환경 변수로 주입했습니다.

[예시 Service]

apiVersion: v1
kind: Service
metadata:
  name: random-generator
  namespace: default
spec:
  selector:
    app: random-generator
  ports:
    - name: http
      port: 80
      targetPort: 8080

[Pod 내부 환경 변수 (자동 주입)]

RANDOM_GENERATOR_SERVICE_HOST=10.96.12.24
RANDOM_GENERATOR_SERVICE_PORT=80
RANDOM_GENERATOR_PORT=tcp://10.96.12.24:80
RANDOM_GENERATOR_PORT_80_TCP_PROTO=tcp
RANDOM_GENERATOR_PORT_80_TCP_ADDR=10.96.12.24
RANDOM_GENERATOR_PORT_80_TCP_PORT=80

[제약 사항]

  • Pod 생성 시점의 Service만 인식 → 이후 추가된 Service는 반영되지 않음
  • Pod 재시작 전까지 변수 갱신 불가
  • 동일 네임스페이스 내 Service에만 적용

이 방식은 정적인 환경에서는 유용했지만 Service 생성과 삭제가 빈번한 동적 환경에서는 부적합했습니다.

따라서 현재는 DNS 기반 탐색 방식이 표준으로 사용됩니다.

3) DNS 기반 디스커버리 (표준 방식)

Kubernetes는 CoreDNS를 통해 각 Service 이름을 FQDN(Fully Qualified Domain Name) 형태로 등록합니다.

[형식]

<ServiceName>.<Namespace>.svc.cluster.local
# 예시
random-generator.default.svc.cluster.local

CoreDNS는 etcd를 직접 읽지 않고, kube-apiserver를 watch하여 Service와 EndpointSlice 정보를 인메모리에 유지합니다. 이 정보를 기반으로 DNS 질의에 응답합니다.

클라이언트는 해당 FQDN으로 Service에 접근하며, 내부적으로는 다음 흐름이 작동합니다.

DNS 질의 → CoreDNS 응답(일반: ClusterIP / Headless: Pod IP) → kube-proxy(iptables/IPVS) → Pod 로드밸런싱

[실제 조회 예시]

$ dig random-generator.default.svc.cluster.local

;; 응답
random-generator.default.svc.cluster.local. 5 IN A 10.96.12.24

[SRV 레코드 조회 (포트 이름 기반)]

$ dig SRV _http._tcp.random-generator.default.svc.cluster.local

;; 응답
_http._tcp.random-generator.default.svc.cluster.local. 5 IN SRV 0 100 80 random-generator.default.svc.cluster.local.

[보충]

  • Headless Service (ClusterIP-None): A/AAAA 질의에 Pod IP들을 직접 반환하여 애플리케이션이 Pod로 바로 연결됩니다.

  • ExternalName Service: DNS가 CNAME을 반환해 외부 도메인으로 위임합니다.

  • kube-proxy: 각 노드에서 iptables 또는 IPVS 규칙을 생성하여 ClusterIP:Port → PodIP:Port로 DNAT하고, rr/lc/wrr 등 스케줄러(특히 IPVS)를 통해 로드밸런싱합니다.

4) Headless Service (고급 디스커버리)

clusterIP를 None으로 설정하면 Kubernetes는 ClusterIP를 생성하지 않고

각 Pod의 실제 IP를 직접 DNS에 등록한다.

[서비스 yaml]

apiVersion: v1
kind: Service
metadata:
  name: my-db
spec:
  clusterIP: None
  selector:
    app: my-db
  ports:
    - port: 5432

[DNS 질의 결과]

$ dig my-db.default.svc.cluster.local

;; ANSWER SECTION:
my-db.default.svc.cluster.local. 5 IN A 10.0.1.12
my-db.default.svc.cluster.local. 5 IN A 10.0.1.13

[StatefulSet Pod별 FQDN]

db-0.my-db.default.svc.cluster.local → 10.0.1.12
db-1.my-db.default.svc.cluster.local → 10.0.1.13

Headless Service는 Pod 개별 DNS 레코드가 필요한 상태 저장 워크로드(DB, Kafka, Cassandra 등)에서 사용됩니다.

StatefulSet과 결합하면 Pod별로 고유한 네트워크 ID를 유지할 수 있습니다.

5) 외부 서비스 디스커버리

Kubernetes는 내부뿐만 아니라 외부와의 통신을 위해 다양한 Service Type을 제공합니다.

타입설명주요 사용처특이사항 / 주의사항
ClusterIP기본 Service 타입, 클러스터 내부에서만 접근 가능내부 서비스 간 통신 (Pod → Pod)spec.clusterIP에 내부 IP 부여, 외부 접근 불가
NodePort각 Node의 고정 포트를 개방하여 외부 접근 가능간단한 외부 노출, 로컬 테스트, 비HTTP 트래픽클러스터 외부 IP + 포트번호(30000~32767)로 접근 가능
LoadBalancer클라우드 제공자 L4 로드밸런서와 연동운영 환경에서 외부 서비스 공개클라우드 종속적 (MetalLB 등으로 대체 가능)
ExternalName내부 DNS 이름을 외부 FQDN으로 매핑 (CNAME 역할만 수행)내부 DNS alias 목적 (예: 내부에서 외부 도메인을 “내부 서비스처럼” 보이게)실질적인 트래픽 라우팅 x, 일부 환경(Istio 등)에서 차단됨, 운영에서는 거의 사용하지 않음
Headless ServiceclusterIP: None → 개별 Pod IP를 직접 반환StatefulSet, DB 클러스터, 샤딩 구조로드밸런싱 대신 직접 Pod별 접근 필요
IngressL7 (HTTP/HTTPS) 기반 라우팅 컨트롤러 리소스여러 서비스의 도메인 기반 통합 관리별도의 Ingress Controller 필요 (Nginx, Istio, Traefik 등)
(참고) Egress + ServiceEntry클러스터 외부로 나가는 트래픽 제어 (Istio 등)외부 API 호출 시 보안 제어 및 관찰(Observability)NetworkPolicy나 Istio ServiceEntry로 세밀 제어 가능

[LoadBalancer 타입 예시]

apiVersion: v1
kind: Service
metadata:
  name: web-api
spec:
  type: LoadBalancer
  selector:
    app: web-api
  ports:
    - port: 80
      targetPort: 8080

[실행 결과]

$ kubectl get svc web-api
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
web-api   LoadBalancer   10.0.23.41     52.231.24.187   80:31544/TCP   1m

외부 사용자는 52.231.24.187로 접근하며, 내부적으로는 다음 경로로 트래픽이 전달됩니다.

ExternalIP → NodePort → ClusterIP → Pod

[ExternalName 타입 예시]

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com

이 설정이 생성되면, external-db.default.svc.cluster.local 로 질의한 DNS 요청은

Kubernetes DNS(CoreDNS)를 통해 다음처럼 처리됩니다.

external-db.default.svc.cluster.local.  IN  CNAME  db.example.com.

즉, CoreDNS가 CNAME 레코드로 단순 반환하고, 이후의 실제 IP 조회(A 레코드 조회)는 클러스터 외부의 일반 DNS 리졸버가 처리합니다.

6) CoreDNS와 kube-proxy 내부 동작 (심화)

[CoreDNS]

  1. kube-apiserver를 watch해 Service/EndpointSlice를 인메모리에 캐싱
  2. 질의 유형에 따라 ClusterIP(일반 서비스) 또는 Pod IP(Headless), SRV/ExternalName(CNAME)을 동적으로 생성하여 응답한다

[kube-proxy]

  1. 각 워커 노드에서 iptables 또는 IPVS 규칙을 생성
  2. ClusterIP로 유입된 트래픽을 해당 규칙에 따라 실제 Pod IP로 NAT하여 전달
  3. iptables 모드에서는 랜덤 또는 Round-Robin 기반으로, IPVS 모드에서는 커널 레벨의 로드 밸런싱 알고리즘(LC, RR 등)을 통해 트래픽을 분산

7) Istio 및 Knative — 고급 서비스 디스커버리

기본적인 Kubernetes 서비스 디스커버리는 L4 수준의 연결을 해결하지만,

정책 기반 트래픽 제어, 버전별 라우팅, 가중치 기반 트래픽 분할 등을 위해서는 Istio나 Knative

와 같은 상위 레이어의 솔루션을 사용합니다.

[Istio]

  • Envoy Sidecar를 통해 서비스 호출을 세밀하게 제어합니다.
  • 자체 Service Registry(Pilot)를 사용합니다.
  • 트래픽 분할, 장애 회피, mTLS 암호화 등 고급 기능을 제공합니다.
  • 예시: reviews 서비스 트래픽 분할 (v1 90%, v2 10%)
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: reviews
    spec:
      hosts:
      - reviews
      http:
      - route:
        - destination:
            host: reviews
            subset: v1
          weight: 90
        - destination:
            host: reviews
            subset: v2
          weight: 10

[Knative Serving]

  • Kubernetes의 기본 Service Discovery를 확장합니다.
  • 버전별 트래픽 라우팅, 자동 확장, Scale-to-zero 기능을 제공합니다.
  • 요청이 있을 때만 서비스를 동적으로 활성화할 수 있습니다.

4. 실습

0) 실습코드

실습 코드는 해당 깃 레포지토리에 있습니다.

1) 네임스페이스 & 진단 파드(dig, wget 포함)

  • 네임스페이스 및 DNS/네트워크 진단용 파드 배포
# 네임스페이스 생성
kubectl create ns sd-lab

# DNS/네트워크 진단용 파드
kubectl run dnsutils -n sd-lab --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 -- sleep 36000
kubectl wait pod/dnsutils -n sd-lab --for=condition=Ready --timeout=180s

2) ClusterIP Service로 기본 디스커버리 체험

  • Deployment + Service 코드 작성
    # 01-deploy-svc.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: echo
      namespace: sd-lab
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: echo
      template:
        metadata:
          labels:
            app: echo
        spec:
          containers:
          - name: echo
            image: hashicorp/http-echo:1.0
            args: ["-text=hello-from-echo"]
            ports:
            - containerPort: 5678
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: echo
      namespace: sd-lab
    spec:
      selector:
        app: echo
      ports:
      - name: http
        port: 80
        targetPort: 5678
      type: ClusterIP
  • 배포 및 확인
    kubectl apply -f 01-deploy-svc.yaml
    kubectl -n sd-lab get pod -l app=echo -o wide
    kubectl -n sd-lab get svc echo
    kubectl -n sd-lab get endpoints echo
    kubectl -n sd-lab get endpointslices -l kubernetes.io/service-name=echo
  • DNS로 이름 → ClusterIP 확인
    # A 레코드(ClusterIP)
    kubectl exec dnsutils -n sd-lab -- dig +short echo.sd-lab.svc.cluster.local A
    
    # SRV 레코드(포트명 기반)
    kubectl exec dnsutils -n sd-lab  -- dig +short SRV _http._tcp.echo.sd-lab.svc.cluster.local
    • A 레코드 및 SRV 확인 결과
  • 서비스명으로 HTTP 접근
    kubectl run busybox -n sd-lab --image=busybox:1.36 --restart=Never -it -- sh
    
    # 컨테이너 접근 후 
    wget -qO- http://echo.sd-lab.svc.cluster.local
    
    exit
    
    # 컨테이너 외부에서 busybox 삭제
     kubectl delete pods busybox -n sd-lab
  • 5.2.4 파드 재시작 후에도 안정 접근 되는지 확인
    POD=$(kubectl -n sd-lab get pod -l app=echo -o jsonpath='{.items[0].metadata.name}')
    kubectl delete pod "$POD" -n sd-lab
    kubectl wait -n sd-lab  --for=condition=Ready pod -l app=echo --timeout=180s
    kubectl run busybox -n sd-lab --image=busybox:1.36 --restart=Never -it -- sh
    
    # 컨테이너 접근 후 
    wget -qO- http://echo.sd-lab.svc.cluster.local
    exit
    
    # 컨테이너 외부에서 busybox 삭제
     kubectl delete pods busybox -n sd-lab
    • 접근 되는것을 확인

3) Readiness가 Endpoints/EndpointSlice에 반영되는지 보기

  • 코드 작성
    # 02-deploy-readiness.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: echo-ready
      namespace: sd-lab
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: echo-ready
      template:
        metadata:
          labels:
            app: echo-ready
        spec:
          containers:
          - name: echo
            image: hashicorp/http-echo:1.0
            args: ["-text=ready-echo"]
            ports:
            - containerPort: 5678
            readinessProbe:
              httpGet:
                path: /
                port: 5678
              initialDelaySeconds: 10
              periodSeconds: 2
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: echo-ready
      namespace: sd-lab
    spec:
      selector:
        app: echo-ready
      ports:
      - name: http
        port: 80
        targetPort: 5678
      type: ClusterIP
  • 코드 배포
    kubectl apply -f 02-deploy-readiness.yaml
    kubectl rollout status deploy/echo-ready -n sd-lab --timeout=180s
    
    # 준비 전/후 Endpoints/EndpointSlice 변화 관찰
    watch kubectl get ep echo-ready -n sd-lab
    watch kubectl get endpointslices -n sd-lab -l kubernetes.io/service-name=echo-ready
    
    • Endpoints 확인 : 시간이 지남에 따라 Endpoint가 연결 되는 것을 확인

4) Headless Service + StatefulSet (Pod별 FQDN/고정 네임)

  • 코드 작성
    # 03-headless-stateful.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-hl
      namespace: sd-lab
    spec:
      clusterIP: None
      selector:
        app: demo-hl
      ports:
      - port: 8080
        name: http
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: demo
      namespace: sd-lab
    spec:
      serviceName: demo-hl
      replicas: 2
      selector:
        matchLabels:
          app: demo-hl
      template:
        metadata:
          labels:
            app: demo-hl
        spec:
          containers:
          - name: web
            image: hashicorp/http-echo:1.0
            args: ["-text=stateful"]
            ports:
            - containerPort: 8080
  • 코드 배포 및 확인
    kubectl apply -f 03-headless-stateful.yaml
    kubectl rollout status sts/demo  -n sd-lab --timeout=180s
    
    # Headless는 Pod IP 목록을 바로 반환
    kubectl exec dnsutils -n sd-lab -- dig +short demo-hl.sd-lab.svc.cluster.local A
    
    # Pod별 FQDN → 개별 IP 확인
    for i in 0 1; do
      echo -n "demo-$i: "
      kubectl exec dnsutils -n sd-lab -- dig +short demo-$i.demo-hl.sd-lab.svc.cluster.local A
    done
    
    # 파드 확인
    kubectl get pod -n sd-lab -o wide

5) ExternalName으로 외부 서비스 내부처럼 접근

  • 코드 작성

    # 04-externalname.yaml
    # ExternalName 서비스 (httpbin.org)
    apiVersion: v1
    kind: Service
    metadata:
      name: httpbin-external
      namespace: sd-lab
    spec:
      type: ExternalName
      externalName: httpbin.org
    ---
    # Egress 허용 네트워크 정책 (특정 파드만 외부 접근 가능)
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-external-egress-for-dnsutils
      namespace: sd-lab
    spec:
      podSelector:
        matchLabels:
          app: dnsutils     # 이 라벨이 있는 파드만 외부로 나갈 수 있음
      policyTypes:
        - Egress
      egress:
        - to:
            - ipBlock:
                cidr: 0.0.0.0/0   # 외부 전체 허용
          ports:
            - protocol: TCP
              port: 80
            - protocol: TCP
              port: 443
  • 코드 배포 및 확인

    kubectl apply -f 04-externalname.yaml
    
    # 라벨 추가
    kubectl label -n sd-lab pod dnsutils app=dnsutils --overwrite
    
    # 라벨 확인
    kubectl -n sd-lab get pod dnsutils -o jsonpath='{.metadata.labels}'
    
    # CNAME 확인
    kubectl exec dnsutils -n sd-lab -- dig +short httpbin-external.sd-lab.svc.cluster.local CNAME
    
    kubectl run busybox -n sd-lab --image=busybox:1.36 --restart=Never -it -- sh![](https://velog.velcdn.com/images/gjrjr4545/post/fde5a9a1-f52a-4001-9ca3-1bb722a697c0/image.png)
    
    
    # 외부 도메인 직접 접근
    wget -qO- http://httpbin.org/get | head -c 200
    
    # ExternalName 서비스 경유 접근 (내부 DNS → 외부 CNAME)
    wget -qO- http://httpbin-external.sd-lab.svc.cluster.local/get | head -c 200
    exit
    
    # 컨테이너 외부에서 busybox 삭제
    kubectl delete pods busybox -n sd-lab
    • CNAME 확인
    • 외부 도메인 접근 → 200 OK
    • ExternalName 서비스 경유 접근 → 503 에러 발생

6) Service 타입 체험

[NodePort 배포 및 확인]

  • 명령어
    kubectl  expose deploy/echo -n sd-lab --name=echo-np --type=NodePort --port=80 --target-port=5678
    kubectl get svc -n sd-lab  echo-np -o wide
    open http://10.110.241.216:30218
  • NodePort 적용
  • 사이트 접근 : 10.110.241.216은 클러스터 내부 전용 IP(ClusterIP)이기 때문에 Mac 호스트(즉, 클러스터 외부)에서는 접근이 불가능
  • 노드 확인
    kubectl get nodes -o wide
  • 노드 IP로 통해서 접근
    # ${NODE_INTERNAL-IP}:${NODEPORT}
    open http://192.168.65.185:30218

[LoadBalancer (MetalLB/클라우드 LB 구성 시)]

  • LB 구성
    kubectl -n sd-lab expose deploy/echo --name=echo-lb --type=LoadBalancer --port=80 --target-port=5678
    kubectl -n sd-lab get svc echo-lb -w
    
    # ${EXTERNAL-IP}:${NODEPORT}
    open http://192.168.65.202:80
  • 생성 결과
  • 사이트 접근

[Ingress]

  • ingress Controller 여부 확인 → 미설치
     kubectl get pods -A | grep ingress
  • (없는 경우) Ingress Controller 설치
    kubectl create ns ingress-nginx
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
    kubectl get pods -n ingress-nginx -w
  • Ingress Controller 설치 확인
     kubectl get pods -A | grep ingress
  • 코드 작성
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: echo
      namespace: sd-lab
    spec:
      ingressClassName: nginx
      rules:
        - host: echo.sd.local
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: echo
                    port:
                      number: 80
  • 코드 배포 및 확인
    kubectl apply -f 05-ingress.yaml
    kubectl get ingress echo -n sd-lab
  • host 등록 → 실제로 등록된 도메인이 아니므로.. /etc/hosts에 등록 필요
    192.168.65.203 echo.sd.local
  • 사이트 접근
    # 사이트 접근
    open http://echo.sd.local

7) kube-proxy 모드/규칙 관찰

  • 모드 확인
    # 모드 확인 (iptables 또는 IPVS)
    kubectl -n kube-system get cm kube-proxy -o yaml | grep -A2 mode:
    
    # (노드에서) iptables 모드 예시
    sudo iptables-save | grep -i KUBE-SVC | head
    
    # (노드에서) IPVS 모드 예시
    # 만약 없다면 설치 진행
    sudo ipvsadm -Ln
    
  • 모드 확인
    • “” : 기본 iptables 모드 (default)

    • "ipvs” : IPVS 로드밸런싱 사용 중

    • "nftables” : (v1.28+에서 추가) nftables 백엔드 사용 중

  • iptables 확인 : 서비스 트래픽을 처리하기 위해 만든 NAT 체인 목록 (각 개별 체인은 개별의 Service 객체(ClusterIP)에 대응)

    • 어느 서비스가 어느 체인에 매칭되는지 확인 하는 명령어

      sudo iptables-save | grep -A3 KUBE-SERVICES

      체인 이름역할
      KUBE-SERVICES모든 Kubernetes Service 트래픽의 진입점
      KUBE-SVC-*각 Service(ClusterIP)에 대한 라우팅 규칙
      KUBE-SEP-*Service가 참조하는 각 Pod Endpoint 규칙
      KUBE-EXTERNAL-SERVICESNodePort / LoadBalancer 트래픽 처리
      KUBE-PROXY-FIREWALL방화벽 성격의 기본 정책
      FLANNEL-*CNI(Flannel) 네트워크 관련 NAT / Forward 규칙

8) (선택) Istio가 있을 때 트래픽 분할

  • 코드 작성
    # 06-echo-split.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: echo-v1
      namespace: sd-lab
    spec:
      replicas: 1
      selector:
        matchLabels: { app: echo, version: v1 }
      template:
        metadata:
          labels: { app: echo, version: v1 }
        spec:
          containers:
          - name: echo
            image: hashicorp/http-echo:1.0
            args: ["-text=echo-v1"]
            ports: [{containerPort: 5678}]
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: echo-v2
      namespace: sd-lab
    spec:
      replicas: 1
      selector:
        matchLabels: { app: echo, version: v2 }
      template:
        metadata:
          labels: { app: echo, version: v2 }
        spec:
          containers:
          - name: echo
            image: hashicorp/http-echo:1.0
            args: ["-text=echo-v2"]
            ports: [{containerPort: 5678}]
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: echo-mesh
      namespace: sd-lab
    spec:
      selector:
        app: echo
      ports:
      - name: http
        port: 80
        targetPort: 5678
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: echo
      namespace: sd-lab
    spec:
      host: echo-mesh
      subsets:
      - name: v1
        labels: { version: v1 }
      - name: v2
        labels: { version: v2 }
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: echo
      namespace: sd-lab
    spec:
      hosts: ["echo-mesh"]
      http:
      - route:
        - destination: { host: echo-mesh, subset: v1 }
          weight: 80
        - destination: { host: echo-mesh, subset: v2 }
          weight: 20
    
  • 코드 배포 및 확인
    # 사이드카 주입 네임스페이스로 전환
    kubectl label ns sd-lab istio-injection=enabled --overwrite
    
    # 매니페스트 적용
    kubectl apply -f 06-echo-split.yaml
    kubectl -n sd-lab rollout status deploy/echo-v1 --timeout=180s
    kubectl -n sd-lab rollout status deploy/echo-v2 --timeout=180s
    
    # busybox 생성
    kubectl run busybox -n sd-lab --image=busybox:1.36 --restart=Never -it -- sh
    
    # 여러 번 호출해 가중치(80/20) 체감
    for i in $(seq 1 100); do
      wget -qO- http://echo-mesh.sd-lab.svc.cluster.local | grep echo-;
    done | sort | uniq -c
    exit
    
    # 확인후 삭제
    kubectl delete pods busybox -n sd-lab
  • 트래픽 확인 결과 : 81:19
    • "정확한 확률 기반(weighted load balancing)"이 아니라 “비율 기반 라우팅 힌트(hint)” 로 동작하므로 다소 차이가 발생

9) Knative Serving (+ istio) => 해당 테스트 진행중 (링크텍스트

  • 작업하기전 주의사항 : istio-ingressgateway가 설치되어 있는 환경이여야 함

    항목istio-ingressistio-ingressgateway
    설치 기본값별도로 설치 필요Istio 기본 설치 시 포함
    커스터마이징제한적 (Ingress 스펙에 의존)자유로움 (Gateway + VirtualService 조합)
    TLS/HTTPS 처리Ingress Controller 수준Gateway 리소스에서 완전 지원
    외부 접근 제어Kubernetes 네이티브 방식Istio 정책 (AuthorizationPolicy 등)과 연동
    권장 여부❌ (과거 호환용)✅ (Istio 공식 권장)
  • IngressGateway의 External IP 확인
    kubectl get svc -n istio-ingress istio-ingressgateway
  • 네임스페이스 생성 및 CRD, Knative Istio Controller(istio용 네트워크 레이어 플러그인) 설치

    # 네임스페이스 생성
    kubectl create ns knative-serving
    kubectl create ns sd-lab
    
    # CRD, Serving Core, Knative Istio Controller 설치
    kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.13.0/serving-crds.yaml
    kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.13.0/serving-core.yaml
    kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.13.0/net-istio.yaml

  • ingressclasss 생성 및 확인

    kubectl apply -f - <<'EOF'
    apiVersion: networking.k8s.io/v1
    kind: IngressClass
    metadata:
      name: istio.ingress.networking.knative.dev
    spec:
      controller: istio.io/ingress-controller
    EOF
    
    # 확인
    kubectl get ingressclasses

  • gateway 존재 여부 확인

    kubectl get gateway -A

  • 상세 확인
     kubectl get gateway -n knative-serving knative-ingress-gateway -o yaml | grep selector -A 3
     kubectl get gateway -n knative-serving knative-local-gateway -o yaml | grep selector -A 3
  • Knative Service 및 configmap 코드 작성 (07-knative.yaml)

    # -------------------------------
    # 1. Knative ↔ Istio Gateway 매핑 설정 (수정 완료)
    # -------------------------------
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: config-istio
      namespace: knative-serving
      labels:
        serving.knative.dev/release: "v1.11.2"
    data:
      # Istio Ingress Gateway 네임스페이스
      gateway.knative-serving.knative-ingress-gateway: "istio-ingress/istio-ingressgateway"
      #(내부 트래픽용)
      local-gateway.knative-serving.knative-local-gateway: "istio-ingress/istio-ingressgateway"
    
    ---
    # -------------------------------
    # 2. Knative Service (echo-kn)
    # -------------------------------
    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      name: echo-kn
      namespace: sd-lab
    spec:
      template:
        metadata:
          annotations:
            autoscaling.knative.dev/minScale: "0"
            autoscaling.knative.dev/maxScale: "5"
            networking.knative.dev/ingress-class: "istio.ingress.networking.knative.dev"
            serving.knative.dev/rollout-duration: "10s"
        spec:
          containers:
            - image: hashicorp/http-echo:1.0
              args: ["-text=hello-knative"]
              ports:
                - name: http1
                  containerPort: 5678
              securityContext:
                allowPrivilegeEscalation: false
                runAsNonRoot: true
                seccompProfile:
                  type: RuntimeDefault
                capabilities:
                  drop: ["ALL"]
    
  • Knative 베포
    kubectl apply -f 07-knative.yaml
  • knative 배포 확인
     kubectl get configmap -n knative-serving config-istio -o yaml
     kubectl get svc -n sd-lab echo-kn -o yaml
  • rbac 권한 부여 (09-knative-rbac.yaml)
    # 09-knative-rbac.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: istio-reader
    rules:
      - apiGroups: [""]
        resources: ["services", "endpoints", "pods"]
        verbs: ["get", "list", "watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: knative-serving-istio-access
      namespace: istio-ingress
    subjects:
      - kind: ServiceAccount
        name: controller
        namespace: knative-serving
    roleRef:
      kind: ClusterRole
      name: istio-reader
      apiGroup: rbac.authorization.k8s.io  
  • 권한 부여 및 확인
    kubectl apply -f 09-knative-rbac.yaml
    kubectl get clusterrole istio-reader
    kubectl get rolebinding knative-serving-istio-access -n istio-system
  • Knative Controller 재시작
    kubectl rollout restart deployment controller -n knative-serving
  • Knative → Istio 연동 설정 확인
    kubectl get configmap config-network -n knative-serving -o yaml | grep ingress.class
  • Knative Service URL 확인
    watch -n 2 kubectl get ksvc -n sd-lab echo-kn
  • 확인 결과
  • 클러스터 내부에서 테스트

    # Busybox Pod 생성 및 테스트
    kubectl run busybox -n sd-lab --image=busybox:1.36 --restart=Never -it -- sh
    
    # Pod 내부에서 실행
    wget -qO- http://echo-kn.sd-lab.svc.cluster.local
    # 예상 출력: hello-knative
  • 클러스터 내부 통신 테스트 결과 : bad address 발생

=> 내부 테스트 실패 (bad address)
wget -qO- http://echo-kn.sd-lab.svc.cluster.local
wget: bad address 'echo-kn.sd-lab.svc.cluster.local'
이 메시지는 DNS 해석 실패를 의미합니다. 즉, echo-kn.sd-lab.svc.cluster.local 이라는 이름을 CoreDNS가 인식하지 못함.

  • Pod 스케일 변화 확인

    # 요청 전 (Pod 없음)
    kubectl -n sd-lab get pod -l serving.knative.dev/service=echo-kn
    
    # 요청 후 자동 생성 확인 (watch 모드)
    kubectl -n sd-lab get pod -l serving.knative.dev/service=echo-kn -w
  • Ingress gateway 의 External-IP / 주소 확인

 kubectl get svc -n istio-ingress istio-ingressgateway
 kubectl get ksvc -n sd-lab echo-kn
  • 외부에서 통신 테스트
     # 양식
     curl -v -H "Host: ${DOMAIN}" http://${EXTERNAL-IP}
     #예시
       curl -v -H "Host: echo-kn.sd-lab.svc.cluster.local" http://192.168.65.201

curl -v -H "Host: echo-kn.sd-lab.svc.cluster.local" http://192.168.65.201
< HTTP/1.1 404 Not Found
< server: istio-envoy
여기서 중요한 점은:

  • istio-envoy 응답 → Istio Gateway까지는 접근됨
  • 404 Not Found → VirtualService 매칭 실패
  • 자동 스케일링 동작 확인

    # 여러 개의 요청 전송 (부하 생성)
    for i in {1..100}; do
      curl -s http://echo-kn.sd-lab.192-168-65-201.sslip.io &
    done
    
    # Pod 개수 증가 확인 (최대 5개까지)
    kubectl -n sd-lab get pod -l serving.knative.dev/service=echo-kn -w
    # 요청이 없으면 약 90초 후 Pod 개수가 0으로 감소
    
  • 삭제

    kubectl delete ksvc echo-kn -n sd-lab

10) 정리

kubectl delete ns sd-lab --grace-period=0 --force
kubectl delete ns knative-serving --grace-period=0 --force

5. 참조

개념 및 아키텍처

Client-side / Server-side Discovery

Kubernetes Service 리소스

CoreDNS & kube-proxy 내부 동작

Ingress & LoadBalancer

Istio 기반 Service Discovery

Knative 기반 고급 Discovery

실습 및 진단 도구

Networking 심화 학습

profile
DevOps Engineer

2개의 댓글

comment-user-thumbnail
2025년 10월 26일

안녕하세요. 작성해주신 글 잘 읽었습니다. ExternalName 실습하신 부분에서 궁금한 부분이 있어 여쭤보려 합니다.

일반적으로 cluster 내부 -> 외부에 대한 제어가 필수적으로 사용되는걸까요? 예제 내용에서 NetworkPolicy로 규칙을 적용하여 특정 Pod만 외부로 연결이 가능하도록 하셨던데 보안 측면에서 일반적으로 사용되는 방식인가해서 질문드립니다. 클러스터 운영이나 관리쪽으로 경험이 거의 없어서요.

1개의 답글