관찰 가능성이란 외부 신호만으로 시스템의 내부 상태를 이해하고 추론할 수 있는 시스템의 특성으로, 안정적 제어와 문제 대응에 필수적이다. 이스티오는 네트워크 계층에서 메트릭을 수집해 이러한 관찰 가능성을 보조하지만, 이스티오만으로 완전한 관찰 가능성이 보장되는 것은 아니다.

 
 
 

관찰 가능성은 다양한 계층의 계측과 데이터 결합을 포함하는 시스템의 특성이며, 이스티오는 그중 애플리케이션 수준 네트워크 계측을 보조한다.

 

관찰 가능성은 시스템의 내부 상태와 문제의 근본 원인까지 파악하기 위해 다양한 데이터(메트릭, 로그, 트레이스 등)를 폭넓게 수집·분석하는 개념이다.
반면 모니터링은 미리 정의된 임계값이나 상태를 중심으로 주요 지표를 감시하고, 이상이 감지되면 즉각적으로 알림을 제공한다.
즉, 모니터링은 관찰 가능성의 일부로, 관찰 가능성은 예측 불가능한 문제까지 대응할 수 있도록 더 많은 데이터와 유연한 분석을 지향한다.

이스티오는 엔보이 프록시를 통해 서비스 간 모든 네트워크 트래픽에서 메트릭, 로그, 트레이스를 자동으로 수집하여, 별도의 코드 수정 없이 서비스 동작을 상세히 관찰할 수 있게 해준다.
또한 프로메테우스, 그라파나, 키알리 등과 연동해 서비스 상태와 트래픽 흐름을 시각화하고, 분산 트레이싱으로 요청의 전체 경로까지 추적할 수 있다.

 
이스티오는 엔보이 프록시를 통해 HTTP/TCP 요청 수, 지연 시간, 오류율 등의 데이터 플레인 메트릭을 자동 수집하며, 프로메테우스와 그라파나 연동으로 실시간 모니터링이 가능하다.
 
 
 
실습 시작
#
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
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
  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.1.0/24
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






# 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
istioctl version --remote=false

# default 프로파일 컨트롤 플레인 배포
istioctl install --set profile=default -y

# 빠져나오기
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 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "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


# 내부 접속 테스트용 netshoot 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot
spec:
  containers:
  - name: netshoot
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF





# istioinaction 네임스페이스 초기화
kubectl delete -n istioinaction deploy,svc,gw,vs,dr,envoyfilter --all

# catalog 앱 기동
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction

# webapp 앱 기동
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction

# gateway, virtualservice 설정
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction

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

# 호출테스트
curl -s http://webapp.istioinaction.io:30000
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
...

 

  • 다음 히스토그램은 각 프록시가 인바운드 및 아웃바운드 호출에 유지하는 표준 이스티오 메트릭이다. - Docs
    • istio_requests_total : This is a COUNTER incremented for every request handled by an Istio proxy.
    • istio_request_bytes : This is a DISTRIBUTION which measures HTTP request body sizes
    • istio_response_bytes : This is a DISTRIBUTION which measures HTTP response body sizes.
    • istio_request_duration_milliseconds : This is a DISTRIBUTION which measures the duration of requests.
    →  A COUNTER is a strictly increasing integer , A DISTRIBUTION maps ranges of values to frequency. - Docs
  • ⇒ The telemetry component is implemented as a Proxy-wasm plugin.

메트릭이 수집되는것을 알 수 있다.

 

 

이밖에 더 많은 메트릭을 수집하려면 다음과 같이 진행한다.

###방법 1 (IstioOperator 명세) : 메시 전체에 적용 - DocsapiVersion: install.istio.io/v1alpha1

kind: IstioOperator
metadata:
  name: control-plane
spec:
  profile: demo
  meshConfig:
    defaultConfig: # Defines the default proxy configuration for all services
      proxyStatsMatcher: # Customizes the reported metrics
        inclusionPrefixes: # Metrics matching the prefix will be reported alongside the default ones.
        - "cluster.outbound|80||catalog.istioinaction"
        
        
        
        
        
###방법 2 (해당 워크로드 별 명세) : 워크로드 단위로 설정(애노테이션으로 포함할 메트릭 지정) ← 권장 방법


# cat ch7/webapp-deployment-stats-inclusion.yaml
...
  template:
    metadata:
      annotations:
        proxy.istio.io/config: |-
          proxyStatsMatcher:
            inclusionPrefixes:
            - "cluster.outbound|80||catalog.istioinaction"
      labels:
        app: webapp
        
        
        
# 호출테스트
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq

# 적용 전 확인
kubectl exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl localhost:15000/stats | grep catalog

# 적용
cat ch7/webapp-deployment-stats-inclusion.yaml
kubectl apply -n istioinaction -f ch7/webapp-deployment-stats-inclusion.yaml

# 호출테스트
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq

# 적용 후 확인 : catalog.istioinaction 에 대한 metrics 추가
# upstream 클러스터로 향햐는 커넥션 혹은 요청 시 circuit breaking 작동 확인
kubectl exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl localhost:15000/stats | grep catalog
...
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_cx_active: 2
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_cx_close_notify: 0
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_cx_connect_attempts_exceeded: 0
...
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_rq_200: 2
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_rq_2xx: 2
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.upstream_rq_active: 0
...

 

 

  • 엔보이는 트래픽을 식별 할 때 출처가 내부인지 외부인지를 구분한다.
  • 내부는 보통 메시 내부 트래픽이라 인식하는 것을 말하고, 외부는 메시 외부에서 시작한 트래픽(인그레스 게이트웨이로 들어온 트래픽)을 말한다.
# cluster_name.internal.*. 메트릭을 보면 메시 내부에서 시작해 성공한 요청 개수를 확인 할 수 있다.
kubectl exec -it deploy/**webapp** -c istio-proxy -n istioinaction -- **curl localhost:15000/stats | grep catalog | grep internal
...**
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.**internal.upstream_rq_200**: 2
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.internal.upstream_rq_2xx: 2
cluster.outbound|80||catalog.istioinaction.svc.cluster.local.internal.upstream_rq_completed: 2
**...**

 

 

 

컨트롤 플레인 istiod는 xDS 설정 동기화 횟수, 인증서 발급/갱신 상태, 구성 오류 등의 메트릭을 제공하여 메시 운영 상태를 종합적으로 모니터링할 수 있게 한다.

# istiod 파드에 tcp LISTEN port 정보 확인
kubectl exec -it deploy/istiod -n istio-system -- netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.1:9876          0.0.0.0:*               LISTEN     
tcp6       0      0 :::8080                 :::*                    LISTEN     
tcp6       0      0 :::15017                :::*                    LISTEN     
tcp6       0      0 :::15010                :::*                    LISTEN     
tcp6       0      0 :::15012                :::*                    LISTEN     
tcp6       0      0 :::15014                :::*                    LISTEN

# 다음 명령어를 실행해 컨트롤 플레인 메트릭을 보자
## CSR : Certificate Signing Request 인증서 발급 요청
## Citadel : Istio 보안 컴포넌트
kubectl exec -it -n istio-system deploy/istiod -n istio-system -- curl localhost:15014/metrics
kubectl exec -it -n istio-system deploy/istiod -n istio-system -- curl localhost:15014/metrics | grep citadel
# HELP citadel_server_csr_count The number of CSRs received by Citadel server.
# TYPE citadel_server_csr_count counter
citadel_server_csr_count 4
# HELP citadel_server_root_cert_expiry_timestamp The unix timestamp, in seconds, when Citadel root cert will expire. A negative time indicates the cert is expired.
# TYPE citadel_server_root_cert_expiry_timestamp gauge
citadel_server_root_cert_expiry_timestamp 2.060988622e+09
# HELP citadel_server_success_cert_issuance_count The number of certificates issuances that have succeeded.
# TYPE citadel_server_success_cert_issuance_count counter
citadel_server_success_cert_issuance_count 4

# 컨트롤 플레인 버전에 대한 런타임 정보 확인 : istio 버전정보
kubectl exec -it -n istio-system deploy/istiod -n istio-system -- curl localhost:15014/metrics | grep istio_build
istio_build{component="pilot",tag="1.17.8"} 1



#
kubectl exec -it -n istio-system deploy/istiod -n istio-system -- curl localhost:15014/metrics | grep convergence
# HELP pilot_proxy_convergence_time Delay in seconds between config change and a proxy receiving all required configuration.
# TYPE pilot_proxy_convergence_time histogram
pilot_proxy_convergence_time_bucket{le="0.1"} 24 # 0.1초 내에 24개의 업데이트가 프록시에 배포됐다
pilot_proxy_convergence_time_bucket{le="0.5"} 25 # 요청 하나는 좀 더 걸려서 0.1~0.5초 범위에 속했다
pilot_proxy_convergence_time_bucket{le="1"} 25
pilot_proxy_convergence_time_bucket{le="3"} 25
pilot_proxy_convergence_time_bucket{le="5"} 25
pilot_proxy_convergence_time_bucket{le="10"} 25
pilot_proxy_convergence_time_bucket{le="20"} 25
pilot_proxy_convergence_time_bucket{le="30"} 25
pilot_proxy_convergence_time_bucket{le="+Inf"} 25
pilot_proxy_convergence_time_sum 0.020836250000000004
pilot_proxy_convergence_time_count 25

이스티오의 데이터·컨트롤 플레인 메트릭은 시스템 운영 세부사항을 노출해 관찰 가능성 구축에 핵심적인 역할을 한다.
수동 접근 대신 프로메테우스 등 시계열 DB와 시각화 도구를 활용해 메트릭 수집·분석을 자동화해야 실용적인 모니터링이 가능하다.

 

 

Scraping Istio metrics with Prometheus (실습)

이스티오 메트릭을 프로메테우스로 수집하려면 풀(pull) 기반 모델을 사용한다. 프로메테우스가 이스티오 프록시의 메트릭 엔드포인트(예: istio_requests_total)를 주기적으로 스크랩해서 HTTP 요청 수, 오류율 같은 데이터를 자동으로 가져간다. 쿠버네티스 환경에서는 서비스 디스커버리 기능으로 파드 메트릭 엔드포인트를 자동 탐지하기 때문에 설정이 간편하고, 고가용성을 위해 여러 프로메테우스 서버를 병렬로 운영할 수  있다.

15020 포트는 Envoy, 애플리케이션, Istio 에이전트의 메트릭을 통합해 /stats/prometheus 엔드포인트로 제공하고, 헬스체크 및 디버깅 기능까지 포함하는 등 여러 역할을 수행해서 실질적으로 메인 포트라 할 수 있다.
반면 15090은 Envoy 프록시의 원본 메트릭만 노출하는 보조 포트에 가깝다.

 

 

 

kube-prometheus-stack은 Helm으로 프로메테우스 오퍼레이터·그라파나·Alertmanager 등을 통합 배포해 쿠버네티스 클러스터 모니터링을 자동화하는 솔루션이다.

#
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

#
cat ch7/prom-values.yaml
open ch7/prom-values.yaml

cat << EOF > prom-values-2.yaml
prometheusOperator:
  tls:
    enabled: false
  admissionWebhooks:
    patch:
      enabled: false

prometheus:
  service:
    type: NodePort
    nodePort: 30001
    
grafana:
  service:
    type: NodePort
    nodePort: 30002
EOF

# helm 설치
kubectl create ns prometheus
helm install prom prometheus-community/kube-prometheus-stack --version 13.13.1 \
-n prometheus -f ch7/prom-values.yaml -f prom-values-2.yaml

# 확인
helm list -n prometheus
kubectl get-all -n prometheus # krew plugin
kubectl get sts,deploy,pod,svc,ep,cm,secret -n prometheus
kubectl get crd | grep monitoring
kubectl get prometheus,servicemonitors -n prometheus

# Prometheus 접속 : Service Discovery, Target 확인
open http://127.0.0.1:30001

# 
kubectl get servicemonitors -n prometheus
NAME                                                 AGE
prom-kube-prometheus-stack-grafana                   12m
prom-kube-prometheus-stack-kube-controller-manager   12m
prom-kube-prometheus-stack-operator                  12m
prom-kube-prometheus-stack-prometheus                12m


# (참고) 프로메테우스 버전 확인
kubectl exec -it sts/prometheus-prom-kube-prometheus-stack-prometheus -n prometheus -c prometheus -- prometheus --version
prometheus, version 2.24.0 (branch: HEAD, revision: 02e92236a8bad3503ff5eec3e04ac205a3b8e4fe)
...

# Grafana 접속 : admin / prom-operator
open http://127.0.0.1:30002



##(참고) kube-controller-manager 메트릭 수집 설정
# https://stackoverflow.com/questions/65901186/kube-prometheus-stack-issue-scraping-metrics
docker exec -it myk8s-control-plane curl -s https://172.18.0.2:10257/metrics -k
kubectl edit svc -n kube-system prom-kube-prometheus-stack-kube-controller-manager # 10252 -> 10257로 포트 변경
...
  ports:
  - name: http-metrics
    port: 10257
    protocol: TCP
    targetPort: 10257
...
kubectl edit servicemonitors -n prometheus prom-kube-prometheus-stack-kube-controller-manager
...
spec:
  endpoints:
  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
    port: http-metrics
    scheme: https
    tlsConfig:
      caFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      insecureSkipVerify: true
  jobLabel: jobLabel
  namespaceSelector:
    matchNames:
    - kube-system
  selector:
    matchLabels:
      app: kube-prometheus-stack-kube-controller-manager
      release: prom

 

 

 

 

 

 

이스티오 컨트롤 플레인과 워크로드를 긁어가도록 프로메테우스 오퍼레이터 설정하기

프로메테우스가 이스티오에서 메트릭을 수집하도록 설정하기 위해 프로메테우스 오퍼레이터의 커스텀 리소스 ServiceMonitorPodMonitor 를 사용할 것이다.

 

##이스티오 컨트롤 플레인 구성 요소를 긁어오도록 ServiceMonitor 리소스를 설정하는 방법은 다음과 같다.

# cat ch7/service-monitor-cp.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: istio-component-monitor
  namespace: prometheus
  labels:
    monitoring: istio-components
    release: prom
spec:
  jobLabel: istio
  targetLabels: [app]
  selector:
    matchExpressions:
    - {key: istio, operator: In, values: [pilot]}
  namespaceSelector:
    any: true
  endpoints:
  - port: http-monitoring # 15014
    interval: 15s
    
    
    
    
    #  istiod의 Service Spec ServiceMonitor 에서 selector 에 istio=pilot 매칭 확인
kubectl describe svc istiod -n istio-system
Name:                     istiod
Labels:                   app=istiod
                          ...
                          istio=pilot
...
Port:                     http-monitoring  15014/TCP
TargetPort:               15014/TCP
Endpoints:                10.10.0.7:15014
...

#
kubectl get pod -n istio-system -l istio=pilot              
NAME                      READY   STATUS    RESTARTS   AGE
istiod-7df6ffc78d-826zx   1/1     Running   0          52m

# ServiceMonitor 적용
kubectl apply -f ch7/service-monitor-cp.yaml -n prometheus

# 확인
kubectl get servicemonitor -n prometheus
NAME                                                 AGE
istio-component-monitor                              9s
prom-kube-prometheus-stack-grafana                   43m
prom-kube-prometheus-stack-kube-controller-manager   43m
prom-kube-prometheus-stack-operator                  43m
prom-kube-prometheus-stack-prometheus                43m

# 
kubectl get svc,ep istiod -n istio-system
kubectl exec -it netshoot -- curl -s istiod.istio-system:15014/metrics
kubectl exec -it netshoot -- curl -s istiod.istio-system:15014/metrics | grep pilot_xds
kubectl exec -it netshoot -- curl -s istiod.istio-system:15014/metrics | grep citadel

 

 

 

데이터 플레인 수집 활성화 : PodMonitor 리소스를 사용해 istio-proxy 컨테이너를 포함하는 모든 파드에서 메트릭을 수집하자

이 이미지는 이스티오 사이드카(Envoy 프록시와 파일럿 에이전트)가 파드 내부에서 어떻게 동작하며, 주요 포트들이 어떤 역할을 하는지 알려준다.

  • 15020 포트: 메인 엔드포인트로, Envoy, 파일럿 에이전트, (설정 시) 애플리케이션 메트릭을 집계해 Prometheus가 스크랩할 수 있게 노출합니다. 헬스체크와 디버깅 정보도 제공
  • 15090 포트: Envoy 프록시가 자체적으로 생성하는 원본 메트릭(xDS, 커넥션, HTTP 통계 등)을 노출
  • 15000 포트: Envoy 관리 인터페이스를 노출
  • 15004, 15053 포트: 파일럿 에이전트의 디버그, DNS 프록시 등의 내부 통신에 사용
  • 15001, 15006, 15021 포트: 각각 아웃바운드 트래픽, 인바운드 트래픽, 쿠버네티스 레디니스 프로브에 사용
#
kubectl describe pod -n istioinaction
...
Annotations:      ...
                  prometheus.io/path: /stats/prometheus
                  prometheus.io/port: 15020
                  prometheus.io/scrape: true
                  
# 
cat ch7/pod-monitor-dp.yaml
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: envoy-stats-monitor
  namespace: prometheus
  labels:
    monitoring: istio-proxies
    release: prom
spec:
  selector:
    matchExpressions:
    - {key: istio-prometheus-ignore, operator: DoesNotExist}
  namespaceSelector:
    any: true
  jobLabel: envoy-stats
  podMetricsEndpoints:
  - path: /stats/prometheus
    interval: 15s
    relabelings:
    - action: keep
      sourceLabels: [__meta_kubernetes_pod_container_name]
      regex: "istio-proxy"
    - action: keep
      sourceLabels: [__meta_kubernetes_pod_annotationpresent_prometheus_io_scrape]
    - sourceLabels: [
    __address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
      action: replace
      regex: ([^:]+)(?::\d+)?;(\d+)
      replacement: $1:$2
      targetLabel: __address__
    - action: labeldrop
      regex: "__meta_kubernetes_pod_label_(.+)"
    - sourceLabels: [__meta_kubernetes_namespace]
      action: replace
      targetLabel: namespace
    - sourceLabels: [__meta_kubernetes_pod_name]
      action: replace
      targetLabel: pod_name

# PodMonitor 설정 적용
kubectl apply -f ch7/pod-monitor-dp.yaml -n prometheus

#
kubectl get podmonitor -n prometheus
NAME                  AGE
envoy-stats-monitor   6s

# metric 확인을 위해서 호출테스트
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/ ; sleep 0.5; done
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done

# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# 
WEBAPP=$(kubectl get pod -n istioinaction -l app=webapp -o jsonpath='{.items[0].status.podIP}')
kubectl exec -it netshoot -- curl -s $WEBAPP:15020/stats/prometheus
...
kubectl exec -it netshoot -- curl -s $WEBAPP:15090/stats/prometheus
...

 

 

이스티오의 표준 메트릭(예: istio_requests_total)은 Telemetry API를 통해 커스터마이징 가능하다.

  • 디멘션 추가: request_host, destination_port 같은 속성을 메트릭에 추가해 세부 분석 가능.
  • 태그 제거: grpc_response_status 같은 불필요한 태그 삭제 가능.
  • 새 메트릭 생성: COUNTER, DISTRIBUTION 타입의 사용자 정의 메트릭 정의 가능.
  • 버전별 차이: 1.18+는 Telemetry API 권장, 이전 버전은 EnvoyFilter 설정 필요.
# 메트릭 정보 수정 시 모든 버전의 envoyfilter 에 반영(업데이트)되는지 확인해보자.
kubectl get envoyfilter -n istio-system
NAME                    AGE
stats-filter-1.13       13h # 스터디 실습에서 사용
stats-filter-1.14       13h
stats-filter-1.15       13h
stats-filter-1.16       13h
stats-filter-1.17       13h # 현재 실습 istiod 버전
tcp-stats-filter-1.13   13h
tcp-stats-filter-1.14   13h
tcp-stats-filter-1.15   13h
tcp-stats-filter-1.16   13h
tcp-stats-filter-1.17   13h

#
kubectl get envoyfilter stats-filter-1.13 -n istio-system -o yaml
...
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.13.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats # 필터 이름
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config: # 필터 설정
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "debug": "false",
                    "stat_prefix": "istio"
                  }
              root_id: stats_outbound
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_outbound
...


###ADDING DIMENSIONS TO EXISTING METRICS 기존 메트릭에 디멘션 추가하기

#
cat ch7/metrics/istio-operator-new-dimensions.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              metrics:
              - name: requests_total
                dimensions: # 추가한 새 디멘션
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove: # 제거한 태그 목록
                - request_protocol
            outboundSidecar:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove:
                - request_protocol
            gateway:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove:
                - request_protocol

# 기존 설정 확인
kubectl get istiooperator installed-state -n istio-system -o yaml | grep -E "prometheus:|telemetry:" -A2
    telemetry:
      enabled: true
      v2:
--
        prometheus:
          enabled: true
          wasmEnabled: false

# 메트릭 확인 : request_protocol 디멘션이 메트릭에 있는지 먼저 확인 >> 아래 설정 적용 후에 확인 시 해당 디멘션 없이 출력됨.
# 프로메테우스 UI 에서도 확인 : istio_requests_total - Link
kubectl -n istioinaction exec -it deploy/webapp -c istio-proxy \
-- curl localhost:15000/stats/prometheus | grep istio_requests_total
...

# 설정 적용
docker exec -it myk8s-control-plane bash
----------------------------------------
# 파일 작성
cat << EOF > istio-operator-new-dimensions.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove:
                - request_protocol
            outboundSidecar:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove:
                - request_protocol
            gateway:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_proxy_version: upstream_peer.istio_version
                  source_mesh_id: node.metadata['MESH_ID']
                tags_to_remove:
                - request_protocol
EOF

istioctl verify-install -f istio-operator-new-dimensions.yaml # 리소스별로 적용결과를 출력
istioctl install -f istio-operator-new-dimensions.yaml -y

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

# 변경 설정 확인
kubectl get istiooperator -n istio-system installed-state -o yaml | grep -E "prometheus:" -A9
        prometheus:
          configOverride:
            gateway:
              metrics:
              - dimensions:
                  source_mesh_id: node.metadata['MESH_ID']
                  upstream_proxy_version: upstream_peer.istio_version
                name: requests_total
                tags_to_remove:
                - request_protocol
                
# envoyfilter "stats-filter-{stat-postfix}"도 업데이트 확인
kubectl get envoyfilter stats-filter-1.13 -n istio-system -o yaml
...
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.13.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {"metrics":[{"dimensions":{"source_mesh_id":"node.metadata['MESH_ID']","upstream_proxy_version":"upstream_peer.istio_version"},"name":"requests_total","tags_to_remove":["request_protocol"]}]}
              root_id: stats_outbound
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_outbound
...

# 나머지 버전에서도 업데이트 반영되었는지 확인해보자.
kubectl get envoyfilter stats-filter-1.14 -n istio-system -o yaml | grep MESH_ID
kubectl get envoyfilter stats-filter-1.15 -n istio-system -o yaml | grep MESH_ID
kubectl get envoyfilter stats-filter-1.16 -n istio-system -o yaml | grep MESH_ID
kubectl get envoyfilter stats-filter-1.17 -n istio-system -o yaml | grep MESH_ID
...

 

 

 

 

7.4.2 Creating new metrics 새로운 메트릭 만들기

# cat ch7/metrics/istio-operator-new-metric.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
            outboundSidecar:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
            gateway:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
                
                
                
                
# 설정 적용
docker exec -it myk8s-control-plane bash
----------------------------------------
cat << EOF > istio-operator-new-metric.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
            outboundSidecar:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
            gateway:
              definitions:
              - name: get_calls
                type: COUNTER
                value: "(request.method.startsWith('GET') ? 1 : 0)"
EOF

istioctl verify-install -f istio-operator-new-metric.yaml # 리소스별로 적용결과를 출력
istioctl install -f istio-operator-new-metric.yaml -y

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

# 확인
kubectl get istiooperator -n istio-system installed-state -o yaml  | grep -A2 get_calls$
              - name: get_calls
                type: COUNTER
                value: '(request.method.startsWith(''GET'') ? 1 : 0)''
...

kubectl get envoyfilter -n istio-system stats-filter-1.13 -o yaml | grep get_calls
...
{"definitions":[{"name":"get_calls","type":"COUNTER","value":"(request.method.startsWith('GET') ? 1 : 0)"}]}
...                





# webapp 디플로이먼트의 파드 사양에 애너테이션을 추가한다
cat ch7/metrics/webapp-deployment-new-metric.yaml
...
  template:
    metadata:
      annotations:
        proxy.istio.io/config: |-
          proxyStatsMatcher:
            inclusionPrefixes:
            - "istio_get_calls"
      labels:
        app: webapp
...

#
kubectl -n istioinaction apply -f ch7/metrics/webapp-deployment-new-metric.yaml




# metric 확인을 위해서 호출테스트
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 메트릭 확인
kubectl -n istioinaction exec -it deploy/webapp -c istio-proxy -- curl localhost:15000/stats/prometheus | grep istio_get_calls
# TYPE istio_get_calls counter
istio_get_calls{} 20

 

 

 

 

7.4.3 Grouping calls with new attributes 새 속성으로 호출 그룹화하기

cat ch7/metrics/attribute-gen.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: attribute-gen-example
  namespace: istioinaction
spec:
  configPatches:
  ## Sidecar Outbound 
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: istio.stats
      proxy:
        proxyVersion: ^1\.13.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.attributegen
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                    "attributes": [
                      {
                        "output_attribute": "istio_operationId", # 속성 이름
                        "match": [
                         {
                           "value": "getitems", # 속성 값
                           "condition": "request.url_path == '/items' && request.method == 'GET'"
                         },
                         {
                           "value": "createitem",
                           "condition": "request.url_path == '/items' && request.method == 'POST'"
                         },     
                         {
                           "value": "deleteitem",
                           "condition": "request.url_path == '/items' && request.method == 'DELETE'"
                         }                                             
                       ]
                      }
                    ]
                  }
              vm_config:
                code:
                  local:
                    inline_string: envoy.wasm.attributegen
                runtime: envoy.wasm.runtime.null
                
                
                
                
                
#아래 attribute-gen.yaml 을 적용하기 전에 proxyVersion: ^1\.16.* 을 설치된 istio 버전에 맞게 1.16 혹은 1.17 로 수정.
docker exec -it myk8s-control-plane istioctl version
client version: 1.17.8
control plane version: 1.17.8
data plane version: 1.17.8 (4 proxies)

#
vi ch7/metrics/attribute-gen.yaml # 혹은 open ch7/metrics/attribute-gen.yaml 후 수정
...
      proxy:
        proxyVersion: ^1\.17.* # 수정
...

# 버전을 수정 후 envoyfilter 를 배포합니다. envoyfilter를 배포한 네임스페이스의 istio-proxy들에 적용 됩니다
kubectl apply -f ch7/metrics/attribute-gen.yaml -n istioinaction

# 확인
kubectl get envoyfilter -n istioinaction -o yaml | kubectl neat
kubectl get envoyfilter -n istioinaction
NAME                    AGE
attribute-gen-example   12s
                
                
                
                
# stats 플러그인 설정을 업데이트 하자.                
# 설정 적용
docker exec -it myk8s-control-plane bash
----------------------------------------
cat << EOF > istio-operator-new-attribute.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            outboundSidecar:
              metrics:
              - name: requests_total
                dimensions:
                  upstream_operation: istio_operationId # 새 디멘션
EOF
istioctl verify-install -f istio-operator-new-attribute.yaml # 리소스별로 적용결과를 출력
istioctl install -f istio-operator-new-attribute.yaml -y

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

# 확인 : outboundSidecar 에만 적용됨
kubectl get istiooperator -n istio-system installed-state -o yaml | grep -B2 -A1 istio_operationId$
              metrics:
              - dimensions:
                  upstream_operation: istio_operationId
                name: requests_total

#
kubectl get envoyfilter -n istio-system stats-filter-1.17 -o yaml | kubectl neat
...
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
      proxy:
        proxyVersion: ^1\.17.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/stats.PluginConfig
          value:
            metrics:
            - dimensions:
                upstream_operation: istio_operationId
              name: requests_total
...

kubectl get envoyfilter -n istio-system stats-filter-1.16 -o yaml | grep istio_operationId -B15 -A5
kubectl get envoyfilter -n istio-system stats-filter-1.15 -o yaml | grep istio_operationId -B15 -A5
kubectl get envoyfilter -n istio-system stats-filter-1.14 -o yaml | grep istio_operationId -B15 -A5
kubectl get envoyfilter -n istio-system stats-filter-1.13 -o yaml | grep istio_operationId -B15 -A5
...

 

 

 

 

 

이스티오는 서비스 간 네트워크 메트릭(지연 시간, 처리량, 오류율 등)을 코드 수정 없이 자동 수집해 마이크로서비스 아키텍처의 통합 관찰 가능성을 제공한다.

 

 

내용이 너무 길어서 오늘은 2개로 나눈다.

실습환경 구성은 다음과 같다.

#
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.1.0/24
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



# 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
istioctl version --remote=false

# default 프로파일 컨트롤 플레인 배포
istioctl install --set profile=default -y

# 설치 확인 : 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 apply -f istio-$ISTIOV/samples/addons
kubectl get pod -n istio-system

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

# istio-proxy 로그 출력 설정 : configmap 에 mesh 바로 아래에 accessLogFile 부분 추가
KUBE_EDITOR="nano"  kubectl edit cm -n istio-system istio
...
  mesh: |-
    accessLogFile: /dev/stdout
...

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

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "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 접속 1 : NodePort
open http://127.0.0.1:30003

# (옵션) Kiali 접속 2 : Port forward
kubectl port-forward deployment/kiali -n istio-system 20001:20001 &
open http://127.0.0.1:20001

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


# 접속 테스트용 netshoot 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot
spec:
  containers:
  - name: netshoot
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

 

 

6.1 Building resilience into the application

마이크로서비스는 분산 환경에서 불가피한 장애에 대비해, 재시도, 타임아웃, 서킷 브레이커 등 복원력 패턴을 일관되게 적용하여 서비스 연쇄 장애를 방지하고 전체 시스템의 안정성과 가용성을 높여야 합니다.

서비스 메시 기술이 등장하기 전에는, 개발자들이 각 애플리케이션 코드에 직접 복원력 패턴(재시도, 타임아웃, 서킷 브레이커 등)을 구현해야 했고, 트위터 Finagle, 넷플릭스 Hystrix·Ribbon 같은 오픈소스 프레임워크가 등장했지만, 언어와 프레임워크마다 구현 방식이 달라 유지보수와 일관성 확보에 어려움이 있었습니다.

이스티오의 서비스 프록시는 각 애플리케이션 옆에 사이드카로 배포되어 모든 네트워크 트래픽을 가로채고, 애플리케이션 코드 수정 없이 재시도, 타임아웃, 서킷 브레이킹, 클라이언트 측 로드 밸런싱 등 다양한 복원력 패턴을 프록시 레벨에서 일관되게 적용할 수 있게 해줍니다.

이스티오는 애플리케이션 인스턴스 옆에 배치된 사이드카 프록시를 통해 복원력 패턴(재시도/서킷브레이커 등)을 중앙 게이트웨이 없이 분산 처리하며, 기존의 중앙 집중식 하드웨어/미들웨어 방식보다 동적 클라우드 환경에 적합한 유연성과 확장성을 제공합니다.

 

6.2 Client-side load balancing 클라이언트 측 로드 밸런싱 (실습)

Server-side 로드 밸런싱은 중앙 집중식 장치가 트래픽을 분배하는 방식이고, client-side 로드 밸런싱은 클라이언트(또는 프록시)가 엔드포인트 정보를 직접 받아 분산 처리하는 방식으로, 분산성과 유연성은 client-side가 높지만, 관리와 보안은 server-side가 더 용이합니다.

 
 
실습시작
kubectl delete gw,vs,deploy,svc,destinationrule --all -n istioinaction

# (옵션) kiali 에서 simple-backend-1,2 버전 확인을 위해서 labels 설정 : ch6/simple-backend.yaml
open ch6/simple-backend.yaml
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: simple-backend
    version: v1
  name: simple-backend-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-backend
  template:
    metadata:
      labels:
        app: simple-backend
        version: v1
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: simple-backend
    version: v2
  name: simple-backend-2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: simple-backend
  template:
    metadata:
      labels:
        app: simple-backend
        version: v2
...

# 예제 서비스 2개 배포
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
kubectl apply -f ch6/simple-web.yaml -n istioinaction

# 확인
kubectl get deploy,pod,svc,ep -n istioinaction -o wide
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS       IMAGES                                 SELECTOR
deployment.apps/simple-backend-1   1/1     1            1           105m   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
deployment.apps/simple-backend-2   2/2     2            2           105m   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
deployment.apps/simple-web         1/1     1            1           105m   simple-web       nicholasjackson/fake-service:v0.17.0   app=simple-web
...

# gw,vs 배포
cat ch6/simple-web-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: simple-web-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "simple-web.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-web-vs-for-gateway
spec:
  hosts:
  - "simple-web.istioinaction.io"
  gateways:
  - simple-web-gateway
  http:
  - route:
    - destination:
        host: simple-web
        
kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction

# 확인
kubectl get gw,vs -n istioinaction

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
istio-ingressgateway-996bc6bb6-ztcx5.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
simple-backend-1-7449cc5945-d9zmc.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
simple-backend-2-6876494bbf-vdttr.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
simple-backend-2-6876494bbf-zn6v9.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
simple-web-7cd856754-tjdv6.istioinaction              Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8


# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       simple-web.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3

# 호출
curl -s http://simple-web.istioinaction.io:30000
open http://simple-web.istioinaction.io:30000

# 신규 터미널 : 반복 접속 실행 해두기
while true; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# 로그 확인
kubectl stern -l app=simple-web -n istioinaction
kubectl stern -l app=simple-web -n istioinaction -c istio-proxy
kubectl stern -l app=simple-web -n istioinaction -c simple-web
kubectl stern -l app=simple-backend -n istioinaction
kubectl stern -l app=simple-backend -n istioinaction -c istio-proxy
kubectl stern -l app=simple-backend -n istioinaction -c simple-backend


# (옵션) proxy-config
# proxy-config : simple-web
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/simple-web.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction | grep backend
80                                                            simple-backend, simple-backend.istioinaction + 1 more...     /* 

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local
SERVICE FQDN                                       PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
simple-backend.istioinaction.svc.cluster.local     80       -          outbound      EDS

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
       "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||simple-backend.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.14:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.15:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.16:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
 
 
 
 
 
 
 
  • 이스티오 DestinationRule 리소스로 simple-backend 서비스를 호출하는 모든 클라이언트의 로드 밸런싱을 ROUND_ROBIN으로 설정하자.
  • **DestinationRule**는 특정 목적지를 호출하는 메시 내 클라이언트들에 정책을 지정한다.
  • simple-backend 용 첫 DestinationRule는 다음과 같다.
# cat ch6/simple-backend-dr-rr.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN # 엔드포인트 결정을 '순서대로 돌아가며'
      
      
# DestinationRule 적용 : ROUND_ROBIN
cat ch6/simple-backend-dr-rr.yaml
kubectl apply -f ch6/simple-backend-dr-rr.yaml -n istioinaction

# 확인 : DestinationRule 단축어 dr
kubectl get dr -n istioinaction
NAME                HOST                                             AGE
simple-backend-dr   simple-backend.istioinaction.svc.cluster.local   11s

kubectl get destinationrule simple-backend-dr -n istioinaction \
 -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'
ROUND_ROBIN

# 호출 : 이 예시 서비스 집합에서는 호출 체인을 보여주는 JSON 응답을 받느다
## simple-web 서비스는 simple-backend 서비스를 호출하고, 우리는 궁극적으로 simple-backend-1 에서 온 응답 메시지 Hello를 보게 된다.
## 몇 번 더 반복하면 simple-backend-1 과 simple-backend-2 에게 응답을 받는다. 
curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"

# 반복 호출 확인 : 파드 비중은 backend-2가 2개임
for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr


# 로그 확인 : backend 요청을 하면 요청을 처리할 redirect 주소를 응답 (301), 전달 받은 redirect(endpoint)로 다시 요청
kubectl stern -l app=simple-web -n istioinaction -c istio-proxy
## simpleweb → simple-backend (301) redirect 응답 수신
simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.317Z] "GET // HTTP/1.1" 301 - via_upstream - "-" 0 36 3 3 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-backend:80" "10.10.0.16:8080" outbound|80||simple-backend.istioinaction.svc.cluster.local 10.10.0.17:46590 10.200.1.161:80 172.18.0.1:0 - default
## simpleweb → simple-backend (200)
simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.324Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 278 156 156 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-backend:80" "10.10.0.14:8080" outbound|80||simple-backend.istioinaction.svc.cluster.local 10.10.0.17:38336 10.200.1.161:80 172.18.0.1:0 - default
## simpleweb → 외부 curl 응답(200)
simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.307Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 889 177 177 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-web.istioinaction.io:30000" "10.10.0.17:8080" inbound|8080|| 127.0.0.6:40981 10.10.0.17:8080 172.18.0.1:0 outbound_.80_._.simple-web.istioinaction.svc.cluster.local default

kubectl stern -l app=simple-backend -n istioinaction -c istio-proxy
## simple-backend → (응답) simpleweb (301)
simple-backend-2-6876494bbf-zn6v9 istio-proxy [2025-04-20T04:22:45.209Z] "GET // HTTP/1.1" 301 - via_upstream - "-" 0 36 3 3 "172.18.0.1" "curl/8.7.1" "71ba286a-a45f-41bc-9b57-69710ea576d7" "simple-backend:80" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:54105 10.10.0.14:8080 172.18.0.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default
## simple-backend → (응답) simpleweb (200)
simple-backend-1-7449cc5945-d9zmc istio-proxy [2025-04-20T04:22:45.216Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 278 152 152 "172.18.0.1" "curl/8.7.1" "71ba286a-a45f-41bc-9b57-69710ea576d7" "simple-backend:80" "10.10.0.15:8080" inbound|8080|| 127.0.0.6:43705 10.10.0.15:8080 172.18.0.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
       "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||simple-backend.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST", # RR은 기본값(?)이여서, 해당 부분 설정이 이전과 다르게 없다
...

# 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json

 

 

 

위와같이 라운드로빈에 의해 29대21정도로 접속되는것을 확인할 수 있다. 이는 백앤드2 서비스가 파드가 더 많아서 그렇다.

  • 부하 생성기를 사용해 simple-backend 서비스 지연 시간을 변화시키는 어느 정도 현실적인 시나리오를 살펴보자.
  • 그러면 이런 상황에서 어떤 이스티오의 로드 밸런싱 전략이 가장 적합한지 선택하는 데 도움이 될 것이다.

우리는 Fortio 라는 CLI 부하 생성 도구를 사용해 서비스를 실행하고 클라이언트 측 로드 밸런싱의 차이를 관찰할 것이다.

# mac 설치
brew install fortio
fortio -h
fortio server
open http://127.0.0.1:8080/fortio

# windows 설치
1. 다운로드 https://github.com/fortio/fortio/releases/download/v1.69.3/fortio_win_1.69.3.zip
2. 압축 풀기
3. Windows Command Prompt : fortio.exe server
4. Once fortio server is running, you can visit its web UI at http://localhost:8080/fortio/

 

  • 이제 Fortio 로드 테스트 클라이언트를 사용할 준비가 됐으므로 사용 사례를 살펴보자.
  • Fortio를 사용해서 60초 동안 10개의 커넥션을 통해 초당 1000개의 요청을 보낼 것이다.
    • Fortio to send 1,000 rps (requests per seconds) for 60 seconds through 10 connections
  • Fortio는 각 호출의 지연 시간을 추적하고 지연 시간 백분위수 분석과 함께 히스토그램에 표시한다.
  • 테스트를 하기 전에 지연 시간을 1초까지 늘린 simple-backend-1 서비스를 도입할 것이다.
  • 이는 엔드포인트 중 하나에 긴 가비지 컬렉션 이벤트 또는 기타 애플리케이션 지연 시간이 발생한 상황을 시뮬레이션한다.
  • 우리는 로드 밸런싱 전략을 라운드 로빈, 랜덤, 최소 커넥션으로 바꿔가면서 차이점을 관찰할 것이다.

 

#
cat ch6/simple-backend-delayed.yaml
...
      - env:
        - name: "LISTEN_ADDR"
          value: "0.0.0.0:8080"
        - name: "SERVER_TYPE"
          value: "http"                      
        - name: "NAME"
          value: "simple-backend"      
        - name: "MESSAGE"
          value: "Hello from simple-backend-1"                     
        - name: "TIMING_VARIANCE"
          value: "10ms"                              
        - name: "TIMING_50_PERCENTILE"
          value: "1000ms"                                      
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: nicholasjackson/fake-service:v0.17.0
...
kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
kubectl rollout restart deployment -n istioinaction simple-backend-1

# 확인???
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
TIMING_VARIANCE=10ms
TIMING_50_PERCENTILE=150ms # ???

kubectl exec -it deploy/simple-backend-2 -n istioinaction -- env | grep TIMING
TIMING_VARIANCE=10ms
TIMING_50_PERCENTILE=150ms

# 직접 deploy 편집 수정???
KUBE_EDITOR="nano" kubectl edit deploy/simple-backend-1 -n istioinaction
...
      - name: TIMING_50_PERCENTILE
          value: 1000ms
...

kubectl rollout restart deployment -n istioinaction simple-backend-1
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
TIMING_VARIANCE=10ms
TIMING_50_PERCENTILE=150ms # ???


# 동작 중 파드에 env 직접 수정..
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
-----------------------------------
export TIMING_50_PERCENTILE=1000ms
exit
-----------------------------------

#
kubectl describe pod -n istioinaction -l app=simple-backend | grep TIMING_50_PERCENTILE:
      TIMING_50_PERCENTILE:  1000ms
      TIMING_50_PERCENTILE:  150ms
      TIMING_50_PERCENTILE:  150ms

# 테스트
curl -s http://simple-web.istioinaction.io:30000 | grep duration  
curl -s http://simple-web.istioinaction.io:30000 | grep duration 
curl -s http://simple-web.istioinaction.io:30000 | grep duration              
  "duration": "1.058699s",
      "duration": "1.000934s",

 

 

설정중 특이사항은 정상적으로 TIMING_50_PERCENTILE 반영이 안된다. 그래서 파드에 직접 접속해서 변수를 지정하는 방법을 사용한다.

 

 

이제 fortio를 다음과 같이 설정한다.

⇒ Start 클릭

여기서 특이사항은 실습에선 URL부분에 http를 넣었는데, 나는 넣으면 에러난다. 그래서 빼고 했다.

 

 

 

 

이제 로드밸런싱 알고리즘을 리스트 커넥션으로 바꿔본다.

#
cat ch6/simple-backend-dr-least-conn.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_CONN

kubectl apply -f ch6/simple-backend-dr-least-conn.yaml -n istioinaction

# 확인
kubectl get destinationrule simple-backend-dr -n istioinaction \
 -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep lbPolicy
"lbPolicy": "LEAST_REQUEST",

 

사용자경험이 좋아졌다.

 

 

6.3 Locality-aware load balancing 지역 인식 로드 밸런싱 (실습)

이스티오 컨트롤 플레인은 서비스 토폴로지 분석을 기반으로 동일 리전/가용 영역 내 서비스 호출을 우선시하는 지능형 로드 밸런싱을 자동화하여 네트워크 지연 시간과 비용을 최소화합니다.

 
이스티오는 쿠버네티스 노드의 리전/영역 레이블(예: topology.kubernetes.io/region)을 기반으로 지역 인식 로드 밸런싱을 수행하며, 단일 노드 환경에서는 파드에 istio-locality 레이블을 직접 설정해 테스트할 수 있습니다.

 

# cat ch6/simple-service-locality.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: simple-web
  name: simple-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-web
  template:
    metadata:
      labels:
        app: simple-web
        istio-locality: us-west1.us-west1-a
...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: simple-backend
  name: simple-backend-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-backend
  template:
    metadata:
      labels:
        app: simple-backend
        istio-locality: us-west1.us-west1-a
        version: v1 # 추가해두자!
...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: simple-backend
  name: simple-backend-2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: simple-backend
  template:
    metadata:
      labels:
        app: simple-backend
        istio-locality: us-west1.us-west1-b
        version: v2 # 추가해두자!
...
  • simple-backend 서비스를 배포할 때 지역 레이블을 다양하게 지정할 것이다.
  • simple-web과 같은 지역인 us-west1-a 에 simple-backend-1을 배포한다.
  • 그리고 us-west1-b 에 simple-backend-2 를 배포한다. 이 경우, 리전은 동일하지만 영역이 다르다.
  • 지역 간에 로드 밸런싱을 수행할 수 있는 이스티오의 기능에는 리전, 영역, 심지어는 더 세밀한 하위 영역 subzone 도 포함된다.
#
kubectl apply -f ch6/simple-service-locality.yaml -n istioinaction

# 확인
## simple-backend-1 : us-west1-a  (same locality as simple-web)
kubectl get deployment.apps/simple-backend-1 -n istioinaction \
-o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
us-west1.us-west1-a

## simple-backend-2 : us-west1-b
kubectl get deployment.apps/simple-backend-2 -n istioinaction \
-o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
us-west1.us-west1-b

 

 

이스티오의 지역 인식 로드 밸런싱은 기본적으로 활성화되어 동일 지역의 서비스로 트래픽을 우선 분배하지만, 인스턴스 수 불균형 등 실제 부하 특성에 따라 설정을 세밀하게 튜닝하는 것이 중요합니다.

 

 

호출테스트 1

# 신규 터미널 : 반복 접속 실행 해두기
while true; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 호출 : 이 예시 서비스 집합에서는 호출 체인을 보여주는 JSON 응답을 받느다
curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"

# 반복 호출 확인 : 파드 비중은 backend-2가 2개임
for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr

 

 

위와같이 1,2 왔다갔다 한다. 즉 다른 리전으로도 트래픽이 흐르는것.

 

헬스체크를 설정해서 올바르게 트래픽이 흐르도록(동일 리전으로) 설정해보자.

 

#
cat ch6/simple-backend-dr-outlier.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 1
      interval: 5s
      baseEjectionTime: 30s
      maxEjectionPercent: 100

kubectl apply -f ch6/simple-backend-dr-outlier.yaml -n istioinaction

# 확인
kubectl get dr -n istioinaction simple-backend-dr -o jsonpath='{.spec}' | jq


# 반복 호출 확인 : 파드 비중 확인
for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr


# proxy-config : simple-web 에서 simple-backend 정보 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local        
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
        },
        "outlierDetection": {
            "consecutive5xx": 1,
            "interval": "5s",
            "baseEjectionTime": "30s",
            "maxEjectionPercent": 100,
            "enforcingConsecutive5xx": 100,
            "enforcingSuccessRate": 0
        },
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },
                "weight": 1,
                "priority": 1,
                "locality": {
                    "region": "us-west1",
                    "zone": "us-west1-b"
                }
            
...

# 로그 확인
kubectl logs -n istioinaction -l app=simple-backend -c istio-proxy -f
kubectl stern -l app=simple-backend -n istioinaction
...

 

 

위와같이 지역인식 로드밸런싱이 되는것을 알 수 있다.

 

  • 호출 테스트 2 ⇒ 오동작 주입 후 확인
    • 트래픽이 가용 영역을 넘어가는 것을 보기 위해 simple-backend-1 서비스를 오동작 상태로 만들어보자.
    • simple-web 에서 simple-backend-1 호출하면 항상 HTTP 500 오류를 발생하게 하자
# HTTP 500 에러를 일정비율로 발생
cat ch6/simple-service-locality-failure.yaml
...
        - name: "ERROR_TYPE"
          value: "http_error"           
        - name: "ERROR_RATE"
          value: "1"                              
        - name: "ERROR_CODE"
          value: "500"  
...
kubectl apply -f ch6/simple-service-locality-failure.yaml -n istioinaction

# simple-backend-1- Pod 가 Running 상태로 완전히 배포된 후에 호출 확인

# 반복 호출 확인 : 파드 비중 확인
for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr


# 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'        
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.23:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.24:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.25:8080     HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local

# simple-backend-1 500에러 리턴으로 outliercheck 실패 상태로 호출에서 제외됨
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "healthStatus": {
                    "failedOutlierCheck": true,
                    "edsHealthStatus": "HEALTHY"
                },
                ...
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },

 

 

원래는 1번으로만 트래픽이 흐르다가 헬스체크에 의해 2번으로만 트래픽이 흐르는것을 알 수 있다...(와우 !)

 

이는 다음과 같이 24번 아이피(동일리전)에 문제가 발생해서이다.

6.3.2 More control over locality load balancing with weighted distribution : 가중치 분포로 지역 인식 LB 제어 강화

이스티오의 지역 인식 로드 밸런싱은 기본적으로 동일 지역에 우선 트래픽을 보내지만, 필요에 따라 여러 지역에 트래픽을 가중치로 분산(지역 가중 분포)하여 과부하를 예방할 수 있습니다.

 
  • 특정 영역에서 리전이 처리 할 수 없는 부하가 들어온다고 해보자.
  • 트래픽의 70%가 최인접 지역으로 가고, 30%가 인접 지역으로 가길 원한다.
  • 앞선 예제를 따라 simple-backend 서비스로 가는 트래픽 70%를 us-west1-a로, 30%를 us-west1-b로 보낼 것이다.
#
cat ch6/simple-backend-dr-outlier-locality.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    loadBalancer: # 로드 밸런서 설정 추가
      localityLbSetting:
        distribute:
        - from: us-west1/us-west1-a/* # 출발지 영역
          to:
            "us-west1/us-west1-a/*": 70 # 목적지 영역
            "us-west1/us-west1-b/*": 30 # 목적지 영역
    connectionPool:      
      http:
        http2MaxRequests: 10
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 1
      interval: 5s
      baseEjectionTime: 30s
      maxEjectionPercent: 100

kubectl apply -f ch6/simple-backend-dr-outlier-locality.yaml -n istioinaction


# 반복 호출 확인 : 파드 비중 확인
for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr


# endpoint 에 weight 는 모두 1이다. 위 70/30 비중은 어느곳의 envoy 에 설정 되는 걸까?...
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.23:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.24:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.26:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

 

 

설정한것처럼 대략 7:3정도로 로드밸런싱이 된다.

 

 

6.4 Transparent timeouts and retries (실습)

이스티오는 다양한 타임아웃과 재시도 설정을 통해 네트워크 지연과 실패 같은 신뢰성 문제를 극복하여 분산 시스템의 안정성을 높입니다.

분산 시스템에서 타임아웃 계층화는 연쇄 장애 방지를 위해 외부 서비스(edge)에서 긴 타임아웃, 내부 서비스(backend)로 갈수록 짧은 타임아웃을 설정하는 전략입니다. 이스티오는 VirtualService를 통해 서비스 호출별 타임아웃을 세밀하게 제어하며, 상위 서비스의 타임아웃이 하위 서비스보다 우선 적용되어 시스템 전반의 안정성을 확보합니다.

 

 
실습을 진행하기 위해 다음 코드를 실행한다.
kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
kubectl delete destinationrule simple-backend-dr -n istioinaction


# 호출 테스트 : 보통 10~20ms 이내 걸림
curl -s http://simple-web.istioinaction.io:30000 | jq .code
time curl -s http://simple-web.istioinaction.io:30000 | jq .code
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

# simple-backend-1를 1초 delay로 응답하도록 배포
cat ch6/simple-backend-delayed.yaml
kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction

kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
TIMING_VARIANCE=10ms
TIMING_50_PERCENTILE=150ms

# 동작 중 파드에 env 직접 수정..
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
-----------------------------------
export TIMING_50_PERCENTILE=1000ms
exit
-----------------------------------

# 호출 테스트 : simple-backend-1로 로드밸런싱 될 경우 1초 이상 소요 확인
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 6% cpu 0.200 total
jq .code  0.00s user 0.00s system 3% cpu 0.199 total
500
...


# 
cat ch6/simple-backend-vs-timeout.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    timeout: 0.5s
    
kubectl apply -f ch6/simple-backend-vs-timeout.yaml -n istioinaction

#
kubectl get vs -n istioinaction
NAME                        GATEWAYS                 HOSTS                             AGE
simple-backend-vs                                    ["simple-backend"]                14s
simple-web-vs-for-gateway   ["simple-web-gateway"]   ["simple-web.istioinaction.io"]   6h11m


# 호출 테스트 : 0.5s 이상 걸리는 호출은 타임아웃 발생 (500응답)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 2% cpu 0.537 total
jq .code  0.00s user 0.00s system 0% cpu 0.535 total
500
...

# istio-proxy config 에서 위 timeout 적용 envoy 설정 부분 찾아 두자.
 
 
 

 

 

500에러시 재시도 하는 실습을 진행하기 위해 기존 설정 초기화 

kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-backend.yaml -n istioinaction

 

그리고 다음과 같이 이스티오의 기본 설정을 종료

#
docker exec -it myk8s-control-plane bash
----------------------------------------
# Retry 옵션 끄기 : 최대 재시도 0 설정
istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
y
exit
----------------------------------------

# 확인
kubectl get istiooperators -n istio-system -o yaml
...
    meshConfig:
      defaultConfig:
        proxyMetadata: {}
      defaultHttpRetryPolicy:
        attempts: 0
      enablePrometheusMerge: true
...

# istio-proxy 에서 적용 부분 찾아보자

 

 

 

이제 503 발생시 재시도 하도록 설정한다.

#
cat ch6/simple-backend-periodic-failure-503.yaml
...
        - name: "ERROR_TYPE"
          value: "http_error"           
        - name: "ERROR_RATE"
          value: "0.75"                              
        - name: "ERROR_CODE"
          value: "503"  
...

#
kubectl apply -f ch6/simple-backend-periodic-failure-503.yaml -n istioinaction

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep ERROR

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=503
exit
---------------------------------------------------------------

# 호출테스트 : simple-backend-1 호출 시 failures (500) 발생
# simple-backend-1 --(503)--> simple-web --(500)--> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 6% cpu 0.200 total
jq .code  0.00s user 0.00s system 3% cpu 0.199 total
500
...

# app, istio-proxy log 에서 500, 503 로그 확인해보자.

 

 

 

이제 이스티오의 재시도 정책을 다시 바꾼다.

#
cat ch6/simple-backend-enable-retry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2

kubectl apply -f ch6/simple-backend-enable-retry.yaml -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80 -o json
...
               "name": "simple-backend.istioinaction.svc.cluster.local:80",
                "domains": [
                    "simple-backend.istioinaction.svc.cluster.local",
                    "simple-backend",
                    "simple-backend.istioinaction.svc",
                    "simple-backend.istioinaction",
                    "10.200.1.161"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
...

# 호출테스트 : 모두 성공!
# simple-backend-1 --(503, retry 후 정상 응답)--> simple-web --> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

# app, istio-proxy log 에서 503 로그 확인해보자.

 

 

500에러가 없어졌다.

 

사용자 경험이 좋아지고 있다.

 

503에러가 아닌 500 에러에 대해서는 ?

 

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2 # 최대 재시도 횟수
      retryOn: gateway-error,connect-failure,retriable-4xx # 다시 시도해야 할 오류
      perTryTimeout: 300ms # 타임 아웃
      retryRemoteLocalities: true # 재시도 시 다른 지역의 엔드포인트에 시도할지 여부
      

# 500 에러 코드 리턴
cat ch6/simple-backend-periodic-failure-500.yaml
...
        - name: "ERROR_TYPE"
          value: "http_error"           
        - name: "ERROR_RATE"
          value: "0.75"                              
        - name: "ERROR_CODE"
          value: "500"
...

kubectl apply -f ch6/simple-backend-periodic-failure-500.yaml -n istioinaction

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=500
exit
---------------------------------------------------------------

# envoy 설정 확인 : 재시도 동작(retryOn) 에 retriableStatusCodes 는 503만 있음.
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
...
                       "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    503
                                ]
                            },
                            "maxGrpcTimeout": "0s"
                        },
...


# 호출테스트 : Retry 동작 안함.
# simple-backend-1 --(500, retry 안함)--> simple-web --(500)> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 30% cpu 0.036 total
jq .code  0.00s user 0.00s system 14% cpu 0.035 total
200
curl -s http://simple-web.istioinaction.io:30000  0.00s user 0.01s system 5% cpu 0.184 total
jq .code  0.00s user 0.00s system 2% cpu 0.183 total
500
...

 

위와같이 500 에러가 발생하도록 하고...

 

#
cat ch6/simple-backend-vs-retry-500.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5xx # HTTP 5xx 모두에 재시도

kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction

# 호출테스트 : 모두 성공!
# simple-backend-1 --(500, retry)--> simple-web --(200)> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

 

 

RetryOn을 5xx로 했떠니 전부 200 응답이 나는것을 확인할 수 있다.

 

 

그럼 타임아웃에 따른 재시도는 ?

재시도 정책 설정 시
(시도별 타임아웃 * 재시도 횟수) + (백오프 지연 * (재시도 횟수-1)) < 전체 타임아웃 조건을 반드시 충족시켜야 의도한 재시도 동작을 보장합니다.

이스티오의 재시도 동작은 다음과 같이 작동합니다:

  1. 기본 재시도 정책: HTTP 요청 실패 시 최대 2회 재시도 (총 3회 시도), 재시도 간 백오프는 25ms부터 시작해 지수적으로 증가 (예: 25ms → 50ms → 100ms).
  2. Thundering Herd 위험: 다중 계층 서비스 체인에서 재시도 시 요청 수가 기하급수적으로 증가 (예: 5계층 시 최대 32회 요청 발생.
  3. 안전하지 않은 기본 설정: 503 오류 자동 재시도로 인해 중복 처리 위험 (결제 중복 등), retryOn 설정을 connect-failure 등으로 제한 권장.
  4. 지역 제한 재시도: 기본적으로 동일 지역 엔드포인트만 재시도하지만, retryRemoteLocalities: true 설정 시 다른 리전으로 재시도 확장 가능.
  5. 세부 제어 필요: VirtualService에서 attempts, perTryTimeout, retryOn 조건을 서비스별로 명시적 설정해야 안전성 보장.

※ 재시도 정책은 VirtualService 리소스에서 retries 필드로 제어되며, 백오프 알고리즘은 현재 커스터마이징 불가.

 

6.4.3 Advanced retries : Istio Extension API (EnvoyFilter)

이스티오는 기본적으로 HTTP 503 오류에 대해 25ms 백오프 시간으로 재시도를 수행하지만, 상세 설정(재시도 조건, 백오프 시간)은 EnvoyFilter API를 통해 엔보이 프록시 설정을 직접 수정해야 커스터마이징이 가능합니다.

※ 주의: EnvoyFilter 사용은 호환성 문제를 유발할 수 있으므로 Istio 버전과 Envoy 문서를 반드시 확인해야 합니다.

# cat ch6/simple-backend-ef-retry-status-codes.yaml                                            
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: simple-backend-retry-status-codes
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: simple-web
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: SIDECAR_OUTBOUND
      routeConfiguration:
        vhost:
          name: "simple-backend.istioinaction.svc.cluster.local:80"          
    patch:
      operation: MERGE
      value:
        route:
          retry_policy: # 엔보이 설정에서 직접 나온다?
            retry_back_off: 
              base_interval: 50ms # 기본 간격을 늘린다
            retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
            - 408
            - 400
            

# 408 에러코드를 발생
kubectl apply -f ch6/simple-backend-periodic-failure-408.yaml -n istioinaction

# 파드 정상 기동 후 수정
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=408
exit
---------------------------------------------------------------

# 호출테스트 : 408 에러는 retryOn: 5xx 에 포함되지 않으므로 에러를 리턴.
# simple-backend-1 --(408)--> simple-web --(500)--> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...

 

 

이제 408 에러도 재시도 하도록 적용한다.

#
cat ch6/simple-backend-ef-retry-status-codes.yaml
...
    patch:
      operation: MERGE
      value:
        route:
          retry_policy: # 엔보이 설정에서 직접 나온다?
            retry_back_off: 
              base_interval: 50ms # 기본 간격을 늘린다
            retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
            - 408
            - 400

kubectl apply -f ch6/simple-backend-ef-retry-status-codes.yaml -n istioinaction

# 확인
kubectl get envoyfilter -n istioinaction -o json
kubectl get envoyfilter -n istioinaction
NAME                                AGE
simple-backend-retry-status-codes   45s

# VirtualService 에도 재시도 할 대상 코드 추가
cat ch6/simple-backend-vs-retry-on.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5xx,retriable-status-codes # retryOn 항목에 retriable-status-codes 를 추가

kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction

# envoy 설정 확인 : 재시도 동작(retryOn) 에 5xx 과 retriableStatusCodes 는 408,400 있음.
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
...
                        "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "5xx,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    408,
                                    400
                                ],
                                "retryBackOff": {
                                    "baseInterval": "0.050s"
                                }
                            },
                            "maxGrpcTimeout": "0s"
                        }
...

# 호출테스트 : 성공
# simple-backend-1 --(408, retry 성공)--> simple-web --> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...

 

 

 

408에러도 200에러로 응답되는것을 알 수 있다.

 

 

6.5 Circuit breaking with Istio

  1. 서킷 브레이커 구현 방식: 이스티오는 DestinationRule의 connectionPool (최대 연결/요청 수 제한)과 outlierDetection (이상 엔드포인트 감지) 설정을 통해 서킷 브레이커 패턴을 구현합니다.
  2. 과부하 방지:
    • connectionPool은 동시 연결 수(http1MaxPendingRequests)와 대기 요청 수(maxRequestsPerConnection)를 제한해 과부하 시 503 오류로 즉시 실패 처리하여 연쇄 장애를 차단합니다.
  3. 장애 엔드포인트 격리:
    • outlierDetection은 연속 오류(consecutive5xxErrors) 발생 시 해당 엔드포인트를 일시적으로 풀에서 제거(ejection)해 시스템 회복 시간을 확보합니다.

서킷 브레이커를 2가지 방식으로 제공을 하는데..

 

6.5.1 Guarding against slow services wih connection-pool control* : 커넥션 풀 제어로 느린 서비스에 대응하기

# tracing.sampling=100
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.accessLogFile=/dev/stdout --set meshConfig.defaultConfig.tracing.sampling=100 --set meshConfig.defaultHttpRetryPolicy.attempts=0
y
exit
----------------------------------------

# 확인
kubectl describe cm -n istio-system istio
...
defaultConfig:
  discoveryAddress: istiod.istio-system.svc:15012
  proxyMetadata: {}
  tracing:
    sampling: 100.0
    zipkin:
      address: zipkin.istio-system:9411
...

# 적용 : rollout 
kubectl rollout restart deploy -n istio-system istiod
kubectl rollout restart deploy -n istio-system istio-ingressgateway
kubectl rollout restart deploy -n istioinaction simple-web
kubectl rollout restart deploy -n istioinaction simple-backend-1



# 현재 적용되어 있는 상태
kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction
kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction

# destinationrule 삭제
kubectl delete destinationrule --all -n istioinaction

# simple-backend-2 제거
kubectl scale deploy simple-backend-2 --replicas=0 -n istioinaction

# 응답지연(1초)을 발생하는 simple-backend-1 배포
kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction

# 동작 중 파드에 env 직접 수정..
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
-----------------------------------
export TIMING_50_PERCENTILE=1000ms
exit
-----------------------------------

# 테스트
curl -s http://simple-web.istioinaction.io:30000 | grep duration              
  "duration": "1.058699s",
      "duration": "1.000934s",

위와같이 실습환경을 구성하고..

fortio로 테스트를 해본다.

 

# cat ch6/simple-backend-dr-conn-limit.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1 # 커넥션 총 개수 Total number of connections
      http:
        http1MaxPendingRequests: 1 # 대기 중인 요청 Queued requests
        maxRequestsPerConnection: 1 # 커넥션당 요청 개수 Requests per connection
        maxRetries: 1 # Maximum number of retries that can be outstanding to all hosts in a cluster at a given time.
        http2MaxRequests: 1 # 모든 호스트에 대한 최대 동시 요청 개수 Maximum concurrent requests to all hosts

# DestinationRule 적용 (connection-limiting) 
kubectl apply -f ch6/simple-backend-dr-conn-limit.yaml -n istioinaction
kubectl get dr -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction | egrep 'RULE|backend'
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
                                                        8080      -          inbound       ORIGINAL_DST     simple-backend-dr.istioinaction
simple-backend.istioinaction.svc.cluster.local          80        -          outbound      EDS              simple-backend-dr.istioinaction

# 설정 적용 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 1, # tcp.maxConnections, 커넥션 총 개수 Total number of connections
                    "maxPendingRequests": 1, # http.http1MaxPendingRequests, 대기 중인 요청 Queued requests
                    "maxRequests": 1, # http.http2MaxRequests, 모든 호스트에 대한 최대 동시 요청 개수 
                    "maxRetries": 1, # http.maxRetries
                    "trackRemaining": true
                }
            ]
        },
        "typedExtensionProtocolOptions": {
            "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
                "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
                "commonHttpProtocolOptions": { 
                    "maxRequestsPerConnection": 1 # http.maxRequestsPerConnection, 커넥션당 요청 개수
...

# (참고) 기본값?
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn simple-web.istioinaction.svc.cluster.local -o json
...
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295,
                    "trackRemaining": true
...

이스티오의 DestinationRule에서 maxConnections는 대상 호스트당 HTTP1/TCP 커넥션 최대 수를 제한하고, http1MaxPendingRequests는 사용 가능한 커넥션이 없을 때 대기 중인 요청 수를 제한하며, http2MaxRequests(이름과 달리 HTTP1.1/HTTP2 모두 적용)는 호스트별 동시 요청 수를 제어합니다.

 

이스티오는 기본적으로 프록시 메트릭 카디널리티를 줄이기 위해 통계를 제한하지만, 특정 서비스(예: simple-web)에 대해 상세 통계 수집을 활성화해 서킷 브레이커 동작과 업스트림 서비스(simple-backend) 장애를 명확히 구분할 수 있습니다.

 

좀더 명확한 테스트를 위해 다음과 같이 설정한다.

# simple-web 디플로이먼트에 sidecar.istio.io/statsInclusionPrefixes="cluster.<이름>" 애너테이션 추가하자
## sidecar.istio.io/statsInclusionPrefixes: cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local
cat ch6/simple-web-stats-incl.yaml | grep statsInclusionPrefixes 
        sidecar.istio.io/statsInclusionPrefixes: "cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local"        
kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction

# 정확한 확인을 위해 istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# simple-web 에 istio-proxy 의 stats 조회
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

위와같이 초기화를 진행하고

  • 커넥션 개수와 초당 요청 수를 2로 늘리면 어떨까? 2개의 커넥션에서 요청을 초당 하나씩 보내기 시작해보자.
    • 커넥션과 요청이 지정한 임계값(병렬 요청이 너무 많거나 요청이 너무 많이 쌓임)을 충분히 넘겨 서킷 브레이커를 동작시켰음을 확인.
# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 요청이 17개 실패한 것으로 반환됐다(HTTP 5xx)
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 19 (for perfect keepalive, would be 2)
Code 200 : 30 (63.8 %)
Code 500 : 17 (36.2 %)
All done 47 calls (plus 2 warmup) 925.635 ms avg, 1.5 qps
...

# 로그 확인 : simple-web
kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
...
# 오류 요청 (503 Service Unavailable, UO 플래그
## HTTP 503: 서비스가 일시적으로 사용 불가능. Envoy가 업스트림 서버(simple-backend:80)에 요청을 전달하지 못함.
## UO 플래그: "Upstream Overflow"로, Envoy의 서킷 브레이커가 트리거되었거나 최대 연결/요청 제한에 도달했음을 의미.
## upstream_reset_before_response_started{overflow}: 업스트림 서버가 응답을 시작하기 전에 연결이 리셋되었으며, 이는 오버플로우(리소스 제한)로 인함.
[2025-04-22T03:17:24.830Z] "GET // HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 4 - ...

# 오류 요청 (500 Internal Server Error) : 최종 사용자에게 500 에러 리턴
[2025-04-22T03:17:24.825Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 687 11 11 ...
## simple-web 서비스에서 backend 정보를 가져오지 못하여 애플리케이션 레벨 오류 발생
## HTTP 500: 서버 내부 오류. 업스트림 서버(simple-web:30000)가 요청을 처리하는 중 예기치 않은 오류 발생.
## via_upstream: 오류가 Envoy가 아니라 업스트림 서버에서 발생했음을 나타냄.
...

# 통계 확인 : 18개로 +/- 1개 정도는 무시하고 보자. 성능 테스트 실패 갯수(17개)와 아래 통계값이 일치 한다(18-1).
# 큐 대기열이 늘어나 결국 서킷 브레이커를 발동함. 
# fail-fast 동작은 이렇게 보류 중 혹은 병행 요청 갯수가 서킷 브레이커 임계값을 넘어 수행된다.
# The fail-fast behavior comes from those pending or parallel requests exceeding the circuit-breaking thresholds. 
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 18
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

 

 

 

병렬로 발생하는 요청(현재 로드테스트 동시 요청 2)을 더 처리하고자 http2MaxRequests(parallel requests)를 늘려보자

# 설정 전 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                    "maxRequests": 1,
                    "maxRequestsPerConnection": 1

# http2MaxRequests 조정: 1 → 2, '동시요청 처리개수'를 늘림
kubectl patch destinationrule simple-backend-dr -n istioinaction \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 2}}}}}'

# 설정 후 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                    "maxRequests": 2,
                    "maxRequestsPerConnection": 1

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 로그 확인 : simple-web >> 아래 500(503) 발생 로그 확인
kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
... 
## jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13
[2025-04-22T03:55:22.424Z] "GET / HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 0 - "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-backend:80" "-" outbound|80||simple-backend.istioinaction.svc.cluster.local - 10.200.1.137:80 172.18.0.1:0 - -
[2025-04-22T03:55:22.410Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 688 15 15 "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-web.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:43259 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.simple-web.istioinaction.svc.cluster.local default
...

# 로그 확인 : simple-backend >> 503 에러가 발생하지 않았다??? 
kubectl logs -n istioinaction -l app=simple-backend -c istio-proxy -f


# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 동시요청 처리개수가 기존 1 에서 2로 증가되어서 거의 대부분 처리했다. >> 참고로 모두 성공 되기도함.
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 3 (for perfect keepalive, would be 2)
Code 200 : 33 (97.1 %)
Code 500 : 1 (2.9 %)
All done 34 calls (plus 2 warmup) 1789.433 ms avg, 1.1 qps
...

# 'cx_overflow: 40' 대비 'rq_pending_overflow: 1' 가 현저히 낮아짐을 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 40
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 1
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

맥스 리퀘스트를 1개에서 2개로 늘려주니 503 에러가 확연히 줄어들었다.

 

이번엔 보류 대기열 댑스를 2로 늘리고 실행해본다.

 

# http1MaxPendingRequests : 1 → 2, 'queuing' 개수를 늘립니다
kubectl patch destinationrule simple-backend-dr \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 2}}}}}'

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests           
                    "maxPendingRequests": 2,
                    
# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters


# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 모두 성공!
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 2 (for perfect keepalive, would be 2) # 큐 길이 증가 덕분에, 소켓을 2개만 사용했다.
Code 200 : 33 (100.0 %)
All done 33 calls (plus 2 warmup) 1846.745 ms avg, 1.1 qps
...

# 'cx_overflow가 45이 발생했지만, upstream_rq_pending_overflow 는 이다!
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

이제 500에러는 발생하지 않는다.

 

Istio 서킷 브레이커가 발동되면 x-envoy-overloaded 헤더를 통해 요청 실패 원인이 서킷 브레이커 임계값 초과임을 식별할 수 있으며, 이를 통해 애플리케이션/네트워크 장애와 구분합니다.

 
# 
kubectl patch destinationrule simple-backend-dr \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 1}}}}}'

kubectl patch destinationrule simple-backend-dr -n istioinaction \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 1}}}}}'

# 설정 적용 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 로드 테스트
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000

# 로드 테스트 하는 상태에서 아래 curl 접속 
curl -v http://simple-web.istioinaction.io:30000
{
  "name": "simple-web",
  "uri": "/",
  "type": "HTTP",
  "ip_addresses": [
    "10.10.0.18"
  ],
  "start_time": "2025-04-22T04:23:50.468693",
  "end_time": "2025-04-22T04:23:50.474941",
  "duration": "6.247ms",
  "body": "Hello from simple-web!!!",
  "upstream_calls": [
    {
      "uri": "http://simple-backend:80/",
      "headers": {
        "Content-Length": "81",
        "Content-Type": "text/plain",
        "Date": "Tue, 22 Apr 2025 04:23:50 GMT",
        "Server": "envoy",
        "X-Envoy-Overloaded": "true" # Header indication
      },
      "code": 503,
      "error": "Error processing upstream request: http://simple-backend:80//, expected code 200, got 503"
    }
  ],
  "code": 500
}
 
 
 
 
 

 

 

 

 

6.5.2 Guarding against unhealthy services with outlier detection* : 이상값 감지로 비정상 서비스에 대응하기

Istio는 엔보이의 이상값 감지(Outlier Detection) 기능을 활용해 오동작하는 호스트를 서비스에서 자동 제거하여 시스템 안정성을 유지합니다.

 

- 실습 환경 초기화
    - 동작을 살펴보기 위해 이스티오의 **기본 재시도 메커니즘도 비활성화** 한다.
    - 재시도와 이상값 감지는 잘 어울리지만, 이 예제에서는 **이상값 감지 기능을 고립**시키려고 한다.
    - 재시도는 마지막에 추가해서 이상값 감지와 재시도가 서로 어떻게 보완하는지 확인해본다.

#
kubectl delete destinationrule --all -n istioinaction
kubectl delete vs simple-backend-vs -n istioinaction

# disable retries (default) : 이미 적용 되어 있음
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
y
exit
----------------------------------------

#
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction # 통계 활성화

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 호출 테스트 : 모두 성공
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000

# 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
 
 
 
 
 
#
kubectl apply -n istioinaction -f ch6/simple-backend-periodic-failure-500.yaml
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep ERROR

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=500
exit
---------------------------------------------------------------

# 정보 확인
kubectl get deploy,pod -n istioinaction -o wide
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS       IMAGES                                 SELECTOR
deployment.apps/simple-backend-1   1/1     1            1           20h   simple-backend   nicholasjackson/fake-service:v0.14.1   app=simple-backend
deployment.apps/simple-backend-2   2/2     2            2           20h   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
deployment.apps/simple-web         1/1     1            1           21h   simple-web       nicholasjackson/fake-service:v0.17.0   app=simple-web

NAME                                   READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
pod/simple-backend-1-bdb6c7ff8-rqqlr   2/2     Running   0          2m25s   10.10.0.30   myk8s-control-plane   <none>           <none>
pod/simple-backend-2-6799f8bf-d4b6t    2/2     Running   0          11m     10.10.0.27   myk8s-control-plane   <none>           <none>
pod/simple-backend-2-6799f8bf-dk78j    2/2     Running   0          11m     10.10.0.29   myk8s-control-plane   <none>           <none>
pod/simple-web-865f4949ff-56kbq        2/2     Running   0          3h32m   10.10.0.18   myk8s-control-plane   <none>           <none>


# 로드 테스트 실행 : 재시도를 끄고, backend-1 엔드포인트에 주기적인 실패를 설정했으니, 테스트 일부는 실패
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 19 (for perfect keepalive, would be 2)
Code 200 : 43 (71.7 %)
Code 500 : 17 (28.3 %)
All done 60 calls (plus 2 warmup) 134.138 ms avg, 2.0 qps
...

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

설정에서처럼 약 75프로 가량 실패하는것을 확인할 수 있다.

 

 

 

 

  • 정기적으로 실패하는 서비스에 요청을 보내고 있는데 서비스의 다른 엔드포인트들은 실패하지 않고 있다면, 해당 엔드포인트가 과부하됐거나 어떤 이유로든 성능이 저하된 상태일 수 있으므로 당분간 그 엔드포인트로 트래픽을 전송하는 것을 멈춰야 한다.
  • 이상값 감지를 설정해보자 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다. - Docs
    • consecutive5xxErrors: 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5, 연속적인 에러 횟수 threshold
    • interval: 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
    • baseEjectionTime: 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime.
      • 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초.
    • maxEjectionPercent: 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%).
      • 100% 설정 시모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%
#
cat ch6/simple-backend-dr-outlier-5s.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 1 # 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5
      interval: 5s # 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
      baseEjectionTime: 5s # 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime. 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초. 
      maxEjectionPercent: 100 # 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%). 모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%

kubectl apply -f ch6/simple-backend-dr-outlier-5s.yaml -n istioinaction
kubectl get dr -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
        "outlierDetection": {
            "consecutive5xx": 1,
            "interval": "5s",
            "baseEjectionTime": "5s",
            "maxEjectionPercent": 100,
            "enforcingConsecutive5xx": 100,
            "enforcingSuccessRate": 0
        },
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local



# 통계 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
	while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done
	ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local


# 로드 테스트 실행 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다.
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 5 (for perfect keepalive, would be 2)
Code 200 : 58 (96.7 %)
Code 500 : 2 (3.3 %)
All done 60 calls (plus 2 warmup) 166.592 ms avg, 2.0 qps
...

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

# 엔드포인트 이상 감지 전에 3번 실패했고, 이상 상태가 되고 나면 로드 밸런서 풀에서 제거되어서 이후 부터는 정상 엔드포인트로 호출 응답됨.
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep outlier
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_active: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_gateway_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_local_origin_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_gateway_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_local_origin_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_total: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_total: 3


# 5초 후(baseEjectionTime: 5s) 다시 엔드포인트 모니터링
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

 

위와같이 오류율이 확 떨어진것을 알 수 있다.

오류율이 2개가 뜨는 이유는 ? 처음 2개 실패 후 이후부터 성공한것이기 때문이다.

 

 

오류율을 더 떨어트리려면 ?

 

#
cat ch6/simple-backend-vs-retry-500.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5x

kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction

# 통계 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done

# 로드 테스트 실행 : 모두 성공!
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 2 (for perfect keepalive, would be 2)
Code 200 : 60 (100.0 %)
All done 60 calls (plus 2 warmup) 173.837 ms avg, 2.0 qps
...

# 엔드포인트 이상 감지 전에 3번 실패했지만, 재시도 retry 덕분에 결과적으로 모두 성공!
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep outlier

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream | grep retry
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry: 4
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_exponential: 4
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_ratelimited: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_limit_exceeded: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_success: 4

 

 

 

6장 요약

  1. 로드 밸런싱 알고리즘: DestinationRule을 통해 ROUND_ROBIN, RANDOM, LEAST_CONN 등의 알고리즘 선택 가능하며, 기본값은 LEAST_CONN(최소 연결 우선)입니다.
  2. 지역 기반 트래픽 제어:
    • Locality-aware LB: 동일 리전/영역 내 엔드포인트 우선 라우팅 (outlierDetection 활성화 필수).
    • 가중치 분배: DestinationRule의 distribute 설정으로 특정 지역에 트래픽 비율 할당 가능(예: 70% 로컬, 30% 타 영역).
  3. 복원력 설정:
    • 재시도/타임아웃: VirtualService에서 retries.attempts, timeout 지정.
    • 서킷 브레이커: DestinationRule의 connectionPool+outlierDetection으로 연결 수/에러 임계값 관리.
    • EnvoyFilter: Istio 미지원 기능(예: 백오프 시간 커스텀)을 Envoy 프록시 레벨에서 확장 적용.

istio3주차 스터디를 시작한다.

5.1 Reducing the risk of deploying new code

블루/그린 배포 전략은 현재에도 굉장히 많이 쓰이지만 해당 배포 전략은 모든 코드 변경사항이 한번에 해제된다. 이 리스크를 줄이는 방안을 살펴볼껀데, 그전에 배포와 릴리스가 뭔지 알아본다.

  • 배포(Deploy)는 새 버전의 코드를 운영 환경 리소스에 설치하는 과정이지만, 이때는 실제 사용자 트래픽이 전달되지 않습니다.
  • 배포된 코드는 운영 환경에서 테스트와 검증이 가능하며, 사용자에게 영향을 주지 않습니다.
  • 릴리스(Release)는 운영 환경에 배포된 새 코드를 실제 사용자 트래픽에 노출하는 행위입니다.
  • 배포와 릴리스를 분리하면, 카나리 릴리스처럼 일부 사용자만 신버전을 사용하게 하여 위험을 점진적으로 관리할 수 있습니다.
  • 이렇게 하면 문제가 있을 때 빠르게 롤백할 수 있고, 안정적으로 전체 사용자에게 릴리스를 확장할 수 있습니다

5.2 Routing requests with Istio (실습)

VirtualService

VirtualService는 Istio에서 트래픽을 어떤 서비스로, 어떤 조건에 따라, 어떻게 라우팅할지 세밀하게 제어하는 역할을 하는 핵심 리소스

# Let’s deploy v1 of our catalog service. From the root of the book’s source code, run the following command
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
catalog-6cf4b97d-ftl77   2/2     Running   0          42s   10.10.0.14   myk8s-control-plane   <none>           <none>

# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3

# netshoot로 내부에서 catalog 접속 확인
kubectl exec -it netshoot -- curl -s http://catalog.istioinaction/items | jq

# 외부 노출을 위해 Gateway 설정
cat ch5/catalog-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: catalog-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "catalog.istioinaction.io"

kubectl apply -f ch5/catalog-gateway.yaml -n istioinaction

# 트래픽을 catalog 서비스로 라우팅하는 VirtualService 리소스 설정
cat ch5/catalog-vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - route:
    - destination:
        host: catalog

kubectl apply -f ch5/catalog-vs.yaml -n istioinaction

# 확인
kubectl get gw,vs -n istioinaction

NAME                                          AGE
gateway.networking.istio.io/catalog-gateway   95s

NAME                                                    GATEWAYS              HOSTS                          AGE
virtualservice.networking.istio.io/catalog-vs-from-gw   ["catalog-gateway"]   ["catalog.istioinaction.io"]   3s


# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3


# istio-ingressgateway Service(NodePort)에 포트 정보 확인
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
[
  {
    "name": "status-port",
    "nodePort": 31674,
    "port": 15021,
    "protocol": "TCP",
    "targetPort": 15021
  },
  {
    "name": "http2",
    "nodePort": 30000, # 순서1
    "port": 80,        
    "protocol": "TCP",
    "targetPort": 8080 # 순서2
  },
  {
    "name": "https",
    "nodePort": 30005,
    "port": 443,
    "protocol": "TCP",
    "targetPort": 8443
  }
]

# 호스트에서 NodePort(Service)로 접속 확인
curl -v -H "Host: catalog.istioinaction.io" http://localhost:30000
kubectl stern -l app=catalog -n istioinaction

open http://localhost:30000
open http://catalog.istioinaction.io:30000
open http://catalog.istioinaction.io:30000/items


# 신규 터미널 : 반복 접속 실행 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items/ ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 0.5; echo; done

 

위와 같이 카탈로그 서비스를 배포한다.

 

그리고 v2도 배포한다.

# catalog 서비스 v2 를 배포 : v2에서는 imageUrl 필드가 추가
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction

#
kubectl get deploy -n istioinaction --show-labels
NAME         READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
catalog      1/1     1            1           30m   app=catalog,version=v1
catalog-v2   1/1     1            1           34s   app=catalog,version=v2

kubectl get pod -n istioinaction -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
catalog-6cf4b97d-ftl77        2/2     Running   0          43m   10.10.0.14   myk8s-control-plane   <none>           <none>
catalog-v2-6df885b555-6hmcl   2/2     Running   0          13m   10.10.0.15   myk8s-control-plane   <none>           <none>

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
catalog-6cf4b97d-ftl77.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
catalog-v2-6df885b555-6hmcl.istioinaction             Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
istio-ingressgateway-996bc6bb6-zvtdc.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8


# 호출 테스트 : v1 , v2 호출 확인
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done


# istio-ingressgateway proxy-config 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295,
                    "trackRemaining": true
                }
            ]
        },
        "commonLbConfig": {
            "localityWeightedLbConfig": {}
        },
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...
   {
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "addedViaApi": true,
        "hostStatuses": [
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.14",
                        "portValue": 3000
                    }
                },
                "stats": [
                    {
                        "name": "cx_connect_fail"
                    },
                    {
                        "value": "8",
                        "name": "cx_total"
                    },
                    {
                        "name": "rq_error"
                    },
                    {
                        "value": "315",
                        "name": "rq_success"
                    },
                    {
                        "name": "rq_timeout"
                    },
                    {
                        "value": "315",
                        "name": "rq_total"
                    },
                    {
                        "type": "GAUGE",
                        "value": "8",
                        "name": "cx_active"
                    },
                    {
                        "type": "GAUGE",
                        "name": "rq_active"
                    }
                ],
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },
                "weight": 1,
                "locality": {}
            },
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.15",
                        "portValue": 3000
                    }
                },
                "stats": [
                    {
                        "name": "cx_connect_fail"
                    },
                    {
                        "value": "8",
                        "name": "cx_total"
                    },
                    {
                        "name": "rq_error"
                    },
                    {
                        "value": "308",
                        "name": "rq_success"
                    },
                    {
                        "name": "rq_timeout"
                    },
                    {
                        "value": "308",
                        "name": "rq_total"
                    },
                    {
                        "type": "GAUGE",
                        "value": "8",
                        "name": "cx_active"
                    },
                    {
                        "type": "GAUGE",
                        "name": "rq_active"
                    }
                ],
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },
                "weight": 1,
                "locality": {}
            }
        ],
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295
                },
                {
                    "priority": "HIGH",
                    "maxConnections": 1024,
                    "maxPendingRequests": 1024,
                    "maxRequests": 1024,
                    "maxRetries": 3
                }
            ]
        },
        "observabilityName": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "edsServiceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
    },
...

# catalog proxy-config 도 직접 확인해보자

 

 

 

5.2.4 Routing all traffic to v1 of the catalog service

  • 모든 트래픽을 catalog v1 으로 라우팅. 이것이 다크런치를 시작하기 전의 일반적인 트래픽 패턴이다.
  • 어느 워크로드가 v1, v2 인지 이스티오에게 힌트를 줘야한다.
  • catalog v1은 deployment 리소스에서 레이블 app:catalog, version:v1 을 사용한다.
  • catalog v2은 deployment 리소스에서 레이블 app:catalog, version:v2 을 사용한다.
  • 이스티오에게는 이렇게 다른 버전들의 부분집합 subset 으로 지정하는 DestinationRule 을 만들어준다.
#
kubectl get pod -l app=catalog -n istioinaction --show-labels
NAME                          READY   STATUS    RESTARTS   AGE   LABELS
catalog-6cf4b97d-ftl77        2/2     Running   0          56m   app=catalog,...,version=v1
catalog-v2-6df885b555-6hmcl   2/2     Running   0          26m   app=catalog,...,version=v2

#
cat ch5/catalog-dest-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: catalog
spec:
  host: catalog.istioinaction.svc.cluster.local
  subsets:
  - name: version-v1
    labels:
      version: v1
  - name: version-v2
    labels:
      version: v2

kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction

# 확인
kubectl get destinationrule -n istioinaction
NAME      HOST                                      AGE
catalog   catalog.istioinaction.svc.cluster.local   8s


# catalog proxy-config 확인 : SUBSET(v1, v2, -) 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
...

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v2 -o json
...
        "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
        },
...

# 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
10.10.0.16:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
10.10.0.17:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local



# VirtualService 수정 (subset 추가)
cat ch5/catalog-vs-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v1.yaml -n istioinaction

# 호출 테스트 : v1
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done


# 세부 정보 확인
# routes 에 virtualHosts 항목에 routes.route 에 cluster 부분이 ...version-v1... 설정 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.io:80",
                "domains": [
                    "catalog.istioinaction.io"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
...

# cluster 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1
SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
        },
        ...
        "metadata": {
            "filterMetadata": {
                "istio": {
                    "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                    "default_original_port": 80,
                    "services": [
                        {
                            "host": "catalog.istioinaction.svc.cluster.local",
                            "name": "catalog",
                            "namespace": "istioinaction"
                        }
                    ],
                    "subset": "version-v1"
...

# endpoint 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
...

# istio-proxy (catalog)
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --subset version-v1 -o json
...
        "metadata": {
            "filterMetadata": {
                "istio": {
                    "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                    "default_original_port": 80,
                    "services": [
                        {
                            "host": "catalog.istioinaction.svc.cluster.local",
                            "name": "catalog",
                            "namespace": "istioinaction"
                        }
                    ],
                    "subset": "version-v1"
                }
            }
        },
...

 

 

destination rule에 의해 subset을 기반으로 catalog 로만 트래픽이 간다.

 

5.2.5 Routing specific requests to v2

http 요청 헤더를 기반으로 v2로 보내는 방법을 알아본다.

#
cat ch5/catalog-vs-v2-request.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - match:
    - headers:
        x-istio-cohort:
          exact: "internal"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v2-request.yaml -n istioinaction

# 호출 테스트 : 여전히 v1
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done

# 요청 헤더 포함 호출 테스트 : v2!
curl http://catalog.istioinaction.io:30000/items -H "x-istio-cohort: internal"

# (옵션) 신규 터미널 : v2 반복 접속
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# 상세 확인
# route 추가 : routes 에 2개의 route 확인 - 위에서 부터 적용되는 순서 중요!
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080
NAME          DOMAINS                      MATCH     VIRTUAL SERVICE
http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction
http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.io:80",
                "domains": [
                    "catalog.istioinaction.io"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/",
                            "caseSensitive": true,
                            "headers": [
                                {
                                    "name": "x-istio-cohort",
                                    "stringMatch": {
                                        "exact": "internal"
                                    }
                                }
                            ]
                        },
                        "route": {
                            "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                ...
                   {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'

# istio-proxy (catalog)에는 routes 정보가 아래 cluster 로 보내는 1개만 있다. 즉 istio-proxy(istio-ingressgateway)가 routes 분기 처리하는 것을 알 수 있다.
## "cluster": "outbound|80||catalog.istioinaction.svc.cluster.local"
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog
80                                                            catalog, catalog.istioinaction + 1 more...          /*

위와같이 헤더를 포함하여 접속하게 되면

 

위와같이 일반 트래픽(0.1초 sleep 접속)외에 2초에 한번씩 접속해둔 트래픽이 흐르는것을 볼 수 있다.

 

 

 

5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)

 

이 스크린샷은 Istio를 사용해 마이크로서비스 아키텍처에서 트래픽을 세밀하게 제어하는 방법을 설명하고 있습니다.

  • 왼쪽의 Istio ingress gateway는 외부에서 들어오는 요청을 받아 webapp service로 전달합니다.
  • webapp service는 내부적으로 catalog 서비스의 두 버전(v1, v2) 중 하나를 호출합니다.
  • 여기서 중요한 점은, 트래픽 라우팅(즉, 어떤 요청이 catalog v1으로 갈지, v2로 갈지 결정하는 것)이 반드시 ingress gateway에서만 일어나는 것이 아니라, 서비스 간 호출(예: webapp → catalog)처럼 호출 그래프 내의 더 깊은 위치에서도 적용될 수 있다는 점입니다.
  • 즉, Istio의 VirtualService 같은 리소스를 활용하면, webapp에서 catalog로 가는 요청 중에서도 요청의 내용(예: 헤더, 경로, 파라미터 등)에 따라 세밀하게 트래픽을 분기할 수 있습니다.
  • 이런 방식으로, 예를 들어 특정 조건을 만족하는 요청만 catalog v2로 보내고, 나머지는 v1으로 보내는 식의 "미세 조정된 트래픽 제어"가 가능합니다.

즉, 이 그림과 설명은 Istio가 단순히 외부 트래픽만 제어하는 게 아니라, 서비스 간 내부 호출까지도 세밀하게 트래픽을 라우팅할 수 있음을 보여줍니다.

# 초기화
kubectl delete gateway,virtualservice,destinationrule --all -n istioinaction

# webapp 기동
kubectl apply -n istioinaction -f services/webapp/kubernetes/webapp.yaml
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태

# 확인
kubectl get deploy,pod,svc,ep -n istioinaction
NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/catalog      1/1     1            1           55m
deployment.apps/catalog-v2   1/1     1            1           48m
deployment.apps/webapp       1/1     1            1           42s

NAME                              READY   STATUS    RESTARTS   AGE
pod/catalog-6cf4b97d-jxpb8        2/2     Running   0          55m
pod/catalog-v2-6df885b555-rg9f5   2/2     Running   0          48m
pod/webapp-7685bcb84-2q7rg        2/2     Running   0          42s

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.200.1.254   <none>        80/TCP    55m
service/webapp    ClusterIP   10.200.1.61    <none>        80/TCP    42s

NAME                ENDPOINTS                         AGE
endpoints/catalog   10.10.0.16:3000,10.10.0.17:3000   55m
endpoints/webapp    10.10.0.18:8080                   42s


# Now, set up the Istio ingress gateway to route to the webapp service
cat services/webapp/istio/webapp-catalog-gw-vs.yaml
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webapp-virtualservice
spec:
  hosts:
  - "webapp.istioinaction.io"
  gateways:
  - coolstore-gateway
  http:
  - route:
    - destination:
        host: webapp
        port:
          number: 80

kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction

# 확인
kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   3s

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


# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3


# 호출테스트 : 외부(web, curl) → ingressgw → webapp → catalog (v1, v2)
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq

# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 해두기
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# proxy-config : istio-ingressgateway
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 
NAME          DOMAINS                     MATCH     VIRTUAL SERVICE
http.8080     webapp.istioinaction.io     /*        webapp-virtualservice.istioinaction
=> route."cluster": "outbound|80||webapp.istioinaction.svc.cluster.local"

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system | egrep 'webapp|catalog'
catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS            
webapp.istioinaction.svc.cluster.local                  80        -          outbound      EDS

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||webapp.istioinaction.svc.cluster.local",
        "type": "EDS",
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||webapp.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:8080     HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local


# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction

# proxy-config : catalog
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes 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


# webapp istio-proxy 로그 활성화
# 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# webapp istio-proxy 로그 활성화 적용
cat << EOF | kubectl apply -f -
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: webapp
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  accessLogging:
  - providers:
    - name: envoy #2 액세스 로그를 위한 프로바이더 설정
    disabled: false #3 disable 를 false 로 설정해 활성화한다
EOF

# webapp → catalog 는 k8s service(clusterIP) 라우팅 사용 확인!
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-18T13:27:57.178Z] "HEAD /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 0 8 8 "172.18.0.1" "curl/8.7.1" "8d425652-17a9-4b41-a21c-874acab3b1f4" "webapp.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:51809 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
=> 이 로그는 webapp 서비스의 사이드카 프록시가 클라이언트로부터 직접 HTTP 요청을 받은 장면이고, 이 요청을 10.10.0.18:8080 (즉, webapp 서비스의 실제 컨테이너)으로 보냄을 의미
[2025-04-18T13:27:58.237Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "172.18.0.1" "beegoServer" "49b55b86-2505-4a5c-aadf-950d03705b87" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.18:45152 10.200.1.254:80 172.18.0.1:0 - default
=>  이 로그는 webapp 서비스가 catalog 서비스로 HTTP 요청을 보낸 상황이에요. Envoy는 catalog.istioinaction이라는 Kubernetes catalog 서비스(clusterIp 10.200.1.254:80)로 라우팅하고, 실제 Pod IP는 10.10.0.16:3000으로 연결되었어요.
...

 

위와같이 로그 활성화까지 진행한다.

 

위와 같이 catalog VS에서 트래픽을 분산시킬 수 있다.

 

#
cat ch5/catalog-vs-v2-request-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
    - mesh
  http:
  - match:
    - headers:
        x-istio-cohort:
          exact: "internal"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v2-request-mesh.yaml -n istioinaction


# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 >> v1, v2 각기 라우팅 확인
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 추가된 2개 항목 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json | jq
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.svc.cluster.local:80",
                "domains": [
                    "catalog.istioinaction.svc.cluster.local",
                    "catalog",
                    "catalog.istioinaction.svc",
                    "catalog.istioinaction",
                    "10.200.1.254"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/",
                            "caseSensitive": true,
                            "headers": [
                                {
                                    "name": "x-istio-cohort",
                                    "stringMatch": {
                                        "exact": "internal"
                                    }
                                }
                            ]
                        },
                        "route": {
                            "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                     ....
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
...


docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'

# proxy-config (catalog) : mesh 이므로 VS가 아래 routes(catalog)에도 적용됨
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes 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

 

 

catalog VS에서 catalog-v2 파드로 트래픽이 흐르게끔 할 수 있다. 

 

 

 

5.3 Traffic shifting (실습)

Istio의 가중치 기반 라우팅을 활용해 신규 버전(catalog v2)에 트래픽을 10%씩 점진적으로 분배함으로써 카나리 릴리스를 수행하고, 문제 발생 시 가중치 조정으로 빠르게 롤백할 수 있어 배포 위험을 최소화합니다.

 

# 이전 절부터 다음 서비스가 실행 중이다 : 파드에 labels 에 버전 정보 없을 경우 latest 설정되며, kiali 에 Workloads Details 에 'Missing Version' 표기됨
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태
kubectl get deploy,rs,pod -n istioinaction --show-labels
NAME                          READY   STATUS    RESTARTS   AGE     LABELS
catalog-6cf4b97d-q4xv5        2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v1,version=v1
catalog-v2-6df885b555-df7g4   2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v2,version=v2
webapp-7685bcb84-skzgg        2/2     Running   0          2m48s   app=webapp,...,service.istio.io/canonical-revision=latest

# 반복 호출테스트 : 신규터미널
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 모든 트래픽을 catalog service v1 으로 재설정하자
cat ch5/catalog-vs-v1-mesh.yaml
...
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction

# 호출테스트
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq


#
cat ch5/catalog-vs-v2-10-90-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1
      weight: 90
    - destination:
        host: catalog
        subset: version-v2
      weight: 10 
kubectl apply -f ch5/catalog-vs-v2-10-90-mesh.yaml -n istioinaction

#
kubectl get vs -n istioinaction catalog
NAME      GATEWAYS   HOSTS         AGE
catalog   ["mesh"]   ["catalog"]   112s

# 호출 테스트 : v2 호출 비중 확인
for i in {1..10};  do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l

# proxy-config(webapp) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
...
                        "route": {
                            "weightedClusters": {
                                "clusters": [
                                    {
                                        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                        "weight": 90
                                    },
                                    {
                                        "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                        "weight": 10
                                    }
                                ],
                                "totalWeight": 100
...

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog


# proxy-config(catalog) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
...

 

 

 

9:1 로 트래픽이 맞춰지는걸 볼 수 있다.

 

  • 각 서비스 버전의 트래픽 가중치를 1에서 100 사이로 바꿀 수 있지만, 가중치 총합은 반드시 100이어야 한다.
  • 그렇지 않으면 예기치 못한 트래픽 라우팅이 발생할 수 있다.
  • v1, v2 외 다른 버전이 있을 경우, DestinationRule 에서 subnet 으로 선언해야 한다는 것도 유념하자.

5.3.1 (Automating) Canary releasing with Flagger (Progressive Delivery Operator for Kubernetes) - Link

Flagger는 Istio와 연동해 카나리 배포를 자동화하는 도구로, 릴리스 진행, 트래픽 분배, 모니터링, 롤백까지 모든 과정을 메트릭 기반으로 자동 처리하여 운영자의 수작업과 실수 가능성을 줄여줍니다

 

실습을 진행하기 위해 다음과 같이 기존 셋팅은 삭제한다.

# catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 제거
kubectl delete virtualservice catalog -n istioinaction
kubectl delete deploy catalog-v2 -n istioinaction
kubectl delete service catalog -n istioinaction
kubectl delete destinationrule catalog -n istioinaction

# 남은 리소스 확인
kubectl get deploy,svc,ep -n istioinaction
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/catalog   1/1     1            1           77m
deployment.apps/webapp    1/1     1            1           78m

NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/webapp   ClusterIP   10.200.1.73   <none>        80/TCP    78m

NAME               ENDPOINTS         AGE
endpoints/webapp   10.10.0.19:8080   78m

kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   73m

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

 

  • Flagger는 서비스 상태를 판단할 때 메트릭에 의존하며, 카나리 릴리스를 사용할 때 특히 그렇다.
  • Flagger가 성공 메트릭을 사용하려면 프로메테우스를 설치해 이스티오 데이터 플레인수집해야 한다.

Flagger를 다음과 같이 설치한다.

# CRD 설치 
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
kubectl get crd | grep flagger
alertproviders.flagger.app                 2025-04-19T03:11:50Z
canaries.flagger.app                       2025-04-19T03:11:50Z
metrictemplates.flagger.app                2025-04-19T03:11:50Z

# Helm 설치
helm repo add flagger https://flagger.app
helm install flagger flagger/flagger \
  --namespace=istio-system \
  --set crd.create=false \
  --set meshProvider=istio \
  --set metricServer=http://prometheus:9090

# 디플로이먼트 flagger 에 의해 배포된 파드 확인
kubectl get pod -n istio-system -l app.kubernetes.io/name=flagger
NAME                       READY   STATUS    RESTARTS   AGE
flagger-6d4ffc5576-q78ls   1/1     Running   0          2m54s

# 시크릿
kubectl get secret -n istio-system | grep flagger-token
flagger-token-v2f5z                                kubernetes.io/service-account-token   3      4m11s

# 시크릿 확인 : ca.crt 는 k8s 루프 인증서
kubectl view-secret -n istio-system flagger-token-v2f5z --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTI1MDQxOTAxNDEyMVoXDTM1MDQxNzAxNDEyMVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzN
onEbSSXBHfHhJICwREU4EX4D0K2Bwg7SXNwZNl3QwwPOpjFoaRbr6Hdog88jmo8A
Mo/RDKDj+Lvr0FE3hBvm5igLndWgnjYqpIHfDq31AYvWCoJvbBQ/omDIal4u1HHI
8XNqEpxl3JhsV9M9pMEx2+Gvlp1og8qjbB3B5amutriNQom6VOG0HBzJQuvNG8op
2GhWD4IOQf3vfKltGE9Y/KzbBLajtPueMkHZr/kH4Qy/Xu9kSGc8lhdsxrRSqoTX
ytyr2rOe83vliKhGKYtkiWESIm35BcVF1rp+jl0nLGs8IMhmR5Ll9A9pZ5xsqFzN
ndg7DjpdcKKwCxzw9BsCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFPm+n2+NblRv8ZWaoVW4fMvmFzuNMBUGA1UdEQQO
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAFkDbnnWS+9u9AKnlih0
Cltedk01oId5I31SWzvfI1owgudBH6pGxs3dGeij5iNDlV2StDzG5mqUIW6Y5iXR
hVMUUl/GvscaoSqFb5cIEgfmzdDSsNm1eBON8HpoN4VuEyuRZn1O39JAYIzVQcwD
LgO/dTCZwqM6hs6LC2Qx/PlxlQLt3agT3sZWXkztbOjLpLCuqVrz0NIRzFS3M2hA
b1+ACPllYGIRiEpSNxzbybgjui4W8bt8W2AjTPuqXIB/TuEcQrgAzrVcQsVf2XID
nC+WEpmUURKxQ51dLpLLQgFfojz+LXrdrlGJ4hpcfj0aN5j221+rjHTnI6PrsQdT
qME=
-----END CERTIFICATE-----
'
namespace='istio-system'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InFIUnV5blBfSUVDaDQ0MUxyOXR2MFRqV1g5ekVjaU1wdWZvSDFXZXl6Z3cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJpc3Rpby1zeXN0ZW0iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZmxhZ2dlci10b2tlbi12MmY1eiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJmbGFnZ2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNWIxZWM4MjUtODU4My00OGViLWI4MGMtNmYyNzEzZTBlMzA3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmlzdGlvLXN5c3RlbTpmbGFnZ2VyIn0.Eb14h5EKU9FfYZa3XkydrFFYUSk4gPYUM0j76Cbsb4XTtAL0U54-RfMiNcX5rfyK6WFOUhU5W6yuAChRhsl7TEzZCpgj3aVRNNe5TRsy-mYpG89FfBSpU0H6wmZyJnvHDcweo1eh-BLIThH6-_1GuUeJDc18WsapllkcHNIXiR_7gudgY7tfn29KoKxlv72K_HPYIerIsTGZe9tHr7K__lvl0Yz779yKNXKUlSerqho0-z2cPsmhFRR1KvPwrhi6UQck70s_snMlaecVJvkrYXCnEvsMkUwpaa6JmDmamKC3NNm9zWJYKtEt0fHHomZoJQFHHQCiYDTVkjYi8ErE2A'

# token 을 jtw.io 에서 Decoded 확인
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "istio-system",
  "kubernetes.io/serviceaccount/secret.name": "flagger-token-v2f5z",
  "kubernetes.io/serviceaccount/service-account.name": "flagger",
  "kubernetes.io/serviceaccount/service-account.uid": "5b1ec825-8583-48eb-b80c-6f2713e0e307",
  "sub": "system:serviceaccount:istio-system:flagger"
}

 

그리고 Flagger 카나리 리소스를 사용해서 카나리 릴리스에 대한 설정을 정의한다

  • 이 Canary 리소스에서는 어떤 쿠버네티스 Deployment가 카나리 대상인지, 어떤 쿠버네티스 Service와 이스티오 VirtualService가 자동으로 만들어져야 하는지, 카나리를 어떻게 진행해야 하는지 등을 지정한다.
  • Canary 리소스의 마지막 부분은 카나리를 얼마나 빨리 진행할지, 생존을 판단하기 위해 지켜볼 메트릭은 무엇인지, 성공을 판단할 임계값은 얼마인지를 기술하고 있다.
  • 45초마다 카나리의 각 단계를 평가하고, 단계별로 트래픽을 10%씩 늘린다. 트래픽이 **50%에 도달하면 100%**로 바꾼다.
  • 성공률 메트릭의 경우 1분 동안의 성공률이 99% 이상이어야 한다. 또한 P99(상위 99%) 요청 시간은 500ms까지 허용한다.
  • 이 메트릭들이 연속으로 5회를 초과해 지정한 범위와 다르면, 롤백한다.
#  cat ch5/flagger/catalog-release.yaml        
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: catalog-release
  namespace: istioinaction
spec:   
  targetRef: #1 카나리 대상 디플로이먼트 https://docs.flagger.app/usage/how-it-works#canary-target
    apiVersion: apps/v1
    kind: Deployment
    name: catalog  
  progressDeadlineSeconds: 60
  # Service / VirtualService Config
  service: #2 서비스용 설정 https://docs.flagger.app/usage/how-it-works#canary-service
    name: catalog
    port: 80
    targetPort: 3000
    gateways:
    - mesh    
    hosts:
    - catalog
  analysis: #3 카니리 진행 파라미터 https://docs.flagger.app/usage/how-it-works#canary-analysis
    interval: 45s
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    match: 
    - sourceLabels:
        app: webapp
    metrics: # https://docs.flagger.app/usage/metrics , https://docs.flagger.app/faq#metrics
    - name: request-success-rate # built-in metric 요청 성공률
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration # built-in metric 요청 시간
      thresholdRange:
        max: 500
      interval: 30s
      
      
      
# 반복 호출테스트 : 신규터미널
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# flagger (operator) 가 catalog를 위한 canary 배포환경을 구성
kubectl apply -f ch5/flagger/catalog-release.yaml -n istioinaction

# flagger 로그 확인 : Service, Deployment, VirtualService 등을 설치하는 것을 확인할 수 있습니다.
kubectl logs -f deploy/flagger -n istio-system
...

# 확인
kubectl get canary -n istioinaction -w
NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
catalog-release   Initializing  0        2025-04-19T05:10:00Z
catalog-release   Initialized   0        2025-04-19T05:15:54Z

kubectl get canary -n istioinaction -owide
NAME              STATUS        WEIGHT   SUSPENDED   FAILEDCHECKS   INTERVAL   MIRROR   STEPWEIGHT   STEPWEIGHTS   MAXWEIGHT   LASTTRANSITIONTIME
catalog-release   Initialized   0                    0              45s                 10                         50          2025-04-19T05:15:54Z

# flagger Initialized 동작 확인
## catalog-primary deployment/service 가 생성되어 있음, 기존 catalog deploy/service 는 파드가 0으로 됨
kubectl get deploy,svc,ep -n istioinaction -o wide

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                         SELECTOR
deployment.apps/catalog           0/0     0            0           3h34m   catalog      istioinaction/catalog:latest   app=catalog,version=v1
deployment.apps/catalog-primary   1/1     1            1           6m41s   catalog      istioinaction/catalog:latest   app=catalog-primary
deployment.apps/webapp            1/1     1            1           3h36m   webapp       istioinaction/webapp:latest    app=webapp

NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
service/catalog           ClusterIP   10.200.1.168   <none>        80/TCP    5m56s   app=catalog-primary
service/catalog-canary    ClusterIP   10.200.1.242   <none>        80/TCP    6m41s   app=catalog
service/catalog-primary   ClusterIP   10.200.1.119   <none>        80/TCP    6m41s   app=catalog-primary
service/webapp            ClusterIP   10.200.1.73    <none>        80/TCP    3h36m   app=webapp

NAME                        ENDPOINTS         AGE
endpoints/catalog           10.10.0.23:3000   5m56s
endpoints/catalog-canary    <none>            6m41s
endpoints/catalog-primary   10.10.0.23:3000   6m41s
endpoints/webapp            10.10.0.19:8080   3h36m

## VS catalog 생성되었음
kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   137m

NAME                                                       GATEWAYS                HOSTS                         AGE
virtualservice.networking.istio.io/catalog                 ["mesh"]                ["catalog"]                   8m17s
virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   137m

## VS catalog 확인
kubectl get vs -n istioinaction catalog -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  annotations:
    helm.toolkit.fluxcd.io/driftDetection: disabled
    kustomize.toolkit.fluxcd.io/reconcile: disabled
  name: catalog
  namespace: istioinaction
spec:
  gateways:
  - mesh
  hosts:
  - catalog
  http:
  - match:
    - sourceLabels:
        app: webapp
    route:
    - destination:
        host: catalog-primary
      weight: 100
    - destination:
        host: catalog-canary
      weight: 0
  - route:
    - destination:
        host: catalog-primary
      weight: 100

# destinationrule 확인
kubectl get destinationrule -n istioinaction
NAME              HOST              AGE
catalog-canary    catalog-canary    15m
catalog-primary   catalog-primary   15m

kubectl get destinationrule -n istioinaction catalog-primary -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: catalog-primary
  namespace: istioinaction
spec:
  host: catalog-primary

kubectl get destinationrule -n istioinaction catalog-canary -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: catalog-canary
  namespace: istioinaction
spec:
  host: catalog-canary

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 
NAME     DOMAINS                                                        MATCH     VIRTUAL SERVICE
80       catalog-canary, catalog-canary.istioinaction + 1 more...       /*        
80       catalog-primary, catalog-primary.istioinaction + 1 more...     /*        
80       catalog, catalog.istioinaction + 1 more...                     /*        catalog.istioinaction
...


docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | egrep 'RULE|catalog'
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
catalog-canary.istioinaction.svc.cluster.local          80        -          outbound      EDS              catalog-canary.istioinaction
catalog-primary.istioinaction.svc.cluster.local         80        -          outbound      EDS              catalog-primary.istioinaction
catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS 

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-primary.istioinaction.svc.cluster.local -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-canary.istioinaction.svc.cluster.local -o json

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog-primary.istioinaction.svc.cluster.local
10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# 해당 EDS에 메트릭 통계 값 0.
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...

# 현재 EDS primary 에 메트릭 통계 값 출력 중. 해당 EDS 호출.
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog-primary.istioinaction.svc.cluster.local' -o json
...

 

 

 

  • 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않았다.
  • Flagger는 원본 디폴로이먼트 대상(여기서는 catalog 디플로이먼트 ?catalog-primary가 아닌가?)의 변경 사항을 지켜보고, 카나리 디폴로이먼트(catalog-canary) 및 서비스(catalog-canary)를 생성하고, VirtualService 의 가중치를 조정한다.

 

 

# 반복 호출테스트 : 신규터미널1 - 부하 만들기
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# flagger 로그 확인 : 신규터미널2
kubectl logs -f deploy/flagger -n istio-system
{"level":"info","ts":"2025-04-19T05:15:54.453Z","caller":"controller/events.go:33","msg":"Initialization done! catalog-release.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:09.442Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:09:09.444Z","caller":"controller/events.go:33","msg":"New revision detected! Scaling up catalog.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:54.441Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:09:54.446Z","caller":"controller/events.go:33","msg":"Starting canary analysis for catalog.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:54.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 10","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:10:39.443Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:10:39.469Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 20","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:11:24.437Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:11:24.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 30","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:12:09.445Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:12:09.472Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 40","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:12:54.429Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:12:54.445Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 50","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:13:39.444Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:13:39.453Z","caller":"controller/events.go:33","msg":"Copying catalog.istioinaction template spec to catalog-primary.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:14:24.438Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:14:24.440Z","caller":"controller/events.go:33","msg":"Routing all traffic to primary","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:15:09.436Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:15:09.642Z","caller":"controller/events.go:33","msg":"Promotion completed! Scaling down catalog.istioinaction","canary":"catalog-release.istioinaction"}

# flagger 상태 확인 : 신규터미널3
## 카나리는 Carary 오브젝트에 설정한 대로 45초마다 진행될 것이다.
## 트래픽의 50%가 카나리로 이동할 때까지는 단계별로 10%씩 증가한다.
## flagger가 메트릭에 문제가 없고 기준과 차이가 없다고 판단되면, 모든 트래픽이 카나리로 이동해 카나리가 기본 서비스로 승격 될 때까지 카나리가 진행된다.
## 만약 문제가 발생하면 flagger는 자동으로 카나리 릴리스를 롤백할 것이다.
kubectl get canary -n istioinaction -w
NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
catalog-release   Initialized   0        2025-04-19T05:15:54Z
catalog-release   Progressing   0        2025-04-19T06:09:09Z
catalog-release   Progressing   10       2025-04-19T06:09:54Z # 45초 간격
catalog-release   Progressing   20       2025-04-19T06:10:39Z
catalog-release   Progressing   30       2025-04-19T06:11:24Z
catalog-release   Progressing   40       2025-04-19T06:12:09Z
catalog-release   Progressing   50       2025-04-19T06:12:54Z
catalog-release   Promoting     0        2025-04-19T06:13:39Z
catalog-release   Finalising    0        2025-04-19T06:14:24Z
catalog-release   Succeeded     0        2025-04-19T06:15:09Z


# imageUrl 출력 (v2)을 포함하는 catalog deployment v2 배포
cat ch5/flagger/catalog-deployment-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: catalog
    version: v1
  name: catalog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: catalog
      version: v1
  template:
    metadata:
      labels:
        app: catalog
        version: v1
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: SHOW_IMAGE
          value: "true"
        image: istioinaction/catalog:latest
        imagePullPolicy: IfNotPresent
        name: catalog
        ports:
        - containerPort: 3000
          name: http
          protocol: TCP
        securityContext:
          privileged: false

kubectl apply -f ch5/flagger/catalog-deployment-v2.yaml -n istioinaction
	kubectl get vs -n istioinaction catalog -o yaml -w # catalog vs 에 가중치 변경 모니터링
	---
    route:
    - destination:
        host: catalog-primary
      weight: 90
    - destination:
        host: catalog-canary
      weight: 10
  - route:
    - destination:
        host: catalog-primary
      weight: 90
---
    route:
    - destination:
        host: catalog-primary
      weight: 80
    - destination:
        host: catalog-canary
      weight: 20
  - route:
    - destination:
        host: catalog-primary
      weight: 80
---
    route:
    - destination:
        host: catalog-primary
      weight: 70
    - destination:
        host: catalog-canary
      weight: 30
  - route:
    - destination:
        host: catalog-primary
      weight: 70
---
    route:
    - destination:
        host: catalog-primary
      weight: 60
    - destination:
        host: catalog-canary
      weight: 40
  - route:
    - destination:
        host: catalog-primary
      weight: 60
---
    route:
    - destination:
        host: catalog-primary
      weight: 50
    - destination:
        host: catalog-canary
      weight: 50
  - route:
    - destination:
        host: catalog-primary
      weight: 50
---
    route:
    - destination:
        host: catalog-primary
      weight: 100
    - destination:
        host: catalog-canary
      weight: 0
  - route:
    - destination:
        host: catalog-primary
      weight: 100
---
 
# canary CRD 이벤트 확인
kubectl describe canary -n istioinaction catalog-release | grep Events: -A20
Events:
  Type    Reason  Age    From     Message
  ----    ------  ----   ----     -------
  Normal  Synced  10m    flagger  New revision detected! Scaling up catalog.istioinaction
  Normal  Synced  9m54s  flagger  Starting canary analysis for catalog.istioinaction
  Normal  Synced  9m54s  flagger  Advance catalog-release.istioinaction canary weight 10
  Normal  Synced  9m9s   flagger  Advance catalog-release.istioinaction canary weight 20
  Normal  Synced  8m24s  flagger  Advance catalog-release.istioinaction canary weight 30
  Normal  Synced  7m39s  flagger  Advance catalog-release.istioinaction canary weight 40
  Normal  Synced  6m54s  flagger  Advance catalog-release.istioinaction canary weight 50
  Normal  Synced  6m9s   flagger  Copying catalog.istioinaction template spec to catalog-primary.istioinaction
  Normal  Synced  5m24s  flagger  Routing all traffic to primary
  Normal  Synced  4m39s  flagger  (combined from similar events): Promotion completed! Scaling down catalog.istioinaction

# 최종 v2 접속 확인
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
     100

 

위와같이 신규 버전을 배포하고 모니터링해본다.

 

 

 

 

 

 

자동으로 WEIGHT이 오르고 있다. 그리고 당연하게 kiali에서는 트래픽이 점점 많이 흐르는것기 확인된다.

 

 

 

5.4 Reducing risk even further: Traffic mirroring

트래픽 미러링은 실제 운영 환경 트래픽을 복사해 별도의 서비스(catalog-v2)로 전송하되, 사용자 응답에는 영향을 주지 않는 방식입니다. 이를 통해 신규 버전의 동작을 실제 트래픽으로 검증하면서도 서비스 중단 위험을 제로에 가깝게 관리할 수 있습니다.

 

 

# cat ch5/catalog-vs-v2-mirror.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
    - mesh
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1
      weight: 100
    mirror:
      host: catalog
      subset: version-v2
      
      
# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; don

# catalog istio-proxy 로그 활성화
cat << EOF | kubectl apply -f -
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: catalog
  namespace: istioinaction
spec:
  accessLogging:
  - disabled: false
    providers:
    - name: envoy
  selector:
    matchLabels:
      app: catalog
EOF

kubectl get telemetries -n istioinaction
NAME      AGE
catalog   16s
webapp    5h49m

# istio-proxy 로그 확인 : 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl logs -n istioinaction -l version=v1 -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -l version=v2 -c istio-proxy -f
혹은
kubectl stern -n istioinaction -l app=catalog -c istio-proxy

# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | grep catalog
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog

# proxy-config : catalog
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep catalog 


# 미러링 VS 설정
kubectl apply -f ch5/catalog-vs-v2-mirror.yaml -n istioinaction

# v1 으로만 호출 확인
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
       0

# v1 app 로그 확인
kubectl logs -n istioinaction -l app=catalog -l version=v1 -c catalog -f
request path: /items
blowups: {}
number of blowups: 0
GET catalog.istioinaction:80 /items 200 502 - 0.375 ms
GET /items 200 0.375 ms - 502
...

# v2 app 로그 확인 : 미러링된 트래픽이 catalog v2로 향할때, Host 헤더가 수정돼 미러링/섀도잉된 트래픽임을 나타낸다.
## 따라서 Host:catalog:8080 대신 Host:catalog-shadow:8080이 된다.
## -shadow 접미사가 붙은 요청을 받는 서비스는 그 요청이 미러링된 요청임을 식별할 수 있어, 요청을 처리할 때 고려할 수 있다
## 예를 들어, 응답이 버려질 테니 트랜잭션을 롤백하지 않거나 리소스를 많이 사용하는 호출을 하지 않는 것 등.
kubectl logs -n istioinaction -l app=catalog -l version=v2 -c catalog -f
request path: /items
blowups: {}
number of blowups: 0
GET catalog.istioinaction-shadow:80 /items 200 698 - 0.503 ms
GET /items 200 0.503 ms - 698


#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json
...
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    503
                                ]
                            },
                            "requestMirrorPolicies": [
                                {
                                    "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                    "runtimeFraction": {
                                        "defaultValue": {
                                            "numerator": 100
                                        }
                                    },
                                    "traceSampled": false
...


# 위 webapp과 상동 : 그거슨 mesh(gateway)이니까...
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json > webapp-routes.json
cat catalog-routes.json
...

 

catalog에 미러링 표시가 추가된것을 볼 수 있다.

 

# Istio 메시 내부망에서 모든 mTLS 통신 기능 끄기 설정 : (참고) 특정 네임스페이스 등 세부 조절 설정 가능 
cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: DISABLE
EOF

kubectl get PeerAuthentication -n istio-system
NAME      MODE      AGE
default   DISABLE   6h13m


--------------------------------------------------------------------------------
# catalog v2 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
## catalog v2 파드 IP 확인
C2IP=$(kubectl get pod -n istioinaction -l app=catalog -l version=v2 -o jsonpath='{.items[*].status.podIP}')
echo $C2IP

## veth 이름 확인
docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }'
C2VETH=$(docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }')
echo $C2VETH
veth853288c7 <- 해당 이름을 메모해두기
--------------------------------------------------------------------------------

# ngrep 확인(메모 정보 직접 기입!) : catalog v2 파드 tcp 3000
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d veth4ede8164 '' 'tcp port 3000'"
## 요청
T 2025/04/19 08:13:56.766981 10.10.0.19:49446 -> 10.10.0.27:3000 [AP] #19
GET /items HTTP/1.1.
host: catalog.istioinaction-shadow:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1,10.10.0.19.
x-forwarded-proto: http.
x-request-id: 769c8e1f-2a2a-4498-b98d-0683c7c46281.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: 8a650108ee32974ad419ff2948d9f8f2.
x-b3-spanid: 12c05a3f651b36fa.
x-b3-parentspanid: 2c77705aee579141.
x-b3-sampled: 0.
.

## 응답
T 2025/04/19 08:13:56.770458 10.10.0.27:3000 -> 10.10.0.19:49446 [AP] #20
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 698.
etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
date: Sat, 19 Apr 2025 08:13:56 GMT.
x-envoy-upstream-service-time: 2.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
...

 

위와같이 패킷덤프를 하기 위한 설정을 추가한다.

 

 

 

미러링된 대상으로 트래픽이 흐르는게 의아했다. 역시나 실습에서 잘 설명해준다.

--------------------------------------------------------------------------------
# webapp 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
## webapp 파드 IP 확인
WEBIP=$(kubectl get pod -n istioinaction -l app=webapp -o jsonpath='{.items[*].status.podIP}')
echo $WEBIP

## veth 이름 확인
docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }'
WEBVETH=$(docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }')
echo $WEBVETH
vetha33715c3 <- 해당 이름을 메모해두기
--------------------------------------------------------------------------------

# ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 8080 
## => 아래 tcp 3000에서 미러링 응답이 있지만 8080에 없다는건, webapp istio-proxy 가 최초 외부 요청자에게는 전달하지 않고 무시(drop?).
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 8080'"
## 요청 
T 2025/04/19 08:16:05.698957 10.10.0.8:39476 -> 10.10.0.19:8080 [AP] #10
HEAD /api/catalog HTTP/1.1.
host: webapp.istioinaction.io:30000.
user-agent: curl/8.7.1.
accept: */*.
x-forwarded-for: 172.18.0.1.
x-forwarded-proto: http.
x-envoy-internal: true.
x-request-id: a6cee7e5-277a-420d-a41f-78f7ca4266a4.
x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuOAoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi4KBE5BTUUSJhokaXN0aW8taW5ncmVzc2dhdGV3YXktOTk2YmM2YmI2LThzY3BwChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ==.
x-envoy-peer-metadata-id: router~10.10.0.8~istio-ingressgateway-996bc6bb6-8scpp.istio-system~istio-system.svc.cluster.local.
x-envoy-attempt-count: 1.
x-b3-traceid: d07b1cb671a13906948c4e8cdb04fc5f.
x-b3-spanid: 948c4e8cdb04fc5f.
x-b3-sampled: 0.
.

## 응답
T 2025/04/19 08:16:05.708158 10.10.0.19:8080 -> 10.10.0.8:39476 [AP] #11
HTTP/1.1 200 OK.
content-length: 357.
content-type: application/json; charset=utf-8.
date: Sat, 19 Apr 2025 08:16:05 GMT.
x-envoy-upstream-service-time: 8.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.


# ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 3000
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 3000'"
## webapp 파드가 catalog v1 요청
T 2025/04/19 08:19:17.965769 10.10.0.19:59682 -> 10.10.0.26:3000 [AP] #7
GET /items HTTP/1.1.
host: catalog.istioinaction:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1.
x-forwarded-proto: http.
x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: acea9966a116ec369261bf1007049ccf.
x-b3-spanid: a9db407fee14ea00.
x-b3-parentspanid: 88ea1c8edba97e58.
x-b3-sampled: 0.
.

## webapp 파드가 catalog v2 미러링 전달
T 2025/04/19 08:19:17.965805 10.10.0.19:49454 -> 10.10.0.27:3000 [AP] #8
GET /items HTTP/1.1.
host: catalog.istioinaction-shadow:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1,10.10.0.19.
x-forwarded-proto: http.
x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: acea9966a116ec369261bf1007049ccf.
x-b3-spanid: 06a47967e15bebdf.
x-b3-parentspanid: a9db407fee14ea00.
x-b3-sampled: 0.
.

## catalog v1 에서 응답 받음
T 2025/04/19 08:19:17.968372 10.10.0.26:3000 -> 10.10.0.19:59682 [AP] #11
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 502.
etag: W/"1f6-ih2h+hDQ0yLLcKIlBvwkWbyQGK4".
date: Sat, 19 Apr 2025 08:19:17 GMT.
x-envoy-upstream-service-time: 1.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI2ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjEKDwoHdmVyc2lvbhIEGgJ2MQoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhhjYXRhbG9nLTZkNWI5YmJiNjYtdnpnNGoKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUQoFT1dORVISSBpGa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZwoXChFQTEFURk9STV9NRVRBREFUQRICKgAKGgoNV09SS0xPQURfTkFNRRIJGgdjYXRhbG9n.
x-envoy-peer-metadata-id: sidecar~10.10.0.26~catalog-6d5b9bbb66-vzg4j.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00"
  },
...

## catalog v2 에서 응답 받음
T 2025/04/19 08:19:17.968259 10.10.0.27:3000 -> 10.10.0.19:49454 [AP] #9
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 698.
etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
date: Sat, 19 Apr 2025 08:19:17 GMT.
x-envoy-upstream-service-time: 1.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
...

위와같이 확인해보면, 미러링된 파드로는 트래픽이 흐르고 처리는 하지만 실제로 드랍하는것을 알 수 있다.

 

 

 

5.5 Routing to services outside your cluster by using Istio’s service discovery

이스티오는 기본적으로 서비스 메시 밖으로 나가는 트래픽을 허용하지만, 정책을 변경해 외부 트래픽을 차단할 수 있다.

외부 트래픽 차단은 내부 서비스가 손상됐을 때 악의적 행위자가 외부로 데이터를 유출하는 것을 방지하는 심층 방어 전략이다.

그러나 프록시 우회 등 추가 위협에 대비해 3, 4계층 네트워크 차단 등 복합적인 보안 대책이 필요하다.

 

# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
                "meshConfig": {
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "enablePrometheusMerge": true
                },
...

# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml

# webapp 로그 : 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -

# 다음 명령을 실행해 이스티오의 기본값을 ALLOW_ANY 에서 REGISTRY_ONLY 로 바꾸자.
# 이느 서비스 메시 저장소에 명시적으로 허용된 경우(화이트 리스트)에만 트래픽이 메시를 떠나도록 허용하겠다는 의미다.
# 아래 설정 방법 이외에도 IstioOperator 로 설정을 변경하거나, istio-system 의 istio configmap 을 변경해도 됨.
# outboundTrafficPolicy 3가지 모드 : ALLOW_ANY (default) , REGISTRY_ONLY , ALLOW_LIST
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
y 

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

# 배포 확인
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
catalog-6d5b9bbb66-vzg4j.istioinaction                 Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
catalog-v2-6df885b555-n9nxw.istioinaction              Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
istio-ingressgateway-6bb8fb6549-s4pt8.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
webapp-7685bcb84-skzgg.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8


# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
Connecting to raw.githubusercontent.com (185.199.109.133:443)
wget: error getting response: Connection reset by peer
command terminated with exit code 1

# webapp 로그 : BlackHoleCluster 차단
# UH : NoHealthyUpstream - No healthy upstream hosts in upstream cluster in addition to 503 response code.
# https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -

# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn BlackHoleCluster -o json
[
    {
        "name": "BlackHoleCluster",
        "type": "STATIC",
        "connectTimeout": "10s"
    }
]

# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
                "meshConfig": {
                    "accessLogFile": "/dev/stdout",
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "enablePrometheusMerge": true,
                    "extensionProviders": [
                        {
                            "envoyOtelAls": {
                                "port": 4317,
                                "service": "opentelemetry-collector.istio-system.svc.cluster.local"
                            },
                            "name": "otel"
                        },
                        {
                            "name": "skywalking",
                            "skywalking": {
                                "port": 11800,
                                "service": "tracing.istio-system.svc.cluster.local"
                            }
                        }
                    ],
                    "outboundTrafficPolicy": {
                        "mode": "REGISTRY_ONLY"
                    }
                },

 

 

 

서비스 엔트리란

ServiceEntry는 이스티오 서비스 메시 내부 서비스들이 외부(클러스터 밖) 서비스나 메시 외부의 엔드포인트와 통신할 수 있도록, 해당 외부 서비스를 이스티오의 내부 서비스 레지스트리에 등록해주는 리소스입니다

# cat ch5/forum-serviceentry.yaml                                        
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: jsonplaceholder
spec:
  hosts:
  - jsonplaceholder.typicode.com
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS
  location: MESH_EXTERNAL
  
  
  
# forum 설치
cat services/forum/kubernetes/forum-all.yaml
kubectl apply -f services/forum/kubernetes/forum-all.yaml -n istioinaction

# 확인
kubectl get deploy,svc -n istioinaction -l app=webapp
docker exec -it myk8s-control-plane istioctl proxy-status


# webapp 웹 접속
open http://webapp.istioinaction.io:30000/

 

 

 

이때 curl -s http://webapp.istioinaction.io:30000/api/users 명령어로 포럼 서비스를 호출하면 포럼 서비스에 서비스 엔트리 설정 적용 안돼있으니 아래와 같이 에러가 난다.

 

 

서비스엔트리 설정을 추가해보자.

# istio proxy (forum)
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-1.json

#
cat ch5/forum-serviceentry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: jsonplaceholder
spec:
  hosts:
  - jsonplaceholder.typicode.com
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS
  location: MESH_EXTERNAL
  
kubectl apply -f ch5/forum-serviceentry.yaml -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-2.json
diff forum-1.json forum-2.json
25a26
> jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS       
96a98
> 80                                                            jsonplaceholder.typicode.com                       /*  

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction | grep json
80                                                            jsonplaceholder.typicode.com                       /* 

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction --fqdn jsonplaceholder.typicode.com -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction | grep json
jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS  

# 목적 hosts 의 도메인 질의 응답 IP로 확인된 엔드포인트들 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction --cluster 'outbound|80||jsonplaceholder.typicode.com'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
104.21.112.1:80     HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.16.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.32.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.48.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.64.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.80.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.96.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com


# 메시 안에서 새로운 포럼 서비스를 호출 : 사용자 목록 반환 OK
curl -s http://webapp.istioinaction.io:30000/api/users


# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/ ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# webapp 웹 접속
open http://webapp.istioinaction.io:30000/

 

 

외부에서 잘 가져와는지는것을 확인할 수 있따.

 

 

 

5장에 대한 요약

 

  • DestinationRule 로 워크로드를 v1, v2 버전과 같이 더 작은 부분집합들로 분리할 수 있다.
  • VirtualService는 이런 부분집합들을 사용해 트래픽을 세밀하게 라우팅한다.
  • VirtualService는 HTTP 헤더 같은 애플리케이션 계층 정보를 기반으로 라우팅 결정을 설정한다.
    • 이 덕분에 베타 테스터 같은 특정 사용자 집합을 서비스의 신 버전으로 보내 테스트하는 다크 런치 기법을 사용할 수 있다.
  • 가중치 라우팅(VirtualService 리소스로 설정)을 사용하는 서비스 프록시는 트래픽을 점진적으로 새 배포로 라우팅할 수 있는데, 덕분에 카나리 배포(트래픽 전환이라고도 함) 같은 방법을 사용할 수 있다.
  • 트래픽 전환은 Flagger를 사용해 자동화할 수 있다. Flagger는 수집한 메트릭을 사용해 새 배포로 라우팅되는 트래픽을 점진적으로 늘리는 오픈소스 솔루션이다.
  • outboundTrafficPolicyREGISTER_ONLY로 설정하면 어떤 트래픽도 클러스터를 떠나지 못하게 함으로써 악의적인 사용자가 외부로 정보를 전송하는 것을 방지할 수 있다.
  • outboundTrafficPolicyREGISTER_ONLY로 설정했을 때는 ServiceEntry로 외부로 향하는 트래픽을 허용할 수 있다.

+ Recent posts