이스티오 확장을 통한 맞춤형 네트워킹 구현
기본 기능으로 부족한 조직별 요구사항 충족을 위해 Envoy 프록시의 설정을 직접 수정하거나 확장한다. EnvoyFilter 리소스를 활용해 엔보이의 동작을 세부 제어하며, Lua 스크립트WebAssembly(Wasm) 모듈을 적용해 요청/응답 경로를 커스터마이징할 수 있다.

주요 확장 사례

  • 외부 인가 서비스 연동, HMAC 서명 검증 등 비표준 프로토콜 처리
  • 헤더 조작, 페이로드 보강을 위한 추가 서비스 호출
  • 조직별 보안 토큰 처리 로직 구현

이를 통해 Istio의 코어 변경 없이 특정 사용 사례에 최적화된 네트워킹 레이어를 구축할 수 있다.

 

 

실습환경 구성

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # kube-ops-view
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.0.0/22
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server



# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 172.18.0.100
/myk8s-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 myk8s-control-plane
docker exec -it myk8s-control-plane ping -c 1 mypc



# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml

# 확인
kubectl get crd  
kubectl get pod -n metallb-system

# IPAddressPool, L2Advertisement 설정 : MetalLB 파드(speaker) 배포 정상 완료 후 아래 설정 실행하자.
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A





#
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF


# 확인
kubectl get deploy,pod,svc,ep 
kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
LBIP=$(kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 외부?에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
docker exec -it mypc curl -s $LBIP
docker exec -it mypc curl -s $LBIP -v -I


# 확인 후 삭제
kubectl delete deploy,svc --all




# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# demo 프로파일 컨트롤 플레인 배포
istioctl install --set profile=demo --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

# 빠져나오기
exit
-----------------------------------

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

# 실습을 위한 네임스페이스 설정
kubectl create ns istioinaction
kubectl label namespace istioinaction istio-injection=enabled
kubectl get ns --show-labels

# istio-ingressgateway 서비스 : nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

# Kiali 접속 : NodePort
open http://127.0.0.1:30003

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004

 

이스티오 고급 설정을 위한 EnvoyFilter 활용

기존 API로 구현 불가한 시나리오에서 EnvoyFilter를 사용해 엔보이 프록시의 리스너·라우트·클러스터·필터를 직접 설정한다. 이 리소스는 이스티오의 고수준 추상화를 벗어나 저수준 엔보이 기능을 제어할 수 있지만, 버전 간 호환성 보장 없어 신중한 검증이 필수다. 잘못된 구성은 데이터 플레인 전체 장애로 이어질 수 있으므로, 반드시 테스트 환경에서 검증 후 배포해야 한다. 비상 시에만 사용하는 것이 권장되며, 일반적인 경우에는 VirtualService·DestinationRule 등 표준 API를 우선 적용한다.

 


Tap 필터를 적용해 webapp 서비스의 HTTP 트래픽을 샘플링·스트리밍하며, workloadSelector로 특정 워크로드에만 적용한다.
VirtualService/DestinationRule 이후 적용되며, 엔보이 저수준 설정 변경으로 인한 메시 장애 리스크 존재해 신중한 구성이 필수다.

 

# cat ch14/tap-envoy-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: tap-filter
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: webapp # 워크로드 셀렉터
  configPatches:
  - applyTo: HTTP_FILTER # 설정할 위치
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 8080
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch: # 엔보이 설정 패치
      operation: INSERT_BEFORE
      value:
       name: envoy.filters.http.tap
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap"
          commonConfig:
            adminConfig:
              configId: tap_config
              
              
              
              
#
cat ch14/tap-envoy-filter.yaml
kubectl apply -f ch14/tap-envoy-filter.yaml
kubectl get envoyfilter -n istioinaction
NAME         AGE
tap-filter   10s

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
                                {
                                    "name": "envoy.filters.http.tap",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap",
                                        "commonConfig": {
                                            "adminConfig": {
                                                "configId": "tap_config"
                                            }
                                        }
                                    }
                                },
                                {
                                    "name": "envoy.filters.http.router",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
                                    }
                                }
...

EnvoyFilter 설정 요약

  1. workloadSelector로 istioinaction 네임스페이스의 특정 워크로드에 적용 대상을 지정한다.
  2. SIDECAR_INBOUND 리스너의 8080 포트 HTTP 필터 체인에서 envoy.filters.http.router 필터 앞에 tap 필터를 추가한다.
  3. tap 필터는 envoy.filters.http.tap 타입으로 설정되며, streaming_admin 출력을 통해 실시간 트래픽 디버깅이 가능하다.

 

 

 

# 터미널 1 : 포트 포워딩 설정 후 tap 시작
kubectl port-forward -n istioinaction deploy/webapp 15000 &
curl -X POST -d @./ch14/tap-config.json localhost:15000/tap

# 터미널 2 : 기존 반복 접속하는 것 활용
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
while true; do docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; echo ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 터미널 3 : 로그 확인
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level http:debug
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level tap:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-18T05:59:47.704918Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:329	[C2100] new stream	thread=31
2025-05-18T05:59:47.705028Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1049	[C2100][S11845543740779751481] request headers complete (end_stream=true):
':authority', 'webapp.istioinaction.io'
':path', '/api/catalog'
':method', 'GET'
'user-agent', 'curl/8.7.1'
'accept', '*/*'
'x-app-tap', 'true'
...
2025-05-18T05:59:47.713852Z	debug	envoy tap external/envoy/source/extensions/filters/http/tap/tap_config_impl.cc:172	submitting buffered trace sink	thread=31
2025-05-18T05:59:47.713860Z	debug	envoy tap external/envoy/source/extensions/common/tap/admin.cc:145	admin submitting buffered trace to main thread	thread=31
...

 

 

 

14.3 외부 호출로 요청 속도 제한하기

엔보이 글로벌 속도 제한은 모든 서비스 인스턴스가 동일한 외부 속도 제한 서버(예: Redis 백엔드)를 호출해 요청 허용 여부를 결정하며, 서비스 복제본 수와 무관하게 일관된 트래픽 제한을 적용할 수 있다.

 

속도 제한 서버 설정
x-loyalty 헤더 값에 따라 골드(10/분), 실버(5/분), 브론즈(3/분) 등급별 요청 제한을 적용하고, 미확인 등급은 1/분으로 제한한다.
Redis 백엔드를 사용해 글로벌 카운터를 관리하며, Envoy의 RateLimitService 설정을 통해 외부 서버와 통합한다.

 

# cat ch14/rate-limit/rlsconfig.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-ratelimit-config
  namespace: istioinaction
data:
  config.yaml: |
    domain: catalog-ratelimit
    descriptors:
      - key: header_match
        value: no_loyalty
        rate_limit:
          unit: MINUTE
          requests_per_unit: 1
      - key: header_match
        value: gold_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 10
      - key: header_match
        value: silver_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 5
      - key: header_match
        value: bronze_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 3

 

요청 경로에 속도 제한 걸기* CONFIGURING THE REQUEST PATH FOR RATE LIMITING

EnvoyFilter를 통해 /items 경로 요청의 x-loyalty 헤더를 포착하는 rate-limit action을 정의해, 특정 로열티 등급별로 속도 제한 서버에 전달할 descriptor 생성을 구성한다.
VirtualService 라우트에 rate_limits 섹션 추가 없이, EnvoyFilter의 header_value_match 액션으로 헤더 기반 속성 추출 및 Redis 기반 글로벌 제한 정책을 적용한다.

# cat ch14/rate-limit/catalog-ratelimit-actions.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: catalog-ratelimit-actions
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: catalog
  configPatches:
    - applyTo: VIRTUAL_HOST
      match:
        context: SIDECAR_INBOUND
        routeConfiguration:
          vhost:
            route:
              action: ANY
      patch:
        operation: MERGE
        # Applies the rate limit rules.
        value:
          rate_limits: # 속도 제한 조치
            - actions:
              - header_value_match:
                  descriptor_value: no_loyalty
                  expect_match: false
                  headers:
                  - name: "x-loyalty"
            - actions:
              - header_value_match:
                  descriptor_value: bronze_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: bronze
            - actions:
              - header_value_match:
                  descriptor_value: silver_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: silver
            - actions:
              - header_value_match:
                  descriptor_value: gold_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: gold
                    
                    
                    
#
tree ch14/rate-limit
ch14/rate-limit
├── catalog-ratelimit-actions.yaml
├── catalog-ratelimit.yaml
├── rls.yaml
└── rlsconfig.yaml

cat ch14/rate-limit/rlsconfig.yaml
cat ch14/rate-limit/rls.yaml

#
kubectl apply -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/rls.yaml -n istioinaction

# 확인
kubectl get cm -n istioinaction catalog-ratelimit-config
kubectl get pod -n istioinaction                                
NAME                       READY   STATUS    RESTARTS      AGE
ratelimit-99d5d9c5-q4zcm   1/1     Running   2 (25s ago)   34s
redis-6cf4ff9768-x97m8     1/1     Running   0             34s
...





# 기존에 반복 호출은 취소해두자

# 
cat ch14/rate-limit/catalog-ratelimit.yaml
cat ch14/rate-limit/catalog-ratelimit-actions.yaml
kubectl apply -f ch14/rate-limit/catalog-ratelimit.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/catalog-ratelimit-actions.yaml -n istioinaction
kubectl get envoyfilter -A
NAMESPACE       NAME                        AGE
istioinaction   catalog-ratelimit-actions   27s
istioinaction   catalog-ratelimit-filter    28s
...

 

 

속도 제한 기능을 시험해보기 위해 sleep 앱을 배포하고 catalog 서비스를 호출하는 클라이언트를 시뮬레이션해보자.

# sleep 앱에서 catalog 서비스를 호출 시도 : 대략 1분에 한 번 정도 호출 성공! >> x-loyalty 헤더가 없을 때 속도 제한 값!
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
...

# silver 헤더는? 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
...


#
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4' -o json | grep actions

docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json | grep actions
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json
...
                "rateLimits": [
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "no_loyalty",
                                    "expectMatch": false,
                                    "headers": [
                                        {
                                            "name": "x-loyalty"
                                        }
                                    ]
                                }
                            }
                        ]
                    },
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "bronze_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "bronze"
...

 

 

14.4 루아로 이스티오의 데이터 플레인 확장하기

Lua를 활용한 이스티오 데이터 플레인 확장
Lua 스크립트를 엔보이 필터 체인에 주입해 요청/응답 경로의 헤더 조작·바디 검사 등 커스텀 로직 구현이 가능하다.
예를 들어 A/B 테스트 그룹 분류를 위해 외부 서비스 호출 후 결과를 헤더에 추가하는 동적 라우팅을 구성할 수 있다.
단, 바디 전체 메모리 적재 등 무거운 연산은 성능 저하를 유발할 수 있으니 주의가 필요하다.

| 일전에 실 예로 내가 운영하던 서비스중 프론트/백 사이에서 데이터가 안들어온다는 개발자 확인 요청으로 Lua 활용해서 바디 전체를 로깅하는 루아 스크립트 작성했다가 뻗었던 기억이 있다.

#
cat ch14/bucket-tester-service.yaml
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bucket-tester
    version: v1
  name: bucket-tester
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bucket-tester
      version: v1
  template:
    metadata:
      labels:
        app: bucket-tester
        version: v1
    spec:
      containers:
      - image: hashicorp/http-echo:1.0 # 수정 https://hub.docker.com/r/hashicorp/http-echo/tags
        imagePullPolicy: IfNotPresent
        name: bucket-tester
        args:
        - "-text=dark-launch-7"        
        ports:
        - containerPort: 5678
          name: http
          protocol: TCP
        securityContext:
          privileged: false

#
kubectl apply -f ch14/httpbin.yaml -n istioinaction
kubectl apply -f ch14/bucket-tester-service.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction
NAME                             READY   STATUS    RESTARTS      AGE
bucket-tester-688c598b47-86fbr   2/2     Running   0             25s
httpbin-85d76b4bb6-dz6b5         2/2     Running   0             2m56s
...




# cat ch14/lua-filter.yaml
...
            function envoy_on_request(request_handle)
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle)
              response_handle:headers():add("istioinaction", "it works!")
            end
...

 

Lua 스크립트는 envoy_on_request/envoy_on_response 함수로 요청·응답 헤더를 조작하고, httpCall()을 통해 외부 서비스와 논블로킹 통신해 동적 헤더 주입을 구현한다.

# cat ch14/lua-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: httpbin-lua-extension
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value: 
       name: envoy.lua
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            function envoy_on_request(request_handle) # 아래 줄에 코드 입력
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle) # 아래 줄에 코드 입력
              response_handle:headers():add("istioinaction", "it works!")
            end
  - applyTo: CLUSTER
    match:
      context: SIDECAR_OUTBOUND
    patch:
      operation: ADD
      value: # cluster specification
        name: bucket_tester
        type: STRICT_DNS
        connect_timeout: 0.5s
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: bucket_tester
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    protocol: TCP
                    address: bucket-tester.istioinaction.svc.cluster.local
                    port_value: 80

 

#
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction

# istio-proxy config 확인 내용 추가해두자


# httpbin 서비스 호출 확인! 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/ -v
...
< HTTP/1.1 503 Service Unavailable
< content-length: 39
< content-type: text/plain
< istioinaction: it works!
< date: Sun, 18 May 2025 07:59:34 GMT
< server: envoy
< x-envoy-upstream-service-time: 51
...
invalid header value for: x-test-cohort

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/headers
...
invalid header value for: x-test-cohort


# 정상 실습 시..
{
    "headers": {
        "Accept": "*/*",
        "Content-Length": "0",
        "Host": "httpbin.istioinaction:8000",
        "User-Agent": "curl/7.69.1",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "1d066f4b17ee147b",
        "X-B3-Traceid": "1ec27110e4141e131d066f4b17ee147b",
        "X-Test-Cohort": "dark-launch-7" # A/B 테스트 서비스를 호출할 때 덧붙이는 새 헤더 x-test-cohort 가 보임
    }
}

 

 

 

14.5 웹어셈블리로 이스티오의 데이터 플레인 확장하기

**웹어셈블리(Wasm)**를 통해 Envoy 필터를 동적 로드해 Istio 데이터 플레인을 확장하며, WasmPlugin CRD로 OCI 레지스트리에서 모듈을 배포해 커스텀 인증·트래픽 제어 기능을 구현할 수 있다. **웹어셈블리(Wasm)**는 이식성·고성능·안전성을 갖춘 바이너리 포맷으로, 다양한 언어로 작성된 커스텀 로직을 엔보이 필터에 동적 로드해 이스티오 데이터 플레인을 확장한다.

 

웹어셈블리(Wasm) 사용 이유
C++ 종속성·정적 빌드 문제 해결: 런타임 동적 로드로 커스텀 필터를 즉시 적용하며, 다양한 언어 지원으로 유연한 확장 가능. 안전한 샌드박스 환경에서 실행되어 메모리/CPU 제어·프록시 장애 격리되며, OCI 레지스트리 통합으로 배포 관리가 간소화된다.

**WebAssembly(Wasm)**로 Envoy 필터를 생성하려면 wasme 도구를 사용해 어셈블리스크립트·러스트 등 지원 언어로 코드 작성 후 빌드하며, 실험적 기능이므로 운영 전 철저한 테스트가 필요하다.

 

Distributing WebAssembly Modules 실습

Istio의 WasmPlugin을 사용해 OCI 레지스트리에서 원격 Wasm 모듈을 동적으로 로드하며, HTTP 기본 인증 확장 등을 구성할 수 있다.
WasmPlugin 리소스로 필터 단계(phase)와 설정을 정의하면 Istio 에이전트가 자동으로 모듈을 다운로드해 Envoy에 주입한다.

# 설정 전 호출 확인
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


#
kubectl explain wasmplugins.extensions.istio.io
...

# 
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: basic-auth
  namespace: istio-system # 모든 네임스페이스에 영향
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/istio-ecosystem/wasm-extensions/basic_auth:1.12.0
  phase: AUTHN
  pluginConfig:
    basic_auth_rules:
      - prefix: "/api"
        request_methods:
          - "GET"
          - "POST"
        credentials:
          - "ok:test"
          - "YWRtaW4zOmFkbWluMw=="
EOF

#
kubectl get WasmPlugin -A
NAMESPACE      NAME         AGE
istio-system   basic-auth   21s



#구성된 Wasm 모듈을 확인
# 자격 증명 없이 테스트
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


# 자격 증명으로 테스트
docker exec -it mypc curl -s -H "Authorization: Basic YWRtaW4zOmFkbWluMw==" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v

자격증명 유무(WASM 플러그인 동작)에 따른 성공

 

 

 

Istio 에 Coraza (코라자) WAF 적용 - KrBlog

CorazaGo 언어로 개발된 **오픈소스 웹 애플리케이션 방화벽(WAF)**으로, **OWASP CRS(핵심 규칙 세트)**와 완벽 호환되며 ModSecurity SecLang 규칙을 지원한다.

 

 

 

#
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: coraza-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/corazawaf/coraza-proxy-wasm
  phase: AUTHN
  pluginConfig:
    default_directives: default
    directives_map:
      default:
      - Include @demo-conf
      - SecDebugLogLevel 9
      - SecRuleEngine On
      - Include @crs-setup-conf
      - Include @owasp_crs/*.conf
EOF

#
kubectl logs -n istio-system -l app=istio-ingressgateway | grep -i wasm 
2025-05-18T09:22:39.344842Z     info    wasm    fetching image corazawaf/coraza-proxy-wasm from registry ghcr.io with tag latest

#
kubectl get WasmPlugin -A
NAMESPACE      NAME                    AGE
istio-system   coraza-ingressgateway   9s
#동작 확인

#
curl -s http://webapp.istioinaction.io:30000/api/catalog

# XSS (Cross-Site Scripting) 공격 시도 : REQUEST-941-APPLICATION-ATTACK-XSS
curl -s 'http://webapp.istioinaction.io:30000/api/catalog?arg=<script>alert(0)</script>' -IL
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:34:21.049522Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 1). Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11347"] [id "949111"] [rev ""] [msg "Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog?arg=<script>alert(0)</script>"] [unique_id "ztBHScSiiDnOiwJfmOy"]      thread=30
[2025-05-18T09:34:21.014Z] "HEAD /api/catalog?arg=<script>alert(0)</script> HTTP/1.1" 403 -  - "-" 0 0 35 - "172.18.0.1" "curl/8.7.1" "27f43c69-359d-9d95-99cd-553081bb9346" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.10:8080 172.18.0.1:65166 - -


# SQLI phase 2 (reading the body request)
curl -i -X POST 'http://webapp.istioinaction.io:30000/api/catalog' --data "1%27%20ORDER%20BY%203--%2B"
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:42:40.613124Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 10) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11358"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 10)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog"] [unique_id "GYSnZlurGTXFtujeNgl"]  thread=34
[2025-05-18T09:42:40.575Z] "POST /api/catalog HTTP/1.1" 403 -  - "-" 26 0 39 - "172.18.0.1" "curl/8.7.1" "c30d47e1-5631-99ae-91f2-5dfd8174b421" "webapp.istioinaction.io:30000" "10.10.0.16:8080" outbound|80||webapp.istioinaction.svc.cluster.local 10.10.0.10:44518 10.10.0.10:8080 172.18.0.1:63266 - -
...

WAF(WASM)으로 인해 악의적인 접근이 차단된것을 알 수 있다.

 

 

다중 클러스터 서비스 메시는 장애 격리 및 고가용성을 통해 클러스터 장애 시 트래픽을 다른 클러스터로 자동 전환하며, 다중 클라우드 지원 및 지연 시간 최소화를 위해 다양한 환경에서 워크로드를 실행하고 지리적 근접성을 기반으로 라우팅할 수 있다.

 

다중 클러스터 서비스 메시는 클러스터 간 워크로드 디스커버리·연결성·공통 신뢰를 통해 트래픽 제어·보안·관측 기능을 유지하며, Istio는 쿠버네티스 API 연동으로 서비스 프록시를 자동 구성한다. 보안 리스크 시 **메시 연합(Mesh Federation)**을 적용해 클러스터 API 접근 권한 분리와 독립적인 메시 운영이 가능하다.

 

이스티오 컨트롤 플레인은 쿠버네티스 API 서버에 접근해 서비스와 엔드포인트 정보를 수집한다.
API 접근은 서비스 어카운트와 토큰, RBAC 권한으로 제어한다.
istiod에 원격 클러스터의 서비스 어카운트 토큰

을 제공해 인증 후 워크로드를 디스커버리한다.


클러스터가 플랫 네트워크라면 워크로드가 IP로 바로 연결된다.
서로 다른 네트워크라면 이스티오 인그레스 게이트웨이(동서 게이트웨이)가 클러스터 간 트래픽을 프록시한다.
이 구조는 중복 IP, 네트워크 경계, 장애 허용, 네트워크 분리 등 다양한 요구를 충족한다.

 


다중 클러스터 메시에서는 클러스터 간 공통 신뢰가 필요하다.
공통 루트 CA에서 발급한 플러그인 중간 CA 인증서나 외부 CA 통합 방식이 있다.
플러그인 CA 방식은 간단하지만 노출 위험이 있고, 외부 CA 통합은 더 안전하지만 복잡하다.

 

 

 

12.3 다중 클러스터, 다중 네트워크, 다중 컨트롤 플레인 서비스 메시 개요

인프라 구성 환경

실세계 엔터프라이즈 서비스를 모방하는 인프라를 준비해보자. 이 서비스는 여러 클러스터에서 실행되고, 여러 리전에 걸쳐 배포되며, 서로 다른 네트워크에 위치한다.

  • west-cluster : 사설 네트워크가 us-west 리전에 있는 쿠버네티스 클러스터. 여기서 webapp 서비스를 실행.
  • east-cluster : 사설 네트워크가 us-east 리전에 있는 쿠버네티스 클러스터. 여기서 catalog 서비스를 실행할 것이다.

 

#실습환경 - west 배포

# 
kind create cluster --name west --image kindest/node:v1.23.17 --kubeconfig ./west-kubeconfig --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # istio-ingrssgateway HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # kube-ops-view
    hostPort: 30005
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.100.0.0/24
EOF

# 설치 확인
docker ps
cat west-kubeconfig
kubectl get node --kubeconfig=./west-kubeconfig
kubectl get pod -A --kubeconfig=./west-kubeconfig
    
# 노드에 기본 툴 설치
docker exec -it west-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30005 --set env.TZ="Asia/Seoul" --namespace kube-system --kubeconfig=./west-kubeconfig
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view --kubeconfig=./west-kubeconfig

## kube-ops-view 접속 URL 확인
open "http://localhost:30005/#scale=1.5"
open "http://localhost:30005/#scale=1.3"

 

 

# 실습환경 - east 배포

# 
kind create cluster --name east --image kindest/node:v1.23.17 --kubeconfig ./east-kubeconfig --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 31000 # istio-ingrssgateway HTTP
    hostPort: 31000
  - containerPort: 31001 # Prometheus
    hostPort: 31001
  - containerPort: 31002 # Grafana
    hostPort: 31002
  - containerPort: 31003 # Kiali
    hostPort: 31003
  - containerPort: 31004 # Tracing
    hostPort: 31004
  - containerPort: 31005 # kube-ops-view
    hostPort: 31005
networking:
  podSubnet: 10.20.0.0/16
  serviceSubnet: 10.200.0.0/24
EOF

# 설치 확인
docker ps
cat east-kubeconfig
kubectl get node --kubeconfig=./east-kubeconfig
kubectl get pod -A --kubeconfig=./east-kubeconfig

# 노드에 기본 툴 설치
docker exec -it east-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=31005 --set env.TZ="Asia/Seoul" --namespace kube-system --kubeconfig=./east-kubeconfig
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view --kubeconfig=./east-kubeconfig

## kube-ops-view 접속 URL 확인
open "http://localhost:31005/#scale=1.5"
open "http://localhost:31005/#scale=1.3"

 

# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 172.18.0.100
/east-control-plane 172.18.0.3
/west-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 172.18.0.3
docker exec -it mypc ping -c 1 west-control-plane
docker exec -it mypc ping -c 1 east-control-plane

#
docker exec -it west-control-plane ping -c 1 east-control-plane
docker exec -it east-control-plane ping -c 1 west-control-plane

docker exec -it west-control-plane ping -c 1 mypc
docker exec -it east-control-plane ping -c 1 mypc
#metal LB 배포

# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml \
  --kubeconfig=./west-kubeconfig

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml \
  --kubeconfig=./east-kubeconfig

# 확인
kubectl get crd --kubeconfig=./west-kubeconfig                  
kubectl get crd --kubeconfig=./east-kubeconfig           
       
kubectl get pod -n metallb-system --kubeconfig=./west-kubeconfig
kubectl get pod -n metallb-system --kubeconfig=./east-kubeconfig


# IPAddressPool, L2Advertisement 설정
cat << EOF | kubectl apply --kubeconfig=./west-kubeconfig -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

cat << EOF | kubectl apply --kubeconfig=./east-kubeconfig -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.201-172.18.255.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A --kubeconfig=./west-kubeconfig
kubectl get IPAddressPool,L2Advertisement -A --kubeconfig=./east-kubeconfig
#샘플 애플리케이션 배포 후 확인
#
cat << EOF | kubectl apply --kubeconfig=./west-kubeconfig -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF

#
cat << EOF | kubectl apply --kubeconfig=./east-kubeconfig -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF

# 확인
kubectl get deploy,pod,svc,ep --kubeconfig=./west-kubeconfig
kubectl get deploy,pod,svc,ep --kubeconfig=./east-kubeconfig

kubectl get svc nginx-service --kubeconfig=./west-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
kubectl get svc nginx-service --kubeconfig=./east-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

WNIP=$(kubectl get svc nginx-service --kubeconfig=./west-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
ENIP=$(kubectl get svc nginx-service --kubeconfig=./east-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 외부(mypc)에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
docker exec -it mypc curl -s $WNIP
docker exec -it mypc curl -s $WNIP -v -I

docker exec -it mypc curl -s $ENIP
docker exec -it mypc curl -s $ENIP -v -I


# 확인 후 삭제
kubectl delete deploy,svc --all --kubeconfig=./west-kubeconfig
kubectl delete deploy,svc --all --kubeconfig=./east-kubeconfig


#
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
alias keast='kubectl --kubeconfig=./east-kubeconfig'

# 확인
kwest get node
keast get node

 

 

12.3.3 플러그인 CA 인증서 설정
기본 CA 대신 사용자 정의 CA를 적용해 클러스터 간 공통 신뢰를 구축한다. cacerts 시크릿에 루트/중간 CA 인증서와 키를 포함시켜 Istio가 워크로드 인증서에 서명하도록 한다. 제공된 스크립트로 동일 루트 CA에서 파생된 중간 CA를 생성해 다중 클러스터 메시 보안을 통합한다.

플러그인 CA 인증서 적용하기

# cat ./ch12/scripts/generate-certificates.sh
#!/bin/bash

set -ex

cert_dir=`dirname "$BASH_SOURCE"`/../certs

echo "Clean up contents of dir './chapter12/certs'"
rm -rf ${cert_dir}

echo "Generating new certificates"
mkdir -p ${cert_dir}/west-cluster
mkdir -p ${cert_dir}/east-cluster

# step CLI 설치 확인 : step CLI가 설치되어 있지 않으면 에러 출력 후 종료.
## macOS : brew install step
if ! [ -x "$(command -v step)" ]; then 
  echo 'Error: Install the smallstep cli (https://github.com/smallstep/cli)'
  exit 1
fi

step certificate create root.istio.in.action ${cert_dir}/root-cert.pem ${cert_dir}/root-ca.key \
  --profile root-ca --no-password --insecure --san root.istio.in.action \
  --not-after 87600h --kty RSA 

step certificate create west.intermediate.istio.in.action ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/west-cluster/ca-key.pem --ca ${cert_dir}/root-cert.pem --ca-key ${cert_dir}/root-ca.key --profile intermediate-ca --not-after 87600h --no-password --insecure --san west.intermediate.istio.in.action --kty RSA 
step certificate create east.intermediate.istio.in.action ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/east-cluster/ca-key.pem --ca ${cert_dir}/root-cert.pem --ca-key ${cert_dir}/root-ca.key --profile intermediate-ca --not-after 87600h --no-password --insecure --san east.intermediate.istio.in.action --kty RSA 

cat ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/west-cluster/cert-chain.pem
cat ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/east-cluster/cert-chain.pem

 

#앞서 구성한 실습 환경 확인
tree ch12/certs 
ch12/certs
├── east-cluster
│   ├── ca-cert.pem    # 중간 CA 인증서
│   ├── ca-key.pem     # 중간 CA 개인zl
│   └── cert-chain.pem # 인증서 체인
├── root-ca.key        # 루트 인증서
├── root-cert.pem      # 루트 개인키
└── west-cluster
    ├── ca-cert.pem
    ├── ca-key.pem
    └── cert-chain.pem

# (참고) 인증서 체인 생성
## 중간 CA 인증서(ca-cert.pem)와 루트 CA 인증서(root-cert.pem)를 결합 -> 결과는 {west/east}-cluster/cert-chain.pem 에 저장
cat ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/west-cluster/cert-chain.pem
cat ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/east-cluster/cert-chain.pem


# 인증서 계층 구조
root.istio.in.action (Root CA)
   │
   └── east.intermediate.istio.in.action (Intermediate CA)


# 루트 CA 인증서 확인
openssl x509 -in ch12/certs/root-cert.pem -noout -text
...
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=root.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1
...

#
openssl x509 -in ch12/certs/east-cluster/ca-cert.pem -noout -text
...
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=east.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0 # 이 중간 CA는 추가적인 하위 CA를 만들 수 없음
...

openssl x509 -in ch12/certs/east-cluster/cert-chain.pem -noout -text
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=east.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

#
openssl x509 -in ch12/certs/west-cluster/ca-cert.pem -noout -text
openssl x509 -in ch12/certs/west-cluster/cert-chain.pem -noout -text
# istio-system 네임스페이스를 만든 후 인증서를 cacerts 라는 시크릿으로 배포하여 각 클러스터에 중간 CA를 만들어두기
# west-cluster 용 인증서 설정하기
kwest create namespace istio-system
kwest create secret generic cacerts -n istio-system \
--from-file=ch12/certs/west-cluster/ca-cert.pem \
--from-file=ch12/certs/west-cluster/ca-key.pem \
--from-file=ch12/certs/root-cert.pem \
--from-file=ch12/certs/west-cluster/cert-chain.pem

# east-cluster 용 인증서 설정하기
keast create namespace istio-system
keast create secret generic cacerts -n istio-system \
--from-file=ch12/certs/east-cluster/ca-cert.pem \
--from-file=ch12/certs/east-cluster/ca-key.pem \
--from-file=ch12/certs/root-cert.pem \
--from-file=ch12/certs/east-cluster/cert-chain.pem

# 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get ns istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret cacerts  -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl view-secret cacerts -n istio-system --all --kubeconfig=./$i-kubeconfig; echo; done

 

12.3.4 각 클러스터에 (Istiod) 컨트롤 플레인 설치하기* Installing the control planes in each cluster

클러스터에 네트워크 메타데이터 추가로 이스티오가 토폴로지 인지 → 근접 워크로드 우선 라우팅 및 동서 게이트웨이 자동 설정 가능하다.

 

#클러스터 간 연결을 위해 네트워크에 레이블 붙이기
kwest label namespace istio-system topology.istio.io/network=west-network
keast label namespace istio-system topology.istio.io/network=east-network

# 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get ns istio-system --show-labels --kubeconfig=./$i-kubeconfig; echo; done

 

 

# cat ./ch12/controlplanes/cluster-west.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways: # 이그레스 게이트웨이 비활성화
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh # 메시 이름
      multiCluster:
        clusterName: west-cluster # 멀티 클러스터 메시 내부의 클러스터 식별자
      network: west-network # 이 설치가 이뤄지는 네트워크
      
      
# cat ./ch12/controlplanes/cluster-east.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways: # 이그레스 게이트웨이 비활성화
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh # 메시 이름
      multiCluster:
        clusterName: east-cluster
      network: east-network

 

 

# west-control-plane 진입 후 설치 진행
docker exec -it west-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# IstioOperator 파일 작성
cat << EOF > west-istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: west-cluster
      network: west-network
EOF

# 컨트롤 플레인 배포
istioctl install -f west-istio.yaml --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

# 빠져나오기
exit
-----------------------------------

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kwest get istiooperators -n istio-system -o yaml
...
        meshID: usmesh
        meshNetworks: {}
        mountMtlsCerts: false
        multiCluster:
          clusterName: west-cluster
          enabled: false
        network: west-network
...

kwest get all,svc,ep,sa,cm,secret,pdb -n istio-system
kwest get secret -n istio-system cacerts -o json # 미리 만들어둔 인증서/키 확인

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kwest patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kwest patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kwest describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kwest patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kwest patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kwest patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kwest patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

# Kiali 접속 : NodePort
open http://127.0.0.1:30003

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004

 

 

이번엔 east 클러스터에도 비슷하게 설정해준다.

# west-control-plane 진입 후 설치 진행
docker exec -it east-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# IstioOperator 파일 작성
cat << EOF > east-istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: east-cluster
      network: east-network
EOF

# 컨트롤 플레인 배포
istioctl install -f east-istio.yaml --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

# 빠져나오기
exit
-----------------------------------

# 설치 확인 : istiod, istio-ingressgateway, crd 등
keast get istiooperators -n istio-system -o yaml
...
        meshID: usmesh
        meshNetworks: {}
        mountMtlsCerts: false
        multiCluster:
          clusterName: east-cluster
          enabled: false
        network: east-network
...

keast get all,svc,ep,sa,cm,secret,pdb -n istio-system
keast get secret -n istio-system cacerts -o json # 미리 만들어둔 인증서/키 확인


# NodePort 변경 및 nodeport 31001~31003으로 변경 : prometheus(31001), grafana(31002), kiali(31003), tracing(31004)
keast patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 31001}]}}'
keast patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 31002}]}}'
keast patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 31003}]}}'
keast patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 31004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:31001

# Grafana 접속
open http://127.0.0.1:31002

# Kiali 접속
open http://127.0.0.1:31003

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:31004

 

# istioctl alias 설정

#
docker exec -it west-control-plane istioctl -h
docker exec -it east-control-plane istioctl -h

#
alias iwest='docker exec -it west-control-plane istioctl'
alias ieast='docker exec -it east-control-plane istioctl'

#
iwest proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                      VERSION
istio-ingressgateway-5db74c978c-9qmt9.istio-system     west-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-5585445f4c-zf8g6     1.17.8

ieast proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
istio-ingressgateway-7f6f8f8d99-4mlp5.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8

#
iwest proxy-config secret deploy/istio-ingressgateway.istio-system
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
default           Cert Chain     ACTIVE     true           80349990876331570640723939723244297816      2025-05-17T08:47:28Z     2025-05-16T08:45:28Z
ROOTCA            CA             ACTIVE     true           100900981840825465297757884708490534092     2032-06-25T14:11:35Z     2022-06-28T14:11:35Z

iwest proxy-config secret deploy/istio-ingressgateway.istio-system -o json
...

# 아래는 default 에 inlineBytes 값을 decode64 -d 시 3개의 인증서 정보 출력 후 각 개별 인증서를 openssl x509 -in Y.pem -noout -text 로 출력 확인 
## (1) 사용자 인증서
-----BEGIN CERTIFICATE-----
MIIDdjCCAl6gAwIBAgIQPHLYaJhiIjAwJkg6cAVeWDANBgkqhkiG9w0BAQsFADAs
...
5xYNi7u0APTzE1swNbx2TF5eeTsFvYcbFh56ahLp0izGkahOv97bEgnZdeTsLRyH
K+5+1ZdJ2n8CuxoSY+FXUlMDwGjdvCXAKBM=
-----END CERTIFICATE-----

        Issuer: CN=west.intermediate.istio.in.action
        Validity
            Not Before: May 16 08:45:28 2025 GMT
            Not After : May 17 08:47:28 2025 GMT
        Subject: 
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                D3:83:9A:3A:51:D9:03:62:35:8F:6A:A4:DA:99:88:BB:74:70:4F:33
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account


## (2) 중간 CA 루트 인증서
-----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIRAMkJ23sotpqiiWps+38Df/YwDQYJKoZIhvcNAQELBQAw
...
usSjiM6KR77xogslodbQw4QQG+w5HQOwMa1k8WTCNrplxdsnaQJjdqUwCdixicq2
DeHuSkz4cykAI/NWc2cZIw==
-----END CERTIFICATE-----

        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=west.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

## (3) 최상위 루트 인증서
-----BEGIN CERTIFICATE-----
MIIDDTCCAfWgAwIBAgIQS+jSffZX7itohjyrautczDANBgkqhkiG9w0BAQsFADAf
...
3fRtDApNHbbmi7WXrM+pG4D+Buk2FUEHJVpu16Ch2K2vpRzpkliqes+T/5E92uY9
ob7MBgt61g4VZ/p8+RMJWYw=
-----END CERTIFICATE-----

        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=root.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1

#
iwest proxy-config listener deploy/istio-ingressgateway.istio-system
iwest proxy-config route deploy/istio-ingressgateway.istio-system
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system
iwest proxy-config secret deploy/istio-ingressgateway.istio-system
iwest proxy-config bootstrap deploy/istio-ingressgateway.istio-system
iwest proxy-config ecds deploy/istio-ingressgateway.istio-system

#
ieast proxy-config listener deploy/istio-ingressgateway.istio-system
ieast proxy-config route deploy/istio-ingressgateway.istio-system
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system
ieast proxy-config secret deploy/istio-ingressgateway.istio-system
ieast proxy-config bootstrap deploy/istio-ingressgateway.istio-system
ieast proxy-config ecds deploy/istio-ingressgateway.istio-system

 

두 클러스터 모두에 워크로드 실행하기 Running workloads on both clusters

#
kwest create ns istioinaction
kwest label namespace istioinaction istio-injection=enabled
kwest -n istioinaction apply -f ch12/webapp-deployment-svc.yaml
kwest -n istioinaction apply -f ch12/webapp-gw-vs.yaml
kwest -n istioinaction apply -f ch12/catalog-svc.yaml # Stub catalog service to which webapp makes request
cat ch12/catalog-svc.yaml
piVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: catalog
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: catalog


# 확인
kwest get deploy,pod,svc,ep -n istioinaction
kwest get svc,ep catalog -n istioinaction
NAME              TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.2.43   <none>        80/TCP    2m

NAME                ENDPOINTS   AGE
endpoints/catalog   <none>      2m

kwest get gw,vs,dr -A
NAMESPACE       NAME                                            AGE
istioinaction   gateway.networking.istio.io/coolstore-gateway   16s

NAMESPACE       NAME                                                       GATEWAYS                HOSTS                         AGE
istioinaction   virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   16s

#
iwest proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
istio-ingressgateway-5db74c978c-9j96n.istio-system     west-cluster     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-5585445f4c-zcvp4     1.17.8
webapp-5c8b4fff64-tfs8m.istioinaction                  west-cluster     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-5585445f4c-zcvp4     1.17.8

# endpoint 에 IP 는 10.10.0.0/16 대역들 다수 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS 

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog

 

 

stub 서비스 필요성 은 FQDN의 DNS 해결 실패를 방지해 애플리케이션 트래픽이 프록시로 리다이렉션될 수 있게 한다.

 

#east cluast에 catalog 배포

#
keast create ns istioinaction
keast label namespace istioinaction istio-injection=enabled
keast -n istioinaction apply -f ch12/catalog.yaml
cat ch12/catalog.yaml


# 확인
keast get deploy,pod,svc,ep -n istioinaction
keast get svc,ep catalog -n istioinaction
keast get gw,vs,dr -A # 없음

#
ieast proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
catalog-6cf4b97d-ff7lq.istioinaction                   east-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-85976468f-9q8ck     1.17.8
istio-ingressgateway-7f6f8f8d99-fjn92.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-9q8ck     1.17.8

# endpoint 에 IP 는 10.20.0.0/16 대역들 다수 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS

ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

 

지금까지 실습환경 구성이 끝났다.

 

12.3.5 클러스터 간 워크로드 디스커버리 활성화하기 Enabling cross-cluster workload discovery

istio-reader-service-account 생성 → 원격 클러스터 인증 및 워크로드 정보 조회 가능, 토큰/인증서 공유로 보안 연결 설정.

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get sa -n istio-system --kubeconfig=./$i-kubeconfig; echo; done

# east
keast describe sa -n istio-system istio-reader-service-account      
...
Mountable secrets:   istio-reader-service-account-token-566n2
Tokens:              istio-reader-service-account-token-566n2

keast get sa -n istio-system istio-reader-service-account -o yaml
...
  name: istio-reader-service-account
  namespace: istio-system
  resourceVersion: "16805"
  uid: 40fa07c3-2e49-4003-a09b-afccdbcec7a2
secrets:
- name: istio-reader-service-account-token-566n2

keast get sa -n istio-system istio-reader-service-account -o jsonpath='{.secrets[0].name}'
eirsa=$(keast get sa -n istio-system istio-reader-service-account -o jsonpath='{.secrets[0].name}')
keast get secret -n istio-system $eirsa
keast get secret -n istio-system $eirsa -o json
{
    "apiVersion": "v1",
    "data": {
        "ca.crt": "LS0t,,,==",
        "namespace": "aXN0aW8tc3lzdGVt", # istio-system
        "token": "ZXl...VN2Zw=="
    },
...

kubectl rolesum istio-reader-service-account -n istio-system --kubeconfig=./east-kubeconfig
...
Policies:

• [CRB] */istio-reader-clusterrole-istio-system ⟶  [CR] */istio-reader-clusterrole-istio-system
  Resource                                                                                         Name  Exclude  Verbs  G L W C U P D DC  
  *.[config.istio.io,security.istio.io,networking.istio.io,authentication.istio.io,rbac.istio.io]  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  customresourcedefinitions.apiextensions.k8s.io                                                   [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  endpoints                                                                                        [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  endpointslices.discovery.k8s.io                                                                  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  namespaces                                                                                       [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  nodes                                                                                            [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  pods                                                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  replicasets.apps                                                                                 [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  replicationcontrollers                                                                           [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  secrets                                                                                          [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  serviceexports.multicluster.x-k8s.io                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✖ ✖ ✔ ✖   
  serviceimports.multicluster.x-k8s.io                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  services                                                                                         [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  subjectaccessreviews.authorization.k8s.io                                                        [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  tokenreviews.authentication.k8s.io                                                               [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  workloadentries.networking.istio.io                                                              [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   


• [CRB] */istio-reader-istio-system ⟶  [CR] */istio-reader-istio-system
  Resource                                                                                         Name  Exclude  Verbs  G L W C U P D DC  
  *.[config.istio.io,security.istio.io,networking.istio.io,authentication.istio.io,rbac.istio.io]  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
...

keast auth can-i --list
keast auth can-i --as=system:serviceaccount:istio-system:istio-reader-service-account --list
Resources                                        Non-Resource URLs                     Resource Names   Verbs
tokenreviews.authentication.k8s.io               []                                    []               [create]
selfsubjectaccessreviews.authorization.k8s.io    []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io     []                                    []               [create]
subjectaccessreviews.authorization.k8s.io        []                                    []               [create]
serviceexports.multicluster.x-k8s.io             []                                    []               [get list watch create delete]
endpoints                                        []                                    []               [get list watch]
namespaces                                       []                                    []               [get list watch]
nodes                                            []                                    []               [get list watch]
pods                                             []                                    []               [get list watch]
replicationcontrollers                           []                                    []               [get list watch]
secrets                                          []                                    []               [get list watch]
services                                         []                                    []               [get list watch]
...

#
ieast x create-remote-secret --help
Create a secret with credentials to allow Istio to access remote Kubernetes apiservers

ieast x create-remote-secret --name="east-cluster"
# This file is autogenerated, do not edit.
apiVersion: v1
kind: Secret
metadata:
  annotations:
    networking.istio.io/cluster: east-cluster
  creationTimestamp: null
  labels:
    istio/multiCluster: "true" # 이 레이블이 true로 설정된 시크릿은 이스티오의 컨트롤 플레인이 새 클러스터를 등록하기 위해 감시한다
  name: istio-remote-secret-east-cluster
  namespace: istio-system
stringData:
  east-cluster: |
    apiVersion: v1
    clusters:
    - cluster: # 아래 'certificate-authority-data'는 이 클러스터에 보안 커넥션을 시작하는 데 사용하는 CA
        certificate-authority-data: LS0tLS1CR....
        server: https://east-control-plane:6443
      name: east-cluster
    contexts:
    - context:
        cluster: east-cluster
        user: east-cluster
      name: east-cluster
    current-context: east-cluster
    kind: Config
    preferences: {}
    users:
    - name: east-cluster
      user: # 아래 'token'은 서비스 어카운트의 ID를 나타내는 토큰
        token: eyJhb...
---

## certificate-authority-data 정보 : k8s 루트 인증서
openssl x509 -in YYY -noout -text
...
        Issuer: CN=kubernetes
        Validity
            Not Before: May 16 05:13:20 2025 GMT
            Not After : May 14 05:13:20 2035 GMT
        Subject: CN=kubernetes
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE

## user.token 정보 : 
jwt decode YYY
Token header
------------
{
  "alg": "RS256",
  "kid": "oyrLHJhI-1aEvcPmbnFZEI6avASPC3fJttjoEaXt-iU"
}

Token claims
------------
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "istio-system",
  "kubernetes.io/serviceaccount/secret.name": "istio-reader-service-account-token-566n2",
  "kubernetes.io/serviceaccount/service-account.name": "istio-reader-service-account",
  "kubernetes.io/serviceaccount/service-account.uid": "40fa07c3-2e49-4003-a09b-afccdbcec7a2",
  "sub": "system:serviceaccount:istio-system:istio-reader-service-account"
}

 

# 시크릿 내용을 출력하는 대신, kubectl 명령어에 파이프해 west-cluster 에 적용하자
# west 에 시크릿 생성
ieast x create-remote-secret --name="east-cluster" | kwest apply -f -
secret/istio-remote-secret-east-cluster created

# istiod 로그 확인 : 시크릿이 생성되면, 바로 istiod가 이 시크릿을 가지고 새로 추가된 원격 클러스터(east)에 워크로드를 쿼리한다.
kwest logs deploy/istiod -n istio-system | grep 'Adding cluster'
2025-05-16T22:45:00.679666Z     info    Adding cluster  cluster=east-cluster secret=istio-system/istio-remote-secret-east-cluster

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
kwest get secret -n istio-system istio-remote-secret-east-cluster
kwest get secret -n istio-system istio-remote-secret-east-cluster -o yaml
...

# west 확인 : east 의 모든 CDS/EDS 정보를 west 에서도 확인 가능!
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# west 에서 10.20.0.15(10.20.0.0/16)로 라우팅이 가능한 상태인가?
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...
                "address": {
                    "socketAddress": {
                        "address": "10.20.0.15",
                        "portValue": 3000
...

# east 확인 : west 의 CDS/EDS 정보를 아직 모름!
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

 

 서로다른 대역(10.10.x 와 10.20.x)이 동시에 불러와지는데, west에서 east 정보가 가져와지는것을 알 수 있다. east 가 west 쿼리 할 수 있게 반대로도 설정하자.

# east 에 시크릿 생성
iwest x create-remote-secret --name="west-cluster" | keast apply -f -
secret/istio-remote-secret-west-cluster created

# istiod 로그 확인 : 시크릿이 생성되면, 바로 istiod가 이 시크릿을 가지고 새로 추가된 원격 클러스터(east)에 워크로드를 쿼리한다.
keast logs deploy/istiod -n istio-system | grep 'Adding cluster'
2025-05-17T00:09:02.438756Z     info    Adding cluster  cluster=west-cluster secret=istio-system/istio-remote-secret-west-cluster

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
keast get secret -n istio-system istio-remote-secret-west-cluster
keast get secret -n istio-system istio-remote-secret-west-cluster -o yaml
...

# east 확인 : west 의 모든 CDS/EDS 정보를 east 에서도 확인 가능!
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep webapp

# east 에서 10.10.0.15(10.10.0.0/16)로 라우팅이 가능한 상태인가?
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep webapp
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

 

이제 컨트롤 플레인이 상대 클러스터의 워크로드를 퀴리할 수 있다.

 

12.3.6 클러스터 간 연결 설정하기 Setting up cross-cluster connectivity

north-south 트래픽: 외부→내부 네트워크 진입 트래픽으로, 이스티오 인그레스 게이트웨이를 통해 처리된다.

east-west 트래픽: 내부 네트워크 간 통신으로, VPC 피어링 불가 시 이스티오 east-west 게이트웨이를 사용해 크로스 클라우드/온프레미스 연결을 구성한다.

클러스터 간 트래픽을 암호화된 상호 인증으로 투명하게 라우팅하며, 운영자 추가 설정 없이 정밀한 트래픽 제어가 가능하다.

 

SNI 클러스터SNI 자동 통과 기능이 게이트웨이 동작을 구현해 클러스터 경계 넘어 자동화된 라우팅을 지원한다.

클라이언트는 SNI 헤더에 목적지 클러스터 정보(서비스명·포트·버전)를 인코딩해 전송하며, 게이트웨이는 이를 파싱해 암호화된 트래픽을 정확한 워크로드로 라우팅한다.mTLS 연결 유지 상태에서 프록시가 트래픽을 복호화 없이 전달하므로 보안성을 확보하면서도 클러스터 간 세부 라우팅 제어가 가능하다. 이 구조는 운영자의 추가 설정 없이 자동화된 크로스 클러스터 통신과 상호 인증을 동시에 구현한다.

 

SNI 클러스터가 있는 east-west 게이트웨이 설치하기 Installing the east-west gateway with SNI clusters

east-west 게이트웨이에 SNI 클러스터를 활성화하려면 IstioOperator에서 ISTIO_META_ROUTER_MODE를 sni-dnat으로 설정하면 된다. 이 설정으로 게이트웨이가 SNI 기반 트래픽 라우팅을 자동 지원하며, 추가 리소스 없이 다중 클러스터 mTLS 통신이 가능해진다.

# cat ch12/gateways/cluster-east-eastwest-gateway.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-eastwestgateway # IstioOperator 이름은 앞 선 이스티오 설정 이름과 겹치지 않아야 한다
  namespace: istio-system
spec:
  profile: empty # empty 프로필은 추가 이스티오 구성 요소를 설치하지 않는다
  components:
    ingressGateways:
    - name: istio-eastwestgateway # 게이트웨이 이름
      label:
        istio: eastwestgateway
        app: istio-eastwestgateway
        topology.istio.io/network: east-network
      enabled: true
      k8s:
        env:
        - name: ISTIO_META_ROUTER_MODE # sni-dnat 모드는 트래픽을 프록시하는 데 필요한 SNI 클러스터를 추가한다
          value: "sni-dnat"
        # The network to which traffic is routed
        - name: ISTIO_META_REQUESTED_NETWORK_VIEW # 게이트웨이가 트래픽을 라우팅하는 네트워크
          value: east-network
        service:
          ports:
          ... (생략) ...
  values:
    global:
      meshID: usmesh # 메시, 클러스터, 네트워크 식별 정보
      multiCluster:
        clusterName: east-cluster
      network: east-network
# 설치 전 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
kwest get IstioOperator -n istio-system installed-state-istio-controlplane -o yaml
keast get IstioOperator -n istio-system installed-state-istio-controlplane -o yaml

# 설치 전 확인 : west 에서 catalog endpoint 에 IP 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# IstioOperator 로 east 클러스터에 east-west 게이트웨이를 설치
cat ch12/gateways/cluster-east-eastwest-gateway.yaml
docker cp ./ch12/gateways/cluster-east-eastwest-gateway.yaml east-control-plane:/cluster-east-eastwest-gateway.yaml
ieast install -f /cluster-east-eastwest-gateway.yaml --set values.global.proxy.privileged=true -y

# east 클러스터에 east-west 게이트웨이를 설치 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
...
NAME                                     READY   STATUS    RESTARTS        AGE
istio-eastwestgateway-866794c798-k9xfl   1/1     Running   0               14s
istio-ingressgateway-7f6f8f8d99-4mlp5    1/1     Running   1 (3h38m ago)   17h
istiod-85976468f-vp4zg                   1/1     Running   1 (3h38m ago)   17h

keast get IstioOperator -n istio-system installed-state-istio-eastwestgateway -o yaml
...
    ingressGateways:
    - enabled: true
      k8s:
        env:
        - name: ISTIO_META_ROUTER_MODE
          value: sni-dnat
        - name: ISTIO_META_REQUESTED_NETWORK_VIEW
          value: east-network
        service:
          ports:
          - name: status-port
            port: 15021
            targetPort: 15021
          - name: mtls
            port: 15443
            targetPort: 15443
          - name: tcp-istiod
            port: 15012
            targetPort: 15012
          - name: tcp-webhook
            port: 15017
            targetPort: 15017
      label:
        app: istio-eastwestgateway
        istio: eastwestgateway
        topology.istio.io/network: east-network
      name: istio-eastwestgateway
...

# east 정보 확인
ieast proxy-status
NAME                                                    CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
catalog-6cf4b97d-9c995.istioinaction                    east-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-85976468f-vp4zg     1.17.8
istio-eastwestgateway-866794c798-k9xfl.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8
istio-ingressgateway-7f6f8f8d99-4mlp5.istio-system      east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8

# east 에 istio-ingressgateway  에 istio-config 정보 확인 : west 의 CDS/EDS 모두 알고 있음!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# east 에 istio-eastwestgateway 에 istio-config 정보 확인 : webapp(CDS) OK, west 에 EDS 아직 모름!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : eastwestgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-eastwestgateway.istio-system; echo; done

ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system | grep istioinaction
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS            
webapp.istioinaction.svc.cluster.local                       80        -          outbound      EDS

ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json | grep sni
                        "sni": "outbound_.80_._.webapp.istioinaction.svc.cluster.local"

ieast proxy-config endpoint deploy/istio-eastwestgateway.istio-system | grep istioinaction
ieast proxy-config endpoint deploy/istio-eastwestgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json


# west 정보 확인
iwest proxy-status

# west 에 istio-ingressgateway 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep istioinaction
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json | grep sni
                        "sni": "outbound_.80_._.catalog.istioinaction.svc.cluster.local"

# west 에 istio-ingressgateway : east EDS 모든 정보에서 east의 eastwestgateway에 mtls 정보로 변경!
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep istioinaction
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# 출력되는 172.18.X.Y에 IP 확인 : east 에 eastwestgateway 의 Service(LoadBalancer)의 External-IP.
keast get svc,ep -n istio-system istio-eastwestgateway
NAME                            TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)                                                           AGE
service/istio-eastwestgateway   LoadBalancer   10.200.0.90   172.18.255.202   15021:32281/TCP,15443:30196/TCP,15012:32628/TCP,15017:32346/TCP   27m

NAME                              ENDPOINTS                                                        AGE
endpoints/istio-eastwestgateway   10.20.0.16:15021,10.20.0.16:15017,10.20.0.16:15012 + 1 more...   27m


# west 에 webapp 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : webapp - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/webapp.istioinaction; echo; done
iwest proxy-config endpoint deploy/webapp.istioinaction | grep istioinaction
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local


# west 에서 호출 시도
kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    63m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    63m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            63m
endpoints/webapp    10.10.0.15:8080   63m

kwest exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl catalog.istioinaction.svc.cluster.local -v
*   Trying 10.100.0.170:80...
* connect to 10.100.0.170 port 80 failed: Connection refused
...

배포전
배포후

 

east-west 게이트웨이 설치 후 라우터 모드를 sni-dnat으로 설정하면, SNI 자동 통과 모드를 통해 다중 클러스터 mTLS 포트를 노출시켜 암호화된 트래픽을 복호화 없이 목적지 클러스터로 라우팅할 수 있다.

# cat ch12/gateways/expose-services.yaml              
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: cross-network-gateway
  namespace: istio-system
spec:
  selector:
    istio: eastwestgateway # 셀렉터와 일치하는 게이트웨이에만 설정이 적용된다.
  servers:
    - port:
        number: 15443 # 이스티오에서 15443 포트는 멀티 클러스터 상호 TLS 트래픽 용도로 지정된 특수 포트다
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH # SNI 헤더를 사용해 목적지를 해석하고 SNI 클러스터를 사용한다.
      hosts:
        - "*.local" # 정규식 *.local 과 일치하는 SNI에 대해서만 트래픽을 허용한다.

 

# east 클러스터에 적용하자. east-cluster의 워크로드를 west-cluster 에 노출한다.
cat ch12/gateways/expose-services.yaml
keast apply -n istio-system -f ch12/gateways/expose-services.yaml

# 확인
keast get gw,vs,dr -A
NAMESPACE      NAME                                                AGE
istio-system   gateway.networking.istio.io/cross-network-gateway   85s

# west 에서 호출 시도
kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    63m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    63m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            63m
endpoints/webapp    10.10.0.15:8080   63m

kwest exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl catalog.istioinaction.svc.cluster.local -v
*   Trying 10.100.0.170:80...
* connect to 10.100.0.170 port 80 failed: Connection refused
...


# east 에 istio-ingressgateway  에 istio-config 정보 확인 : 이전 내용과 동일하게 west 의 CDS/EDS 모두 알고 있음!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# east 에 istio-eastwestgateway 에 istio-config 정보 확인 : SNI 자동 통과를 위한 listener 추가 확인!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : eastwestgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-eastwestgateway.istio-system; echo; done

ieast proxy-config listener deploy/istio-eastwestgateway.istio-system
ieast proxy-config listener deploy/istio-eastwestgateway.istio-system | grep istioinaction
0.0.0.0 15443 SNI: outbound_.80_._.webapp.istioinaction.svc.cluster.local; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2                    Cluster: outbound_.80_._.webapp.istioinaction.svc.cluster.local
0.0.0.0 15443 SNI: outbound_.80_._.catalog.istioinaction.svc.cluster.local; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2                   Cluster: outbound_.80_._.catalog.istioinaction.svc.cluster.local

ieast proxy-config listener deploy/istio-eastwestgateway.istio-system --port 15443  -o json
...
                "filterChainMatch": {
                    "serverNames": [
                        "outbound_.80_._.catalog.istioinaction.svc.cluster.local"
                 ...
                },
                "filters": [
                    ...
                    {
                        "name": "envoy.filters.network.tcp_proxy",
                        "typedConfig": {
                            "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
                            "statPrefix": "outbound_.80_._.catalog.istioinaction.svc.cluster.local",
                            "cluster": "outbound_.80_._.catalog.istioinaction.svc.cluster.local",
...



# west 정보 확인
iwest proxy-status

# west 에 istio-ingressgateway 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# west 에 webapp 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : webapp - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/webapp.istioinaction; echo; done

 

#반대편 클러스터에도 작업 수행. west-cluster 에 east-west 게이트웨이를 만들고, 그 서비스를 east-cluster 워크로드에 노출.

# IstioOperator 로 west 클러스터에 east-west 게이트웨이를 설치
cat ch12/gateways/cluster-west-eastwest-gateway.yaml
docker cp ./ch12/gateways/cluster-west-eastwest-gateway.yaml west-control-plane:/cluster-west-eastwest-gateway.yaml
iwest install -f /cluster-west-eastwest-gateway.yaml --set values.global.proxy.privileged=true -y

# west 클러스터에 east-west 게이트웨이를 설치 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
kwest get IstioOperator -n istio-system installed-state-istio-eastwestgateway -o yaml
iwest proxy-status


# west 클러스터에 적용하자. east-cluster의 워크로드를 west-cluster 에 노출한다.
cat ch12/gateways/expose-services.yaml
kwest apply -n istio-system -f ch12/gateways/expose-services.yaml

# 확인
kwest get gw,vs,dr -A
NAMESPACE       NAME                                                AGE
istio-system    gateway.networking.istio.io/cross-network-gateway   5s
istioinaction   gateway.networking.istio.io/coolstore-gateway       89m

NAMESPACE       NAME                                                       GATEWAYS                HOSTS                         AGE
istioinaction   virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   89m

kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    93m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    93m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            93m
endpoints/webapp    10.10.0.15:8080   93m

ieast pc clusters deploy/istio-eastwestgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                                       80        -          outbound      EDS            
outbound_.80_._.catalog.istioinaction.svc.cluster.local                       -         -          -             EDS 

# sni 클러스터 확인
ieast pc clusters deploy/istio-eastwestgateway.istio-system | grep catalog | awk '{printf "CLUSTER: %s\n", $1}'
CLUSTER: catalog.istioinaction.svc.cluster.local
CLUSTER: outbound_.80_._.catalog.istioinaction.svc.cluster.local # catalog 서비스용 SNI 클러스터

지금까지의 설정으로 SNI 자동 통과로 클러스터 간 라우팅 활성화 → 제어 플레인이 원격 엔드포인트 감지 후 워크로드 설정 업데이트, mTLS 기반 보안 통신 유지한다.

 

클러스터 간 워크로드 디스커버리 검증하기* VALIDATING CROSS-CLUSTER WORKLOAD DISCOVERY

east-west 게이트웨이 IP 확인으로 webapp 엔보이의 catalog 엔드포인트 노출 검증 → 클러스터 간 트래픽 라우팅 가능 확인한다.

#이를 확인하기 위해 east-cluster 의 east-west 게이트웨이 주소(Service)를 알아보자.
keast -n istio-system get svc istio-eastwestgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
172.18.255.202



# 이제 이 값을 west-cluster 의 워크로드가 클러스터 간 트래픽을 라우팅할 때 사용하는 주소와 비교해보자.
iwest pc endpoints deploy/webapp.istioinaction | grep catalog
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
# west 에 istio-ingressgateway 인입을 위한 접속 정보 확인
kwest get svc -n istio-system istio-ingressgateway
NAME                           TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)                                                                      AGE
service/istio-ingressgateway   LoadBalancer   10.100.0.82   172.18.255.101   15021:30627/TCP,80:30000/TCP,443:31615/TCP,31400:32694/TCP,15443:32016/TCP   119m

EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $EXT_IP
172.18.255.101

#
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog | jq
[
  {
    "id": 1,
    "color": "amber",
...

# 신규 터미널 : 반복 접속
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

 

 

다른 클러스터를 통해서 트래픽이 통신되는것을 알 수 있다.

 

 

12.3.7 클러스터 간 로드 밸런싱 Load-balancing across clusters

#
tree ch12/locality-aware/west
ch12/locality-aware/west
├── simple-backend-deployment.yaml
├── simple-backend-dr.yaml
├── simple-backend-gw.yaml
├── simple-backend-svc.yaml
└── simple-backend-vs.yaml

# west-cluster 에 간단한 백엔드 디플로이먼트/서비스를 배포
cat ch12/locality-aware/west/simple-backend-deployment.yaml
...
        - name: "MESSAGE"
          value: "Hello from WEST"    
...

cat ch12/locality-aware/west/simple-backend-svc.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-deployment.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-svc.yaml
kwest get deploy -n istioinaction simple-backend-west
kwest get svc,ep -n istioinaction simple-backend

# 트래픽을 허용하기 위해 Gateway, 게이트웨이에서 백엔드 워크로드로 트래픽을 라우팅하기 위해 VirtualService 적용
cat ch12/locality-aware/west/simple-backend-gw.yaml
cat ch12/locality-aware/west/simple-backend-vs.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-gw.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-vs.yaml
kwest get gw,vs,dr -n istioinaction
NAME                                                 AGE
gateway.networking.istio.io/coolstore-gateway        7h15m
gateway.networking.istio.io/simple-backend-gateway   3m10s

NAME                                                               GATEWAYS                     HOSTS                                 AGE
virtualservice.networking.istio.io/simple-backend-vs-for-gateway   ["simple-backend-gateway"]   ["simple-backend.istioinaction.io"]   3m10s
virtualservice.networking.istio.io/webapp-virtualservice           ["coolstore-gateway"]        ["webapp.istioinaction.io"]           7h15m

 

 

# west-cluster의 서비스로 요청하고 클러스터 이름을 반환하는지 확인
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body"
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP
{
  "name": "simple-backend-west",
  "uri": "/",
  "type": "HTTP",
  "ip_addresses": [
    "10.10.0.17"
  ],
  "start_time": "2025-05-17T14:48:43.973591",
  "end_time": "2025-05-17T14:48:44.124935",
  "duration": "151.346ms",
  "body": "Hello from WEST",
  "code": 200
}

# 신규 터미널 : 반복 접속
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done



#east에도 배포
tree ch12/locality-aware/east
ch12/locality-aware/east
├── simple-backend-deployment.yaml
└── simple-backend-svc.yaml

# east-cluster 에 서비스를 배포
cat ch12/locality-aware/east/simple-backend-deployment.yaml
...
        - name: "MESSAGE"
          value: "Hello from EAST"
...

cat ch12/locality-aware/east/simple-backend-svc.yaml
keast apply -f ch12/locality-aware/east/simple-backend-deployment.yaml
keast apply -f ch12/locality-aware/east/simple-backend-svc.yaml
keast get deploy -n istioinaction simple-backend-east
keast get svc,ep -n istioinaction simple-backend

 

 

# 10회 요청 후 확인
for i in {1..10}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done
for i in {1..10}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done | sort | uniq -c
   4 "Hello from EAST"
   6 "Hello from WEST"


# 정보 확인
kwest get svc,ep -n istioinaction simple-backend
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/simple-backend   ClusterIP   10.100.0.156   <none>        80/TCP    37m

NAME                       ENDPOINTS         AGE
endpoints/simple-backend   10.10.0.17:8080   37m # k8s service 에 endpoint 에는 west 에 파드 ip만 출력

#
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
>> k8s cluster : west - istio-config listener <<
ADDRESS PORT  MATCH DESTINATION
0.0.0.0 8080  ALL   Route: http.8080
...

>> k8s cluster : west - istio-config route <<
NAME          DOMAINS                             MATCH                  VIRTUAL SERVICE
http.8080     simple-backend.istioinaction.io     /*                     simple-backend-vs-for-gateway.istioinaction
...

>> k8s cluster : west - istio-config cluster <<
SERVICE FQDN                                                 PORT      SUBSET     DIRECTION     TYPE           DESTINATION RULE      
simple-backend.istioinaction.svc.cluster.local               80        -          outbound      EDS     
...

>> k8s cluster : west - istio-config endpoint <<
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.17:8080                                         HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
...

#
iwest proxy-config listener deploy/istio-ingressgateway.istio-system
iwest proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json

iwest proxy-config route deploy/istio-ingressgateway.istio-system
iwest proxy-config route deploy/istio-ingressgateway.istio-system --name http.8080
iwest proxy-config route deploy/istio-ingressgateway.istio-system --name http.8080 -o json

iwest proxy-config cluster deploy/istio-ingressgateway.istio-system
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn simple-backend.istioinaction.svc.cluster.local -o json

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep simple
10.10.0.17:8080                                         HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
[
    {
        "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
        "addedViaApi": true,
        "hostStatuses": [
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.17",
                        "portValue": 8080
                    }
                "weight": 1,
                "locality": {}
            ...
            {
                "address": {
                    "socketAddress": {
                        "address": "172.18.255.202",
                        "portValue": 15443
                    }
                "weight": 1,
                "locality": {}
...

 

 

클러스터 간 지역 인식 라우팅 검증하기 VERIFYING LOCALITY-AWARE ROUTING ACROSS CLUSTERS

#
kwest label node west-control-plane 'topology.kubernetes.io/region=westus'
kwest label node west-control-plane 'topology.kubernetes.io/zone=0'
kwest get node -o yaml
...
      topology.kubernetes.io/region: westus
      topology.kubernetes.io/zone: "0"
...

keast label node east-control-plane 'topology.kubernetes.io/region=eastus'
keast label node east-control-plane 'topology.kubernetes.io/zone=0'
keast get node -o yaml
...
      topology.kubernetes.io/region: eastus
      topology.kubernetes.io/zone: "0"
...

# istio eds 에 정보 반영을 위해 파드 재기동하자 : isiotd 가 노드의 지역성 정보 레이블을 엔드포인트 설정할 때 워크로드로 전파.
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": {}
...

kwest rollout restart -n istio-system deploy/istio-ingressgateway
kwest rollout restart -n istio-system deploy/istio-eastwestgateway
kwest rollout restart -n istioinaction deploy/simple-backend-west
keast rollout restart -n istio-system deploy/istio-ingressgateway
keast rollout restart -n istio-system deploy/istio-eastwestgateway
keast rollout restart -n istioinaction deploy/simple-backend-east

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": {
                    "region": "eastus", # east-cluster 에 있는 워크로드의 위치 정보
                    "zone": "0"
                }
   ...
                "weight": 1,
                "locality": {
                    "region": "westus", # west-cluster 에 있는 워크로드의 위치 정보
                    "zone": "0"
                }
...

 

 

#엔드포인트 상태를 수동적으로 확인하도록, 이상값 감지를 사용하는 DestinationRole 을 적용해보자.
cat ch12/locality-aware/west/simple-backend-dr.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
  namespace: istioinaction
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 10
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 1
      interval: 20s
      baseEjectionTime: 30s

kwest apply -f ch12/locality-aware/west/simple-backend-dr.yaml
kwest get gw,vs,dr -n istioinaction

# 확인
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT                 STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:8080          HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local



# 
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body"

# 동일 클러스터 안에서 라우팅 되는 것을 확인
for i in {1..20}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done | sort | uniq -c
 20 "Hello from WEST"



#
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": { 
                    "region": "westus", # priority 가 없으면(생략 시), 0으로 우선 순위가 가장 높음
                    "zone": "0"
                }
            ...
                "weight": 1,
                "priority": 1, # priority 0 다음으로, 두 번쨰 우선순위
                "locality": {
                    "region": "eastus",
                    "zone": "0"
                }
...

지역별 라우팅으로인해, 한쪽으로만 트래픽이 흐르는것을 알 수 있다.

 

 

 

클러스터 간 장애 극복 확인하기 VERIFYING CROSS-CLUSTER FAILOVER

# 신규 터미널 : 반복 접속 해두기
while true; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
...
"Hello from WEST"
                 2025-05-18 09:31:21

"Hello from EAST" # failover 시점 
                 2025-05-18 09:31:23
...


#
kwest -n istioinaction set env deploy simple-backend-west ERROR_RATE='1'
kwest exec -it -n istioinaction deploy/simple-backend-west -- env | grep ERROR
ERROR_RATE=1

#
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT                 STATUS      OUTLIER CHECK     CLUSTER
10.10.0.21:8080          HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

 

 

인가 정책을 사용해 클러스터 간 접근 제어 확인하기 VERIFYING CROSS-CLUSTER ACCESS CONTROL USING AUTHORIZATION POLICIES

AuthorizationPolicy 적용으로 east-cluster의 서비스 트래픽을 인그레스 게이트웨이 출처만 허용하고 외부 접근을 차단한다.

 

# 적용 전에 west-cluster 서비스를 제거해서 east 에서만 트래픽을 처리하게 하자 >> 이미 위에서 장애 상황이라 안해도 되긴함
kwest delete deploy simple-backend-west -n istioinaction

#
cat ch12/security/allow-only-ingress-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-only-ingress"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: simple-backend
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]

keast apply -f ch12/security/allow-only-ingress-policy.yaml
keast get authorizationpolicy -A



#업데이트가 전파되고 나면, west-cluster 의 워크로드에서 요청을 만들어 정책을 시험해보자. 이를 위해 임시 파드를 실행한다.
kwest run netshoot -n istioinaction --rm -it --image=nicolaka/netshoot -- zsh
-----------------------------------
#
curl -s webapp.istioinaction/api/catalog

# 직접 요청하면 실패!
curl -s simple-backend.istioinaction.svc.cluster.local
RBAC: access denied

# istio-ingressgateway 로 요청하면 성공!
curl -s -H "Host: simple-backend.istioinaction.io" http://istio-ingressgateway.istio-system
...

# kiali 등 확인을 위해 반복 접속 실행
watch curl -s simple-backend.istioinaction.svc.cluster.local
watch 'curl -s -H "Host: simple-backend.istioinaction.io" http://istio-ingressgateway.istio-system'

exit
-----------------------------------

접속하는 환경에따라 성공/실패

AuthorizationPolicy로 인그레스 게이트웨이 출처 트래픽만 허용해 외부 접근 차단 가능함을 확인된다. 이는 상호 인증된 mTLS 기반 메타데이터를 활용해 클러스터 간 세밀한 접근 제어 구현 가능하다는것을 알 수 있다.
이를 통해 다중 클러스터 환경에서도 별도 설정 없이 일관된 보안 정책 적용 및 기능 통합 가능하다는것.

이스티오 컨트롤 플레인은 쿠버네티스 이벤트를 실시간 감지해 서비스 메시 설정을 업데이트한다. 설정 동기화가 지연되면 유령 워크로드가 발생해 삭제된 엔드포인트로 트래픽이 라우팅된다. 이로 인해 요청 실패와 시스템 불안정성이 생기지만, 재시도와 이상값 감지로 단기 지연은 완화된다.

이스티오 데이터 플레인은 궁극적 일관성 설계로 인해 설정 동기화에 잠깐의 지연이 발생할 수 있다.
비정상 워크로드 이벤트 발생 시 컨트롤 플레인(istiod)이 새로운 설정을 생성하지만, 업데이트 지연으로 인해 프록시가 낡은 설정을 유지하면 유령 엔드포인트로 트래픽이 라우팅된다.
이스티오는 **재시도(기본 2회)**와 이상값 감지(실패 엔드포인트 자동 제거) 메커니즘을 통해 단기 지연을 완화하지만, 장기간 지연 시 사용자 영향이 불가피하다.

이스티오 컨트롤 플레인(istiod)은 들어오는 이벤트를 처리할 때 디바운싱스로틀링 기법을 활용한다.
첫째, 이벤트가 발생하면 DiscoveryServer가 이를 수신하고 일정 시간(PILOT_DEBOUNCE_AFTER) 동안 후속 이벤트를 모아 병합한다. 이 과정에서 중복 이벤트는 제거되고, 설정 업데이트 빈도가 줄어든다.
둘째, 디바운싱이 완료되면 병합된 이벤트를 푸시 대기열에 추가한다. 이때 스로틀링(PILOT_PUSH_THROTTLE)으로 동시 처리 가능한 푸시 요청 수를 제한해 CPU 자원 낭비를 방지한다.
셋째, 처리된 이벤트는 엔보이 설정으로 변환되어 워크로드에 전파된다. 디바운싱 시간 단축과 스로틀링 조정을 통해 컨트롤 플레인의 성능을 최적화할 수 있다.

 

 

# 실습 환경 준비
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f ch11/catalog-virtualservice.yaml
kubectl -n istioinaction apply -f ch11/catalog-gateway.yaml

# 확인
kubectl get deploy,gw,vs -n istioinaction

# 반복 설정 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# 컨트롤 플레인 메트릭 확인
kubectl exec -it -n istio-system deploy/istiod -- curl localhost:15014/metrics
# HELP citadel_server_csr_count The number of CSRs received by Citadel server.
# TYPE citadel_server_csr_count counter
citadel_server_csr_count 3
...

이스티오 컨트롤 플레인에서 데이터 플레인 업데이트 지연 시간은 pilot_proxy_convergence_time 메트릭으로 측정되며, 이는 이벤트 발생부터 엔보이 프록시에 설정이 동기화되기까지의 전체 시간을 나타낸다.
pilot_proxy_queue_time은 이벤트가 처리 대기열에서 대기하는 시간을, pilot_xds_push_time은 xDS 푸시 작업 자체에 소요되는 시간을 각각 측정해 지연 원인을 세부적으로 진단할 수 있다.
이 메트릭들은 컨트롤 플레인의 동기화 효율성을 모니터링하고, 설정 전파 지연으로 인한 유령 워크로드 문제를 예방하는 데 활용된다.

pilot_proxy_convergence_time 은 프록시 푸시 요청이 대기열에 안착한 순간 부터 워크로드에 배포 되기까지 전체 과정의 지속 시간을 측정한다.

 

- **Proxy Queue Time** : PromQL - `pilot_proxy_queue_time`
    

    histogram_quantile(0.5, sum(rate(**pilot_proxy_queue_time_bucket**[1m])) by (le))
    histogram_quantile(0.9, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
    histogram_quantile(0.99, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
    histogram_quantile(0.999, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
    
- **XDS Push Time** : PromQL - `pilot_xds_push_time_bucket`
    

    histogram_quantile(0.5, sum(rate(**pilot_xds_push_time_bucket**[1m])) by (le))
    histogram_quantile(0.9, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
    histogram_quantile(0.99, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
    histogram_quantile(0.999, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))

 

이스티오 컨트롤 플레인(istiod)의 포화도는 주로 CPU 사용률로 측정되며, 90% 이상 도달 시 설정 업데이트 지연으로 인해 데이터 플레인 동기화가 느려져 유령 워크로드 문제가 발생할 수 있다.

핵심 메트릭인 container_cpu_usage_seconds_total(쿠버네티스 컨테이너 CPU)와 process_cpu_seconds_total(istiod 프로세스 CPU)로 모니터링하며, 포화 상태 시 이벤트 처리 최적화(디바운싱/스로틀링) 또는 리소스 스케일업이 필요하다.

 

 

이스티오 컨트롤 플레인의 트래픽 부하수신 트래픽(설정 변경 이벤트)과 송신 트래픽(데이터 플레인 푸시)으로 구성된다.
주요 메트릭인 pilot_xds_pushes는 xDS 푸시 빈도를, pilot_inbound_updates는 초당 설정 업데이트 수를 측정해 부하 정도를 파악한다.
이 메트릭들을 모니터링해 컨트롤 플레인 병목 현상을 식별하고, 디바운싱/스로틀링 설정을 조정해 성능을 최적화한다.

kubectl top pod -n istio-system -l app=istiod --containers=true
POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   3m           62Mi            

kubectl top pod -n istioinaction --containers=true
POD                      NAME          CPU(cores)   MEMORY(bytes)   
catalog-6cf4b97d-5jtzt   catalog       0m           20Mi            
catalog-6cf4b97d-5jtzt   istio-proxy   6m           46Mi

#
kubectl resource-capacity -n istioinaction -c -u -a
kubectl resource-capacity -n istioinaction -c -u   
NODE                  POD                      CONTAINER     CPU REQUESTS   CPU LIMITS    CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL                                                                                                                                       
myk8s-control-plane   *                        *             10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     67Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   *             10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     67Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   catalog       0m (0%)        0m (0%)       0m (0%)    0Mi (0%)          0Mi (0%)        21Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   istio-proxy   10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     47Mi (0%)

#
kubectl get pod -n istio-system -l istio.io/rev=default
kubectl resource-capacity -n istio-system -c -u
kubectl resource-capacity -n istio-system -c -u -a -l istio.io/rev=default
kubectl resource-capacity -n istio-system -c -u -l istio.io/rev=default
NODE                  POD                                     CONTAINER     CPU REQUESTS   CPU LIMITS    CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL                                                                                                                                                      
myk8s-control-plane   *                                       *             30m (0%)       4000m (50%)   27m (0%)   180Mi (1%)        2048Mi (17%)    164Mi (1%)
myk8s-control-plane   istio-egressgateway-85df6b84b7-m4699    *             10m (0%)       2000m (25%)   9m (0%)    40Mi (0%)         1024Mi (8%)     49Mi (0%)
myk8s-control-plane   istio-egressgateway-85df6b84b7-m4699    istio-proxy   10m (0%)       2000m (25%)   9m (0%)    40Mi (0%)         1024Mi (8%)     49Mi (0%)
myk8s-control-plane   istio-ingressgateway-6bb8fb6549-k4ln6   *             10m (0%)       2000m (25%)   11m (0%)   40Mi (0%)         1024Mi (8%)     50Mi (0%)
myk8s-control-plane   istio-ingressgateway-6bb8fb6549-k4ln6   istio-proxy   10m (0%)       2000m (25%)   11m (0%)   40Mi (0%)         1024Mi (8%)     50Mi (0%)
myk8s-control-plane   istiod-8d74787f-cqhs2                   *             10m (0%)       0m (0%)       8m (0%)    100Mi (0%)        0Mi (0%)        66Mi (0%)
myk8s-control-plane   istiod-8d74787f-cqhs2                   discovery     10m (0%)       0m (0%)       8m (0%)    100Mi (0%)        0Mi (0%)        66Mi (0%)

sum(irate(pilot_xds_pushes{type="cds"}[1m]))
sum(irate(pilot_xds_pushes{type="eds"}[1m]))
sum(irate(pilot_xds_pushes{type="lds"}[1m]))
sum(irate(pilot_xds_pushes{type="rds"}[1m]))
  • Pilot Pushes: 클러스터, 엔드포인트, 리스너, 라우트별로 초당 설정 푸시 횟수를 보여준다.
  • Pilot Errors: 이스티오 컨트롤 플레인에서 발생한 에러 수를 나타낸다.
  • Proxy Push Time: 프록시로 설정을 푸시하는 데 걸린 시간을 p50, p90, p99, p99.9 백분위수로 보여준다.
  • Conflicts: 설정 충돌 발생 건수를 나타낸다.
  • ADS Monitoring: VirtualService, Service, Connected Endpoint 등 ADS(Envoy와의 연결) 상태를 모니터링한다.
  • Envoy Details: Envoy 관련 세부 이벤트나 동작 횟수를 초당 단위로 보여준다.
  • XDS Active Connections: 활성화된 XDS(Envoy와의 설정 동기화) 연결 수를 나타낸다.
  • XDS Requests Size: XDS 요청/응답의 최대 및 평균 바이트 크기를 시간별로 보여준다.

메트픽 설명

pilot_total_xds_rejects 설정 푸시 거부 횟수
pilot_xds_’cds/lds/rds/cds’_reject pilot_total_xds_rejects 메트릭의 부분집합. 어느 API 푸시가 거부됐는지 수사망을 좁히는 데 유용함
pilot_xds_write_timeout push를 시작할 때 발생한 오류와 타임아웃의 합계
pilot_xds_push_context_errors 엔보이 설정을 생성하는 동안 발생한 이스티오 파일럿 오류 횟수. 주로 이스티오 파일럿의 버그와 관련

 

 

이제 본격적으로 성능 튜닝을 진행해보자.

이스티오 컨트롤 플레인 성능은 클러스터 변경 빈도, 리소스 할당량, 워크로드 수, 설정 크기에 따라 좌우된다.
성능 최적화를 위해 이벤트 배치 처리(디바운싱), Istiod 수평/수직 확장, Sidecar 리소스로 설정 범위 제한 등의 전략을 사용한다.
핵심 메트릭(pilot_proxy_convergence_time, container_cpu_usage_seconds_total)을 모니터링해 병목 현상을 식별하고 조치해야 한다

 

# 실습 환경 준비 : 11.2.1 에서 이미 설정함
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f ch11/catalog-virtualservice.yaml
kubectl -n istioinaction apply -f ch11/catalog-gateway.yaml
kubectl get deploy,gw,vs -n istioinaction

# 반복 설정 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 모니터링
while true; do kubectl top pod -n istio-system -l app=istiod --containers=true ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   7m           65Mi            
2025-05-11 15:04:34

POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   27m          82Mi            
2025-05-11 15:04:36
...


# 더미 워크로드 10개 생성
cat ch11/sleep-dummy-workloads.yaml
...
apiVersion: v1
kind: Service
...
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: sleep
---
apiVersion: apps/v1
kind: Deployment
...
    spec:
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: governmentpaas/curl-ssl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
...

kubectl -n istioinaction apply -f ch11/sleep-dummy-workloads.yaml


# 확인
kubectl get deploy,svc,pod -n istioinaction
...

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
catalog-6cf4b97d-5jtzt.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
istio-egressgateway-85df6b84b7-m4699.istio-system      Kubernetes     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-8d74787f-cqhs2     1.17.8
istio-ingressgateway-6bb8fb6549-k4ln6.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
sleep-6f8cfb8c8f-2nfrm.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --fqdn sleep.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
10.10.0.16:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.17:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.18:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.19:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
...

 

 

 

쪼끔 올라가긴 한다.

## 부하를 더 심하게 줘보자. 200개씩 생성하기

#
cat ch11/resources-600.yaml
cat ch11/resources-600.yaml | wc -l
    9200

# 각각 200개
cat ch11/resources-600.yaml | grep 'kind: Service' | wc -l
cat ch11/resources-600.yaml | grep 'kind: Gateway' | wc -l
cat ch11/resources-600.yaml | grep 'kind: VirtualService' | wc -l
     200

# 배포 : svc 200개, vs 200개, gw 200개
kubectl -n istioinaction apply -f ch11/resources-600.yaml


# 확인
kubectl get deploy,svc,pod -n istioinaction
...

# k8s service 개수 202개
kubectl get svc -n istioinaction --no-headers=true | wc -l 
     202

kubectl get gw,vs -n istioinaction
...

#
docker exec -it myk8s-control-plane istioctl proxy-status
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction

202개씩 생성..

 

이제 테스트는 서비스를 반복적으로 만들어 부하를 생성하고, 프록시에 설정을 업데이트하는 데 걸리는 지연 시간P99 값푸시 개수를 측정한다.

 

#!/bin/bash

main(){
  ## Pass input args for initialization
  init_args "$@"

  SLEEP_POD=$(kubectl -n istioinaction get pod -l app=sleep -o jsonpath={.items..metadata.name} -n istioinaction | cut -d ' ' -f 1)

  PRE_PUSHES=$(kubectl exec -n istio-system deploy/istiod -- curl -s localhost:15014/metrics | grep pilot_xds_pushes | awk '{total += $2} END {print total}') 

  if [[ -z "$PRE_PUSHES" ]]; then
    echo "Failed to query Pilot Pushes from prometheus."
    echo "Have you installed prometheus as shown in chapter 7?"
    exit 1
  fi

  echo "Pre Pushes: $PRE_PUSHES"

  INDEX="0"
  while [[ $INDEX -lt $REPS ]]; do
    SERVICE_NAME="service-`openssl rand -hex 2`-$INDEX" 

    create_random_resource $SERVICE_NAME &
    sleep $DELAY
    INDEX=$[$INDEX+1]
  done

  ## Wait until the last item is distributed
  while [[ "$(curl --max-time .5 -s -o /dev/null -H "Host: $SERVICE_NAME.istioinaction.io" -w ''%{http_code}'' $GATEWAY:30000/items)" != "200" ]]; do 
    # curl --max-time .5 -s -o /dev/null -H "Host: $SERVICE_NAME.istioinaction.io" $GATEWAY/items
    sleep .2
  done

  echo ==============

  sleep 10

  POST_PUSHES=$(kubectl exec -n istio-system deploy/istiod -- curl -s localhost:15014/metrics | grep pilot_xds_pushes | awk '{total += $2} END {print total}')

  echo
  
  LATENCY=$(kubectl -n istioinaction exec -it $SLEEP_POD -c sleep -- curl "$PROM_URL/api/v1/query" --data-urlencode "query=histogram_quantile(0.99, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))" | jq  '.. |."value"? | select(. != null) | .[1]' -r)

  echo "Push count:" `expr $POST_PUSHES - $PRE_PUSHES`
  echo "Latency in the last minute: `printf "%.2f\n" $LATENCY` seconds" 
}

create_random_resource() {
  SERVICE_NAME=$1
  cat <<EOF | kubectl apply -f -
---
kind: Gateway
apiVersion: networking.istio.io/v1alpha3
metadata:
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  servers:
    - hosts:
        - "$SERVICE_NAME.istioinaction.io"
      port:
        name: http
        number: 80
        protocol: HTTP
  selector:
    istio: ingressgateway
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: catalog
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata: 
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  hosts:
  - "$SERVICE_NAME.istioinaction.io"
  gateways:
  - "$SERVICE_NAME"
  http:
  - route:
    - destination:
        host: $SERVICE_NAME.istioinaction.svc.cluster.local
        port:
          number: 80
---
EOF
}

help() {
    cat <<EOF
Poor Man's Performance Test creates Services, Gateways and VirtualServices and measures Latency and Push Count needed to distribute the updates to the data plane.
       --reps         The number of services that will be created. E.g. --reps 20 creates services [0..19]. Default '20'
       --delay        The time to wait prior to proceeding with another repetition. Default '0'
       --gateway      URL of the ingress gateway. Defaults to 'localhost'
       --namespace    Namespace in which to create the resources. Default 'istioinaction'
       --prom-url     Prometheus URL to query metrics. Defaults to 'prom-kube-prometheus-stack-prometheus.prometheus:9090'
EOF
    exit 1
}

init_args() {
  while [[ $# -gt 0 ]]; do
      case ${1} in
          --reps)
              REPS="$2"
              shift
              ;;
          --delay)
              DELAY="$2"
              shift
              ;;
          --gateway)
              GATEWAY="$2"
              shift
              ;;
          --namespace)
              NAMESPACE="$2"
              shift
              ;;
          --prom-url)
              PROM_URL="$2"
              shift
              ;;
          *)
              help
              ;;
      esac
      shift
  done

  [ -z "${REPS}" ] &&  REPS="20"
  [ -z "${DELAY}" ] &&  DELAY=0
  [ -z "${GATEWAY}" ] &&  GATEWAY=localhost
  [ -z "${NAMESPACE}" ] &&  NAMESPACE=istioinaction
  [ -z "${PROM_URL}" ] &&  PROM_URL="prom-kube-prometheus-stack-prometheus.prometheus.svc.cluster.local:9090"
}

main "$@"
# (참고) 호출
curl -H "Host: catalog.istioinaction.io" localhost:30000/items

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

# :30000 포트 정보 추가해둘것!
cat bin/performance-test.sh
...
Poor Man's Performance Test creates Services, Gateways and VirtualServices and measures Latency and Push Count needed to distribute the updates to the data plane.
       --reps         The number of services that will be created. E.g. --reps 20 creates services [0..19]. Default '20'
       --delay        The time to wait prior to proceeding with another repetition. Default '0'
       --gateway      URL of the ingress gateway. Defaults to 'localhost'
       --namespace    Namespace in which to create the resources. Default 'istioinaction'
       --prom-url     Prometheus URL to query metrics. Defaults to 'prom-kube-prometheus-stack-prometheus.prometheus:9090'
...

# 성능 테스트 스크립트 실행!
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
Pre Pushes: 335
...
ateway.networking.istio.io/service-00a9-9 created
service/service-00a9-9 created
virtualservice.networking.istio.io/service-00a9-9 created
==============

Push count: 510 # 변경 사항을 적용하기 위한 푸시 함수
Latency in the last minute: 0.45 seconds # 마지막 1분 동안의 지연 시간


# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

부하가 발생중이다.

 

 

#딜레이없이 실행

# 성능 테스트 스크립트 실행 : 딜레이 없이
./bin/performance-test.sh --reps 10 --prom-url prometheus.istio-system.svc.cluster.local:9090
Push count: 51
Latency in the last minute: 0.47 seconds

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

 

사이드카를 사용해 푸시 횟수 및 설정 크기 줄이기 REDUCING CONFIGURATION SIZE AND NUMBER OF PUSHES USING SIDECARS

 

이스티오는 기본적으로 모든 서비스 프록시에 메시 내 모든 워크로드 정보를 제공해, 불필요하게 설정 크기가 커지는 문제가 발생한다.

#
CATALOG_POD=$(kubectl -n istioinaction get pod -l app=catalog -o jsonpath={.items..metadata.name} | cut -d ' ' -f 1)
kubectl -n istioinaction exec -ti $CATALOG_POD -c catalog -- curl -s localhost:15000/config_dump > /tmp/config_dump
du -sh /tmp/config_dump
1.8M    /tmp/config_dump

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | wc -l
     275

  • 지금 설정 크키가 대략 2MB 인데, 이는 엄청 많은 것이다!
  • 워크로드가 200개인 중간 클러스터만 돼도 엔보이 설정이 400MB로 늘어나며, 이로 인해 연산 성능, 네트워크 대역폭, 메모리가 더 많이 필요하다.
  • 이 설정이 모든 사이드카 프록시에 저장되기 때문이다.

이스티오 Sidecar 리소스는 특정 워크로드의 트래픽 제어를 세밀하게 설정한다.
workloadSelector로 대상 워크로드를 지정하고, egress 필드로 허용할 외부 서비스를 명시적으로 정의한다.
outboundTrafficPolicy를 REGISTRY_ONLY로 설정하면 등록된 서비스만 접근 가능해 보안이 강화된다.
예시 YAML에서는 app: foo 워크로드가 bar.istioinaction과 istio-system 서비스만 접근하도록 제한한다.
이를 통해 불필요한 설정 전파를 막아 CPU/메모리 사용량과 네트워크 부하를 줄인다.

apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
  name: default
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: foo
  egress:
  -hosts:
   - "./bar.istioinaction.svc.cluster.local"
   - "istio-system/*"
  outboundTrafficPolicy:
    mode: REGISTRY_ONLY

 

 

메시 범위 사이드카 설정으로 더 나은 기본값 정의하기 DEFINING BETTER DEFAULTS WITH A MESH-WIDE SIDECAR CONFIGURATION

 

**Sidecar 리소스**를 사용해 트래픽 송신을 istio-system 및 prometheus 네임스페이스의 서비스로만 제한한다.
이를 통해 프록시에 전달되는 엔보이 설정 크기가 현저히 줄어들어 컨트롤 플레인 부하가 감소한다.
설정 최소화로 CPU/메모리 사용량네트워크 대역폭이 절약되며, 서비스 간 명시적 의존성 정의가 강제되어 운영 안정성이 향상된다.

# cat ch11/sidecar-mesh-wide.yaml
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
  name: default # istio-system 네임스페이스의 사이드카는 메시 전체에 적용된다.
  namespace: istio-system # 위 설명 동일.
spec:
  egress:
  - hosts:
    - "istio-system/*" # istio-system 네임스페이스의 워크로드만 트래픽 송신을 할 수 있게 설정한다.
    - "prometheus/*"   # 프로메테우스 네임스페이스도 트래픽 송신을 할 수 있게 설정한다.
  outboundTrafficPolicy:
    mode: REGISTRY_ONLY # 모드는 사이드카에 설정한 서비스로만 트래픽 송신을 허용한다
    
    
    
# 테스트를 위해 샘플 nginx 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF

# catalog 에서 nginx 서비스 접속 확인
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction | grep nginx
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep nginx                                        
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local

kubectl exec -it deploy/catalog -n istioinaction -- curl nginx.default | grep title
<title>Welcome to nginx!</title>


# istio-system, prometheus 네임스페이스만 egress 허용 설정
kubectl -n istio-system apply -f ch11/sidecar-mesh-wide.yaml
kubectl get sidecars -A

# catalog 에서 nginx 서비스 접속 확인
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction | grep nginx
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep nginx                                        
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep nginx
kubectl exec -it deploy/catalog -n istioinaction -- curl nginx.default | grep title

# envoy config 크기 다시 확인!
CATALOG_POD=$(kubectl -n istioinaction get pod -l app=catalog -o jsonpath={.items..metadata.name} | cut -d ' ' -f 1)
kubectl -n istioinaction exec -ti $CATALOG_POD -c catalog -- curl -s localhost:15000/config_dump > /tmp/config_dump
du -sh /tmp/config_dump
520K    /tmp/config_dump

 

사이드카 배포 후...

안나온다.

설정파일이 4분의 1로..

 

즉. 모든 구간에서 최적화가 이뤄졌다.

 

# 성능 테스트 스크립트 실행!
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
...
Push count: 88 # 변경 사항을 적용하기 위한 푸시 함수
Latency in the last minute: 0.10 seconds # 마지막 1분 동안의 지연 시간

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

 

11.3.3 이벤트 무시하기*: 디스커버리 셀렉터로 디스커버리 범위 줄이기 meshConfig.discoverySelectors

이스티오 컨트롤 플레인은 기본적으로 모든 네임스페이스의 파드/서비스 이벤트를 감시해 대규모 클러스터에서 성능 부담이 커진다.
Istio 1.10부터 discovery selector 기능이 도입되어, 라벨 기반으로 감시할 네임스페이스를 선택적으로 지정할 수 있다.
MeshConfig에 discoverySelectors를 설정해 불필요한 워크로드 이벤트를 필터링하면 컨트롤 플레인 부하를 크게 줄일 수 있다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  meshConfig:
    discoverySelectors: # 디스커버리 셀렉터 활성화
      - matchLabels:
          istio-discovery: enabled # 사용할 레이블 지정
          
          
#
cat ch11/istio-discovery-selector.yaml

#
docker exec -it myk8s-control-plane cat /istiobook/ch11/istio-discovery-selector.yaml
docker exec -it myk8s-control-plane istioctl install -y -f /istiobook/ch11/istio-discovery-selector.yaml

#
kubectl get istiooperators.install.istio.io -A -o json
...
                "meshConfig": {
                    "accessLogEncoding": "JSON",
                    "accessLogFile": "/dev/stdout",
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "discoverySelectors": [
                        {
                            "matchExpressions": [
                                {
                                    "key": "istio-exclude",
                                    "operator": "NotIn",
                                    "values": [
                                        "true"
...
#
kubectl create ns new-ns
kubectl label namespace new-ns istio-injection=enabled
kubectl get ns --show-labels

# 테스트를 위해 샘플 nginx 배포
cat << EOF | kubectl apply -n new-ns -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF

# 확인
kubectl get deploy,svc,pod -n new-ns
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local
10.10.0.27:80                                           HEALTHY     OK                outbound|80||nginx.new-ns.svc.cluster.local

# 설정
kubectl label ns new-ns istio-exclude=true
kubectl get ns --show-labels

# 다시 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local

설정이 빠졌다.

 

11.3.4 이벤트 배치 처리 및 푸시 스로틀링 속성 Event-batching and push-throttling properties

이스티오 컨트롤 플레인은 이벤트를 일정 시간(PILOT_DEBOUNCE_AFTER) 동안 모아 병합해 디바운싱을 수행한다.
이를 통해 중복 이벤트를 제거하고 단일 설정 업데이트로 변환해 푸시 횟수를 줄인다.
PILOT_PUSH_THROTTLE로 동시 처리 가능한 푸시 요청 수를 제한해 CPU 자원 낭비를 방지한다.

그러나 푸시를 너무 미루면 데이터 플레인 설정이 오래돼 최신 상태가 아니게 될 수 있는데, 상술한 것처럼 이런 상황 역시 원하는 바가 아니다.

 

- 배치 기간과 푸시 스로틀링을 정의하는 환경 변수 ENVIRONMENT VARIABLES THAT DEFINE THE BATCHING PERIOD AND PUSH THROTTLING

이스티오 컨트롤 플레인 성능 최적화를 위해 PILOT_DEBOUNCE_AFTERPILOT_DEBOUNCE_MAX로 이벤트 배치 처리 기간을 조정한다.

  • PILOT_DEBOUNCE_AFTER(기본 100ms): 이벤트 발생 후 푸시 대기열 추가 전 대기 시간. 이 기간 내 새 이벤트 발생 시 병합 후 재대기.
  • PILOT_DEBOUNCE_MAX(기본 10s): 최대 배치 처리 허용 시간. 이 시간 초과 시 즉시 푸시.

PILOT_ENABLE_EDS_DEBOUNCE(기본 true)로 엔드포인트 업데이트도 배치 처리 여부를 결정한다.

PILOT_PUSH_THROTTLE(기본 100)로 동시 처리 가능한 푸시 요청 수를 제어한다.

  • 컨트롤 플레인 포화 시: 배치 기간 늘리고 스로틀 감소
  • 빠른 업데이트 필요 시: 배치 기간 줄이고 스로틀 증가.

 

컨트롤 플레인에 리소스 추가 할당하기 ALLOCATING ADDITIONAL RESOURCES TO THE CONTROL PLANE

이스티오 컨트롤 플레인 성능 향상을 위해 스케일 아웃 또는 스케일 업을 선택한다.
송신 트래픽 병목 시 워크로드 분산을 위해 스케일 아웃으로 istiod 인스턴스를 추가한다.
수신 트래픽 병목 시 리소스 처리 능력 강화를 위해 스케일 업으로 기존 인스턴스의 CPU/메모리를 증설한다.

#
kubectl get pod -n istio-system -l app=istiod
kubectl describe pod -n istio-system -l app=istiod
...
    Requests:
      cpu:      10m
      memory:   100Mi
...

kubectl resource-capacity -n istio-system -u -l app=istiod
NODE                  CPU REQUESTS   CPU LIMITS   CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL
myk8s-control-plane   10m (0%)       0m (0%)      8m (0%)    100Mi (0%)        0Mi (0%)        90Mi (0%)


# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# demo 프로파일 컨트롤 플레인 배포 시 적용
istioctl install --set profile=demo \
--set values.pilot.resources.requests.cpu=1000m \
--set values.pilot.resources.requests.memory=1Gi \
--set values.pilot.replicaCount=2 -y

exit
-----------------------------------

#
kubectl get pod -n istio-system -l app=istiod
NAME                      READY   STATUS    RESTARTS   AGE
istiod-5485dd8c48-6ngdc   1/1     Running   0          11s
istiod-5485dd8c48-chjsz   1/1     Running   0          11s

kubectl resource-capacity -n istio-system -u -l app=istiod
NODE                  CPU REQUESTS   CPU LIMITS   CPU UTIL    MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL
myk8s-control-plane   2000m (25%)    0m (0%)      119m (1%)   2048Mi (17%)      0Mi (0%)        107Mi (0%)

kubectl describe pod -n istio-system -l app=istiod
...
    Requests:
      cpu:      1
      memory:   1Gi
...

 

 

  • 컨트롤 플레인 성능 최적화의 요점은 다음과 같다.
    • 항상 워크로드에 사이드카 설정을 정의하자. 이것만으로도 대부분의 이점을 얻을 수 있다.
    • 컨트롤 플레인이 포화 상태인데 이미 리소스를 많이 할당한 경우에만 이벤트 배치를 수정하자.
    • 병목이 송신 트래픽일 때 istiod 스케일 아웃하자.
    • 병목이 수신 트래픽일 때 istiod 스케일 업하자.

 

Istiod 디플로이먼트 오토스케일링 Autoscaling istiod deployment

이스티오 컨트롤 플레인(istiod)의 오토스케일링은 **30분 유지되는 gRPC 연결(ADS)**로 인해 효율성이 제한된다.
새로 추가된 istiod 복제본은 기존 연결이 만료될 때까지 트래픽을 받지 못해 HPA가 무의미하게 축소되며, 이로 인해 퍼덕거림(flapping) 현상이 발생한다.
MaxServerConnectionAge를 조정해 연결 수명을 단축하거나 ProxyConfig/DestinationRule을 활용하면 연결 재분산을 유도해 오토스케일링 효율을 개선할 수 있다.

  • 현재로서 오토스케일링을 구성하는 가장 좋은 방법은 점진적인 부하 증가에 맞추는 것이다.
  • 며칠, 몇주, 심지어는 몇 달 단위에 걸쳐서 말이다.
  • 이렇게 하면 성능을 지속적으로 모니터링하고 디폴리어먼트 스케일링 결정을 내려야 하는 인적 자원의 부담을 줄일 수 있다.

 

+ Recent posts