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

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

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

 

 

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

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

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


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

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

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

 

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

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

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

 

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

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

 

 

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

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

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

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

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

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

메트픽 설명

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

 

 

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

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

 

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

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

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

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


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

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


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

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

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

 

 

 

쪼끔 올라가긴 한다.

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

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

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

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


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

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

kubectl get gw,vs -n istioinaction
...

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

202개씩 생성..

 

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

 

#!/bin/bash

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

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

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

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

  echo "Pre Pushes: $PRE_PUSHES"

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

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

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

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

  sleep 10

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

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

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

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

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

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

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

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

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

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

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

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


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

 

부하가 발생중이다.

 

 

#딜레이없이 실행

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

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

 

 

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

 

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

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

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

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

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

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

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

 

 

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

 

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

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

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

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


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

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

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

 

사이드카 배포 후...

안나온다.

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

 

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

 

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

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

 

 

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

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

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

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

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

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

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

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

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

설정이 빠졌다.

 

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

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

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

 

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

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

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

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

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

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

 

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

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

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

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


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

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

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

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

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

 

 

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

 

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

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

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

 

10장 데이터 플레인 트러블 슈팅하기

이스티오는 네트워크 통신 장애 발생 시 복원력 기능(타임아웃·재시도 등)을 통해 애플리케이션의 자동 대응을 지원한다. 데이터 플레인 동기화를 담당하는 istiod, 트래픽 허용을 위한 인그레스 게이트웨이, 트래픽 제어를 수행하는 서비스 프록시, 실제 요청을 처리하는 애플리케이션이 협력해 요청 흐름을 관리한다. 서비스 프록시의 비정상 동작 시 전체 시스템에 영향을 줄 수 있으므로 각 구성 요소의 정상 작동이 중요하다.

 

10.1 가장 흔한 실수: 잘못 설정한 데이터 플레인

이스티오는 VirtualService, DestinationRule 같은 CRD로 프록시 설정을 관리한다.
이 설정들은 엔보이 설정으로 변환되어 데이터 플레인에 적용된다.
DestinationRule이 없으면 부분집합 정의가 없어 인그레스 게이트웨이에서 모든 요청이 실패한다.

# 샘플 애플리케이션 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # catalog v1 배포
kubectl apply -f ch10/catalog-deployment-v2.yaml -n istioinaction # catalog v2 배포
kubectl apply -f ch10/catalog-gateway.yaml -n istioinaction # catalog-gateway 배포
kubectl apply -f ch10/catalog-virtualservice-subsets-v1-v2.yaml -n istioinaction

# Gateway 
cat ch10/catalog-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: catalog-gateway
  namespace: istioinaction
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - "catalog.istioinaction.io"
    port:
      number: 80
      name: http
      protocol: HTTP

# VirtualService
cat ch10/catalog-virtualservice-subsets-v1-v2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-v1-v2
  namespace: istioinaction
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - "catalog-gateway"
  http:
  - route:
    - destination:
        host: catalog.istioinaction.svc.cluster.local
        subset: version-v1
        port:
          number: 80
      weight: 20
    - destination:
        host: catalog.istioinaction.svc.cluster.local
        subset: version-v2
        port:
          number: 80
      weight: 80

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

 

10.2 데이터 플레인 문제 식별하기

데이터 플레인 문제 해결 시 컨트롤 플레인 동기화 상태를 먼저 확인해야 한다. 데이터 플레인 설정은 궁극적 일관성을 가지므로 환경 변화가 즉시 반영되지 않는다. 예를 들어 파드 장애 시 쿠버네티스가 비정상 엔드포인트를 감지하고 데이터 플레인에서 제거하는 데 지연이 발생할 수 있다. 컨트롤 플레인은 지속적으로 최신 설정을 데이터 플레인에 동기화하여 일관성을 복원한다. 이 과정에서 그림 10.3과 같은 이벤트 흐름을 통해 데이터 플레인 업데이트가 시각화된다.

  • SYNCED : istiod가 보낸 마지막 설정을 엔보이가 확인했다.
  • NOT SENT : istiod가 아무것도 엔보이로 보내지 않았다. 보통은 istiod가 보낼 것이 없기 때문이다.
  • STALE : istiod가 엔보이에 업데이트를 보냈지만 확인받지 못했다. 이는 다음 중 하나를 나타낸다.
    • istiod가 과부하됐거나, 엔보이와 istiod 사이의 커넥션 부족 또는 끊김이거나, 이스티오의 버그다.

컨트롤 플레인에 문제가 없으면 데이터 플레인 워크로드 설정 오류를 키알리로 빠르게 검증해야 한다.

 

10.2.2 키알리로 잘못된 설정 발견하기 Discovering misconfigurations with Kiali

 

10.2.3 istioctl로 잘못된 설정 발견하기* Discovering misconfigurations with istioctl

istioctl analyze는 이스티오 설정 오류를 자동으로 감지하고 진단하는 강력한 도구다.
istioctl describe은 특정 파드/서비스에 적용된 라우팅 규칙과 연관된 리소스를 확인해 설정 문제를 식별한다.

istioctl describe는 워크로드별로 적용된 이스티오 설정을 분석해 요약 정보를 제공한다.
이 명령어로 서비스 메시 포함 여부, 적용된 VirtualService/DestinationRule, 상호 인증 요구사항 등을 쉽게 확인할 수 있다.

#
kubectl get pod -n istioinaction -l app=catalog -o jsonpath='{.items[0].metadata.name}'
CATALOG_POD1=$(kubectl get pod -n istioinaction -l app=catalog -o jsonpath='{.items[0].metadata.name}')

# 단축키 : experimental(x), describe(des)
docker exec -it myk8s-control-plane istioctl experimental describe -h
docker exec -it myk8s-control-plane istioctl x des pod -n istioinaction $CATALOG_POD1
Pod: catalog-6cf4b97d-l44zk
   Pod Revision: default
   Pod Ports: 3000 (catalog), 15090 (istio-proxy)
--------------------
Service: catalog
   Port: http 80/HTTP targets pod port 3000
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: PERMISSIVE


Exposed on Ingress Gateway http://172.18.0.2
VirtualService: catalog-v1-v2
   WARNING: No destinations match pod subsets (checked 1 HTTP routes)
      Warning: Route to subset version-v1 but NO DESTINATION RULE defining subsets!
      Warning: Route to subset version-v2 but NO DESTINATION RULE defining subsets!


# 문제 해결 후 확인
cat ch10/catalog-destinationrule-v1-v2.yaml       
kubectl apply -f ch10/catalog-destinationrule-v1-v2.yaml
docker exec -it myk8s-control-plane istioctl x des pod -n istioinaction $CATALOG_POD1
Pod: catalog-6cf4b97d-l44zk
   Pod Revision: default
   Pod Ports: 3000 (catalog), 15090 (istio-proxy)
--------------------
Service: catalog
   Port: http 80/HTTP targets pod port 3000
DestinationRule: catalog for "catalog.istioinaction.svc.cluster.local"
   Matching subsets: version-v1 # 일치하는 부분집합
      (Non-matching subsets version-v2) # 일치하지 않은 부분집합
   No Traffic Policy
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: PERMISSIVE

Exposed on Ingress Gateway http://172.18.0.2
VirtualService: catalog-v1-v2 # 이 파드로 트래픽을 라우팅하는 VirtualService
   Weight 20%

# 다음 점검 방법을 위해 오류 상황으로 원복
kubectl delete -f ch10/catalog-destinationrule-v1-v2.yaml

에러 확인

 

원인 해결 후 정상 확인

analyze와 describe 명령어로 대부분의 설정 오류를 해결할 수 있지만, 추가 진단이 필요한 경우 더 깊은 분석이 필요하다.

 

 

10.3 엔보이 설정에서 수동으로 잘못된 설정 발견하기

엔보이 관리(admin) 인터페이스는 각 서비스 프록시에서 포트 15000으로 접근할 수 있고, 프록시의 설정 전체를 확인하거나 수정하는 데 사용된다.
설정이 많아 가독성이 떨어지기 때문에, istioctl은 출력 결과를 필터링해 필요한 부분만 쉽게 볼 수 있도록 도와준다.
엔보이 관리 인터페이스를 활용하면 자동화 도구로 잡히지 않는 설정 오류를 수동으로 직접 조사할 수 있다.

kubectl port-forward deploy/catalog -n istioinaction 15000:15000
open http://localhost:15000

# 현재 적재한 엔보이 설정 출력 : 데이터양이 많다!
curl -s localhost:15000/config_dump | wc -l
  13952

 

 

 

 

10.3.2 istioctl 로 프록시 설정 쿼리하기 Querying proxy configurations using istioctl

엔보이 API는 프록시의 리스너를 통해 네트워크 설정(IP/포트)을 정의하고, HTTP 필터 체인에서 라우터 필터가 고급 라우팅을 수행한다. 라우트는 가상 호스트와 클러스터를 매칭하는 규칙을 순차적으로 적용하며, 이스티오는 RDS를 통해 동적으로 관리한다. 클러스터는 유사한 워크로드 엔드포인트 그룹을 구성하고, 부분집합으로 세분화된 트래픽 제어가 가능하다. 엔드포인트는 실제 워크로드 IP 주소를 나타내며, 인그레스 게이트웨이 설정 검증 시 리스너·라우트·클러스터·엔드포인트를 종합적으로 확인해야 한다.

 

 

#엔보이 리느서 설정 쿼리하기

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway -n istio-system
ADDRESS PORT  MATCH DESTINATION
0.0.0.0 8080  ALL   Route: http.8080 # 8080 포트에 대한 요청은 루트 http.8080에 따라 라우팅하도록 설정된다
0.0.0.0 15021 ALL   Inline Route: /healthz/ready*
0.0.0.0 15090 ALL   Inline Route: /stats/prometheus*
## 리스터는 8080 포트에 설정돼 있다.
## 그 리스너에서 트래픽은 http.8080 이라는 루트에 따라 라우팅된다.

#
kubectl get svc -n istio-system  istio-ingressgateway -o yaml | grep "ports:" -A10
  ports:
  - name: status-port
    nodePort: 30840
    port: 15021
    protocol: TCP
    targetPort: 15021
  - name: http2
    nodePort: 30000
    port: 80
    protocol: TCP
    targetPort: 8080

 

nodePort 30000이나 clusterIP/서비스명으로 유입된 트래픽은 인그레스 게이트웨이 파드의 8080 포트로 전달되고, 해당 포트의 리스너와 http.8080 라우트가 이를 처리한다.

 

 

이스티오는 VirtualServiceDestinationRule로 트래픽 라우팅 규칙을 정의하고, 이 설정들이 엔보이 프록시에 적용된다.
DestinationRule이 없으면 부분집합 정의가 누락되어 라우팅 실패가 발생하며, HTTP 헤더(예: x-istio-cohort)를 활용해 특정 버전(v2)으로 트래픽을 제어할 수 있다.

# 엔보이 루트 설정 쿼리하기 QUERYING THE ENVOY ROUTE CONFIGURATION

# http.8080 루트의 트래픽을 어느 클러스터로 라우팅할지 알아내기 위해 설정을 쿼리
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway -n istio-system --name http.8080
NAME          DOMAINS                      MATCH     VIRTUAL SERVICE
http.8080     catalog.istioinaction.io     /*        catalog-v1-v2.istioinaction
## 호스트 catalog.istioinaction.io 의 트래픽 중 URL이 경로 접두사 /*과 일치하는 것이 istioinaction 네임스페이스의 catalog 서비스에 있는 catalog VirtualService 로 라우팅됨을 보여준다.

# 세부 정보 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway -n istio-system --name http.8080 -o json
...
                "routes": [
                    {
                        "match": {
                            "prefix": "/" # 일치해야 하는 라우팅 규칙
                        },
                        "route": {
                            "weightedClusters": {
                                "clusters": [ # 규칙이 일치할 때 트래픽을 라우팅하는 클러스터
                                    {
                                        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                        "weight": 20
                                    },
                                    {
                                        "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                        "weight": 80
                                    }
                                ],
                                "totalWeight": 100
                            },
...

라우팅 규칙에 따라 트래픽은 outbound|80|version-v1 또는 version-v2 부분집합의 catalog.istioinaction.svc.cluster.local 클러스터로 분산된다.

 

 

엔보이 클러스터 설정은 백엔드 서비스 라우팅을 정의하며, 각 클러스터는 여러 엔드포인트로 부하를 분산한다.
istioctl proxy-config clusters 명령어로 특정 클러스터를 필터링(direction/fqdn/port/subset)해 확인할 수 있다.
예를 들어 outbound|80|version-v1|catalog.istioinaction.svc.cluster.local 클러스터는 v1 버전의 catalog 서비스 트래픽을 처리한다.

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

#
docker exec -it myk8s-control-plane istioctl proxy-config clusters deploy/istio-ingressgateway -n istio-system \
--fqdn catalog.istioinaction.svc.cluster.local --port 80 --subset version-v1


# 해당 파일이 없을 경우 'copy & paste'로 작성 후 진행 하자
docker exec -it myk8s-control-plane cat /istiobook/ch10/catalog-destinationrule-v1-v2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: catalog
  namespace: istioinaction
spec:
  host: catalog.istioinaction.svc.cluster.local
  subsets:
  - name: version-v1
    labels:
      version: v1
  - name: version-v2
    labels:
      version: v2

# istioctl analyze 명령어를 사용해서, 설정할 yaml 파일이 식별한 서비스 메시 오류를 고칠 수 있는지 확인
docker exec -it myk8s-control-plane istioctl analyze /istiobook/ch10/catalog-destinationrule-v1-v2.yaml -n istioinaction
✔ No validation issues found when analyzing /istiobook/ch10/catalog-destinationrule-v1-v2.yaml.




# 문제 해결
cat ch10/catalog-destinationrule-v1-v2.yaml
kubectl apply -f ch10/catalog-destinationrule-v1-v2.yaml

# 확인
docker exec -it myk8s-control-plane istioctl proxy-config clusters deploy/istio-ingressgateway -n istio-system \
--fqdn catalog.istioinaction.svc.cluster.local --port 80
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

CATALOG_POD1=$(kubectl get pod -n istioinaction -l app=catalog -o jsonpath='{.items[0].metadata.name}')
docker exec -it myk8s-control-plane istioctl x des pod -n istioinaction $CATALOG_POD1
docker exec -it myk8s-control-plane istioctl analyze -n istioinaction

# 호출 확인
curl http://catalog.istioinaction.io:30000/items
curl http://catalog.istioinaction.io:30000/items

 

결과가 없음.

 

적용에 문제가 없음을 확인
문제 해결 후 정상 확인

 

 

엔보이 클러스터는 **동적 서비스 발견(EDS)**과 **Aggregated Discovery Service(ADS)**를 통해 설정된다.
istioctl proxy-config clusters 명령어로 확인 시, 클러스터는 outbound|80|version-v1|catalog.istioinaction.svc.cluster.local 형식으로 정의되며 포트·부분집합·FQDN 정보를 포함한다.
DestinationRule 리소스에서 정의된 부분집합이 클러스터에 반영되며, ADS를 통해 엔드포인트 정보가 실시간으로 동기화되어 트래픽 라우팅이 관리된다.

 

엔보이 클러스터의 엔드포인트 정보는 istioctl proxy-config endpoints 명령어로 확인한다.
예를 들어 --cluster 플래그에 특정 클러스터명을 지정하면 해당 클러스터에 등록된 **엔드포인트 IP와 상태(HEALTHY/UNHEALTHY)**를 출력한다.
출력된 IP로 kubectl get pod를 실행해 실제 워크로드 존재 여부를 검증함으로써 라우팅 설정의 정확성을 확인할 수 있다.

 

 

 

10.3.3 애플리케이션 문제 트러블슈팅하기 Troubleshooting application issues

서비스 프록시의 로그와 메트릭은 마이크로서비스 환경에서 성능 병목, 실패 엔드포인트, 성능 저하 등 다양한 문제를 트러블슈팅하는 데 활용된다.

 

간헐적으로 제한 시간을 초과하는 느린 워크로드 준비하기* SETTING UP AN INTERMITTENTLY SLOW WORKLOAD THAT TIMES OUT

# 신규 터미널
for in in {1..9999}; do curl http://catalog.istioinaction.io:30000/items -w "\nStatus Code %{http_code}\n"; sleep 1; done


# catalog v2 파드 중 첫 번째 파드 이름 변수 지정
CATALOG_POD=$(kubectl get pods -l version=v2 -n istioinaction -o jsonpath={.items..metadata.name} | cut -d ' ' -f1)
echo $CATALOG_POD
catalog-v2-56c97f6db-d74kv

# 해당 파드에 latency (지연) 발생하도록 설정
kubectl -n istioinaction exec -c catalog $CATALOG_POD \
-- curl -s -X POST -H "Content-Type: application/json" \
-d '{"active": true, "type": "latency", "volatile": true}' \
localhost:3000/blowup ;
blowups=[object Object]


# 신규 터미널
for in in {1..9999}; do curl http://catalog.istioinaction.io:30000/items -w "\nStatus Code %{http_code}\n"; sleep 1; done



#
kubectl get vs -n istioinaction
NAME            GATEWAYS              HOSTS                          AGE
catalog-v1-v2   ["catalog-gateway"]   ["catalog.istioinaction.io"]   6h44m

# 타임아웃(0.5s) 적용
kubectl patch vs catalog-v1-v2 -n istioinaction --type json \
-p '[{"op": "add", "path": "/spec/http/0/timeout", "value": "0.5s"}]'

# 적용확인 
kubectl get vs catalog-v1-v2 -n istioinaction -o jsonpath='{.spec.http[?(@.timeout=="0.5s")]}' | jq
...
  "timeout": "0.5s"
}

# 신규 터미널
for in in {1..9999}; do curl http://catalog.istioinaction.io:30000/items -w "\nStatus Code %{http_code}\n"; sleep 1; done
upstream request timeout
Status Code 504
upstream request timeout
Status Code 504
..

#
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-09T08:45:41.636Z] "GET /items HTTP/1.1" 504 UT response_timeout - "-" 0 24 501 - "172.18.0.1" "curl/8.7.1" "cb846eff-07ac-902e-9890-7af478c84166" "catalog.istioinaction.io:30000" "10.10.0.13:3000" outbound|80|version-v2|catalog.istioinaction.svc.cluster.local 10.10.0.7:58078 10.10.0.7:8080 172.18.0.1:61108 - -
[2025-05-09T08:45:43.175Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 375 374 "172.18.0.1" "curl/8.7.1" "3f2de0c1-5af2-9a33-a6ac-bca08c1ee271" "catalog.istioinaction.io:30000" "10.10.0.13:3000" outbound|80|version-v2|catalog.istioinaction.svc.cluster.local 10.10.0.7:58084 10.10.0.7:8080 172.18.0.1:61118 - -
...

kubectl logs -n istio-system -l app=istio-ingressgateway -f | grep 504
...

#
kubectl logs -n istioinaction -l version=v2 -c istio-proxy -f
[2025-05-09T08:42:38.152Z] "GET /items HTTP/1.1" 0 DC downstream_remote_disconnect - "-" 0 0 500 - "172.18.0.1" "curl/8.7.1" "69fef43c-2fea-9e51-b33d-a0375b382d86" "catalog.istioinaction.io:30000" "10.10.0.13:3000" inbound|3000|| 127.0.0.6:36535 10.10.0.13:3000 172.18.0.1:0 outbound_.80_.version-v2_.catalog.istioinaction.svc.cluster.local default
...

 

 

 

엔보이 액세스 로그 이해하기 + 엔보이 액세스 로그 형식 바꾸기

이스티오 프록시 로그는 기본적으로 TEXT 형식이지만, JSON 형식으로 설정하면 각 값의 의미를 쉽게 파악할 수 있다.

# 형식 설정 전 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f | grep 504
...

# MeshConfig 설정 수정
KUBE_EDITOR="nano" kubectl edit -n istio-system cm istio
...
  mesh: |-
    accessLogFile: /dev/stdout # 기존 설정되어 있음
    accessLogEncoding: JSON # 추가
...

# 형식 설정 후 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f | jq
...
{
  "upstream_host": "10.10.0.13:3000", # 요청을 받는 업스트림 호스트
  "bytes_received": 0,
  "upstream_service_time": null,
  "response_code_details": "response_timeout",
  "upstream_cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
  "duration": 501, # 500ms 인 제한 시간 초과
  "response_code": 504,
  "path": "/items",
  "protocol": "HTTP/1.1",
  "upstream_transport_failure_reason": null,
  "connection_termination_details": null,
  "method": "GET",
  "requested_server_name": null,
  "start_time": "2025-05-09T08:56:38.988Z",
  "downstream_remote_address": "172.18.0.1:59052",
  "upstream_local_address": "10.10.0.7:57154",
  "downstream_local_address": "10.10.0.7:8080",
  "bytes_sent": 24,
  "authority": "catalog.istioinaction.io:30000",
  "x_forwarded_for": "172.18.0.1",
  "request_id": "062ad02a-ff36-9dcc-8a7d-68eabb01bbb5",
  "route_name": null,
  "response_flags": "UT", # 엔보이 응답 플래그, UT(Upstream request Timeout)로 중단됨, '업스트림 요청 제한 시간 초과'
  "user_agent": "curl/8.7.1"
}
...

# slow 동작되는 파드 IP로 느린 동작 파드 확인!
CATALOG_POD=$(kubectl get pods -l version=v2 -n istioinaction -o jsonpath={.items..metadata.name} | cut -d ' ' -f1)
kubectl get pod -n istioinaction $CATALOG_POD -owide
NAME                         READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
catalog-v2-56c97f6db-d74kv   2/2     Running   0          7h11m   10.10.0.13   myk8s-control-plane   <none>           <none>

 

 

 

엔보이 게이트웨이의 로깅 수준 높이기 INCREASING THE LOGGING LEVEL FOR THE INGRESS GATEWAY

엔보이는 none, error, warning, info, debug 등 다양한 로깅 수준을 범위별(connection, http, router, pool 등)로 설정해 필요한 영역의 로그만 상세히 확인할 수 있다.

#
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system \
--level http:debug,router:debug,connection:debug,pool:debug

# 로그 확인
kubectl logs -n istio-system -l app=istio-ingressgateway -f
k logs -n istio-system -l app=istio-ingressgateway -f > istio-igw-log.txt # 편집기로 열어서 보기
...

 

#로그 내용

# 504 검색
2025-05-09T09:17:17.762027Z	debug	envoy http external/envoy/source/common/http/filter_manager.cc:967	[C18119][S12425904214070917868] Sending local reply with details response_timeout	thread=38
2025-05-09T09:17:17.762072Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1687	[C18119][S12425904214070917868] encoding headers via codec (end_stream=false):
':status', '504'
'content-length', '24'
'content-type', 'text/plain'
'date', 'Fri, 09 May 2025 09:17:17 GMT'
'server', 'istio-envoy'
	thread=38

# 커넥션 ID(C18119)로 다시 검색

## [C18119] new stream  # 시작
2025-05-09T09:17:17.262341Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:329	[C18119] new stream	thread=38
2025-05-09T09:17:17.262425Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1049	[C18119][S12425904214070917868] request headers complete (end_stream=true):
':authority', 'catalog.istioinaction.io:30000'
':path', '/items'
':method', 'GET'
'user-agent', 'curl/8.7.1'
'accept', '*/*'
	thread=38

## /items 요청이 cluster로 매칭됨
2025-05-09T09:17:17.262445Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1032	[C18119][S12425904214070917868] request end stream	thread=38
2025-05-09T09:17:17.262468Z	debug	envoy connection external/envoy/source/common/network/connection_impl.h:92	[C18119] current connecting state: false	thread=38
025-05-09T09:17:17.262603Z	debug	envoy router external/envoy/source/common/router/router.cc:470	[C18119][S12425904214070917868] cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local' match for URL '/items'	thread=38
2025-05-09T09:17:17.262683Z	debug	envoy router external/envoy/source/common/router/router.cc:678	[C18119][S12425904214070917868] router decoding headers:
':authority', 'catalog.istioinaction.io:30000'
':path', '/items'
':method', 'GET'
':scheme', 'http'
'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', 'a6bc39e7-9215-950f-96ea-4cb5f6b12deb'
'x-envoy-decorator-operation', 'catalog-v1-v2:80/*'
'x-envoy-peer-metadata', 'ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuNwoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi8KBE5BTUUSJxolaXN0aW8taW5ncmVzc2dhdGV3YXktNmJiOGZiNjU0OS1oY2RuYwobCglOQU1FU1BBQ0USDhoMaXN0aW8tc3lzdGVtCl0KBU9XTkVSElQaUmt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9pc3Rpby1zeXN0ZW0vZGVwbG95bWVudHMvaXN0aW8taW5ncmVzc2dhdGV3YXkKFwoRUExBVEZPUk1fTUVUQURBVEESAioACicKDVdPUktMT0FEX05BTUUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXk='
'x-envoy-peer-metadata-id', 'router~10.10.0.7~istio-ingressgateway-6bb8fb6549-hcdnc.istio-system~istio-system.svc.cluster.local'
'x-envoy-expected-rq-timeout-ms', '500'
'x-envoy-attempt-count', '1'
	thread=38

## upstream timeout 으로 client 에서 끊음 (disconnect)
2025-05-09T09:17:17.262701Z	debug	envoy pool external/envoy/source/common/conn_pool/conn_pool_base.cc:265	[C17947] using existing fully connected connection	thread=38
2025-05-09T09:17:17.262710Z	debug	envoy pool external/envoy/source/common/conn_pool/conn_pool_base.cc:182	[C17947] creating stream	thread=38
2025-05-09T09:17:17.262736Z	debug	envoy router external/envoy/source/common/router/upstream_request.cc:581	[C18119][S12425904214070917868] pool ready	thread=38
2025-05-09T09:17:17.761697Z	debug	envoy router external/envoy/source/common/router/router.cc:947	[C18119][S12425904214070917868] upstream timeout	thread=38 # 업스트림 서버가 설정된 타임아웃 내에 응답하지 않아 요청이 실패
2025-05-09T09:17:17.761762Z	debug	envoy router external/envoy/source/common/router/upstream_request.cc:500	[C18119][S12425904214070917868] resetting pool request	thread=38
2025-05-09T09:17:17.761776Z	debug	envoy connection external/envoy/source/common/network/connection_impl.cc:139	[C17947] closing data_to_write=0 type=1	thread=38
2025-05-09T09:17:17.761779Z	debug	envoy connection external/envoy/source/common/network/connection_impl.cc:250	[C17947] closing socket: 1	thread=38
2025-05-09T09:17:17.761920Z	debug	envoy connection external/envoy/source/extensions/transport_sockets/tls/ssl_socket.cc:320	[C17947] SSL shutdown: rc=0	thread=38
2025-05-09T09:17:17.761982Z	debug	envoy pool external/envoy/source/common/conn_pool/conn_pool_base.cc:484	[C17947] client disconnected, failure reason: 	thread=38
2025-05-09T09:17:17.761997Z	debug	envoy pool external/envoy/source/common/conn_pool/conn_pool_base.cc:454	invoking idle callbacks - is_draining_for_deletion_=false	thread=38

## 504 응답
2025-05-09T09:17:17.762027Z	debug	envoy http external/envoy/source/common/http/filter_manager.cc:967	[C18119][S12425904214070917868] Sending local reply with details response_timeout	thread=38
2025-05-09T09:17:17.762072Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1687	[C18119][S12425904214070917868] encoding headers via codec (end_stream=false):
':status', '504'
'content-length', '24'
'content-type', 'text/plain'
'date', 'Fri, 09 May 2025 09:17:17 GMT'
'server', 'istio-envoy'
	thread=38
2025-05-09T09:17:17.762253Z	debug	envoy pool external/envoy/source/common/conn_pool/conn_pool_base.cc:215	[C17947] destroying stream: 0 remaining	thread=38
2025-05-09T09:17:17.763718Z	debug	envoy connection external/envoy/source/common/network/connection_impl.cc:656	[C18119] remote close	thread=38
2025-05-09T09:17:17.763731Z	debug	envoy connection external/envoy/source/common/network/connection_impl.cc:250	[C18119] closing socket: 0	thread=38

응답이 느린 업스트림의 IP가 액세스 로그와 일치해, 특정 인스턴스만 오동작함을 확인했다.
로그에서 클라이언트(프록시)가 업스트림 커넥션을 종료한 것도 확인되어, 제한 시간 초과로 인한 종료라는 예상과 일치한다.
엔보이 로거를 통해 프록시의 동작 원인과 문제 인스턴스를 정확히 파악할 수 있다.

 

10.3.4 tcpdump로 네트워크 트래픽 검사* Inspect network traffic with ksniff

특정 파드에서 tcpdump 후 wireshark 로 불러오기

# slow 파드 정보 확인
CATALOG_POD=$(kubectl get pods -l version=v2 -n istioinaction -o jsonpath={.items..metadata.name} | cut -d ' ' -f1)
kubectl get pod -n istioinaction $CATALOG_POD -owide

# catalog 서비스 정보 확인
kubectl get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.200.1.178   <none>        80/TCP    10h

NAME                ENDPOINTS                                         AGE
endpoints/catalog   10.10.0.12:3000,10.10.0.13:3000,10.10.0.14:3000   10h

# istio-proxy 에서 기본 정보 확인
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo whoami
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- tcpdump -h
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- ip -c addr
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- ip add show dev eth0
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- ip add show dev lo

# istio-proxy 에 eth0 에서 패킷 덤프
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i eth0 tcp port 3000 -nnq
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i eth0 tcp port 3000 -nn
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i eth0 tcp port 3000

# istio-proxy 에 lo 에서 패킷 덤프
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i lo -nnq

# istio-proxy 에 tcp port 3000 에서 패킷 덤프
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i any tcp port 3000 -nnq
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i any tcp port 3000 -nn

#
kubectl describe pod -n istioinaction $CATALOG_POD
...
    Mounts:
      /etc/istio/pod from istio-podinfo (rw)
      /etc/istio/proxy from istio-envoy (rw)
      /var/lib/istio/data from istio-data (rw)
      /var/run/secrets/credential-uds from credential-socket (rw)
      /var/run/secrets/istio from istiod-ca-cert (rw)
      /var/run/secrets/tokens from istio-token (rw)
      /var/run/secrets/workload-spiffe-credentials from workload-certs (rw)
      /var/run/secrets/workload-spiffe-uds from workload-socket (rw)
...

# istio-proxy 에 tcp port 3000 에서 패킷 덤프에 출력 결과를 파일로 저장 
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- sudo tcpdump -i any tcp port 3000 -w /var/lib/istio/data/dump.pcap
kubectl exec -it -n istioinaction $CATALOG_POD -c istio-proxy -- ls -l /var/lib/istio/data/

# 출력 결과 파일을 로컬로 다운로드
kubectl cp -n istioinaction -c istio-proxy $CATALOG_POD:var/lib/istio/data/dump.pcap ./dump.pcap

# 로컬로 다운 받은 파일을 wireshark 로 불러오기
wireshark dump.pcap

 

 

RST, FIN 을 사이드카에서 주는게 아니라 istio GW에서 준다.

 

그라파나를 통한 Istio 실패 요청 비율 분석

  1. 클라이언트 측 성공률 70% (30% 실패) → 주요 원인은 504 Gateway Timeout
  2. 서버 측 성공률 100% → 실제 서버 문제는 없으나, Envoy 프록시가 응답 코드 0으로 처리
  3. 인그레스 게이트웨이 응답 플래그 UT(Upstream Timeout) vs catalog v2 플래그 DC(Downstream Connection 종료)
  4. 타임아웃 차이 → 클라이언트(istio-ingress)의 0.5초 타임아웃 설정이 서버(catalog) 응답보다 짧아 발생
  5. 트러블슈팅 포인트 : 워크로드(v1/v2)별 상세 메트릭 추적 필요 → PromQL로 파드 단위 분할 분석 권장

 

중간중간 끊기면서 뭔가 제대로 출력이 안된듯..

 

 

프로메테우스로 Istio 문제 파드 진단 핵심

목적 : 그라파나 한계 보완 → 파드 단위 실패 요청 집중 분석

쿼리 전략 : istio_requests_total 메트릭에 DC 응답 플래그 필터링 → 클라이언트 강제 종료 사례 추적

진단 결과 : catalog v2 파드에서만 응답 코드 0 집중 발생 → 타임아웃 설정 불일치 확인

sort_desc( # 가장 높은 값부터 내림차순 정렬
  sum( # irate 값들을 집계
    irate( #  요청 수 초당 증가율
      istio_requests_total {
        reporter="destination",   # 서버(destination) 측에서 보고한 메트릭만 필터링
        destination_service=~"catalog.istioinaction.svc.cluster.local",   # catalog 가 서버(destination)측인 메트릭만 필터링
        response_flags="DC"       # DC (다운스트림 커넥션 종료)로 끝난 메트릭만 필터링
      }[5m]
    )
  )by(response_code, pod, version) # 응답 코드(response_code), 대상 pod, 버전(version) 별로 분리 => sum.. 합산
)

# 쿼리1
istio_requests_total
istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local"}
istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}

# 쿼리2
istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}[5m]
irate(istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}[5m])
sum(irate(istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}[5m]))

# 쿼리3
sum(irate(istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}[5m])) by(response_code, pod, version)
sort_desc(sum(irate(istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}[5m]))by(response_code, pod, version))

 

 

## PromQL 쿼리별 분석

### **쿼리1: 기본 메트릭 필터링**
```promql
istio_requests_total
istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local"}
istio_requests_total{reporter="destination", destination_service=~"catalog.istioinaction.svc.cluster.local",response_flags="DC"}
```
- **목적**: 단계별 필터링을 통한 문제 범위 축소
  1. **전체 메트릭 확인**: `istio_requests_total` (모든 요청)
  2. **서버 측 리포트 필터**: `reporter="destination"` + `destination_service`(catalog 서비스 대상 요청)
  3. **이슈 신호 포착**: `response_flags="DC"` (Downstream Connection 종료 이벤트)

---

### **쿼리2: 실시간 트래픽 패턴 분석**
```promql
istio_requests_total{...}[5m]
irate(...[5m])
sum(irate(...))
```
- **동작 원리**:
  - `[5m]`: **5분 간 데이터 범위** 지정 → 단기 트렌드 파악
  - `irate()`: **초당 요청 증가율** 계산 → 급증/감소 추이 감지
  - `sum()`: 모든 레이블 값 **통합 집계** → 전체 실패율 산출

---

### **쿼리3: 근본 원인 진단**
```promql
sum(...) by(response_code, pod, version)
sort_desc(...)
```
- **핵심 기능**:
  - `by(response_code, pod, version)`: **파드/버전별** 실패 요청 분할 분석
  - `sort_desc()`: **실패율 상위 항목** 우선 표시 → 문제 있는 워크로드 신속 식별

---

### **실무 적용 시나리오**
```python
# 문제 파악 프로세스 예시
if "response_flags=DC" in query_results:
    identify_affected_pods()  # 쿼리3 실행 → catalog-v2 파드 발견
    check_timeout_config()    # istio-ingressgateway 타임아웃 0.5초 확인
    adjust_virtual_service()  # 타임아웃 1초로 조정
```

---

### **요약 표**

| 쿼리 단계 | 주요 연산자 | 출력 예시 | 용도 |
|---------|------------|----------|-----|
| 쿼리1 | `{}` 필터 | `{response_flags="DC", pod="catalog-v2"}` | 이슈 후보군 추출 |
| 쿼리2 | `irate()` | `0.35 req/sec` | 실시간 트래픽 강도 측정 |
| 쿼리3 | `sort_desc()` | `catalog-v2 (70%) → catalog-v1 (5%)` | 문제 파드 우선순위 결정 |

---

 

 

부록 D 이스티오 구성 요소 트러블 슈팅하기

이스티오 사이드카는 헬스체크, 메트릭 수집·노출, DNS 해석, 트래픽 라우팅 등 다양한 기능을 제공한다. 프록시가 트래픽을 처리하기 전에 설정 수신 및 ID 할당 등 추가적인 준비 상태 확인이 필요하다. 메트릭은 애플리케이션, 에이전트, 엔보이 프록시에서 생성되며, 에이전트가 이를 집계해 노출한다.

 

  • 서비스용 포트 Ports facing other services
    • 15020 : (파일럿 에이전트 프로세스) 여러 기능 제공!
      • 메트릭을 집계하고 노출하며, 이때 메트릭에는 엔보이 프록시의 15090 포트에 쿼리한 메트릭, 애플리케이션 메트릭(설정한 경우), 자체 메트릭이 있다.
      • 엔보이 및 DNS 프록시를 헬스 체크. 이 엔드포인트에서 애플리케이션도 헬스 체크하도록 프록시를 설정할 수 있지만, 보통은 가상머신과 같이 쿠버네티스가 아닌 워크로드에만 사용한다.
      • 이스티오 개발 팀에 유용한 파일럿 에이전트 디버깅용 엔드포인트로, 메모리 정보, CPU 프로파일링 등과 같은 정보를 노출한다.
    • 15021 : (엔보이 프로세스) 사이드카 주입된 파드는 이 포트에서 트래픽을 받을 준비가 됐는지 확인하도록 설정된다. Pods with the sidecar injected are configured to check their readiness to receive traffic on this port.
      • 앞서 설명한 것처럼 엔보이 프록시헬스 체크를 15020 포트의 파일럿 에이전트로 라우팅하며, 실제 헬스 체크는 여기서 일어난다. the Envoy proxy routes the health checks to the Pilot agent on port 15020, where the actual healthchecking occurs.
    • 15053 : (파일럿 에이전트 프로세스) 쿠버네티스 DNS 해석이 충분하지 않은 에지 케이스를 해결하기 위해 istiod가 구성한 로컬 DNS 프록시 Local DNS proxy configured by istiod to resolve edge cases where Kubernetes DNS resolution doesn’t suffice.
    • 15001 : (엔보이 프로세스) 애플리케이션에서 나가는 트래픽은 Iptable 규칙에 의해 일단 이 포트로 리다이렉트되며, 이후 프록시가 트래픽을 서비스로 라우팅한다.
    • 15006 : (엔보이 프로세스) 애플리케이션으로 들어오는 트래픽은 Iptable 규칙에 의해 일단 이 포트로 리다이렉트되며, 여기서 로컬 애플리케이션 라우팅된다.
  • 에이전트 디버깅 및 내부 상태 조사에 유용한 포트 useful for debugging and introspecting the agent
    • 15000 : (엔보이 프로세스) 엔보이 프록시 관리 인터페이스
    • 15090 : (엔보이 프로세스) 엔보이 프록시 메트릭을 노출 (xDS 통계, 커넥션 통계, HTTP 통계, 이상값 outlier 통계, 헬스 체크 통계, 서킷 브레이커 통계 등)
    • 15004 : (파일럿 에이전트 프로세스) 에이전트를 통해 이스티오 파일럿 디버그 엔드포인트를 노출. 파일럿과의 연결 문제를 디버깅에 유용.
    • 15020 : (파일럿 에이전트 프로세스) 파일럿 에이전트 디버기용 엔드포인트들을 노출.
#
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-http
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: liveness-http
      version: v1
  template:
    metadata:
      labels:
        app: liveness-http
        version: v1
    spec:
      containers:
      - name: liveness-http
        image: docker.io/istio/health:example
        ports:
        - containerPort: 8001
        livenessProbe:
          httpGet:
            path: /foo
            port: 8001
          initialDelaySeconds: 5
          periodSeconds: 5
EOF

#
kubectl get pod -n istioinaction -l app=liveness-http
kubectl describe pod -n istioinaction -l app=liveness-http
...
Containers:
  liveness-http:
    Container ID:   containerd://edaf01bff5d553e03290b3d44f60bb26958319e615a27a9b38309aad9b2df477
    Image:          docker.io/istio/health:example
    Image ID:       docker.io/istio/health@sha256:d8a2ff91d87f800b4661bec5aaadf73d33de296d618081fa36a0d1cbfb45d3d5
    Port:           8001/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 10 May 2025 16:58:35 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       http-get http://:15020/app-health/liveness-http/livez delay=5s timeout=1s period=5s #success=1 #failure=3
    ...
  istio-proxy:
    Container ID:  containerd://d4b0955372bdb7b3e1490eb3f290c6c6f5a9f2691eabea4cebafaafa8be85fc9
    Image:         docker.io/istio/proxyv2:1.17.8
    Image ID:      docker.io/istio/proxyv2@sha256:d33fd90e25c59f4f7378d1b9dd0eebbb756e03520ab09cf303a43b51b5cb01b8
    Port:          15090/TCP
    ...
    Readiness:  http-get http://:15021/healthz/ready delay=1s timeout=3s period=2s #success=1 #failure=30
    Environment:
      ...                          
      ISTIO_META_POD_PORTS:          [
                                         {"containerPort":8001,"protocol":"TCP"}
                                     ]
      ISTIO_META_APP_CONTAINERS:     liveness-http
      ISTIO_META_CLUSTER_ID:         Kubernetes
      ISTIO_META_NODE_NAME:           (v1:spec.nodeName)
      ISTIO_META_INTERCEPTION_MODE:  REDIRECT
      ISTIO_META_WORKLOAD_NAME:      liveness-http
      ISTIO_META_OWNER:              kubernetes://apis/apps/v1/namespaces/istioinaction/deployments/liveness-http
      ISTIO_META_MESH_ID:            cluster.local
      TRUST_DOMAIN:                  cluster.local
      ISTIO_KUBE_APP_PROBERS:        {"/app-health/liveness-http/livez":{"httpGet":{"path":"/foo","port":8001,"scheme":"HTTP"},"timeoutSeconds":1}}


kubectl get pod -n istioinaction -l app=liveness-http -o json | jq '.items[0].spec.containers[0].livenessProbe.httpGet'
{
  "path": "/app-health/liveness-http/livez",
  "port": 15020,
  "scheme": "HTTP"
}

# 헬스체크 확인
kubectl exec -n istioinaction deploy/liveness-http -c istio-proxy -- curl -s localhost:15020/app-health/liveness-http/livez -v

# 실습 확인 후 삭제
kubectl delete deploy liveness-http -n istioinaction


#
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15020/healthz/ready -v

# webapp 워크로드의 병합된 통계 확인 : istio_agent로 시작하는 메트릭(에이전트에서 온 것) + envoy로 시작하는 메트릭(프록시에서 온 것)
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15020/stats/prometheus
## 응답에서는 istio_agent로 시작하는 메트릭(에이전트에서 온 것)과 envoy로 시작하는 메트릭(프록시에서 온 것)을 볼 수 있는데,
## 이는 이 둘이 병합됐음을 보여준다.

#
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15020/quitquitquit

#
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15020/debug/ndsz

#
kubectl port-forward deploy/webapp -n istioinaction 15020:15020
open http://localhost:15020/debug/pprof # 혹은 웹 브라우저에서 열기

 

15020 포트에는 이스티오 에이전트 트러블슈팅을 위한 여러 엔드포인트가 존재한다.
/healthz/ready는 엔보이와 DNS 프록시의 상태를 검사해 워크로드가 트래픽을 받을 준비가 됐는지 확인한다.
/stats/prometheus는 엔보이 및 애플리케이션 메트릭을 병합해 노출하고, /quitquitquit는 파일럿 에이전트 프로세스를 종료하며, /app-health/는 애플리케이션의 쿠버네티스 프로브를 프록시가 대신 처리한다

 

 

 

#
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15004/debug/syncz -v
kubectl exec -n istioinaction deploy/webapp -c istio-proxy -- curl -s localhost:15004/debug/syncz | jq
...
      "@type": "type.googleapis.com/envoy.service.status.v3.ClientConfig",
      "node": {
        "id": "catalog-6cf4b97d-fbftr.istioinaction", # 워크로드 ID
        "metadata": {
          "CLUSTER_ID": "Kubernetes"
        }
      },
      "genericXdsConfigs": [
        {
          "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
          "configStatus": "SYNCED" # xDS API는 최신 상태로 동기화됬다
        },
        {
          "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
          "configStatus": "SYNCED" # xDS API는 최신 상태로 동기화됬다
        },
        {
          "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
          "configStatus": "SYNCED" # xDS API는 최신 상태로 동기화됬다
        },
        {
          "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
          "configStatus": "SYNCED" # xDS API는 최신 상태로 동기화됬다
        },
...

# 하위 명령 출력 내용과 동일
docker exec -it myk8s-control-plane istioctl x internal-debug -h
docker exec -it myk8s-control-plane istioctl x internal-debug syncz

이스티오 파일럿은 디버그 엔드포인트를 통해 서비스 메시의 구성 및 상태 정보를 노출한다. 주요 엔드포인트로는 클러스터/라우트/리스너 설정을 확인하는 /debug/adsz, 엔드포인트 정보를 제공하는 /debug/edsz, 전체 구성을 조회하는 /debug/configz 등이 있다.

istioctl x internal-debug 명령어는 파일럿 디버그 엔드포인트에 접근해 동기화 상태(syncz), 구성 차이(diff), 엔보이 설정 등을 직접 확인할 수 있다. 인-클러스터/아웃-오브-클러스터 배포 환경에서 보안 옵션(--cert-dir, --xds-address)을 활용해 안전하게 데이터를 수집할 수 있으며, 다중 컨트롤 플레인 환경에서는 --xds-label로 특정 인스턴스를 대상으로 진단이 가능하다.

이 도구들은 Envoy 설정 동기화 문제, 엔드포인트 누락, 라우팅 규칙 오작동 등을 효과적으로 트러블슈팅하는 데 활용된다. 예를 들어 istioctl x internal-debug syncz로 전체 메시의 동기화 상태를 한 번에 확인하거나, 특정 파드의 설정 차이를 비교해 문제 원인을 식별할 수 있다.

 

#
kubectl -n istio-system exec -it deploy/istiod -- 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 :::15017                :::*                    LISTEN     
tcp6       0      0 :::15014                :::*                    LISTEN     
tcp6       0      0 :::15012                :::*                    LISTEN     
tcp6       0      0 :::15010                :::*                    LISTEN     
tcp6       0      0 :::8080                 :::*                    LISTEN 

# pilot-discovery 프로세스 확인
kubectl -n istio-system exec -it deploy/istiod -- ss -tnlp
State          Recv-Q         Send-Q                 Local Address:Port                  Peer Address:Port         Process                                          
LISTEN         0              4096                       127.0.0.1:9876                       0.0.0.0:*             users:(("pilot-discovery",pid=1,fd=8))          
LISTEN         0              4096                               *:15017                            *:*             users:(("pilot-discovery",pid=1,fd=12))         
LISTEN         0              4096                               *:15014                            *:*             users:(("pilot-discovery",pid=1,fd=9))          
LISTEN         0              4096                               *:15012                            *:*             users:(("pilot-discovery",pid=1,fd=10))         
LISTEN         0              4096                               *:15010                            *:*             users:(("pilot-discovery",pid=1,fd=11))         
LISTEN         0              4096                               *:8080                             *:*             users:(("pilot-discovery",pid=1,fd=3)) 

#
kubectl describe pod -n istio-system -l app=istiod
...
Containers:
  discovery:
    Container ID:  containerd://f13d7ad8a32cc0cecf47392ef426ea4687ce12d1abf64b5a6d2a60c2f8934e04
    Image:         docker.io/istio/pilot:1.17.8
    Image ID:      docker.io/istio/pilot@sha256:cb9e7b1b1c7b8dcea37d5173b87c40f38a5ae7b44799adfdcf8574c57a52ad2c
    Ports:         8080/TCP, 15010/TCP, 15017/TCP
    Host Ports:    0/TCP, 0/TCP, 0/TCP
    Args:
      discovery
      --monitoringAddr=:15014
      --log_output_level=default:info
      --domain
      cluster.local
      --keepaliveMaxServerConnectionAge
      30m
    ...
    Readiness:  http-get http://:8080/ready delay=1s timeout=5s period=3s #success=1 #failure=3
    Environment:
      REVISION:                                     default
      JWT_POLICY:                                   third-party-jwt
      PILOT_CERT_PROVIDER:                          istiod
      POD_NAME:                                     istiod-8d74787f-ltkhs (v1:metadata.name)
      POD_NAMESPACE:                                istio-system (v1:metadata.namespace)
      SERVICE_ACCOUNT:                               (v1:spec.serviceAccountName)
      KUBECONFIG:                                   /var/run/secrets/remote/config
      PILOT_TRACE_SAMPLING:                         100
      PILOT_ENABLE_PROTOCOL_SNIFFING_FOR_OUTBOUND:  true
      PILOT_ENABLE_PROTOCOL_SNIFFING_FOR_INBOUND:   true
      ISTIOD_ADDR:                                  istiod.istio-system.svc:15012
      PILOT_ENABLE_ANALYSIS:                        false
      CLUSTER_ID:                                   Kubernetes
...

 

  • 서비스용 포트
    • 15010 : xDS API 및 인증서 발급을 평문으로 노출한다. 트래픽을 스니핑할 수 있으므로 이 포트는 사용하지 않는 것이 좋다.
    • 15012 : 15010 포트와 노출하는 정보는 같지만 보안을 적용한다. 이 포트는 TLS를 사용해 ID를 발급하여, 후속 요청은 상호 인증된다.
    • 15014 : 11장에서 다룬 것과 같은 컨트롤 플레인 메트릭을 노출한다.
    • 15017 : 쿠버네티스 API 서버가 호출하는 웹훅 서버를 노출한다.
      • 쿠버네티스 API 서버는 새로 만들어진 파드에 사이드카를 주입하고, Gateway나 VirtualServie 같은 이스티오 리소스를 검증하기 위해 호출한다.
  • 디버깅 및 검사 포트
    • 8080 : 이스티오 파일럿 디버그 엔드포인트를 노출한다.
    • 9876 : istiod 프로세스에 대한 검사 정보를 노출한다.
#
kubectl -n istio-system port-forward deploy/istiod 8080
open http://localhost:8080/debug

# 파일럿이 알고 있는 서비스 메시 상태
## 클러스터, 루트, 리스너 설정
curl -s http://localhost:8080/debug/adsz | jq

## 이 파일럿이 관리하는 모든 프록시에 대한 푸시를 트리거한다.
curl -s http://localhost:8080/debug/adsz?push=true
Pushed to 4 servers

## /debug/edsz=proxyID=<pod>.<namespace> : 프록시가 알고 있는 엔드포인트들
curl -s http://localhost:8080/debug/edsz=proxyID=webapp.istioninaction

## /debug/authorizationz : 네임스페이스에 적용되는 인가 정책 목록
curl -s http://localhost:8080/debug/authorizationz | jq


# 파일럿이 알고 있는 데이터 플레인 설정을 나타내는 엔드포인트
## 이 파일럿 인스턴스에 연결된 모든 엔보이의 버전 상태 : 현재 비활성화되어 있음
curl -s http://localhost:8080/debug/config_distribution
Pilot Version tracking is disabled. It may be enabled by setting the PILOT_ENABLE_CONFIG_DISTRIBUTION_TRACKING environment variable to true

## 이스티오 파일럿의 현재 알려진 상태에 따라 엔보이 설정을 생성한다.
curl -s http://localhost:8080/debug/config_dump?=proxyID=webapp.istioninaction

## 이 파일럿이 관리하는 프록시들을 표시한다.
curl -s http://localhost:8080/debug/syncz | jq
...
  {
    "cluster_id": "Kubernetes",
    "proxy": "webapp-7685bcb84-lwsvj.istioinaction",
    "istio_version": "1.17.8",
    "cluster_sent": "ff5e6b2c-e857-4e12-b17e-46ad968567f4",
    "cluster_acked": "ff5e6b2c-e857-4e12-b17e-46ad968567f4",
    "listener_sent": "7280c908-010d-4788-807f-7138e74fe72e",
    "listener_acked": "7280c908-010d-4788-807f-7138e74fe72e",
    "route_sent": "2a1916c3-9c05-4ce5-8cfa-d777105b9205",
    "route_acked": "2a1916c3-9c05-4ce5-8cfa-d777105b9205",
    "endpoint_sent": "dffacd32-2674-4e39-8e76-17016ff32514",
    "endpoint_acked": "dffacd32-2674-4e39-8e76-17016ff32514"
  },
...

이스티오 파일럿은 /debug/adsz, /debug/edsz, /debug/authorizationz로 서비스 메시 상태(클러스터·라우트·엔드포인트·인가 정책)를 확인한다.
/debug/syncz는 프록시 동기화 상태와 **논스(nonce)**를 비교해 설정 최신 여부를 판단하며, /debug/config_dump는 특정 프록시의 엔보이 설정을 생성한다.
istioctl proxy-status 같은 도구가 엔드포인트를 활용하지만, 복잡한 문제 시 직접 접근해 세부 진단이 가능하다.

 

 

 

 

 

9.3 서비스 간 트래픽 인가하기

이스티오의 AuthorizationPolicy 리소스는 인증된 주체에 대해 서비스 메시 내에서 메시, 네임스페이스, 워크로드 단위로 세분화된 접근 제어 정책을 선언적으로 정의할 수 있게 해준다. 

각 서비스에 배포된 프록시는 인가 집행 엔진 역할을 하며, AuthorizationPolicy 리소스에 정의된 정책을 바탕으로 요청을 직접 허용하거나 거부한다.
이처럼 접근 제어가 프록시에서 직접 이루어지기 때문에 이스티오의 인가 처리 방식은 매우 효율적이다.

 

#예시 정책

# cat ch9/allow-catalog-requests-in-web-app.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-catalog-requests-in-web-app"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  rules:
  - to:
    - operation:
        paths: ["/api/catalog*"]
  action: ALLOW
  • AuthorizationPolicy 리소스는 워크로드에 적용할 인가 정책을 선언적으로 정의하며, selector(적용 대상), action(허용/거부/커스텀), rules(규칙) 세 가지 주요 필드를 가진다.
  • selector는 정책이 적용될 워크로드(예: 특정 레이블)를 지정한다.
  • rules는 요청의 출처(from: principals, namespaces, ipBlocks)와 작업(to: 경로, 메서드 등), 조건(when)을 조합해 세부 인가 규칙을 정의한다.
  • action 필드는 규칙과 일치하는 요청에 대해 허용(ALLOW), 거부(DENY), 커스텀(CUSTOM) 중 어떤 처리를 할지 결정한다.
  • 정책이 적용되면, istiod가 프록시 설정을 갱신해 해당 워크로드의 프록시가 요청을 실시간으로 허용 또는 거부한다.

 

실습환경 구성

# 9.2.1 에서 이미 배포함
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f services/webapp/kubernetes/webapp.yaml
kubectl -n istioinaction apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml
kubectl -n default apply -f ch9/sleep.yaml

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


# PeerAuthentication 설정 : 앞에서 이미 설정함
cat ch9/meshwide-strict-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT
    
kubectl -n istio-system apply -f ch9/meshwide-strict-peer-authn.yaml
kubectl get peerauthentication -n istio-system

cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "webapp"
  namespace: "istioinaction"
spec:
  selector:
    matchLabels:
      app: webapp 
  mtls:
    mode: PERMISSIVE

kubectl -n istioinaction apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get peerauthentication -n istioinaction

 

 

 

워크로드에 정책 적용 시 동작 확인

워크로드에 ALLOW 인가 정책이 하나라도 적용되면, 명시적으로 허용된 트래픽만 접근할 수 있고 그 외의 모든 트래픽은 기본적으로 거부된다.

 

# cat ch9/allow-catalog-requests-in-web-app.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-catalog-requests-in-web-app"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp # 워크로드용 셀렉터 Selector for workloads
  rules:
  - to:
    - operation:
        paths: ["/api/catalog*"] # 요청을 경로 /api/catalog 와 비교한다 Matches requests with the path /api/catalog
  action: ALLOW # 일치하면 허용한다 If a match, ALLOW
  
  
  
  # 로그
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# 적용 전 확인 
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 404 리턴

# AuthorizationPolicy 리소스 적용
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
kubectl get authorizationpolicy -n istioinaction

# 
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json > webapp-listener.json
...
          {
              "name": "envoy.filters.http.rbac",
              "typedConfig": {
                  "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                  "rules": {
                      "policies": {
                          "ns[istioinaction]-policy[allow-catalog-requests-in-web-app]-rule[0]": {
                              "permissions": [
                                  {
                                      "andRules": {
                                          "rules": [
                                              {
                                                  "orRules": {
                                                      "rules": [
                                                          {
                                                              "urlPath": {
                                                                  "path": {
                                                                      "prefix": "/api/catalog"
                                                                  }
                                                              }
                                                          }
                                                      ]
                                                  }
                                              }
                                          ]
                                      }
                                  }
                              ],
                              "principals": [
                                  {
                                      "andIds": {
                                          "ids": [
                                              {
                                                  "any": true
                                              }
                                          ]
                                      }
                                  }
                              ]
                          }
                      }
                  },
                  "shadowRulesStatPrefix": "istio_dry_run_allow_" #  실제로 차단하지 않고, 정책이 적용됐을 때 통계만 수집 , istio_dry_run_allow_로 prefix된 메트릭 생성됨
              }
          },
...

# 로그 : 403 리턴 체크!
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T10:08:52.918Z] "GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "b272b991-7a79-9581-bb14-55a6ee705311" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:50172 - -

# 적용 후 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 403 리턴
RBAC: access denied

# 다음 실습을 위해 정책 삭제
kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml

 

정책이 있을때와 없을때의 차이. 403/404

 

전체 정책으로 기본적으로 모든 요청 거부하기

보안성을 증가시키고 과정을 단순화하기 위해, ALLOW 정책을 명시적으로 지정하지 않은 모든 요청을 거부하는 메시 범위 정책을 정의해보자.

# cat ch9/policy-deny-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: istio-system # 이스티오를 설치한 네임스페이스의 정책은 메시의 모든 워크로드에 적용된다
spec: {} # spec 이 비어있는 정책은 모든 요청을 거부한다


# 적용 전 확인 
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
curl -s http://webapp.istioinaction.io:30000/api/catalog


# 정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
kubectl get authorizationpolicy -A

# 적용 후 확인 1
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T14:45:31.051Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "f1ec493b-cc39-9573-b3ad-e37095bbfaeb" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:60780 - -

# 적용 후 확인 2
curl -s http://webapp.istioinaction.io:30000/api/catalog
...
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...

 

access denied가 출력됨.

 

특정 네임스페이스에서 온 요청 허용하기

종종 특정 네임스페이스에서 시작한, 모든 서비스에 대한 트래픽을 허용하고 싶을 것이다.

이는 source.namespace 속성으로 할 수 있다.

# 
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "webapp-allow-view-default-ns"
  namespace: istioinaction # istioinaction의 워크로드
spec:
  rules:
  - from: # default 네임스페이스에서 시작한
    - source:
        namespaces: ["default"]
    to:   # HTTP GET 요청에만 적용 
    - operation:
        methods: ["GET"]
EOF

#
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                           AGE
istio-system    deny-all                       11h
istioinaction   webapp-allow-view-default-ns   11h

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
                {
                    "name": "envoy.filters.http.rbac",
                    "typedConfig": {
                        "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                        "rules": {
                            "policies": {
                                "ns[istio-system]-policy[deny-all]-rule[0]": {
                                    "permissions": [
                                        {
                                            "notRule": {
                                                "any": true
                                            }
                                        }
                                    ],
                                    "principals": [
                                        {
                                            "notId": {
                                                "any": true
                                            }
                                        }
                                    ]
                                },
                                "ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
                                    "permissions": [
                                        {
                                            "andRules": {
                                                "rules": [
                                                    {
                                                        "orRules": {
                                                            "rules": [
                                                                {
                                                                    "header": {
                                                                        "name": ":method",
                                                                        "exactMatch": "GET"
                                                                    }
                                                                }
                                                            ]
                                                        }
                                                    }
                                                ]
                                            }
                                        }
                                    ],
                                    "principals": [
                                        {
                                            "andIds": {
                                                "ids": [
                                                    {
                                                        "orIds": {
                                                            "ids": [
                                                                {
                                                                    "filterState": {
                                                                        "key": "io.istio.peer_principal",
                                                                        "stringMatch": {
                                                                            "safeRegex": {
                                                                                "regex": ".*/ns/default/.*"
...

#
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...

sleep 서비스는 레거시 워크로드로, 사이드카 프록시가 없어 ID가 부여되지 않는다. 이로 인해 webapp 프록시는 sleep 서비스의 요청 출처(네임스페이스 등)를 확인할 수 없다. 해결책은 sleep 서비스에 프록시를 주입해 ID와 상호 인증을 지원하는 것이며, 이 방식이 권장된다.

부득이한 경우에는 webapp에서 미인증 요청도 허용하는 인가 정책을 적용할 수 있지만, 이는 보안상 덜 안전하다.

 

#
kubectl label ns default istio-injection=enabled
kubectl delete pod -l app=sleep

#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
sleep-6f8cfb8c8f-wncwh.default                         Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-n4c7b     1.17.8
...

# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...

kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
error calling Catalog service

docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f # webapp -> catalog 는 deny-all 로 거부됨
[2025-05-04T02:36:49.857Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 0 0 "-" "beegoServer" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:33066 10.200.1.46:80 10.10.0.14:48794 - default
[2025-05-04T02:36:49.856Z] "GET /api/catalog HTTP/1.1" 500 - via_upstream - "-" 0 29 1 1 "-" "curl/8.5.0" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:38191 10.10.0.14:8080 10.10.0.17:59998 outbound_.80_._.webapp.istioinaction.svc.cluster.local default


# 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items # default -> catalog 은 성공


# 다음 실습을 위해 default 네임스페이스 원복
kubectl label ns default istio-injection-
kubectl rollout restart deploy/sleep

docker exec -it myk8s-control-plane istioctl proxy-status
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # 거부 확인

 

 

미인증 레거시 워크로드에서 온 요청 허용하기

미인증 워크로드의 요청을 허용하려면 AuthorizationPolicy에서 from 필드를 제거하고, app: webapp 셀렉터로 webapp에만 적용하면 된다.

# cat ch9/allow-unauthenticated-view-default-ns.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "webapp-allow-unauthenticated-view-default-ns"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  rules:
    - to:
      - operation:
          methods: ["GET"]
          
          
          
#
kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                                           AGE
istio-system    deny-all                                       12h
istioinaction   webapp-allow-unauthenticated-view-default-ns   14s
istioinaction   webapp-allow-view-default-ns                   11h

# 여러개의 정책이 적용 시에 우선순위는?
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq
...
         "name": "envoy.filters.http.rbac",
                        "typedConfig": {
                            "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                            "rules": {
                                "policies": {
                                    "ns[istio-system]-policy[deny-all]-rule[0]": {
                                        "permissions": [
                                            {
                                                "notRule": {
                                                    "any": true
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "notId": {
                                                    "any": true
                                                }
                                            }
                                        ]
                                    },
                                    "ns[istioinaction]-policy[webapp-allow-unauthenticated-view-default-ns]-rule[0]": {
                                        "permissions": [
                                            {
                                                "andRules": {
                                                    "rules": [
                                                        {
                                                            "orRules": {
                                                                "rules": [
                                                                    {
                                                                        "header": {
                                                                            "name": ":method",
                                                                            "exactMatch": "GET"
                                                                        }
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "andIds": {
                                                    "ids": [
                                                        {
                                                            "any": true
                                                        }
                                                    ]
                                                }
                                            }
                                        ]
                                    },
                                    "ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
                                        "permissions": [
                                            {
                                                "andRules": {
                                                    "rules": [
                                                        {
                                                            "orRules": {
                                                                "rules": [
                                                                    {
                                                                        "header": {
                                                                            "name": ":method",
                                                                            "exactMatch": "GET"
                                                                        }
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "andIds": {
                                                    "ids": [
                                                        {
                                                            "orIds": {
                                                                "ids": [
                                                                    {
                                                                        "filterState": {
                                                                            "key": "io.istio.peer_principal",
                                                                            "stringMatch": {
                                                                                "safeRegex": {
                                                                                    "regex": ".*/ns/default/.*"
                                                                                }
...

# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...

kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f # webapp -> catalog 는 deny-all 로 거부됨
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
error calling Catalog service

# (옵션) 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items

 

 

 

 

특정 서비스 어카운트에서 온 요청 허용하기

서비스 어카운트 정보가 webapp인 워크로드에서 온 트래픽만 허용하려면, AuthorizationPolicy에서 필터 메타데이터의 서비스 어카운트 값을 기준으로 인가 규칙을 설정하면 된다.

 

# cat ch9/catalog-viewer-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "catalog-viewer"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: catalog
  rules:
  - from:
    - source: 
        principals: ["cluster.local/ns/istioinaction/sa/webapp"] # Allows requests with the identity of webapp
    to:
    - operation:
        methods: ["GET"]
        
        
        
        
#
kubectl apply -f ch9/catalog-viewer-policy.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                                           AGE
istio-system    deny-all                                       13h
istioinaction   catalog-viewer                                 10s
istioinaction   webapp-allow-unauthenticated-view-default-ns   61m
istioinaction   webapp-allow-view-default-ns                   12h

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction --port 15006 -o json
...
            "principals": [
                {
                    "andIds": {
                        "ids": [
                            {
                                "orIds": {
                                    "ids": [
                                        {
                                            "filterState": {
                                                "key": "io.istio.peer_principal",
                                                "stringMatch": {
                                                    "exact": "spiffe://cluster.local/ns/istioinaction/sa/webapp"
                                                }
...

# 호출 테스트 : sleep --(미인증 레거시 허용)--> webapp --(principals webapp 허용)--> catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f

# (옵션) 호출 테스트 : catalog
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
...

 

 

정책의 조건부 적용

특정 조건(예: 사용자가 관리자일 때)에만 인가 정책을 적용하려면, AuthorizationPolicy의 when 속성을 활용하면 된다.

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-mesh-all-ops-admin"
  namespace: istio-system
spec:
  rules:
  - from:
    - source:
        requestPrincipals: ["auth@istioinaction.io/*"]
    when:
    - key: request.auth.claims[groups] # 이스티오 속성을 지정한다
      values: ["admin"] # 반드시 일치해야 하는 값의 목록을 지정한다
  • 이 정책은 다음 두 조건이 모두 충족될 때만 요청을 허용한다.
    • 첫째, 토큰은 요청 주체 auth@istioinaction.io/* 가 발급한 것이어야 한다.
    • 둘째, JWT에 값이 ‘admin’인 group 클레임 claim이 포함돼 있어야 한다.
  • 또는 notValues 속성을 사용해 이 정책을 적용하지 않아야 하는 값들을 정의할 수도 있다.
  • 조건에서 사용할 수 있는 이스티오 속성 전체 목록은 이스티오 문서에서 찾을 수 있다.
  • https://istio.io/latest/docs/reference/config/security/conditions/

 

인가 정책 평가 순서 

  1. CUSTOM 정책이 있으면 가장 먼저 평가
    • 외부 인가 서버 등과 연동된 CUSTOM 정책이 요청을 거부(Deny)하면, 즉시 요청이 거부
  2. DENY 정책이 그다음 평가
    • CUSTOM 정책이 없거나 통과하면, DENY 정책이 요청과 일치하는지 확인
    • 일치하는 DENY 정책이 있으면 요청이 거부
  3. ALLOW 정책이 마지막으로 평가
    • DENY 정책도 없거나 통과하면, ALLOW 정책이 있는지 확인
    • ALLOW 정책이 하나라도 요청과 일치하면 요청이 허용
  4. ALLOW 정책이 있지만, 일치하는 것이 없으면 요청은 거부
    • ALLOW 정책이 아예 없으면 요청은 허용(기본 허용).
    • 하지만 ALLOW 정책이 있는데 아무것도 일치하지 않으면 요청은 거부(기본 거부).

CUSTOM(DENY) → DENY → ALLOW 순서로 평가

 

 

9.4 최종 사용자 인증 및 인가

JWT란

JWT(JSON Web Token)는 클라이언트가 서버에 인증할 때 사용하는 간결한 클레임(정보) 표현 방식이다. 헤더, 페이로드, 서명 세 부분으로 구성되며, 각각은 점(.)으로 구분되고 Base64 URL로 인코딩된다.

헤더에는 토큰 유형과 서명 알고리즘 정보가 담기고, 페이로드에는 사용자 정보 및 클레임이 포함된다. 서명은 JWT의 진위와 무결성을 검증하는 데 사용되며, 서버의 비밀키 또는 공개키/개인키 쌍으로 생성된다.

이 구조 덕분에 JWT는 HTTP 요청에 쉽게 포함할 수 있고, 서버는 별도 세션 저장 없이 토큰만으로 사용자를 인증·인가할 수 있다.

 

#
cat ./ch9/enduser/user.jwt

# 디코딩 방법 1
jwt decode $(cat ./ch9/enduser/user.jwt)

# 디코딩 방법 2
cat ./ch9/enduser/user.jwt | cut -d '.' -f1 | base64 --decode | sed 's/$/}/'  | jq
cat ./ch9/enduser/user.jwt | cut -d '.' -f2 | base64 --decode | sed 's/$/"}/' | jq
{
  "exp": 4745145038, # 만료 시간 Expiration time
  "group": "user",   # 'group' 클레임
  "iat": 1591545038, # 발행 시각 Issue time
  "iss": "auth@istioinaction.io", # 토큰 발행자 Token issuer
  "sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79" # 토큰의 주체 Subject or principal of the token
}

 

JWT의 클레임은 클라이언트의 신원과 인가 정보를 서비스에 제공하며, 이 정보를 신뢰하려면 토큰이 반드시 검증 가능해야 한다.

  • JWT는 인증 서버에서 비밀키로 서명되어 발급되며, 검증을 위해 공개키가 JWKS(JSON Web Key Set) 형태로 HTTP 엔드포인트에 제공된다.
  • 서비스(리소스 서버)는 이 JWKS 엔드포인트에서 공개키를 가져와 JWT의 서명을 검증하고, 토큰의 발급자(issuer), 만료(exp), 대상(audience) 등 클레임 값도 함께 확인한다.
  • 서명과 클레임 검증을 모두 통과하면, 서비스는 토큰의 진위와 신뢰성을 인정하고 클레임 정보를 인가에 활용한다.

1. 인증서버의 역할

  • 인증서버는 두 가지 키를 가진다.
    • private key(비공개 키): JWT 토큰에 서명할 때 사용
    • public key(공개 키): JWT 토큰의 서명을 검증할 때 사용

2. JWT 토큰 발급

  • 사용자가 인증서버에 로그인하면, 인증서버는 private key로 서명된 JWT 토큰을 발급

3. 공개 키(JWKS) 제공

  • 인증서버는 자신의 public key를 JWKS(JSON Web Key Set)라는 형식으로 HTTP 엔드포인트를 통해 제공
  • 서비스 서버는 이 엔드포인트에서 public key를 가져올 수 있다.

4. 서비스 서버의 검증 과정

  • 사용자는 JWT 토큰을 서비스 서버에 보낸다
  • 서비스 서버는 인증서버의 JWKS 엔드포인트에서 public key를 가져와, 토큰의 서명을 검증

5. 서명 검증 방식

  • public key로 JWT의 서명을 복호화해서 해시값을 얻고
  • JWT 토큰 안의 데이터로 다시 해시값을 계산
  • 두 해시값이 같으면, 토큰 데이터(클레임)가 변조되지 않았음을 확인
  • 해시값이 동일하다면, 토큰이 위조되지 않았음을 보장하므로 신뢰한다.

 

인그레스 게이트웨이에서의 최종 사용자 인증 및 인가

Istio 워크로드는 ID 제공자에게 인증받아 토큰을 발급받은 최종 사용자의 JWT를 통해 요청을 인증하고 인가할 수 있으며, 일반적으로 인그레스 게이트웨이에서 이를 수행해 유효하지 않은 요청을 조기에 차단한다. 이 과정에서 JWT를 요청에서 제거해, 후속 서비스에서 토큰이 유출되거나 재전송 공격(replay attack)에 악용되는 것을 방지한다.

 

실습환경 전 기존 실습 삭제

#
kubectl delete virtualservice,deployment,service,\
destinationrule,gateway,peerauthentication,authorizationpolicy --all -n istioinaction

#
kubectl delete peerauthentication,authorizationpolicy -n istio-system --all

# 삭제 확인
kubectl get gw,vs,dr,peerauthentication,authorizationpolicy -A


# 실습 환경 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
cat ch9/enduser/ingress-gw-for-webapp.yaml
kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction

 

 

RequestAuthentication으로 JWT 검증하기

RequestAuthentication 리소스는 JWT 토큰을 검증하고, 유효한 토큰의 클레임을 추출해 필터 메타데이터에 저장한다. 이 필터 메타데이터는 인가 정책(AuthorizationPolicy)이 요청 허용/거부를 결정할 때 근거로 사용된다.

JWT가 있는 요청은 클레임이 메타데이터에 저장되지만, JWT가 없는 요청은 클레임 정보가 없다.

RequestAuthentication 리소스는 인가를 직접 강제하지 않으며, 인가를 위해서는 별도의 AuthorizationPolicy가 필요하다. 즉, 인증(토큰 검증 및 클레임 추출)과 인가(정책 적용)는 분리되어 있으며, 둘 다 설정해야 효과적으로 보안을 적용할 수 있다.

 

RequestAuthentication 리소스 만들기

다음 RequestAuthentication 리소스는 이스티오의 인그레스 게이트웨이에 적용된다. 이는 인그레스 게이트웨이가 auth@istioinaction.io 에서 발급한 토큰을 검증하도록 설정한다.

# cat ch9/enduser/jwt-token-request-authn.yaml 
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "jwt-token-request-authn"
  namespace: istio-system # 적용할 네임스페이스
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  jwtRules:
  - issuer: "auth@istioinaction.io" # 발급자 Expected issuer
    jwks: | # 특정 JWKS로 검증
      { "keys":[ {"e":"AQAB","kid":"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM","kty":"RSA","n":"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ"}]}
      
#
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl get requestauthentication -A

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
        "httpFilters": [
                {
                    "name": "istio.metadata_exchange",
                    "typedConfig": {
                        "@type": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
                        "config": {
                            "vmConfig": {
                                "runtime": "envoy.wasm.runtime.null",
                                "code": {
                                    "local": {
                                        "inlineString": "envoy.wasm.metadata_exchange"
                                    }
                                }
                            },
                            "configuration": {
                                "@type": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange"
                            }
                        }
                    }
                },
                {
                    "name": "envoy.filters.http.jwt_authn",
                    "typedConfig": {
                        "@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
                        "providers": {
                            "origins-0": {
                                "issuer": "auth@istioinaction.io",
                                "localJwks": {
                                    "inlineString": "{ \"keys\":[ {\"e\":\"AQAB\",\"kid\":\"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM\",\"kty\":\"RSA\",\"n\":\"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ\"}]}\n"
                                },
                                "payloadInMetadata": "auth@istioinaction.io"
                            }
                        },
                        "rules": [
                            {
                                "match": {
                                    "prefix": "/"
                                },
                                "requires": {
                                    "requiresAny": {
                                        "requirements": [
                                            {
                                                "providerName": "origins-0"
                                            },
                                            {
                                                "allowMissing": {}
                                            }
                                        ]
                                    }
                                }
                            }
                        ],
                        "bypassCorsPreflight": true
                    }
                },
                {
                    "name": "istio_authn",
                    "typedConfig": {
                        "@type": "type.googleapis.com/istio.envoy.config.filter.http.authn.v2alpha1.FilterConfig",
                        "policy": {
                            "origins": [
                                {
                                    "jwt": {
                                        "issuer": "auth@istioinaction.io"
                                    }
                                }
                            ],
                            "originIsOptional": true,
                            "principalBinding": "USE_ORIGIN"
                        },
                        "skipValidateTrustDomain": true
...

 

유효한 발행자의 토큰이 있는 요청은 받아들여진다

#
cat ch9/enduser/user.jwt
USER_TOKEN=$(< ch9/enduser/user.jwt)
jwt decode $USER_TOKEN

# 호출 
curl -H "Authorization: Bearer $USER_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

# 로그
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug
kubectl logs -n istio-system -l app=istio-ingressgateway -f

 

 

 

유효하지 않은 발행자의 토큰이 있는 요청은 거부된다

#
cat ch9/enduser/not-configured-issuer.jwt
WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)
jwt decode $WRONG_ISSUER
...
Token claims
------------
{
  "exp": 4745151548,
  "group": "user",
  "iat": 1591551548,
  "iss": "old-auth@istioinaction.io", # 현재 설정한 정책의 발급자와 다름 issuer: "auth@istioinaction.io" 
  "sub": "79d7506c-b617-46d1-bc1f-f511b5d30ab0"
}
...


# 호출 
curl -H "Authorization: Bearer $WRONG_ISSUER" \
     -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T06:36:22.089Z] "GET /api/catalog HTTP/1.1" 401 - jwt_authn_access_denied{Jwt_issuer_is_not_configured} - "-" 0 28 1 - "172.18.0.1" "curl/8.7.1" "2e183b2e-0968-971d-adbc-6b149171912b" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:65436 - -

 

 

토큰이 없는 요청은 클러스터로 받아들여진다

# 호출 
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f

토큰이 없는 요청을 거부하려면, 실제로 다양한 무토큰 요청 상황이 존재하므로 추가적인 처리가 필요하다.

 

 

JWT가 없는 요청 거부하기

JWT가 없는 요청을 거부하려면 requestPrincipals가 없는 source를 명시적으로 거부하는 AuthorizationPolicy를 만들어야 하며, requestPrincipals는 JWT의 issuer와 subject 클레임을 ‘iss/sub’ 형태로 결합해 초기화된다.
이렇게 인증된 클레임은 RequestPrincipals 리소스를 통해 커넥션 메타데이터로 가공되어, AuthorizationPolicy 등에서 활용된다.

 

# cat ch9/enduser/app-gw-requires-jwt.yaml # vi/vim, vscode 에서 포트 30000 추가
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: app-gw-requires-jwt
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"] # 요청 주체에 값이 없는 source는 모두 해당된다
    to:
    - operation:
        hosts: ["webapp.istioinaction.io:30000"] # 이 규칙은 이 특정 호스트에만 적용된다
        ports: ["30000"]

#
kubectl apply -f ch9/enduser/app-gw-requires-jwt.yaml

#
kubectl get AuthorizationPolicy -A
NAMESPACE      NAME                  AGE
istio-system   app-gw-requires-jwt   2m14s

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
          {
              "name": "envoy.filters.http.rbac",
              "typedConfig": {
                  "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                  "rules": {
                      "action": "DENY",
                      "policies": {
                          "ns[istio-system]-policy[app-gw-requires-jwt]-rule[0]": {
                              "permissions": [
                                  {
                                      "andRules": {
                                          "rules": [
                                              {
                                                  "orRules": {
                                                      "rules": [
                                                          {
                                                              "header": {
                                                                  "name": ":authority",
                                                                  "stringMatch": {
                                                                      "exact": "webapp.istioinaction.io:30000",
                                                                      "ignoreCase": true
                                                                  }
                                                              }
                                                          }
                                                      ]
                                                  }
                                              }
                                          ]
                                      }
                                  }
                              ],
                              "principals": [
                                  {
                                      "andIds": {
                                          "ids": [
                                              {
                                                  "notId": {
                                                      "orIds": {
                                                          "ids": [
                                                              {
                                                                  "metadata": {
                                                                      "filter": "istio_authn",
                                                                      "path": [
                                                                          {
                                                                              "key": "request.auth.principal"
                                                                          }
                                                                      ],
                                                                      "value": {
                                                                          "stringMatch": {
                                                                              "safeRegex": {
                                                                                  "regex": ".+"
...


# 호출 1
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
403

# 호출 2
curl -H "Authorization: Bearer $USER_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T07:04:01.791Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[ns[istio-system]-policy[app-gw-requires-jwt]-rule[0]] - "-" 0 19 0 - "172.18.0.1" "curl/8.7.1" "41678cf6-6ef8-986e-beb4-4e5af46e7a26" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:65424 - -

 

 

 

JWT 클레임에 기반한 다양한 접근 수준

일반 사용자는 API 데이터 읽기만 허용하고, 관리자는 모든 권한을 갖도록 클레임이 다른 토큰 기반 접근 정책을 설정한다.

# 일반 사용자 토큰 : 'group: user' 클레임
jwt decode $(cat ch9/enduser/user.jwt)
...
{
  "exp": 4745145038,
  "group": "user",
  "iat": 1591545038,
  "iss": "auth@istioinaction.io",
  "sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}

# 관리자 토큰 : 'group: admin' 클레임
jwt decode $(cat ch9/enduser/admin.jwt)
...
{
  "exp": 4745145071,
  "group": "admin",
  "iat": 1591545071,
  "iss": "auth@istioinaction.io",
  "sub": "218d3fb9-4628-4d20-943c-124281c80e7b"
}

 

 

#일반 사용자가 webapp 에서 데이터를 읽을 수 있게 허용하도록 AuthorizationPolicy 리소스 설정

# cat ch9/enduser/allow-all-with-jwt-to-webapp.yaml # vi/vim, vscode 에서 포트 30000 추가
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-all-with-jwt-to-webapp
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["auth@istioinaction.io/*"] # 최종 사용자 요청 주체를 표현 Represents the end-user request principal
    to:
    - operation:
        hosts: ["webapp.istioinaction.io:30000"]
        methods: ["GET"]
        
        
        
        
#관리자에게 모든 작업을 허용하는 AuthorizationPolicy 리소스 설정        
# cat ch9/enduser/allow-mesh-all-ops-admin.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-mesh-all-ops-admin"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["auth@istioinaction.io/*"]
    when:
    - key: request.auth.claims[group]
      values: ["admin"] # 이 클레임을 포함한 요청만 허용.

 

 

#실습

#
kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml
kubectl apply -f ch9/enduser/allow-mesh-all-ops-admin.yaml

#
kubectl get authorizationpolicy -A
NAMESPACE      NAME                           AGE
istio-system   allow-all-with-jwt-to-webapp   5s
istio-system   allow-mesh-all-ops-admin       5s
istio-system   app-gw-requires-jwt            34m

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
                "policies": {
                    "ns[istio-system]-policy[allow-all-with-jwt-to-webapp]-rule[0]": {
                        "permissions": [
                            {
                                "andRules": {
                                    "rules": [
                                        {
                                            "orRules": {
                                                "rules": [
                                                    {
                                                        "header": {
                                                            "name": ":authority",
                                                            "stringMatch": {
                                                                "exact": "webapp.istioinaction.io:30000",
                                                                "ignoreCase": true
...
                    "ns[istio-system]-policy[allow-mesh-all-ops-admin]-rule[0]": {
                        "permissions": [
                            {
                                "andRules": {
                                    "rules": [
                                        {
                                            "any": true
                                        }
                                    ]
                                }
                            }
                        ],
                        "principals": [
                            {
                                "andIds": {
                                    "ids": [
                                        {
                                            "orIds": {
                                                "ids": [
                                                    {
                                                        "metadata": {
                                                            "filter": "istio_authn",
                                                            "path": [
                                                                {
                                                                    "key": "request.auth.principal"
                                                                }
                                                            ],
                                                            "value": {
                                                                "stringMatch": {
                                                                    "prefix": "auth@istioinaction.io/"
                                                                }
                                                            }
                                                        }
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "orIds": {
                                                "ids": [
                                                    {
                                                        "metadata": {
                                                            "filter": "istio_authn",
                                                            "path": [
                                                                {
                                                                    "key": "request.auth.claims"
                                                                },
                                                                {
                                                                    "key": "group"
                                                                }
                                                            ],
                                                            "value": {
                                                                "listMatch": {
                                                                    "oneOf": {
                                                                        "stringMatch": {
                                                                            "exact": "admin"
...

# 수집된 메타데이터를 관찰하고자 서비스 프록시에 rbac 로거 설정
## 기본적으로 envoy rbac 로거는 메타데이터를 로그에 출력하지 않는다. 출력을 위해 로깅 수준을 debug 로 설정하자
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug


# 일반유저 : [GET]과 [POST] 호출
USER_TOKEN=$(< ch9/enduser/user.jwt)

curl -H "Authorization: Bearer $USER_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog

curl -H "Authorization: Bearer $USER_TOKEN" \
     -XPOST webapp.istioinaction.io:30000/api/catalog \
     --data '{"id": 2, "name": "Shoes", "price": "84.00"}'

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...
, dynamicMetadata: filter_metadata {
  key: "envoy.filters.http.jwt_authn"
  value {
    fields {
      key: "auth@istioinaction.io"
      value {
        struct_value {
          fields {
            key: "exp"
            value {
              number_value: 4745145038
            }
          }
          fields {
            key: "group"
            value {
              string_value: "user"
            }
          }
...
[2025-05-04T07:39:27.597Z] "POST /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 1 - "172.18.0.1" "curl/8.7.1" "677a3c73-20a1-935a-b039-e2a8beae9d1b" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:57196 - -


# 관리자 : [GET]과 [POST] 호출
ADMIN_TOKEN=$(< ch9/enduser/admin.jwt)

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
     -XPOST webapp.istioinaction.io:30000/api/catalog \
     --data '{"id": 2, "name": "Shoes", "price": "84.00"}'

# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f

일반 사용자
관리자

 

 

9.5 커스텀 외부 인가 서비스와 통합하기

SPIFFE 기반 인증으로 서비스 신뢰성을 확보한 이스티오는 기본 RBAC 외에, 더 정교한 인가가 필요할 때 프록시가 외부 인가 서비스를 호출하도록 CUSTOM 액션을 활용해 커스텀 인가 메커니즘을 쉽게 확장할 수 있다.

서비스 프록시에 들어온 요청은 프록시가 외부 인가(ExtAuthz) 서비스를 호출하는 동안 잠시 멈추고, 외부 인가 서비스가 허용 또는 거부 메시지를 반환하면 프록시가 인가를 집행한다.

외부 인가 서비스는 애플리케이션 사이드카로 메시 내부에 두거나 메시 외부에 배포할 수 있으며, Envoy의 CheckRequest API를 구현해야 한다.

외부 인가를 사용하면 세밀한 인가 정책을 적용할 수 있지만, 요청 경로에 외부 호출이 추가되어 지연 시간이 늘어나는 트레이드오프가 있으므로 성능 영향도 고려해야 한다.

 

 

외부 인가 실습

# 기존 인증/인가 정책 모두 삭제
kubectl delete authorizationpolicy,peerauthentication,requestauthentication --all -n istio-system

# 실습 애플리케이션 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
kubectl apply -f ch9/sleep.yaml -n default

# 이스티오 샘플에서 샘플 외부 인가 서비스 배포
docker exec -it myk8s-control-plane bash
-----------------------------------
# 
ls -l istio-$ISTIOV/samples/extauthz/
total 24
-rw-r--r-- 1 root root 4238 Oct 11  2023 README.md
drwxr-xr-x 3 root root 4096 Oct 11  2023 cmd
drwxr-xr-x 2 root root 4096 Oct 11  2023 docker
-rw-r--r-- 1 root root 1330 Oct 11  2023 ext-authz.yaml
-rw-r--r-- 1 root root 2369 Oct 11  2023 local-ext-authz.yaml

cat istio-$ISTIOV/samples/extauthz/ext-authz.yaml
apiVersion: v1
kind: Service
metadata:
  name: ext-authz
  labels:
    app: ext-authz
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 8000
  - name: grpc
    port: 9000
    targetPort: 9000
  selector:
    app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ext-authz
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ext-authz
  template:
    metadata:
      labels:
        app: ext-authz
    spec:
      containers:
      - image: gcr.io/istio-testing/ext-authz:latest
        imagePullPolicy: IfNotPresent
        name: ext-authz
        ports:
        - containerPort: 8000
        - containerPort: 9000

kubectl apply -f istio-$ISTIOV/samples/extauthz/ext-authz.yaml -n istioinaction

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

# 설치 확인 : ext-authz
kubectl get deploy,svc ext-authz -n istioinaction
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ext-authz   1/1     1            1           72s

NAME                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
service/ext-authz   ClusterIP   10.200.1.172   <none>        8000/TCP,9000/TCP   72s

# 로그
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f

배포한 ext-authz 서비스는 요청에 x-ext-authz: allow 헤더가 있으면 허용하고 없으면 거부하며, 필요에 따라 더 복잡한 외부 인가 로직도 구현할 수 있다.

 

 

이스티오에 외부 인가 설정하기

# includeHeadersInCheck (DEPRECATED)
KUBE_EDITOR="nano" kubectl edit -n istio-system cm istio
--------------------------------------------------------
...
    extensionProviders:
    - name: "sample-ext-authz-http"
      envoyExtAuthzHttp:
        service: "ext-authz.istioinaction.svc.cluster.local"
        port: "8000"
        includeRequestHeadersInCheck: ["x-ext-authz"]
...
--------------------------------------------------------

# 확인
kubectl describe -n istio-system cm istio

이스티오가 새로운 외부 인가 서비스를 인식하도록 하려면, istio-system 네임스페이스의 istio configmap에서 meshConfig의 extensionProviders 항목에 해당 외부 인가 서비스 정보를 추가해야 한다.

예를 들어, sample-ext-authz-http라는 이름으로 HTTP 타입의 외부 인가 서비스를 등록하고, 서비스 주소와 포트, 그리고 전달할 헤더(x-ext-authz)를 명시한다. 이 설정은 kubectl edit -n istio-system cm istio 명령어로 configmap을 수정해 적용할 수 있다.

등록된 extensionProviders는 AuthorizationPolicy 리소스에서 provider 이름으로 참조하여 커스텀 인가 정책을 적용할 수 있다.

마지막으로, 외부 인가 서비스가 실제로 요청에 포함된 헤더를 활용해 인가 결과를 반환하게 되며, 이를 통해 이스티오가 외부 인가를 연동해 동작한다.

 

커스텀 AuthorizationPolicy 리소스 사용하기

action 이 CUSTOMAuthorizationPolicy 를 만들고 정확히 어떤 외부 인가 서비스를 사용할지 지정해본다.

# 아래 AuthorizationPolicy 는 istioinaction 네임스페이스에 webapp 워크로드에 적용되며, 
# sample-ext-authz-http 이라는 외부 인가 서비스에 위임한다.
cat << EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  action: CUSTOM    # custom action 사용
  provider:
    name: sample-ext-authz-http  # meshconfig 이름과 동일해야 한다
  rules:
  - to:
    - operation:
        paths: ["/*"]  # 인가 정책을 적용할 경로
EOF

#
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME        AGE
istioinaction   ext-authz   98s
#호출확인
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f


# 헤더 없이 호출
kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog
denied by ext_authz for not found header `x-ext-authz: allow` in the request

kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-04T08:33:04.765006Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:114      checking request: requestedServerName: , sourceIP: 10.10.0.18:55834, directRemoteIP: 10.10.0.18:55834, remoteIP: 10.10.0.18:55834,localAddress: 10.10.0.20:8080, ssl: none, headers: ':authority', 'webapp.istioinaction'
':path', '/api/catalog'
':method', 'GET'
':scheme', 'http'
'user-agent', 'curl/8.5.0'
'accept', '*/*'
'x-forwarded-proto', 'http'
'x-request-id', 'ffd44f00-19ff-96b7-868b-8f6b09bd447d'
, dynamicMetadata:      thread=31
2025-05-04T08:33:04.765109Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:130      shadow denied, matched policy istio-ext-authz-ns[istioinaction]-policy[ext-authz]-rule[0]thread=31
2025-05-04T08:33:04.765170Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:167      no engine, allowed by default      thread=31
[2025-05-04T08:33:04.764Z] "GET /api/catalog HTTP/1.1" 403 UAEX ext_authz_denied - "-" 0 76 5 4 "-" "curl/8.5.0" "ffd44f00-19ff-96b7-868b-8f6b09bd447d" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.20:8080 10.10.0.18:55834 - -

kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
2025/05/04 08:35:26 [HTTP][denied]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[58148c96f61496a3] X-B3-Sampled:[1] X-B3-Spanid:[960b8d911e81c217] X-B3-Traceid:[ce6c5622c32fd238a934fbf1aa4a9de0] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=491c5bf23be281a5c0c2e798eba242461dfdb7b178d4a4cd842f9eedb05ae47d;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.20] X-Forwarded-Proto:[https] X-Request-Id:[964138e3-d955-97c9-b9a5-dfc88cc7f9c5]], body: []


# 헤더 적용 호출
kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog

kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-04T08:37:40.618775Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:114        checking request: requestedServerName: , sourceIP: 10.10.0.18:36150, directRemoteIP: 10.10.0.18:36150, remoteIP: 10.10.0.18:36150,localAddress: 10.10.0.20:8080, ssl: none, headers: ':authority', 'webapp.istioinaction'
':path', '/api/catalog'
':method', 'GET'
':scheme', 'http'
'user-agent', 'curl/8.5.0'
'accept', '*/*'
'x-ext-authz', 'allow'
'x-forwarded-proto', 'http'
'x-request-id', 'b446ddf8-fb2e-9dd7-ba01-6e31fac717da'
, dynamicMetadata:      thread=30
2025-05-04T08:37:40.618804Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:130        shadow denied, matched policy istio-ext-authz-ns[istioinaction]-policy[ext-authz]-rule[0] thread=30
2025-05-04T08:37:40.618816Z     debug   envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:167        no engine, allowed by default     thread=30
[2025-05-04T08:37:40.622Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "-" "beegoServer" "b446ddf8-fb2e-9dd7-ba01-6e31fac717da" "catalog.istioinaction:80" "10.10.0.19:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.20:60848 10.200.1.165:80 10.10.0.20:45874 - default
[2025-05-04T08:37:40.618Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 6 4 "-" "curl/8.5.0" "b446ddf8-fb2e-9dd7-ba01-6e31fac717da" "webapp.istioinaction" "10.10.0.20:8080" inbound|8080|| 127.0.0.6:43721 10.10.0.20:8080 10.10.0.18:36150 - default

kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
2025/05/04 08:36:34 [HTTP][allowed]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[f9bc85c800aaaa05] X-B3-Sampled:[1] X-B3-Spanid:[bf6cc58161f7ca25] X-B3-Traceid:[af1c826a362ce0382e219cd21afe1fe7] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Ext-Authz:[allow] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=491c5bf23be281a5c0c2e798eba242461dfdb7b178d4a4cd842f9eedb05ae47d;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.20] X-Forwarded-Proto:[https] X-Request-Id:[c9b43ce7-25d4-94ae-b684-1565ad36f533]], body: []

 

 

 

 

정리

PeerAuthentication (피어 인증 정책)

  • PeerAuthentication은 서비스 간 통신에서 상호 TLS(mTLS) 사용 여부와 수준을 정의하는 정책. 이 정책을 통해 서비스 간 트래픽이 암호화되어, 네트워크 상에서 데이터가 노출되거나 도청되는 것을 방지.
  • STRICT 모드를 적용하면, 해당 워크로드(또는 네임스페이스, 또는 전체 mesh)로 들어오는 모든 트래픽에 대해 반드시 mTLS가 적용되어야 하며, 평문 트래픽은 거부. 이를 통해 외부에서 암호화되지 않은 트래픽이 유입되는 것을 원천적으로 차단
  • 반면, PERMISSIVE 모드는 mTLS와 평문 트래픽을 동시에 허용. 이를 활용하면, 기존에 평문 트래픽을 사용하던 환경에서 다운타임 없이 점진적으로 mTLS로 마이그레이션할 수 있다.

AuthorizationPolicy (인가 정책)

  • AuthorizationPolicy는 서비스 간 요청이나 최종 사용자 요청에 대해 허용(Allow) 또는 거부(Deny) 정책을 정의.
  • 이 정책은 워크로드의 ID 인증서(예: SPIFFE ID) 또는 최종 사용자의 JWT에서 추출한 검증 가능한 메타데이터(클레임 등)를 기반으로 동작. 즉, 서비스 또는 사용자 신원에 따라 세밀한 접근 제어가 가능.
  • 정책은 ALLOW, DENY, AUDIT 등 다양한 액션을 지원하며, 여러 정책이 동시에 적용될 경우 DENY가 우선 적용.
  • AuthorizationPolicy는 기본적으로 Istio 프록시가 자체적으로 인가를 집행하지만, 외부 인가 시스템과 연동할 수도 있다.

RequestAuthentication (요청 인증 정책)

  • RequestAuthentication은 JWT가 포함된 최종 사용자 요청을 인증하는 데 사용.
  • 이 정책을 통해 JWT의 위치, 발급자(issuer), 공개키(JWKS) 등 검증에 필요한 정보를 지정할 수 있다. Istio는 요청에 포함된 JWT가 정책에 부합하는지 검증하고, 유효하지 않은 토큰이 있으면 요청을 거부.
  • 단, 토큰이 없는 요청은 기본적으로 허용. 토큰이 없는 요청을 명시적으로 거부하려면 AuthorizationPolicy에서 별도의 규칙을 추가해야 한다.

AuthorizationPolicy의 CUSTOM 액션 (외부 인가 서비스 연동)

  • Istio의 AuthorizationPolicy는 CUSTOM 액션을 통해 외부 인가 서비스(External Authorization Service) 와 연동할 수 있다.
  • CUSTOM 액션을 사용하면, 프록시가 요청을 받을 때 내부 정책이 아닌 외부 인가 서비스로 요청 정보를 전달하고, 인가 결과(허용/거부)를 받아 집행한다. 이를 통해 조직 고유의 인가 로직이나 타사 인가 시스템(예: OPA, 자체 인가 서버 등)과 쉽게 통합할 수 있다.
  • 외부 인가 서비스는 Istio meshconfig의 extensionProviders에 등록되어야 하며, AuthorizationPolicy에서 provider 이름으로 지정.
  • 이 방식은 Istio의 기본 인가 기능으로는 커버할 수 없는 복잡한 요구사항이나, 외부 시스템과의 통합이 필요한 경우에 유용.

요약 


 

PeerAuthentication 피어(서비스 간) 인증 및 트래픽 암호화 STRICT: mTLS만 허용, PERMISSIVE: mTLS+평문 허용, 점진적 마이그레이션 지원
AuthorizationPolicy 서비스/사용자 요청 인가(허용/거부) 워크로드 ID, JWT 등 메타데이터 기반 세밀한 접근 제어, ALLOW/DENY/AUDIT 지원
RequestAuthentication 최종 사용자 JWT 인증 JWT 유효성 검증, 토큰 없는 요청은 기본 허용(추가 정책 필요)
AuthorizationPolicy-CUSTOM 외부 인가 서비스 연동 외부 인가 시스템과 통합, 복잡한 인가 로직 지원
 

이처럼 Istio는 다양한 인증·인가 정책을 조합해 서비스 메시 내 트래픽의 신뢰성, 기밀성, 접근 제어를 유연하고 강력하게 구현할 수 있습니다.

9.1 애플리케이션 네트워크 보안의 필요성

  • 애플리케이션 보안은 인가되지 않은 사용자가 중요한 데이터를 훼손, 탈취, 무단 접근하지 못하도록 보호하는 모든 활동을 의미한다.
  • 이를 위해 사용자 인증·인가 절차와, 네트워크를 오가는 데이터의 암호화가 필수적이다.
  • 인증은 사용자의 정체를 확인하는 과정이고, 인가는 인증된 사용자의 리소스 접근 권한을 판단하는 절차다.

서비스 간 인증 Service-to-service authentication - SPIFFE 프레임워크

  • SPIFFE는 서비스 간 신뢰를 위해 제3자가 발급한 짧은 수명의 암호화된 ID 문서(SVID)를 자동으로 제공하고, 이를 통해 서비스들이 서로를 인증한다.
  • 이 ID는 TLS 연결이나 JWT 서명 검증 등에 사용되어, 서비스 간 안전한 상호 인증을 구현한다.

 

최종 사용자 인증 End-user authentication - JWT 등 자격증명

최종 사용자 인증은 사용자가 인증 서버에서 받은 자격 증명을 서비스에 제시하고, 서비스는 이를 인증 서버에 검증하여 접근을 허용하는 절차다.

 

인가 Authorization - 작업 수행 승인/거부

인가는 인증된 사용자의 ID를 바탕으로 수행 가능한 작업을 확인하여 승인 또는 거부하는 절차이며, 이스티오는 이를 세분화하여 제공한다.

 

MSA에서의 인증/인가 

마이크로서비스 환경에서는 서비스가 동적으로 생성·소멸되고 여러 네트워크와 클라우드에 분산되기 때문에, IP 주소 기반 식별 방식은 신뢰할 수 없다.

이스티오는 SPIFFE 오픈소스 표준을 도입해, 다양한 환경에서 각 워크로드에 고유하고 검증 가능한 ID를 자동으로 부여함으로써 안전한 서비스 식별과 인증을 지원한다.

 

Istio에서의 인증/인가 

이스티오의 보안은 PeerAuthentication, RequestAuthentication, AuthorizationPolicy 세 가지 커스텀 리소스 기반으로 구성된다.

PeerAuthentication은 서비스 간 트래픽에 대해 mTLS 인증을 적용하고, 상대 서비스의 인증서 정보를 추출한다.

RequestAuthentication은 최종 사용자의 자격 증명(JWT 등)을 검증하며, 인증된 사용자 정보를 추출한다.

AuthorizationPolicy는 앞서 추출된 서비스 또는 사용자 정보를 바탕으로 요청을 허용하거나 거부하는 인가 정책을 설정한다.

이 세 리소스는 프록시가 요청의 ID 정보를 필터 메타데이터로 저장하고, 인가 정책에 따라 세분화된 접근 제어를 실현하게 한다.

 

 

9.2 자동 상호 TLS (Auto mTLS)

  • 이스티오는 프록시가 주입된 서비스 간 트래픽을 자동으로 암호화하고 상호 인증하며, 인증서 발급·갱신을 자동화해 휴먼 에러와 서비스 중단을 예방한다.
  • mTLS와 최소 권한 정책을 적용하면 기본적으로 안전한 상태를 유지할 수 있지만, 메시 전체를 더 안전하게 만들기 위해서는 추가적인 보안 작업이 필요하다.

 

실습환경

#mTLS 실습 환경
# catalog와 webapp 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction

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

# default 네임스페이스에 sleep 앱 배포
cat ch9/sleep.yaml
...
    spec:
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: governmentpaas/curl-ssl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /etc/sleep/tls
          name: secret-volume
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true

kubectl apply -f ch9/sleep.yaml -n default

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

 

 

이스티오의 PeerAuthentication 리소스 이해하기

PeerAuthentication 리소스는 mTLS 인증 모드를 서비스 메시 전체, 네임스페이스, 또는 특정 워크로드 단위로 엄격(STRICT) 또는 허용(PERMISSIVE)하게 설정할 수 있다.

 
 
 
메시 범위 정책으로 모든 미인증 트래픽 거부하기
#
cat ch9/meshwide-strict-peer-authn.yaml 
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default" # Mesh-wide policies must be named "default"
  namespace: "istio-system" # Istio installation namespace
spec:
  mtls:
    mode: STRICT # mutual TLS mode

# 적용
kubectl apply -f ch9/meshwide-strict-peer-authn.yaml -n istio-system

# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
000
command terminated with exit code 56

# 확인
kubectl get PeerAuthentication -n istio-system
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-01T08:32:08.511Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.17:8080 10.10.0.16:51930 - -
[2025-05-01T08:32:10.629Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.17:8080 10.10.0.16:53366 - -
# NR → Non-Route. Envoy에서 라우팅까지 가지 못한 단계에서 발생한 에러라는 의미입니다.
# filter_chain_not_found → 해당 Listener에서 제공된 SNI(Server Name Indication), IP, 포트, ALPN 등의 조건에 맞는 filter_chain이 설정에 없다는 뜻입니다.

 

 

상호 인증하기 않은 트래픽 허용하기

네임스페이스 범위 PeerAuthentication 정책은 메시 전체 정책을 덮어쓸 수 있어, 특정 네임스페이스의 워크로드에 맞는 인증 요구사항을 유연하게 적용할 수 있다.

 
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"             # Uses the "default" naming convention so that only one namespace-wide resource exists
  namespace: "istioinaction"  # Specifies the namespace to apply the policy
spec:
  mtls:
    mode: PERMISSIVE          # PERMISSIVE allows HTTP traffic.
EOF


# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

# 확인
kubectl get PeerAuthentication -A 
NAMESPACE       NAME      MODE         AGE
istio-system    default   STRICT       2m51s
istioinaction   default   PERMISSIVE   7s

kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# 다음 실습을 위해 삭제 : PeerAuthentication 단축어 pa
kubectl delete pa default -n istioinaction
 
 
 
워크로드별 PeerAuthentication 정책 적용하기

워크로드 셀렉터를 지정해 PeerAuthentication 정책을 webapp에만 적용하고, 네임스페이스 전체가 아닌 경우 정책 이름을 'webapp'으로 변경하는 것이 권장된다.

# istiod 는 PeerAuthentication 리소스 생성을 수신하고, 이 리소스를 엔보이용 설정으로 변환하며, 
# LDS(Listener Discovery Service)를 사용해 서비스 프록시에 적용
docker exec -it myk8s-control-plane istioctl proxy-status
kubectl logs -n istio-system -l app=istiod -f
...
2025-05-01T09:48:32.854911Z     info    ads     LDS: PUSH for node:catalog-6cf4b97d-2r9bn.istioinaction resources:23 size:85.4kB
2025-05-01T09:48:32.855510Z     info    ads     LDS: PUSH for node:webapp-7685bcb84-jcg7d.istioinaction resources:23 size:94.0kB
...

#
cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "webapp"
  namespace: "istioinaction"
spec:
  selector:
    matchLabels:
      app: "webapp"  # 레이블이 일치하는 워크로드만 PERMISSIVE로 동작
  mtls:
    mode: PERMISSIVE

kubectl apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get pa -A

# 요청 실행
kubectl logs -n istioinaction -l app=webapp -c webapp -f
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl exec deploy/sleep -c sleep -- curl -s http://webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"

#
kubectl logs -n istioinaction -l app=catalog -c catalog -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -c sleep -- curl -s http://catalog.istioinaction/api/items -o /dev/null -w "%{http_code}\n"
2025-05-01T09:32:00.197Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.10.0.18:3000 10.10.0.16:33192 - -
...

 

 

tcpdump로 서비스 간 트래픽 스니핑하기

# 패킷 모니터링 실행 해두기
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy \
  -- sudo tcpdump -l --immediate-mode -vv -s 0 '(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and not (port 53)'
# -l : 표준 출력(stdout)을 라인 버퍼 모드로 설정. 터미널에서 실시간으로 결과를 보기 좋게 함 (pipe로 넘길 때도 유용).
# --immediate-mode : 커널 버퍼에서 패킷을 모아서 내보내지 않고, 캡처 즉시 사용자 공간으로 넘김 → 딜레이 최소화.
# -vv : verbose 출력. 패킷에 대한 최대한의 상세 정보를 보여줌.
# -s 0 : snap length를 0으로 설정 → 패킷 전체 내용을 캡처. (기본값은 262144 bytes, 예전 버전에서는 68 bytes로 잘렸음)
# '(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0) and not (port 53)' : DNS패킷 제외하고 TCP payload 길이가 0이 아닌 패킷만 캡처
# 즉, SYN/ACK/FIN 같은 handshake 패킷(데이터 없는 패킷) 무시, 실제 데이터 있는 패킷만 캡처
# 결론 : 지연 없이, 전체 패킷 내용을, 매우 자세히 출력하고, DNS패킷 제외하고 TCP 데이터(payload)가 1 byte 이상 있는 패킷만 캡처

# 요청 실행
kubectl exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"
...
## (1) sleep -> webapp 호출 HTTP
14:07:24.926390 IP (tos 0x0, ttl 63, id 63531, offset 0, flags [DF], proto TCP (6), length 146)
    10-10-0-16.sleep.default.svc.cluster.local.32828 > webapp-7685bcb84-hp2kl.http-alt: Flags [P.], cksum 0x14bc (incorrect -> 0xa83b), seq 2741788650:2741788744, ack 3116297176, win 512, options [nop,nop,TS val 490217013 ecr 2804101520], length 94: HTTP, length: 94
	GET /api/catalog HTTP/1.1
	Host: webapp.istioinaction
	User-Agent: curl/8.5.0
	Accept: */*

## (2) webapp -> catalog 호출 HTTPS
14:07:24.931647 IP (tos 0x0, ttl 64, id 18925, offset 0, flags [DF], proto TCP (6), length 1304)
    webapp-7685bcb84-hp2kl.37882 > 10-10-0-19.catalog.istioinaction.svc.cluster.local.3000: Flags [P.], cksum 0x1945 (incorrect -> 0x9667), seq 2146266072:2146267324, ack 260381029, win 871, options [nop,nop,TS val 1103915113 ecr 4058175976], length 1252

## (3) catalog -> webapp 응답 HTTPS
14:07:24.944769 IP (tos 0x0, ttl 63, id 7029, offset 0, flags [DF], proto TCP (6), length 1789)
    10-10-0-19.catalog.istioinaction.svc.cluster.local.3000 > webapp-7685bcb84-hp2kl.37882: Flags [P.], cksum 0x1b2a (incorrect -> 0x2b6f), seq 1:1738, ack 1252, win 729, options [nop,nop,TS val 4058610491 ecr 1103915113], length 1737

## (4) webapp -> sleep 응답 HTTP
14:07:24.946168 IP (tos 0x0, ttl 64, id 13699, offset 0, flags [DF], proto TCP (6), length 663)
    webapp-7685bcb84-hp2kl.http-alt > 10-10-0-16.sleep.default.svc.cluster.local.32828: Flags [P.], cksum 0x16c1 (incorrect -> 0x37d1), seq 1:612, ack 94, win 512, options [nop,nop,TS val 2804101540 ecr 490217013], length 611: HTTP, length: 611
	HTTP/1.1 200 OK
	content-length: 357
	content-type: application/json; charset=utf-8
	date: Thu, 01 May 2025 14:07:24 GMT
	x-envoy-upstream-service-time: 18
	server: istio-envoy
	x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*

	[{"id":1,"color":"amber","department":"Eyewear","name":"Elinor Glasses","price":"282.00"},{"id":2,"color":"cyan","department":"Clothing","name":"Atlas Shirt","price":"127.00"},{"id":3,"color":"teal","department":"Clothing","name":"Small Metal Shoes","price":"232.00"},{"id":4,"color":"red","department":"Watches","name":"Red Dragon Watch","price":"232.00"}] [|http]
...

 

 

워크로드 ID가 워크로드 서비스 어카운트에 연결돼 있는지 확인하기

openssl 명령어로 catalog 워크로드의 X.509 인증서를 확인하면, SVID 문서의 유효성, SPIFFE ID가 SAN(Subject Alternative Name) 필드에 URI로 인코딩되어 있는지, 그리고 해당 ID가 워크로드 서비스 어카운트와 일치하는지 직접 검증할 수 있다.

# (참고) 패킷 모니터링 : 아래 openssl 실행 시 동작 확인
kubectl exec -it -n istioinaction deploy/catalog -c istio-proxy \
  -- sudo tcpdump -l --immediate-mode -vv -s 0 'tcp port 3000'
  

# catalog 의 X.509 인증서 내용 확인
kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/istio/root-cert.pem
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout
...

kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- openssl -h
kubectl -n istioinaction exec deploy/webapp -c istio-proxy -- openssl s_client -h

# openssl s_client → TLS 서버에 연결해 handshake와 인증서 체인을 보여줌
# -showcerts → 서버가 보낸 전체 인증서 체인 출력
# -connect catalog.istioinaction.svc.cluster.local:80 → Istio 서비스 catalog로 TCP 80 연결
# -CAfile /var/run/secrets/istio/root-cert.pem → Istio의 root CA로 서버 인증서 검증
# 결론 : Envoy proxy에서 catalog 서비스로 연결하여 TLS handshake 및 인증서 체인 출력 후 사람이 읽을 수 있는 형식으로 해석
kubectl -n istioinaction exec deploy/webapp -c istio-proxy \
  -- openssl s_client -showcerts \
  -connect catalog.istioinaction.svc.cluster.local:80 \
  -CAfile /var/run/secrets/istio/root-cert.pem | \
  openssl x509 -in /dev/stdin -text -noout
...
        Validity 
            Not Before: May  1 09:55:10 2025 GMT # 유효기간 1일 2분
            Not After : May  2 09:57:10 2025 GMT
        ...
        X509v3 extensions:
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication # 사용처 : 웹서버, 웹클라이언트
            ...
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istioinaction/sa/catalog # SPIFFE ID 확인

# catalog 파드의 서비스 어카운트 확인
kubectl describe pod -n istioinaction -l app=catalog | grep 'Service Account'
Service Account:  catalog

 

 

이스티오 보안: SPIFFE

  • PKI(공개 키 인프라)는 서버와 클라이언트가 안전하게 통신할 수 있도록 디지털 인증서(X.509)를 발급·검증하는 표준화된 프레임워크다.
  • 인증서에는 공개 키와 소유자 신원 정보가 포함되며, 서버는 이를 통해 자신의 정체를 증명한다.
  • TLS 프로토콜에서는 핸드셰이크 과정에서 서버가 X.509 인증서를 제시하고, 클라이언트가 신뢰 체인을 검증한다.
  • 검증에 성공하면 클라이언트는 서버의 공개 키로 암호화한 세션 키(대칭 키)를 서버에 전달하고, 서버는 개인 키로 복호화한다.
  • 이렇게 안전하게 교환된 대칭 키로 이후 트래픽을 암호화해 성능과 보안을 모두 확보한다.
  • 최종 사용자 인증은 주로 세션 쿠키나 수명이 짧은 JWT를 발급해, 후속 요청에서 사용자를 인증하는 방식으로 구현된다.
  • 이스티오 등 현대 서비스 메시 환경에서도 JWT 기반의 최종 사용자 인증과 PKI 기반 서비스 인증이 함께 활용된다.

SPIFFE: 모든 이를 위한 안전한 운영 환경 ID 프레임워크

  • SPIFFE ID는 spiffe://trust-domain/path 형식의 RFC 3986 호환 URI로, trust-domain은 발급자를, path는 워크로드를 고유하게 식별한다.
  • path의 구체적 구조는 SPIFFE 구현체가 결정하며, 예를 들어 이스티오는 쿠버네티스 서비스 어카운트를 path로 활용한다.
  • Workload API는 워크로드가 SPIFFE ID가 포함된 SVID(인증서)를 요청·발급받을 수 있도록 표준 gRPC 엔드포인트를 제공한다.
  • 이 API는 워크로드가 자체 비밀을 보유하지 않도록 설계되었으며, 안전하게 SVID를 수령하고, 상호 인증이나 메시지 서명 등에 활용할 수 있게 한다.
  • 워크로드 엔드포인트는 SPIFFE 데이터플레인 구성요소로, 워크로드의 무결성을 attestation(증명)하고 SPIFFE ID가 포함된 CSR을 생성해 워크로드 API에 제출한다.
  • 워크로드 API(예: Istio의 istiod)는 CSR을 검증·서명해 SPIFFE ID가 포함된 SVID(X.509 인증서)를 발급하며, 이 인증서는 SAN 필드에 SPIFFE ID가 URI로 인코딩된다.
  • 이스티오에서는 워크로드 엔드포인트를 프록시(pilot-agent)가, 워크로드 API를 Istio CA(istiod)가 구현하며, 프록시가 ID 부트스트랩과 인증서 수령을 자동화한다.
  • 결과적으로 모든 워크로드는 자동으로 고유한 SVID를 받아 상호 인증과 자동 mTLS 기반 암호화 통신이 가능해진다.

워크로드 ID의 단계별 부트스트랩

  • 쿠버네티스에서 초기화된 모든 파드에는 /var/run/secrets/kubernetes.io/serviceaccount/ 경로에 서비스 어카운트 토큰, 클러스터 CA 인증서, 네임스페이스 정보가 포함된 시크릿이 기본적으로 마운트된다.
  • 이 중 서비스 어카운트 토큰은 파드의 ID와 권한 정보를 담고 있으며, 서명되어 있어 페이로드를 임의로 수정할 수 없고, 쿠버네티스 API와의 안전한 통신 및 워크로드 ID 부트스트랩에 핵심적으로 사용된다.
#
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/kubernetes.io/serviceaccount/
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# 헤더 디코딩 
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
{
  "alg": "RS256",
  "kid": "nKgUYnbjH9BmgEXYbu56GFoBxwDF_jF9Q6obIWvinAM"
}

# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/}/' | jq
{
  "aud": [
    "https://kubernetes.default.svc.cluster.local"
  ],
  "exp": 1777689454,
  "iat": 1746153454,
  "iss": "https://kubernetes.default.svc.cluster.local",
  "kubernetes.io": {
    "namespace": "istioinaction",
    "pod": {
      "name": "webapp-7685bcb84-hp2kl",
      "uid": "98444761-1f47-45ad-b739-da1b7b22013a"
    },
    "serviceaccount": {
      "name": "webapp",
      "uid": "5a27b23e-9ed6-46f7-bde0-a4e4684949c2"
    },
    "warnafter": 1746157061
  },
  "nbf": 1746153454,
  "sub": "system:serviceaccount:istioinaction:webapp"
}

# (옵션) brew install jwt-cli  # Linux 툴 추천 부탁드립니다.
jwt decode $TOKEN
Token header
------------
{
  "alg": "RS256",
  "kid": "nKgUYnbjH9BmgEXYbu56GFoBxwDF_jF9Q6obIWvinAM"
}

Token claims
------------
{
  "aud": [ # 이 토큰의 대상(Audience) : 토큰이 어떤 API나 서비스에서 사용될 수 있는지 정의 -> k8s api가 aud 가 일치하는지 검사하여 올바른 토큰인지 판단.
    "https://kubernetes.default.svc.cluster.local"
  ],
  "exp": 1777689454, # 토큰 만료 시간 Expiration Time (Unix timestamp, 초 단위) , date -r 1777689454 => (1년) Sat May  2 11:37:34 KST 2026
  "iat": 1746153454, # 토큰 발급 시간 Issued At (Unix timestamp), date -r 1746153454 => Fri May  2 11:37:34 KST 2025
  "iss": "https://kubernetes.default.svc.cluster.local", # Issuer, 토큰을 발급한 주체, k8s api가 발급
  "kubernetes.io": {
    "namespace": "istioinaction",
    "pod": {
      "name": "webapp-7685bcb84-hp2kl",
      "uid": "98444761-1f47-45ad-b739-da1b7b22013a" # 파드 고유 식별자
    },
    "serviceaccount": {
      "name": "webapp",
      "uid": "5a27b23e-9ed6-46f7-bde0-a4e4684949c2" # 서비스 어카운트 고유 식별자
    },
    "warnafter": 1746157061 # 이 시간 이후에는 새로운 토큰을 요청하라는 Kubernetes의 신호 (토큰 자동 갱신용) date -r 1746157061 (1시간) => Fri May  2 12:37:41 KST 2025
  },
  "nbf": 1746153454, # Not Before, 이 시간 이전에는 토큰이 유효하지 않음. 보통 iat와 동일하게 설정됩니다.
  "sub": "system:serviceaccount:istioinaction:webapp" # 토큰의 주체(Subject)
}

# sa 에 토큰 유효 시간 3600초 = 1시간 + 7초
kubectl get pod -n istioinaction -l app=webapp -o yaml
...
    - name: kube-api-access-nt4qb
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
...

 

 

파일럿 에이전트는 쿠버네티스 서비스 어카운트 토큰을 디코딩해 SPIFFE ID를 생성하고, 이 ID가 포함된 CSR과 토큰을 이스티오 CA에 전송한다.
이스티오 CA는 TokenReview API로 토큰의 유효성을 검증한 뒤, 검증에 성공하면 CSR에 서명해 인증서를 파일럿 에이전트에 반환한다.

 

파일럿 에이전트는 SDS Secrets Discovery Service 를 통해 인증서와 키를 엔보이 프록시로 전달하고, 이로써 ID 부트스트랩 과정이 마무리된다.

# 유닉스 도메인 소켓 listen 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ss -xpl
Netid           State            Recv-Q           Send-Q                                                    Local Address:Port                      Peer Address:Port           Process                                        
u_str           LISTEN           0                4096                                                etc/istio/proxy/XDS 13207                                * 0               users:(("pilot-agent",pid=1,fd=11))           
u_str           LISTEN           0                4096                       ./var/run/secrets/workload-spiffe-uds/socket 13206                                * 0               users:(("pilot-agent",pid=1,fd=10))  

kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ss -xp
Netid State Recv-Q Send-Q                                Local Address:Port    Peer Address:Port   Process                             
u_str ESTAB 0      0      ./var/run/secrets/workload-spiffe-uds/socket 21902              * 23737   users:(("pilot-agent",pid=1,fd=16))
u_str ESTAB 0      0                               etc/istio/proxy/XDS 1079087            * 1080955 users:(("pilot-agent",pid=1,fd=8)) 
...

# 유닉스 도메인 소켓 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- lsof -U
COMMAND   PID        USER   FD   TYPE             DEVICE SIZE/OFF    NODE NAME
pilot-age   1 istio-proxy    8u  unix 0x00000000bda7185a      0t0 1079087 etc/istio/proxy/XDS type=STREAM # 소켓 경로 및 스트림 타입
pilot-age   1 istio-proxy   10u  unix 0x0000000009112f4b      0t0   13206 ./var/run/secrets/workload-spiffe-uds/socket type=STREAM # SPIFFE UDS (SPIFFE SVID 인증용)
# TYPE 파일 유형 (unix → Unix Domain Socket)
## 8u → 8번 디스크립터, u = 읽기/쓰기
## 10u → 10번 디스크립터, u = 읽기/쓰기

# 유닉스 도메인 소켓 파일 정보 확인
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/workload-spiffe-uds/socket
srw-rw-rw- 1 istio-proxy istio-proxy 0 May  1 23:23 /var/run/secrets/workload-spiffe-uds/socket

# istio 인증서 확인 : 
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
default           Cert Chain     ACTIVE     true           45287494908809645664587660443172732423      2025-05-03T16:13:14Z     2025-05-02T16:11:14Z
ROOTCA            CA             ACTIVE     true           338398148201570714444101720095268162852     2035-04-29T07:46:14Z     2025-05-01T07:46:14Z

docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction -o json
...

echo "." | base64 -d  | openssl x509 -in /dev/stdin -text -noout


# istio ca 관련
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/istio
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout

kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/tokens
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/tokens/istio-token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/tokens/istio-token)
# 헤더 디코딩 
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/"}/' | jq
# (옵션) brew install jwt-cli 
jwt decode $TOKEN


# (참고) k8s ca 관련
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- ls -l /var/run/secrets/kubernetes.io/serviceaccount
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- openssl x509 -in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -text -noout
kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
TOKEN=$(kubectl exec -it -n istioinaction deploy/webapp -c istio-proxy -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 헤더 디코딩 
echo $TOKEN | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
# 페이로드 디코딩
echo $TOKEN | cut -d '.' -f2 | base64 --decode | sed 's/$/}/' | jq
# (옵션) brew install jwt-cli 
jwt decode $TOKEN


# (참고)
kubectl port-forward deploy/webapp -n istioinaction 15000:15000
open http://localhost:15000
curl http://localhost:15000/certs

 

 

 

요청 ID 이해하기

  • 요청 ID는 요청의 필터 메타데이터에 저장되며, 이 메타데이터에는 JWT나 피어 인증서에서 추출한 신뢰할 수 있는 정보(사실, 클레임)가 포함된다.
  • PeerAuthentication 리소스는 워크로드 간 상호 인증(mTLS)을 강제하고, RequestAuthentication 리소스는 JWT를 검증해 최종 사용자 정보를 추출한다.
  • 필터 메타데이터에는 워크로드 ID(Principal), 네임스페이스, 최종 사용자 주체, 사용자 클레임 등 다양한 인증·인가 관련 정보가 저장된다.
  • 이렇게 수집된 메타데이터는 서비스 프록시가 표준 출력 등으로 기록해 관찰할 수 있으며, 인가 정책의 근거로 활용된다.

 

RequestAuthentication 리소스로 수집한 메타데이터

docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug


#초기화
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction

kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml # :30000 포트 추가 필요, 아래 실습 설정 참고.
kubectl get requestauthentication,authorizationpolicy -A


# 로깅
kubectl logs -n istio-system -l app=istio-ingressgateway -f


# admin 토큰을 사용하는 요청 : 필터 메타데이터 확인
ADMIN_TOKEN=$(< ch9/enduser/admin.jwt)

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
...
dynamicMetadata: filter_metadata {
  key: "envoy.filters.http.jwt_authn"
  value {
    fields {
      key: "auth@istioinaction.io"
      value {
        struct_value {
          fields {
            key: "exp"
            value {
              number_value: 4745145071
            }
          }
          fields {
            key: "group"
            value {
              string_value: "admin"
            }
          }
          fields {
            key: "iat"
            value {
              number_value: 1591545071
            }
          }
          fields {
            key: "iss"
            value {
              string_value: "auth@istioinaction.io"
            }
          }
          fields {
            key: "sub"
            value {
              string_value: "218d3fb9-4628-4d20-943c-124281c80e7b"
            }
          }
        }
      }
    }
  }
}
filter_metadata {
  key: "istio_authn"
  value {
    fields {
      key: "request.auth.claims"
      value {
        struct_value {
          fields {
            key: "group"
            value {
              list_value {
                values {
                  string_value: "admin"
                }
              }
            }
          }
          fields {
            key: "iss"
            value {
              list_value {
                values {
                  string_value: "auth@istioinaction.io"
                }
              }
            }
          }
          fields {
            key: "sub"
            value {
              list_value {
                values {
                  string_value: "218d3fb9-4628-4d20-943c-124281c80e7b"
                }
              }
            }
          }
        }
      }
    }
    fields {
      key: "request.auth.principal"
      value {
        string_value: "auth@istioinaction.io/218d3fb9-4628-4d20-943c-124281c80e7b"
      }
    }
    fields {
      key: "request.auth.raw_claims"
      value {
        string_value: "{\"iat\":1591545071,\"sub\":\"218d3fb9-4628-4d20-943c-124281c80e7b\",\"group\":\"admin\",\"exp\":4745145071,\"iss\":\"auth@istioinaction.io\"}"
      }
    }
  }
}
...

 

 

 

 

정리

아래는 첨부한 이미지를 기반으로, Istio에서 서비스 메쉬 워크로드에 인증서가 할당되는 과정을 단계별로 상세히 설명한 내용입니다.

Istio 인증서 발급 및 적용 과정 상세 설명

  1. 서비스 어카운트 토큰 주입 (Inject token)
    • 쿠버네티스는 파드가 생성될 때, 서비스 어카운트 토큰을 /var/run/secrets/kubernetes.io/serviceaccount/ 경로에 자동으로 마운트한다.
    • 이 토큰은 해당 파드(워크로드)의 신원을 증명하는 데 사용된다.
    • 이미지에서 파드 내부의 Istio proxy(Envoy)와 Pilot agent가 이 토큰에 접근할 수 있다.
  2. CSR과 토큰 전송 (CSR + token)
    • Istio 프록시 내부의 Pilot agent는 서비스 어카운트 토큰을 디코딩하여 파드의 신원 정보를 추출한다.
    • 이 정보를 바탕으로 SPIFFE ID가 포함된 CSR(Certificate Signing Request, 인증서 서명 요청)을 생성한다.
    • Pilot agent는 CSR과 서비스 어카운트 토큰을 함께 Istio CA(istiod)로 전송한다.
  3. 토큰 유효성 검증 (Validate token)
    • Istio CA(istiod)는 쿠버네티스 API의 TokenReview 기능을 이용해, 전달받은 토큰이 실제로 쿠버네티스에서 발급한 유효한 토큰인지 확인한다.
    • 이 단계에서 토큰이 위조되었거나 만료된 경우, 인증서 발급이 거부된다.
  4. 인증서 발급 (Certificate)
    • 토큰 검증에 성공하면, Istio CA는 CSR에 서명하여 SPIFFE ID가 포함된 X.509 인증서를 생성한다.
    • 이 인증서는 다시 Pilot agent로 반환된다.
  5. Envoy에 인증서 및 키 적용 (Configure with certificate and private key)
    • Pilot agent는 전달받은 인증서와 개인 키를 Envoy 프록시에 동적으로 전달(SDS: Secret Discovery Service)한다.
    • Envoy는 이 인증서를 사용해 서비스 간 mTLS(상호 TLS) 통신 시 자신의 신원을 증명하고, 암호화된 트래픽을 주고받을 수 있게 된다.

 

 

이스티오는 그라파나로 메트릭 시각화, 예거로 분산 트레이싱, 키알리로 서비스 간 호출 그래프를 제공해 관찰 가능성을 종합적으로 구현한다.

 

 

8.1 Using Grafana to visualize Istio service and control-plane metrics 그라파나로 이스티오 서비스와 컨트롤 플레인 메트릭 시각화

8.1.1 Setting up Istio’s Grafana dashboards 이스티오의 그라파나 대시보드 설정하기 (실습~)

#
cd ch8

kubectl -n prometheus create cm istio-dashboards \
--from-file=pilot-dashboard.json=dashboards/\
pilot-dashboard.json \
--from-file=istio-workload-dashboard.json=dashboards/\
istio-workload-dashboard.json \
--from-file=istio-service-dashboard.json=dashboards/\
istio-service-dashboard.json \
--from-file=istio-performance-dashboard.json=dashboards/\
istio-performance-dashboard.json \
--from-file=istio-mesh-dashboard.json=dashboards/\
istio-mesh-dashboard.json \
--from-file=istio-extension-dashboard.json=dashboards/\
istio-extension-dashboard.json

# 확인
cd ..
kubectl describe cm -n prometheus  istio-dashboards

# Grafana (오퍼레이터)가 configmap(istio-dashboards)을 마운트(인식) 하도록 레이블 지정
kubectl label -n prometheus cm istio-dashboards grafana_dashboard=1

# (참고) Grafana 대시보드 추가
kubectl stern -n prometheus prom-grafana
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap istio-extension-dashboard.json ADDED
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap istio-mesh-dashboard.json ADDED
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap istio-performance-dashboard.json ADDED
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap istio-service-dashboard.json ADDED
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap istio-workload-dashboard.json ADDED
prom-grafana-d7f5cb646-555zp grafana-sc-dashboard [2025-04-27 05:58:21] File in configmap pilot-dashboard.json ADDED
...

 

 

8.2 Distributed tracing 분산 트레이싱

분산 트레이싱은 마이크로서비스 환경에서 요청 경로의 문제를 진단하기 위해 트레이스 ID 상관관계 ID로 호출 흐름을 추적하는 기술
이스티오는 Envoy 프록시를 통해 자동으로 트레이스 메타데이터를 주입·전파해 개발자의 코드 수정 부담을 줄여준다.
**예거(Jaeger)**나 Zipkin과 연동해 서비스 간 지연 구간을 시각화하며, 오픈텔레메트리 표준을 지원
Google의 Dapper 논문에서 기원한 이 기술은 복잡한 분산 시스템의 오류 추적에 필수적
트레이스 데이터는 키알리에서도 통합되어 서비스 의존성과 성능 병목 지점을 한눈에 분석할 수 있게 해준다.

 

분산 트레이싱은 스팬(작업 단위의 시작/종료 시간, 태그, 로그 포함)을 생성해 트레이싱 엔진에 전송하는 방식으로 작동.
각 서비스는 요청 처리 시 트레이스 ID 스팬 ID를 포함한 트레이스 콘텍스트를 다음 서비스로 전파하며, 이를 통해 호출 흐름을 연결.
트레이싱 엔진은 모든 스팬을 조합해 트레이스를 구성하며, 서비스 간 의존성·지연·오류 지점을 시각화.
트레이스 ID는 전체 요청을, 스팬 ID는 개별 서비스 작업을 식별해 상관관계 분석을 가능ㅖ 람자..
이스티오는 Envoy 프록시를 통해 자동으로 트레이스 메타데이터를 주입·전파해 코드 수정 없이 분산 트레이싱을 구현.

 

  1. 요청이 들어온다.
  2. 오! 트레이싱 헤더가 없는 것을 보니 새로운 요청이다.
    • 요청이 서비스 사이를 오가는 과정을 추적할 수 있도록 트레이스 헤더를 생성해두자
  3. 트레이스 헤더가 요청 헤더에 추가됐다. x-request-id: c9421…
  4. 애플리케이션이 다른 서비스를 호출할 때 트레이스 헤더를 전파해야 한다.
  5. 트레이스 헤더를 전파한다. x-request-id: c9421…
  6. 이스티오 프록시는 기존 트레이스 헤더를 애플리케이션으로 전파한다.
  7. 만약 애플리케이션이 요청 헤더를 전파하지 않으면…
  8. 요청에 트레이스 헤더가 누락된다. 앱이 전파하지 않았기 때문이다.

 

8.2.2 Installing a distributed tracing system 분산 트레이싱 시스템 설치하기 (실습~)

# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# 설치 파일 확인
pwd
ls istio-$ISTIOV/samples/addons
cat istio-$ISTIOV/samples/addons/jaeger.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jaeger
  namespace: istio-system
  labels:
    app: jaeger
spec:
  selector:
    matchLabels:
      app: jaeger
  template:
    metadata:
      labels:
        app: jaeger
        sidecar.istio.io/inject: "false"
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "14269"
    spec:
      containers:
        - name: jaeger
          image: "docker.io/jaegertracing/all-in-one:1.35"
          env:
            - name: BADGER_EPHEMERAL
              value: "false"
            - name: SPAN_STORAGE_TYPE
              value: "badger"
            - name: BADGER_DIRECTORY_VALUE
              value: "/badger/data"
            - name: BADGER_DIRECTORY_KEY
              value: "/badger/key"
            - name: COLLECTOR_ZIPKIN_HOST_PORT
              value: ":9411"
            - name: MEMORY_MAX_TRACES
              value: "50000"
            - name: QUERY_BASE_PATH
              value: /jaeger
          livenessProbe:
            httpGet:
              path: /
              port: 14269
          readinessProbe:
            httpGet:
              path: /
              port: 14269
          volumeMounts:
            - name: data
              mountPath: /badger
          resources:
            requests:
              cpu: 10m
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: tracing
  namespace: istio-system
  labels:
    app: jaeger
spec:
  type: ClusterIP
  ports:
    - name: http-query
      port: 80
      protocol: TCP
      targetPort: 16686
    # Note: Change port name if you add '--query.grpc.tls.enabled=true'
    - name: grpc-query
      port: 16685
      protocol: TCP
      targetPort: 16685
  selector:
    app: jaeger
---
# Jaeger implements the Zipkin API. To support swapping out the tracing backend, we use a Service named Zipkin.
apiVersion: v1
kind: Service
metadata:
  labels:
    name: zipkin
  name: zipkin
  namespace: istio-system
spec:
  ports:
    - port: 9411
      targetPort: 9411
      name: http-query
  selector:
    app: jaeger
---
apiVersion: v1
kind: Service
metadata:
  name: jaeger-collector
  namespace: istio-system
  labels:
    app: jaeger
spec:
  type: ClusterIP
  ports:
  - name: jaeger-collector-http
    port: 14268
    targetPort: 14268
    protocol: TCP
  - name: jaeger-collector-grpc
    port: 14250
    targetPort: 14250
    protocol: TCP
  - port: 9411
    targetPort: 9411
    name: http-zipkin
  selector:
    app: jaeger
    
# 설치
kubectl apply -f istio-$ISTIOV/samples/addons/jaeger.yaml
deployment.apps/jaeger created
service/tracing created
service/zipkin created
service/jaeger-collector created

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

# 설치 확인 : 예거는 집킨 형식과 호환됨 Jaeger is compatible with the Zipkin format.
# https://www.jaegertracing.io/docs/1.22/features/#backwards-compatibility-with-zipkin
kubectl get deploy,pod,svc,ep -n istio-system

# NodePort 변경 및 nodeport tracing(30004) 변경
kubectl describe svc -n istio-system tracing
...
Port:                     http-query  80/TCP
TargetPort:               16686/TCP
NodePort:                 http-query  31345/TCP
Endpoints:                10.10.0.20:16686
...

kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

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

 

 

 

- **설치 전 트레이싱 설정하기** : 방법 1 사용
    - 이스티오는 집킨, 데이터독, 예거(집킨 호환)등 분산 트레이싱 백엔드를 지원한다.
    
    
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  meshConfig:
    defaultConfig:
      tracing:
        lightstep: {}
        zipkin: {}
        datadog: {}
        stackdriver: {}    
   
#예를 들어 집킨 호환형인 예거를 사용하려면 다음과 같이 설정한다. → 현재 실습 설정
cat ch8/install-istio-tracing-zipkin.yaml
---
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  meshConfig:
    defaultConfig:
      tracing:
        sampling: 100
        zipkin:
          address: zipkin.istio-system:9411

# 기존 설정 확인
kubectl get IstioOperator -n istio-system installed-state -o json        
kubectl describe cm -n istio-system istio
...
defaultConfig:
  discoveryAddress: istiod.istio-system.svc:15012
  proxyMetadata: {}
  tracing:
    zipkin:
      address: zipkin.istio-system:9411
...

# 적용
docker exec -it myk8s-control-plane bash
-----------------------------------
# 
cat << EOF > install-istio-tracing-zipkin.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  meshConfig:
    defaultConfig:
      tracing:
        sampling: 100
        zipkin:
          address: zipkin.istio-system:9411
EOF

istioctl install -y -f install-istio-tracing-zipkin.yaml

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

# 확인
kubectl describe cm -n istio-system istio
...
  tracing:
    sampling: 100
    zipkin:
      address: zipkin.istio-system:9411
...

 

 

 

 

  • 이스티오가 오픈트레이싱 헤더상관관계 ID자동으로 주입한다는 것을 보여주고자 이스티오 인그레스 게이트웨이를 사용해 외부 httpbin 서비스를 호출하고 요청 헤더를 표시하는 엔드포인트를 호출할 것이다.
    • 실습에서는 httpbin.istioinaction.io 요청 시 외부 서비스 http://httpbin.org 를 호출.
    • http://httpbin.org 은 simple HTTP 테스트 서비스로 응답 시 헤더 정보를 출력.
#이렇게 라우팅하는 이스티오 Gateway, VirtualService 리소스를 배포해보자.

cat ch8/tracing/thin-httpbin-virtualservice.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"
    - "httpbin.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: thin-httbin-virtualservice
spec:
  hosts:
  - "httpbin.istioinaction.io"
  gateways:
  - coolstore-gateway
  http:
  - route:
    - destination:
        host: httpbin.org
---        
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-httpbin-org
spec:
  hosts:
  - httpbin.org 
  ports:
  - number: 80
    name: http
    protocol: HTTP
  location: MESH_EXTERNAL
  resolution: DNS

#
kubectl apply -n istioinaction -f ch8/tracing/thin-httpbin-virtualservice.yaml

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

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




#호스트에서 호출 시, 어떻게 외부 서비스로 전달되는지 살펴보자. 원래 요청에서 사용된 헤더를 반환해야 한다.
#client (curl) → istio-ingress-gateway → httpbin.org (외부)
curl -s http://httpbin.istioinaction.io:30000/headers | jq
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.istioinaction.io",
    "User-Agent": "curl/8.7.1",
    "X-Amzn-Trace-Id": "Root=1-680de9d9-33db643526404d6b0dc37527",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "3726f7dcb215ac12",
    "X-B3-Traceid": "9a4a7076cf8b5f633726f7dcb215ac12",
    "X-Envoy-Attempt-Count": "1",
    "X-Envoy-Decorator-Operation": "httpbin.org:80/*",
    "X-Envoy-Internal": "true",
    "X-Envoy-Peer-Metadata": "ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMjIKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKnAMKBkxBQkVMUxKRAyqOAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwoSCgdyZWxlYXNlEgcaBWlzdGlvCjkKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0CiIKF3NpZGVjYXIuaXN0aW8uaW8vaW5qZWN0EgcaBWZhbHNlChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAouCgROQU1FEiYaJGlzdGlvLWluZ3Jlc3NnYXRld2F5LTk5NmJjNmJiNi03bG5oNwobCglOQU1FU1BBQ0USDhoMaXN0aW8tc3lzdGVtCl0KBU9XTkVSElQaUmt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9pc3Rpby1zeXN0ZW0vZGVwbG95bWVudHMvaXN0aW8taW5ncmVzc2dhdGV3YXkKFwoRUExBVEZPUk1fTUVUQURBVEESAioACicKDVdPUktMT0FEX05BTUUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXk=",
    "X-Envoy-Peer-Metadata-Id": "router~10.10.0.22~istio-ingressgateway-996bc6bb6-7lnh7.istio-system~istio-system.svc.cluster.local"
  }
}

# (참고) X-Envoy-Peer-Metadata 정보 디코딩 확인
echo "ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMjIKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKnAMKBkxBQkVMUxKRAyqOAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwoSCgdyZWxlYXNlEgcaBWlzdGlvCjkKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0CiIKF3NpZGVjYXIuaXN0aW8uaW8vaW5qZWN0EgcaBWZhbHNlChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAouCgROQU1FEiYaJGlzdGlvLWluZ3Jlc3NnYXRld2F5LTk5NmJjNmJiNi03bG5oNwobCglOQU1FU1BBQ0USDhoMaXN0aW8tc3lzdGVtCl0KBU9XTkVSElQaUmt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9pc3Rpby1zeXN0ZW0vZGVwbG95bWVudHMvaXN0aW8taW5ncmVzc2dhdGV3YXkKFwoRUExBVEZPUk1fTUVUQURBVEESAioACicKDVdPUktMT0FEX05BTUUSFhoUaXN0aW8taW5ncmVzc2dhdGV3YXk=" | base64 -d
...

 

 

이스티오 인그레스 게이트웨이는 x-b3-traceid, x-b3-spanid 등 B3 헤더를 자동 주입해 요청 추적을 활성화하고, 해당 헤더는 예거(Jaeger)로 전송되어 분산 트레이싱 스팬을 생성

분산 트레이싱의 성능 부하를 줄이기 위해 트레이스 샘플링을 활용해 수집 비율을 조절하며(예: 기본 1%), 특정 요청은 강제 트레이싱으로 상세 분석이 가능
커스텀 태그를 추가해 트레이스에 비즈니스 관련 메타데이터(사용자 ID, 환경 변수 등)를 포함시켜 디버깅 효율성을 높일 수 있다.

 
 

 

클라이언트에서 트레이싱 강제 방법

x-envoy-force-trace 헤더를 요청에 추가하면, 특정 요청에 대해 샘플링 비율 무시하고 트레이스 데이터를 100% 수집한다. 이스티오의 Envoy 프록시가 해당 헤더를 감지하면 트레이스 ID를 생성하고 모든 하위 서비스 호출에 전파한다.

이점

  • 효율적 자원 활용: 운영 환경에서 기본 샘플링 비율(예: 1%)을 유지하면서 문제 발생 시 특정 요청만 상세 추적 가능
  • 정밀한 문제 진단: 오류 재현 시 헤더 추가만으로 전체 호출 경로의 스팬을 확보해 병목 지점·오류 원인 분석 가능
  • 온디맨드 분석: 글로벌 설정 변경 없이 즉시 트레이싱 활성화 가능

주의점

  • 성능 영향: 강제 트레이싱 남용 시 트레이스 데이터 저장·처리 부하 증가
  • 클라이언트 수정 필요: 애플리케이션 코드에서 헤더 추가 로직 구현 필요 (예: 디버그 모드 전용으로 제한)
  • 헤더 전파 보장: 서비스 간 트레이스 ID 전파를 위해 OpenTelemetry/OpenTracing 라이브러리 연동 필요 (미구현 시 트레이스 단절)
  • 민감 정보 노출: 트레이스에 포함된 메타데이터(예: 사용자 ID)가 외부 유출되지 않도록 보안 설정 필수
#예를 들어 애플리케이션에서 요청에 x-envoy-force-trace 헤더를 추가해, 요청이 만드는 호출 그래프의 스팬과 트레이스를 이스티오가 포착하도록 만들 수 있다.
#샘플 애플리케이션에서 한번 시도해보자.
#
curl -s -H "x-envoy-force-trace: true" http://webapp.istioinaction.io:30000/api/catalog -v
curl -s -H "x-envoy-force-trace: true" http://webapp.istioinaction.io:30000/api/catalog -v
curl -s -H "x-envoy-force-trace: true" http://webapp.istioinaction.io:30000/api/catalog -v
...

 

 

트레이스 태그 커스터마이징은 명시적 값 지정, 환경 변수 참조, 요청 헤더 추출 방식으로 키-값 메타데이터를 스팬에 추가한다.
이를 통해 애플리케이션별 로직(예: 사용자 세션 ID)이나 인프라 정보(예: Pod 버전)를 트레이스에 연동해 디버깅 효율성을 높일 수 있다.

 

#
cat ch8/webapp-deployment-zipkin-tag.yaml
...
  template:
    metadata:
      annotations:
        proxy.istio.io/config: |
          tracing:
            sampling: 100
            customTags:
              custom_tag: # 커스텀 태그의 키
                literal:
                  value: "Test Tag" # 커스텀 태그의 값
            zipkin:
              address: zipkin.istio-system:9411
...

# webapp 에 커스텀 태그 적용
kubectl apply -n istioinaction -f ch8/webapp-deployment-zipkin-tag.yaml

# 호출
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done
...

 

 

이스티오 1.12+에서는 Telemetry API를 통해 Jaeger, Zipkin 등 백엔드 트레이싱 엔진을 유연하게 설정할 수 있으며, extensionProviders 설정으로 엔드포인트·포트·프로토콜을 커스터마이징한다.
MeshConfig 또는 Pod 어노테이션을 활용해 클러스터 전체 또는 워크로드별로 트레이싱 백엔드(예: OpenTelemetry Collector)를 지정하고 샘플링 비율을 조정할 수 있다.
W3C Trace Context/B3 propagation 전환, 커스텀 태그 추가, 멀티 백엔드 전송(예: TSB와 Jaeger 동시 연동) 등 고급 설정도 지원된다.

#기본설정

# 
docker exec -it myk8s-control-plane bash
----------------------------------------
# deploy/webapp 트레이싱 설정 조회 : 현재 기본 설정
istioctl pc bootstrap -n istioinaction deploy/webapp -o json | jq .bootstrap.tracing
{
  "http": {
    "name": "envoy.tracers.zipkin",
    "typedConfig": {
      "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
      "collectorCluster": "zipkin",
      "collectorEndpoint": "/api/v2/spans",
      "traceId128bit": true,
      "sharedSpanContext": false,
      "collectorEndpointVersion": "HTTP_JSON"
    }
  }
}

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



#변경
# 해당 configmap 은 collectorEndpoint 를 변경한 설정 스니펫
cat ch8/istio-custom-bootstrap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: istio-custom-zipkin
data:
  custom_bootstrap.json: |
    {
      "tracing": {
        "http": {
          "name": "envoy.tracers.zipkin",
          "typedConfig": {
            "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
            "collectorCluster": "zipkin",
            "collectorEndpoint": "/zipkin/api/v1/spans",
            "traceId128bit": "true",
            "collectorEndpointVersion": "HTTP_JSON"
          }
        }
      }
    }

# 이 부트스트랩 설정을 덮어 쓰려는 워크로드가 있는 네임스페이스에 configmap 을 적용할 수 있다.
kubectl apply -n istioinaction -f ch8/istio-custom-bootstrap.yaml

# 확인
kubectl get cm -n istioinaction

# 해당 configmap 을 참조하는 Deployment 리소스의 파드 템플릿에 애노테이션을 추가
cat ch8/webapp-deployment-custom-boot.yaml
...
  template:
    metadata:
      annotations:
        sidecar.istio.io/bootstrapOverride: "istio-custom-zipkin" # 부트스트랩 설정을 istio-custom-zipkin 사용
        proxy.istio.io/config: |
          tracing:
            sampling: 10
            zipkin:
              address: zipkin.istio-system:9411
      labels:
        app: webapp
...

# 변경된 설정으로 webapp을 재배포 합니다
kubectl apply -n istioinaction -f ch8/webapp-deployment-custom-boot.yaml


#
docker exec -it myk8s-control-plane bash
----------------------------------------
# deploy/webapp 트레이싱 설정 조회 : 현재 기본 설정
istioctl pc bootstrap -n istioinaction deploy/webapp -o json | jq .bootstrap.tracing
{
  "http": {
    "name": "envoy.tracers.zipkin",
    "typedConfig": {
      "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
      "collectorCluster": "zipkin",
      "collectorEndpoint": "/zipkin/api/v1/spans",
      "traceId128bit": true,
      "collectorEndpointVersion": "HTTP_JSON"
    }
  }
}

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

# 호출
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done
for in in {1..10}; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; sleep 0.5; done
...

 

 

8.3 Visualization with Kiali 키알리를 이용한 시각화 - https://kiali.io/

키알리(Kiali)는 이스티오 서비스 메시의 실시간 통신 토폴로지를 방향성 그래프로 시각화하며, 프로메테우스 메트릭 기반으로 서비스 간 의존성·트래픽 흐름을 직관적으로 보여준다.
그라파나와 달리 상호작용형 서비스 맵을 제공해 특정 노드(서비스/워크로드)를 클릭하면 관련 메트릭(초당 요청 수, 오류율)과 Istio 설정(가상 서비스, 디스티네이션 룰)을 즉시 확인할 수 있다.
이를 통해 병목 지점 탐색, 회로 차단기 상태 모니터링, 트래픽 라우팅 검증 등 런타임 문제 진단에 최적화된 관찰 기능을 제공

 

 

8.3.1 Installing Kiali 키알리 설치하기 (실습)

#먼저 키알리 오퍼레이터 설치부터 시작한다 - 참고 Blog , Helm
# helm repo
helm repo add kiali https://kiali.org/helm-charts
helm repo update 

# kiali-operator install : 책은 1.40.1
helm install --namespace kiali-operator --create-namespace --version 1.63.2 kiali-operator kiali/kiali-operator

# kiali-operator 확인
kubectl get pod -n kiali-operator
NAME                             READY   STATUS    RESTARTS   AGE
kiali-operator-584858fb7-zcjv2   1/1     Running   0          61s



#istio-system 네임스페이스에 키알리 인스턴스 배포 : 웹 대시보드를 갖춘 실제 애플리케이션
# 앞 절에서 배포했던 프로메테우스와 예거에 연결할 수 있게 설정
cat ch8/kiali.yaml
apiVersion: kiali.io/v1alpha1
kind: Kiali
metadata:
  namespace: istio-system
  name: kiali
spec:
  istio_namespace: "istio-system"  
  istio_component_namespaces:
    prometheus: prometheus
  auth:    
    strategy: anonymous # 익명 접근 허용
  deployment:
    accessible_namespaces:
    - '**'
  external_services:    
    prometheus: # 클러스터 내에서 실행 중인 프로메테우스 설정
      cache_duration: 10
      cache_enabled: true
      cache_expiration: 300
      url: "http://prom-kube-prometheus-stack-prometheus.prometheus:9090"    
    tracing: # 클러스터 내에서 실행 중인 예거 설정
      enabled: true
      in_cluster_url: "http://tracing.istio-system:16685/jaeger"
      use_grpc: true

# 키알리 인스턴스(대시보드) 설치
kubectl apply -f ch8/kiali.yaml


# 확인
kubectl get deploy,svc -n istio-system kiali
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kiali   1/1     1            1           36s

NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)              AGE
service/kiali   ClusterIP   10.200.1.179   <none>        20001/TCP,9090/TCP   35s

# NodePort 변경 및 nodeport kiali(30003)
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'

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

 

Kiali의 주요 메뉴

  • Graph(그래프): 서비스 메시 내 서비스 간 트래픽 흐름과 호출 관계를 실시간 방향성 그래프로 시각화. 서비스, 워크로드, 애플리케이션, 오퍼레이션(API 엔드포인트) 단위로 그래프를 볼 수 있어 네트워크 구조와 병목, 오류 지점을 한눈에 파악할 수 있다.
  • Overview(오버뷰): 네임스페이스별로 서비스, 워크로드, 애플리케이션의 상태와 주요 메트릭(트래픽, 오류율 등)을 요약해 보여준다.
  • Applications(애플리케이션): 동일 app 레이블을 가진 여러 워크로드를 논리적으로 묶어 애플리케이션 단위로 상태와 트래픽 정보를 제공
  • Workloads(워크로드): Deployment, StatefulSet 등 쿠버네티스 워크로드별로 Pod 상태, 트래픽, 리소스 사용량 등을 모니터링할 수 있다.
  • Services(서비스): 쿠버네티스 서비스 리소스 단위로 트래픽 현황, 엔드포인트, 라우팅, 연결된 워크로드 정보를 확인한다.
  • Istio Config(설정): VirtualService, DestinationRule, Gateway 등 Istio 리소스의 설정 현황을 검증하고, 설정 오류나 비정상 상태를 탐지한다.
  • Traffic(트래픽): 서비스 간 트래픽 흐름, 지연 시간, 오류율 등 상세 트래픽 메트릭을 제공한다.
  • Distributed Tracing(분산 트레이싱): Jaeger 등과 연동해 서비스 간 요청의 전체 경로와 지연 구간을 추적할 수 있다.

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

 
 
 

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

 

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

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

 
이스티오는 엔보이 프록시를 통해 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로 외부로 향하는 트래픽을 허용할 수 있다.

istio 스터디 2주차 내용을 정리하였다.

Istio 프록시의 실제 동작 도구인 Envoy 프록시에 대한 내용과 secure istio에 대한 내용을 다루었다.

그럼시작한다.

Envoy란 ?

Envoy는 원래 Lyft에서 개발된 고성능 오픈소스 프록시(proxy)입니다. 주로 마이크로서비스 아키텍처에서 서비스 간 트래픽을 중계하고, 로드밸런싱, 서비스 디스커버리, TLS 암호화, 서킷 브레이커, 트래픽 라우팅, 모니터링 등 다양한 네트워크 기능을 제공한다. Envoy의 가장 큰 특징은 API 기반의 실시간 설정 업데이트와 뛰어난 가시성과 성능이다.

istio가 Envoy 프록시를 사이드카로 활용하여 서비스 메시를 구현하고, 트래픽 관리·보안·모니터링 등 다양한 기능을 제공하는 오픈소스 플랫폼이기 때문에 Envoy 프록시를 필히 알아야 한다.

Envoy의 가장 큰 장점은 동적 구성(Dynamic Configuration)과 API 기반 관리이다.
이 장점 덕분에 실시간으로 프록시 설정(트래픽 라우팅, 보안 정책, 로드밸런싱 등)을 변경할 수 있고, 서비스 중단 없이 네트워크 정책을 빠르게 반영할 수 있다.

예를 들어, 대규모 마이크로서비스 환경에서 특정 서비스에 장애가 발생했을 때, 운영자는 Envoy의 xDS API를 통해 실시간으로 트래픽을 우회하거나 라우팅 정책을 수정할 수 있다. 이 과정에서 애플리케이션이나 프록시를 재시작할 필요가 없으므로, 서비스 연속성과 가용성을 향상 시킬 수 있다. DevOps 엔지니어 입장에서는 배포 자동화 파이프라인이나 운영 도구와 연동해 네트워크 정책을 코드로 관리하고, 장애 대응이나 신규 서비스 추가 시에도 빠르고 유연하게 대응할 수 있다.

Envoy 실습

docker pull envoyproxy/envoy:v1.19.0
docker pull curlimages/curl
docker pull mccutchen/go-httpbin

# mccutchen/go-httpbin 는 기본 8080 포트여서, 책 실습에 맞게 8000으로 변경
# docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin -p 8000:8000
docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin 
docker ps

# curl 컨테이너로 httpbin 호출 확인
docker run -it --rm --link httpbin curlimages/curl curl -X GET http://httpbin:8000/headers

여기까지는 envoy를 타지않고 httpbin 컨테이너로 바로 접속한 모습이다.

admin:  # Envoy 관리 인터페이스 설정
  address:  # 관리 인터페이스 주소 구성
    socket_address: { address: 0.0.0.0, port_value: 15000 }  # 모든 IP에서 15000 포트로 관리 인터페이스 개방

static_resources:  # 정적 리소스 정의(리스너, 클러스터 등)
  listeners:  # 트래픽 수신을 위한 리스너 목록
  - name: httpbin-demo  # 리스너 식별 이름
    address:  # 리스너 바인딩 주소
      socket_address: { address: 0.0.0.0, port_value: 15001 }  # 모든 IP에서 15001 포트로 트래픽 수신
    filter_chains:  # 필터 체인 구성
    - filters:  # 네트워크 필터 목록
      - name:  envoy.filters.network.http_connection_manager  # HTTP 연결 관리 필터 사용
        typed_config:  # 타입 지정 구성
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager  # gRPC API 타입 지정
          stat_prefix: ingress_http  # 메트릭 접두사 설정
          http_filters:  # HTTP 필터 스택
          - name: envoy.filters.http.router  # 라우터 필터 사용(기본 요청 라우팅)
          route_config:  # 라우팅 구성
            name: httpbin_local_route  # 라우트 구성 이름
            virtual_hosts:  # 가상 호스트 목록
            - name: httpbin_local_service  # 가상 호스트 이름
              domains: ["*"]  # 모든 도메인 매칭
              routes:  # 라우팅 규칙 목록
              - match: { prefix: "/" }  # 모든 경로(/로 시작) 매칭
                route:  # 라우팅 대상 설정
                  auto_host_rewrite: true  # 업스트림 호스트 헤더 자동 재작성
                  cluster: httpbin_service  # 타겟 클러스터 지정
  clusters:  # 업스트림 서비스 클러스터 정의
    - name: httpbin_service  # 클러스터 이름
      connect_timeout: 5s  # 연결 타임아웃 설정(5초)
      type: LOGICAL_DNS  # DNS 기반 서비스 디스커버리 사용
      dns_lookup_family: V4_ONLY  # IPv4 전용 DNS 조회
      lb_policy: ROUND_ROBIN  # 라운드 로빈 로드 밸런싱 정책
      load_assignment:  # 엔드포인트 할당 정보
        cluster_name: httpbin  # 대상 클러스터 이름(실제 서비스 이름)
        endpoints:  # 엔드포인트 그룹
        - lb_endpoints:  # 로드밸런싱 대상 엔드포인트
          - endpoint:  # 단일 엔드포인트 설정
              address:  # 대상 주소
                socket_address:  # 소켓 주소 지정
                  address: httpbin  # 서비스 도메인 주소(httpbin)
                  port_value: 8000  # 대상 포트(8000)

위의 설정파일을 사용하여 엔보이를 실행시킨다.

그리고 다음 명령어로 envoy

docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple.yaml)"

그다음 다시 다음 명령어로 엔보이 컨테이너로 붙어본다.

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/headers

그럼 다음 화면처럼 아까완 다른 헤더값

  • X-Envoy-Expected-Rq-Timeout-Ms
  • X-Request-Id

들이 추가되면서 결국 httpbin 의 결과가 출력되는것을 알 수 있다. 이는 곧 엔보이 프록시를 통해 httpbin 컨테이너에 접속된것을 알 수 있다.

이것들은 envoy의 설정을 바꾸면 해당값도 바뀐다.

              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  timeout: 1s

보는것처럼 타임아웃 값이 1.5초에서 1초로 바뀐것을 알 수 있다.

# 추가 테스트 : Envoy Admin API(TCP 15000) 를 통해 delay 설정
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/0.5
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/1
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/2
upstream request timeout

위와같이 현재 엔보이 프록시의 로그 설정을 확인할 수 있고 또 debug 모드로도 변경할 수 있다.

그리고 아까 타임아웃을 1초로 해둔 상태인데, delay를 0.5와 1초 했을 때의 결과를 다음과같이 확인 할 수 있다.

Envoy's Admin API를 사용하면 프록시 동작에 대해 이해할 수 있고 메트릭과 설정에 접근 할 수 있다.

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats/prometheus # 엔보이 통계(프로메테우스 레코드 형식)

특히 위와같은 접근으로 프로메테우스 형식의 메트릭을 접근할 수 있다.

이를 활용하여 이스티오 관련 모니터링 대시보드를 만들 수 있을것이다. 예를들면 다음과 같다.

https://grafana.com/orgs/istio/dashboards

[istio Dashboards | Grafana Labs

uploaded on February 28, 2020 Downloads: 33513582 Reviews: 0

grafana.com](https://grafana.com/orgs/istio/dashboards)

docker rm -f proxy

#
cat ch3/simple_retry.yaml
docker run -p 15000:15000 --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_retry.yaml)"
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

# /stats/500 경로로 프록시를 호출 : 이 경로로 httphbin 호출하면 오류가 발생
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/status/500

# 호출이 끝났는데 아무런 응답도 보이지 않는다. 엔보이 Admin API에 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats | grep retry

위 스크립트는 1주차때 진행했던 500 에러 발생시 재시도 하는 테스트이다.

앞선 1주차때 했떤 이스티오 설정과 동일하게 엔보이에서도(당연히) 3회 시도에 대한 메트릭을 확인할 수 있다.

Summary

  • Envoy는 애플리케이션이 애플리케이션 수준의 동작에 사용할 수 있는 프록시입니다.
  • Envoy는 이스티오의 데이터 플레인입니다.
  • Envoy는 클라우드 신뢰성 문제(네트워크 장애, 토폴로지 변경, 탄력성)를 일관되고 정확하게 해결하는 데 도움을 줄 수 있습니다.
  • Envoy는 런타임 제어를 위해 동적 API를 사용합니다(Istio는 이를 사용합니다).
  • Envoy는 애플리케이션 사용 및 프록시 내부에 대한 강력한 지표와 정보를 많이 노출합니다.

istio 실습

실습 환경 구성

실제 실습을 진행하면서, 추가로 현재 운영중인 istio 환경도 함께 확인해볼 예정이다.

K8s 설치

#
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 # New Gateway 
    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'

# (옵션) 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

istio 설치

# 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
-----------------------------------

# 실습을 위한 네임스페이스 설정
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

실습 진행

이스티오 인그레스 게이트웨이는 클러스터 외부에서 내부로 들어오는 트래픽의 진입점을 제어하는 역할을 한다. 인그레스 게이트웨이는 Envoy 프록시 기반의 로드밸런서로 동작하며, 외부 트래픽을 받아 필요한 포트와 프로토콜을 지정해 노출한다. 하지만 실제 트래픽 라우팅(어떤 서비스로 전달할지)은 VirtualService 리소스를 통해 L7(애플리케이션 레이어)에서 세밀하게 제어한다.

즉, Gateway는 L4/L5 계층에서 트래픽을 받아들이고, VirtualService는 L7 계층에서 트래픽의 목적지와 라우팅 정책을 결정한다. 이 구조를 통해 이스티오는 외부 트래픽의 보안, 로드밸런싱, 가상 호스트 라우팅 등 다양한 네트워크 기능을 유연하게 제공한다.

현재 운영중인 istio 환경 정보

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: live-ops-query-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "ops-query.test.co.kr"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ops-query-vs
  namespace: devops
spec:
  hosts:
  - "ops-query.test.co.kr"
  gateways:
  - istio-system/live-ops-query-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: ops-query-svc-active
      weight: 100
    - destination:
        host: ops-query-svc-preview
      weight: 0

 

 

아래는 실습환경이다.

404 에러가 발생한다. 이는 host 헤더가 알맞지 않아서 발생하는 문제이다.

 

헤더 수정후 다시 입력하면 정상적으로 응답되는것을 알 수 있다.

 

 

securing gateway traffic 실습

실무환경에서는 앞단의 ALB에 인증서만 적용해두고 내부의 이스티오간 인증서는 적용돼있지 않은상태다. 추후 보안심사때 문제가 발생하면 그때 적용해야겠다....

 

어쨋든.. 실습 환경에서 현재 적용 돼 있는 인증서는 다음과 같다.

kubectl create -n istio-system secret tls webapp-credential \
--key ch4/certs/3_application/private/webapp.istioinaction.io.key.pem \
--cert ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem

 kubectl apply -f ch4/coolstore-gw-tls.yaml -n istioinaction

 

이제 위 명령어로 시크릿을 만들고 게이트웨이를 재배포한다.

 

그러면 위와같이 새로운 인증서가 추가된것을 볼 수 있다.

그리고 curl 로 접속 테스트를 하면 에러가 나는것을 볼 수 있다.

 

이는 서버(istio-ingressgateway 파드)에서 제공하는 인증서는 기본 CA 인증서 체인을 사용해 확인할 수 없다는 의미다.

다시  cacert 옵션을 줘서 테스트를 한다. 또 에러가 난다. 이번엔 Local host로 접속해서 발생한 문제이다.

 

echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts

위 명령어를 입력해서 임시로 dns 를 설정해준다.

 

다시 호출 테스트를 진행하면 정상적으로 접속되는것을 알 수 있다.

 

 

이번에는 https만 사용하도록 하는 리다이렉트 설정이다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
    tls:
      httpsRedirect: true 
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: webapp-credential
    hosts:
    - "webapp.istioinaction.io"

 

 

이번엔  mTLS이다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: MUTUAL # mTLS 설정
      credentialName: webapp-credential-mtls # 신뢰할 수 있는 CA가 구성된 자격 증명
    hosts:
    - "webapp.istioinaction.io"

 

다음과 같이 클라이언트 인증서 없이 에러가 발생한다.

 

 

클라이언트 인증서를 명시하면 정상적으로 동작하는것을 알 수 있다.

 

 

Serving multiple virtual hosts with TLS

이스티오 인그레스 게이트웨이는 동일한 HTTPS 포트(443)에서 각기 다른 인증서와 비밀 키를 사용해 여러 도메인(가상 호스트)의 트래픽을 동시에 처리할 수 있다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https-webapp
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: webapp-credential
    hosts:
    - "webapp.istioinaction.io"
  - port:
      number: 443
      name: https-catalog
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: catalog-credential
    hosts:
    - "catalog.istioinaction.io"

두개의 버추어 서비스를 처리하는것을 확인 할 수 있다. 이를 처리하는 플로우는 클라이언트가 게이트웨이(엔보이)로 HTTPS 연결을 시작하면, TLS 핸드셰이크의 ClientHello 단계에서 SNI(서버 이름 표시) 확장에 접근하려는 서비스의 도메인을 명시해 전달하고, 엔보이는 이 SNI 정보를 기반으로 올바른 인증서를 선택해 제시하고 해당 서비스로 트래픽을 라우팅한다.

 

TCP 트래픽에 대한 실습이다.

이스티오 게이트웨이는 TCP 기반 서비스도 외부에 노출할 수 있지만, HTTP처럼 세밀한 트래픽 제어나 고급 기능은 사용할 수 없다.
이는 엔보이가 프로토콜을 이해하지 못해 단순 TCP 프록시만 가능하기 때문이다.
클러스터 외부에서 내부 TCP 서비스를 이용하려면 게이트웨이에서 해당 포트를 노출하면 된다.

#
cat ch4/echo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tcp-echo-deployment
  labels:
    app: tcp-echo
    system: example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tcp-echo
  template:
    metadata:
      labels:
        app: tcp-echo
        system: example
    spec:
      containers:
        - name: tcp-echo-container
          image: cjimti/go-echo:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: TCP_PORT
              value: "2701"
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: SERVICE_ACCOUNT
              valueFrom:
                fieldRef:
                  fieldPath: spec.serviceAccountName
          ports:
            - name: tcp-echo-port
              containerPort: 2701
---
apiVersion: v1
kind: Service
metadata:
  name: "tcp-echo-service"
  labels:
    app: tcp-echo
    system: example
spec:
  selector:
    app: "tcp-echo"
  ports:
    - protocol: "TCP"
      port: 2701
      targetPort: 2701

kubectl apply -f ch4/echo.yaml -n istioinaction

#
kubectl get pod -n istioinaction
NAME                                   READY   STATUS    RESTARTS   AGE
tcp-echo-deployment-584f6d6d6b-xcpd8   2/2     Running   0          27s
...

 

# tcp 서빙 포트 추가 : 편집기는 vi 대신 nano 선택 <- 편한 툴 사용
KUBE_EDITOR="nano"  kubectl edit svc istio-ingressgateway -n istio-system
...
  - name: tcp
    nodePort: 30006
    port: 31400
    protocol: TCP
    targetPort: 31400
...

# 확인
kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.name=="tcp")]}'
{"name":"tcp","nodePort":30006,"port":31400,"protocol":"TCP","targetPort":31400}


# 게이트웨이 생성
cat ch4/gateway-tcp.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: echo-tcp-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 31400
      name: tcp-echo
      protocol: TCP
    hosts:
    - "*"
    
kubectl apply -f ch4/gateway-tcp.yaml -n istioinaction
kubectl get gw -n istioinaction

# 에코 서비스로 라우팅하기 위해 VirtualService 리소스 생성
cat ch4/echo-vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: tcp-echo-vs-from-gw
spec:
  hosts:
  - "*"
  gateways:
  - echo-tcp-gateway
  tcp:
  - match:
    - port: 31400
    route:
    - destination:
        host: tcp-echo-service
        port:
          number: 2701

#
kubectl apply -f ch4/echo-vs.yaml -n istioinaction
kubectl get vs -n istioinaction
NAME                  GATEWAYS                HOSTS                          AGE
catalog-vs-from-gw    ["coolstore-gateway"]   ["catalog.istioinaction.io"]   44m
tcp-echo-vs-from-gw   ["echo-tcp-gateway"]    ["*"]                          6s
webapp-vs-from-gw     ["coolstore-gateway"]   ["webapp.istioinaction.io"]    3h59m


# mac 에 telnet 설치
brew install telnet

#
telnet localhost 30006
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Welcome, you are connected to node myk8s-control-plane.
Running on Pod tcp-echo-deployment-584f6d6d6b-xcpd8.
In namespace istioinaction.
With IP address 10.10.0.20.
Service default.
hello istio! # <-- type here
hello istio! # <-- echo here

# telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit

 

 

 

SNI passthrough 실습

이스티오 인그레스 게이트웨이는 TLS 연결을 종료하지 않고 SNI 호스트네임을 기반으로 TCP 트래픽을 라우팅하며, 백엔드 서비스에서 TLS 처리를 담당함으로써 다양한 레거시 애플리케이션 및 TCP 서비스를 서비스 메시에 통합할 수 있다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: sni-passthrough-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 31400 #1 HTTP 포트가 아닌 특정 포트 열기
      name: tcp-sni
      protocol: TLS
    hosts:
    - "simple-sni-1.istioinaction.io" #2 이 호스트를 포트와 연결
    tls:
      mode: PASSTHROUGH #3 통과 트래픽으로 처리

 

# TLS 인증을 직접 처리하는 앱 배포. (gw는 route 만 처리, pass through )
cat ch4/sni/simple-tls-service-1.yaml
kubectl apply -f ch4/sni/simple-tls-service-1.yaml -n istioinaction
kubectl get pod -n istioinaction

# 기존 Gateway 명세(echo-tcp-gateway) 제거 : istio-ingressgateway의 동일한 port (31400, TCP)를 사용하므로 제거함
kubectl delete gateway echo-tcp-gateway -n istioinaction

# 신규 Gateway 설정
kubectl apply -f ch4/sni/passthrough-sni-gateway.yaml -n istioinaction
kubectl get gw -n istioinaction

 

# 두 번째 서비스 배포
cat ch4/sni/simple-tls-service-2.yaml
kubectl apply -f ch4/sni/simple-tls-service-2.yaml -n istioinaction

# gateway 설정 업데이트
cat ch4/sni/passthrough-sni-gateway-both.yaml
kubectl apply -f ch4/sni/passthrough-sni-gateway-both.yaml -n istioinaction

# VirtualService 설정
cat ch4/sni/passthrough-sni-vs-2.yaml
kubectl apply -f ch4/sni/passthrough-sni-vs-2.yaml -n istioinaction

# 호출테스트2
echo "127.0.0.1       simple-sni-2.istioinaction.io" | sudo tee -a /etc/hosts

curl https://simple-sni-2.istioinaction.io:30006 \
--cacert ch4/sni/simple-sni-2/2_intermediate/certs/ca-chain.cert.pem

 

 

Split gateway responsibilities 실습

여러 인그레스 게이트웨이를 배포하면 서비스별 요구사항이나 팀별로 트래픽 경로와 설정을 독립적으로 관리·격리할 수 있다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: my-user-gateway-install
  namespace: istioinaction
spec:
  profile: empty
  values:
    gateways:
      istio-ingressgateway:
        autoscaleEnabled: false
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: false    
    - name: my-user-gateway
      namespace: istioinaction
      enabled: true
      label:
        istio: my-user-gateway
      k8s:
        service:
          ports:
            - name: tcp  # my-user-gateway 에서 사용할 포트 설정
              port: 30007
              targetPort: 31400
#
docker exec -it myk8s-control-plane bash
------------------------------------------
# istioinaction 네임스페이스에 Ingress gateway 설치
cat <<EOF > my-user-gateway-edited.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: my-user-gateway-install
  namespace: istioinaction
spec:
  profile: empty
  values:
    gateways:
      istio-ingressgateway:
        autoscaleEnabled: false
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: false    
    - name: my-user-gateway
      namespace: istioinaction
      enabled: true
      label:
        istio: my-user-gateway
      k8s:
        service:
          ports:
            - name: tcp  # my-user-gateway 에서 사용할 포트 설정
              port: 31400
              targetPort: 31400
              nodePort: 30007 # 외부 접속을 위해 NodePort Number 직접 설정
EOF

# istioctl manifest generate -n istioinaction -f my-user-gateway-edited.yaml
istioctl install -y -n istioinaction -f my-user-gateway-edited.yaml

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

# IstioOperator 확인
kubectl get IstioOperator -A
NAMESPACE       NAME                      REVISION   STATUS   AGE
istio-system    installed-state                               5h48m
istioinaction   my-user-gateway-install                       17s

#
kubectl get deploy my-user-gateway -n istioinaction
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
my-user-gateway   1/1     1            1           65s

# 포트 확인
kubectl get svc my-user-gateway -n istioinaction -o yaml
...
  - name: tcp
    nodePort: 30007
    port: 31400
    protocol: TCP
    targetPort: 31400
...

 

 

 

Gateway injection

게이트웨이 주입(gateway injection)을 사용하면, 사용자가 IstioOperator 리소스 전체 권한 없이도 미완성(서브된) 게이트웨이 Deployment 리소스를 배포할 수 있고, Istio가 나머지 설정을 자동으로 채워준다. 이 방식은 사이드카 주입과 유사하게, 각 팀이 필요한 최소한의 리소스만 정의하면 Istio가 필요한 프록시와 설정을 자동으로 붙여준다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-user-gateway-injected
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      ingress: my-user-gateway-injected
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "true" #1 주입 활성화
        inject.istio.io/templates: gateway #2 gateweay 템플릿  
      labels:
        ingress: my-user-gateway-injected
    spec:
      containers:
      - name: istio-proxy #3 반드시 이 이름이어야 한다
        image: auto #4 미완성 이미지
...

 

Ingress gateway access logs 실습

이스티오 데모 프로필에서는 엔보이 프록시의 액세스 로그가 표준 출력으로 기록되어 컨테이너 로그를 통해 쉽게 확인할 수 있다.

#
kubectl get cm -n istio-system istio -o yaml
data:
  mesh: |-
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:15012
      proxyMetadata: {}
      tracing:
        zipkin:
          address: zipkin.istio-system:9411
    enablePrometheusMerge: true
    rootNamespace: istio-system
    trustDomain: cluster.local
  meshNetworks: 'networks: {}'
..

#
docker exec -it myk8s-control-plane bash
------------------------------------------
# 표준 출력 스트림으로 출력하도록 accessLogFile 속성을 변경
istioctl install --set meshConfig.accessLogFile=/dev/stdout
y 입력

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

# configmap 에 mesh 바로 아래에 accessLogFile 부분 추가됨
kubectl get cm -n istio-system istio -o yaml
...
  mesh: |-
    accessLogFile: /dev/stdout
...

# 애플리케이션 호출에 대한 로그도 출력됨!
kubectl stern -n istioinaction -l app=webapp -c istio-proxy
webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:47.764Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 1 1 "172.18.0.1" "beegoServer" "4c10dba4-f49a-4b58-9805-ac30515dd417" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60052 10.200.1.251:80 172.18.0.1:0 - default
webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:48.805Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 6 6 "172.18.0.1" "beegoServer" "7733f4d0-6e3a-4138-a126-fe25c30e51f4" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60898 10.200.1.251:80 172.18.0.1:0 - default
...

운영 환경에서는 트래픽과 로그량이 많기 때문에, 텔레메트리 API를 이용해 필요한 워크로드에만 액세스 로그를 selectively 활성화하는 것이 효율적이다.

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: ingress-gateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway #1 레이블과 일치하는 파드는 텔레메트리 설정을 가져온다
  accessLogging:
  - providers:
    - name: envoy #2 액세스 로그를 위한 프로바이더 설정
    disabled: false #3 disable 를 false 로 설정해 활성화한다

 

Reducing gateway configuration

이스티오는 기본적으로 모든 프록시가 메시 내 모든 서비스를 알도록 설정되어 있어 서비스가 많아지면 프록시 설정이 비대해지고 성능 및 확장성 문제가 발생할 수 있다. 이를 해결하기 위해 게이트웨이 프록시에는 필요한 설정만 포함하도록 "게이트웨이 설정 잘라내기" 기능을 명시적으로 활성화할 수 있다.

개인적으로 오늘 스터디에서 배운것중 가장 필요했던것이다. 일전에 istio 설정 잘못해서 서비스 연결이 제대로 안됐었는데, 디버깅 하기가 너무 힘들었다. 해당 설정때문에 앞으로 좀 더 수월해질듯 하다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: control-plane
spec:
  profile: minimal
  components:
    pilot:
      k8s:
        env:
        - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG
          value: "true"
  meshConfig:
    defaultConfig:
      proxyMetadata:
        ISTIO_META_DNS_CAPTURE: "true"
    enablePrometheusMerge: true

'job > devops' 카테고리의 다른 글

istio-1  (0) 2025.04.08
taskfile  (0) 2024.03.10
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

사내에서 istio 프록시로 구성하여 사용중인데, 뭐랄까. 제대로 못쓰고 있는듯하다. 굳이 istio를 붙인것같은.. 제대로 못쓸꺼면 아에 빼버리던가(네트워크 문제시 파악 구간이 더 많아져서 싫다.) 아니면 제대로 쓸 수 있는 실력이 필요했다. 예를들어, 서비스 장애가 발생할 경우 istio가 없었다면 단순히 네트워크 구간만 살펴보면 됐다. 그런데 현재는 istio 구간까지 추가로 살펴봐야 한다. 문제는 해당 구간에 대한 모니터링이 안되고 있는 상황. 그래서 명확히 이 구간은 문제가 없다라고 판단하기가 어렵다. 이를 해결하려면 istio에 빠삭히 알아야 할 것 같았다. 그래서 작년에 istio 시험을 사두기만 했다. 그렇게 시간을 보내다 때마침 가시다님께서 istio 스터디를 진행하신다기에 재빨리 신청했다. (과거 여러 스터디를 운영하셨던 분들을 포함한 가시다님, 김원일님, 김석필님 감사합니다.)

스터디를 끝낼 때 istio 운영을 유지하며 시스템을 더욱 고도화(모니터링 추가등)할 지, 아니면 istio를 걷어낼지를 판단 할 수 있는 실력이 쌓였으면 좋겠다.

현재 사내에 istio를 사용중이다. 따라서 실습하면서 실제 운영환경과 비교하고 다른점이 있다면 어떤 설정에 의해 왜 다른지도 다뤄보고자 한다.

그럼 1주차 시작

Istio에 대한 간략한 설명

Istio를 왜 사용해야 하는가 ? Istio를 사용하지 않을 경우 발생할 수 있는 문제.

서비스 간 통신의 복잡성 증가:
네트워크 장애, 과부하, 버그 등으로 인해 서비스 간 요청이 실패하거나 성능 저하가 발생할 수 있음.
예를 들어, 다운스트림 서비스가 느리거나 장애가 발생하면 연쇄적인 서비스 장애로 이어질 가능성이 큼.

복원력 패턴 구현의 어려움:
타임아웃, 재시도, 서킷 브레이커 등의 복원력 패턴을 각 애플리케이션에서 직접 구현해야 함.
여러 언어와 프레임워크를 사용하는 환경에서는 이러한 패턴을 일관되게 구현하기 어려움.

운영 및 유지보수 부담 증가:
라이브러리 의존성을 관리하고 각 애플리케이션에 맞게 코드를 수정해야 하며, 이는 시간이 많이 소요되고 오류 가능성을 높임.
새로운 언어나 프레임워크 도입 시 추가적인 구현 작업이 필요함.

관찰 가능성 부족:
서비스 간 트래픽, 요청 실패율, 성능 병목 등을 실시간으로 파악하기 어려움.
장애 원인을 추적하거나 시스템 상태를 모니터링하는 데 한계가 있음.

Istio를 사용하면 해결되는 점

서비스 간 통신의 표준화:
Istio는 Envoy 프록시를 통해 서비스 간 통신을 관리하며, 재시도, 타임아웃, 서킷 브레이커 등의 기능을 애플리케이션 외부에서 제공.
이를 통해 서비스 간 통신의 안정성과 복원력을 높일 수 있음.

언어 및 프레임워크 독립성:
애플리케이션 코드 수정 없이 네트워크 관련 기능을 제공하므로 언어나 프레임워크에 구애받지 않음.
다양한 기술 스택에서도 일관된 네트워킹 정책 적용 가능.

운영 부담 감소:
Istio가 네트워킹 및 보안 정책을 중앙에서 관리하므로 각 애플리케이션에서 이를 구현할 필요가 없음.
새로운 서비스 추가나 변경 시에도 운영 부담이 줄어듦.

강화된 관찰 가능성:
Istio는 메트릭, 로그, 분산 트레이싱을 통해 실시간으로 시스템 상태를 모니터링 가능.
장애 원인 분석 및 성능 최적화 작업이 용이해짐.

결론
Istio는 네트워킹 관련 문제를 애플리케이션에서 인프라로 전가(내가 생각하는 devops 엔지니어는, 개발자는 회사의 이익에 필요한 개발만 할 수 있도록 환경을 만들어주는거라 생각하는데.. 이 모토랑 일치하는듯)하여 운영 효율성을 높이고, 복잡한 클라우드 환경에서도 안정적으로 서비스를 운영할 수 있도록 돕습니다.

Istio란? 서비스메시란 ? 엔보이프록시? 사이드카?

서비스 메시 (Service Mesh)

서비스 메시란, 여러 서비스가 서로 대화할 때 그 대화를 도와주는 네트워크의 "통신 감독관" 같은 역할을 합니다.
예를 들어, 학교에서 선생님이 학생들끼리 조용히 대화하도록 도와주는 것과 비슷합니다.

엔보이 프록시 (Envoy Proxy)

엔보이 프록시는 서비스들 사이에서 주고받는 메시지를 대신 전달하는 "우체부" 역할을 합니다.
예를 들어, 친구에게 편지를 보낼 때 우체부가 대신 전달해 주는 것처럼, 엔보이는 서비스 간 데이터를 안전하고 빠르게 전달합니다.

사이드카 (Sidecar)

사이드카는 주 컨테이너(주요 프로그램)를 도와주는 "조수" 컨테이너입니다.
예를 들어, 오토바이에 붙어 있는 작은 캐빈처럼, 사이드카는 옆에서 필요한 일을 돕습니다. Istio에서는 이 사이드카가 엔보이 프록시로 동작합니다.

Istio 프록시

Istio 프록시는 엔보이 프록시를 사용하여 각 서비스 옆에 사이드카로 배치됩니다.
예를 들어, 학교에서 각 반마다 선생님(프록시)이 배치되어 학생들(서비스)이 서로 잘 소통하도록 돕는 것과 같습니다. 이 프록시는 메시지를 가로채고, 어디로 보내야 할지 알려주며, 보안도 책임집니다.

연관 관계

  • 서비스 메시 안에는 여러 서비스가 있고, 이들이 서로 대화할 때 엔보이 프록시가 중간에서 도와줍니다.
  • 엔보이 프록시는 각 서비스 옆에 사이드카 형태로 배치되어 Istio라는 시스템의 일부로 작동합니다.
  • Istio는 전체 네트워크를 관리하며 트래픽을 안전하고 효율적으로 제어합니다.

즉 정리하자면, Istio를 사용하지 않는다면 애플리케이션 레벨에서의 리소스가 많이 사용되는데, Istio를 사용하면 효율적으로 인프라 레벨로 옮길 수 있다. 어떻게? 사이드카 프록시로.

Istio의 단점은 ?

디버깅 복잡성 증가

Envoy 프록시가 추가되면서 네트워크 요청 경로가 복잡해지고, 프록시에 익숙하지 않은 경우 디버깅이 어려워질 수 있음.

테넌시 관리의 어려움
서비스 메시 구성 시 적절한 정책과 자동화가 없으면 잘못된 설정으로 인해 다수의 서비스에 영향을 미칠 가능성이 있음.

운영 복잡성 증가
서비스 메시 도입으로 새로운 레이어가 추가되어 시스템 아키텍처와 운영 절차가 복잡해질 수 있음.

조직의 기존 거버넌스 및 팀 간 협업과의 통합이 어려울 수 있음.

실습 진행

istio 설치

$ istioctl version --remote=false
$ kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
NAME                                       READY   STATUS    RESTARTS   AGE
pod/grafana-b854c6c8-rhsvm                 1/1     Running   0          19h
pod/istio-ingressgateway-996bc6bb6-lwqq5   1/1     Running   0          19h
pod/istiod-7df6ffc78d-r4jvv                1/1     Running   0          19h
pod/jaeger-5556cd8fcf-8z2zz                1/1     Running   0          19h
pod/kiali-648847c8c4-wcw8l                 1/1     Running   0          19h
pod/prometheus-7b8b9dd44c-zzsr8            2/2     Running   0          19h

NAME                           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                      AGE
service/grafana                ClusterIP      10.200.1.30    <none>        3000/TCP                                     19h
service/istio-ingressgateway   LoadBalancer   10.200.1.103   <pending>     15021:31155/TCP,80:30491/TCP,443:30079/TCP   19h
service/istiod                 ClusterIP      10.200.1.116   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP        19h
service/jaeger-collector       ClusterIP      10.200.1.31    <none>        14268/TCP,14250/TCP,9411/TCP                 19h
service/kiali                  ClusterIP      10.200.1.99    <none>        20001/TCP,9090/TCP                           19h
service/prometheus             ClusterIP      10.200.1.130   <none>        9090/TCP                                     19h
service/tracing                ClusterIP      10.200.1.38    <none>        80/TCP,16685/TCP                             19h
service/zipkin                 ClusterIP      10.200.1.200   <none>        9411/TCP                                     19h

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/grafana                1/1     1            1           19h
deployment.apps/istio-ingressgateway   1/1     1            1           19h
deployment.apps/istiod                 1/1     1            1           19h
deployment.apps/jaeger                 1/1     1            1           19h
deployment.apps/kiali                  1/1     1            1           19h
deployment.apps/prometheus             1/1     1            1           19h

NAME                                             DESIRED   CURRENT   READY   AGE
replicaset.apps/grafana-b854c6c8                 1         1         1       19h
replicaset.apps/istio-ingressgateway-996bc6bb6   1         1         1       19h
replicaset.apps/istiod-7df6ffc78d                1         1         1       19h
replicaset.apps/jaeger-5556cd8fcf                1         1         1       19h
replicaset.apps/kiali-648847c8c4                 1         1         1       19h
replicaset.apps/prometheus-7b8b9dd44c            1         1         1       19h

NAME                                                       REFERENCE                         TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/istio-ingressgateway   Deployment/istio-ingressgateway   8%/80%    1         5         1          19h
horizontalpodautoscaler.autoscaling/istiod                 Deployment/istiod                 0%/80%    1         5         1          19h

NAME                             ENDPOINTS                                                     AGE
endpoints/grafana                10.10.0.10:3000                                               19h
endpoints/istio-ingressgateway   10.10.0.8:15021,10.10.0.8:8080,10.10.0.8:8443                 19h
endpoints/istiod                 10.10.0.7:15012,10.10.0.7:15010,10.10.0.7:15017 + 1 more...   19h
endpoints/jaeger-collector       10.10.0.9:9411,10.10.0.9:14250,10.10.0.9:14268                19h
endpoints/kiali                  10.10.0.11:9090,10.10.0.11:20001                              19h
endpoints/prometheus             10.10.0.12:9090                                               19h
endpoints/tracing                10.10.0.9:16685,10.10.0.9:16686                               19h
endpoints/zipkin                 10.10.0.9:9411                                                19h

NAME                                                  SECRETS   AGE
serviceaccount/default                                1         19h
serviceaccount/grafana                                1         19h
serviceaccount/istio-ingressgateway-service-account   1         19h
serviceaccount/istio-reader-service-account           1         19h
serviceaccount/istiod                                 1         19h
serviceaccount/istiod-service-account                 1         19h
serviceaccount/kiali                                  1         19h
serviceaccount/prometheus                             1         19h

NAME                                            DATA   AGE
configmap/grafana                               4      19h
configmap/istio                                 2      19h
configmap/istio-ca-root-cert                    1      19h
configmap/istio-gateway-deployment-leader       0      19h
configmap/istio-gateway-status-leader           0      19h
configmap/istio-grafana-dashboards              2      19h
configmap/istio-leader                          0      19h
configmap/istio-namespace-controller-election   0      19h
configmap/istio-services-grafana-dashboards     4      19h
configmap/istio-sidecar-injector                2      19h
configmap/kiali                                 1      19h
configmap/kube-root-ca.crt                      1      19h
configmap/prometheus                            5      19h

NAME                                                      TYPE                                  DATA   AGE
secret/default-token-lmqrr                                kubernetes.io/service-account-token   3      19h
secret/grafana-token-74qt2                                kubernetes.io/service-account-token   3      19h
secret/istio-ca-secret                                    istio.io/ca-root                      5      19h
secret/istio-ingressgateway-service-account-token-tlq54   kubernetes.io/service-account-token   3      19h
secret/istio-reader-service-account-token-8zz99           kubernetes.io/service-account-token   3      19h
secret/istiod-service-account-token-zrr8b                 kubernetes.io/service-account-token   3      19h
secret/istiod-token-x64kr                                 kubernetes.io/service-account-token   3      19h
secret/kiali-token-84msl                                  kubernetes.io/service-account-token   3      19h
secret/prometheus-token-vqmbw                             kubernetes.io/service-account-token   3      19h

NAME                                              MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/istio-ingressgateway   1               N/A               0                     19h
poddisruptionbudget.policy/istiod                 1               N/A               0                     19h

이중 istio-ingressgateway 서비스 리소스의 경우 실제 운영환경은 다음과 같이 EXTERNAL-IP에 CSP의 LB 도메인이 들어가있다. 실습 환경에서는 PENDING이다.

$ k get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   198.19.240.44    istio-syste-istio-ingres-xxx.kr-fin.lb.naverncp.com   15021:31396/TCP,80:30616/TCP,443:31295/TCP   527d
istiod                 ClusterIP      198.19.157.147   <none>                                                                        15010/TCP,15012/TCP,443/TCP,15014/TCP        527d

이는 CSP(NCP)에서 설치된 쿠버네티스 클러스터(NKS)에는 기본적으로 cloud controller manager의 service controller에 의해 자동으로 NKS의 LB를 생성한다. 관련 설정은 kube-system 네임스페이스에 ncloud-config라는 ConfigMap이 적용 돼 있다.

$ k get cm -n kube-system -o yaml ncloud-config
apiVersion: v1
data:
  acgNo: "11111"
  apiUrl: https://nks.apigw.fin-ntruss.com
  basePath: /ncloud-api/v1
  clusterId: "1111"
  lbPublicSubnetNo: "11111"
  lbSubnetNo: "11111"
  regionCode: FKR
  regionNo: "1"
  vpcNo: "1111"
  zoneNo: "111"
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
생략
  creationTimestamp: "2023-10-30T02:09:49Z"
  name: ncloud-config
  namespace: kube-system
  resourceVersion: "1111111"
  uid: 11111-1a71-457e-813c-4b2d13f00446

해당 설정에 의해 서비스타입이 로드밸런서인경우 NCP API와 상호작용하여, 자동으로 NCP의 LB(기본값은 프록시 로드밸런서)를 생성하게 된다.
참고로 로드밸런서 이름(익스터널IP에 들어가는 도메인명)의 생성 규칙은 다음과 같다.
<네임스페이스-서비스이름-포트번호-랜덤문자열>

파드배포(사이드카로 Istio 도 같이 배포)

이제 배포되는 모든 파드들에 사이드카로 istio가 같이 배포되도록 해야 한다. 이는 2가지 방법이 있는데,
docker exec -it myk8s-control-plane istioctl kube-inject -f /istiobook/services/catalog/kubernetes/catalog.yaml
으로 매니페스트에 사이드카 설정을 추가하는 방법과

kubectl label namespace istioinaction istio-injection=enabled
으로, 네임스페이스에 istio-injection=enabled Label에 설정 돼 있는 경우 해당 네임스페이스의 파드 스펙에 자동으로 사이드카 설정을 한다.

이제 파드를 배포하면 다음과 같이 앱이 배포된다.

하나의 파드에 두개의 컨테이너가 있음이 확인된다. 이를 describe로 확인해보면 보는것처럼 catalog라는 메인 컨테이너외에 istio-proxy라는 사이드카 컨테이너가 올라온것을 알 수 있다.

istio proxy 상태 확인

# istioctl proxy-status : 단축어 ps
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
catalog-6cf4b97d-nccfj.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8
istio-ingressgateway-996bc6bb6-mz544.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8
webapp-7685bcb84-c55ck.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8

ISTIOIGW=istio-ingressgateway-996bc6bb6-647tx.istio-system
WEBAPP=webapp-7685bcb84-nfntj.istioinaction

# istioctl proxy-config : 단축어 pc
docker exec -it myk8s-control-plane istioctl proxy-config all $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config all $WEBAPP

docker exec -it myk8s-control-plane istioctl proxy-config listener $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config route $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config cluster $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config endpoint $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config log $ISTIOIGW

docker exec -it myk8s-control-plane istioctl proxy-config listener $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config route $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config cluster $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config endpoint $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config log $WEBAPP

# envoy 가 사용하고 있는 인증서 정보 확인
docker exec -it myk8s-control-plane istioctl proxy-config secret $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config secret $WEBAPP

위 명령어들은 istio 서비스 매쉬 내 프록시의 상태를 확인(istioctl proxy-status)하고, 특정 프록시의 상세 구성 정보를 조회(istioctl proxy-config)하는 명령어다.

Istio 프록시(Envoy)의 네트워크 흐름에서 Listener, Route, Cluster, Endpoint는 트래픽 처리 단계에 따라 아래와 같은 순서로 작동합니다:

1. Listener
역할: Envoy가 수신하는 트래픽을 처리할 준비를 합니다. Listener는 특정 IP와 포트에 바인딩되어 들어오는 요청을 수신하고, 트래픽을 처리하기 위한 첫 번째 진입점입니다.

작동 위치: 네트워크에서 Envoy 프록시가 요청을 가로채고, 트래픽의 방향을 결정하기 위해 필터 체인을 적용합니다.

예시: HTTP 요청이 들어오면 Listener는 이를 처리할 라우팅 규칙을 찾습니다.

2. Route
역할: Listener에서 수신된 요청을 분석하여 어떤 서비스로 전달할지 결정합니다. Route는 요청 경로와 매칭되는 규칙을 기반으로 클러스터를 선택합니다.

작동 위치: Listener에서 필터 체인을 통해 전달된 요청은 Route에서 적절한 클러스터로 연결됩니다.

예시: 특정 URL 경로(/api/v1)에 대한 요청을 특정 클러스터로 라우팅.

3. Cluster
역할: 라우팅된 요청을 처리할 논리적인 서비스 그룹입니다. Cluster는 여러 Endpoint(IP와 포트)로 구성되며, Envoy가 외부 서비스와 연결하는 단위입니다.

작동 위치: Route가 선택한 클러스터는 실제 엔드포인트로 트래픽을 포워드합니다.

예시: service-cluster라는 클러스터가 외부 API 서버를 대표하며, 해당 클러스터의 엔드포인트로 트래픽을 전달.

4. Endpoint
역할: 클러스터 내에서 실제 요청이 전달되는 대상입니다. Endpoint는 IP 주소와 포트를 포함하며, 클라이언트의 요청이 최종적으로 도달하는 곳입니다.

작동 위치: Cluster에서 선택된 엔드포인트로 트래픽이 전달됩니다.

예시: 특정 API 서버의 IP 주소(예: 192.168.1.100)와 포트(예: 8080)이 Endpoint로 설정됩니다.

네트워크 흐름 순서 요약
단계    구성 요소    역할 및 작업
1    Listener    요청 수신 및 필터 체인을 통해 초기 처리
2    Route    요청 경로 분석 및 적절한 클러스터 선택
3    Cluster    논리적 서비스 그룹으로 요청 전달
4    Endpoint    실제 대상(IP/포트)으로 최종 트래픽 전달
이 순서는 Istio 프록시의 기본적인 트래픽 처리 흐름이며, 각 단계는 Envoy 내부의 설정과 xDS API를 통해 동적으로 구성됩니다.

의도적으로 ingress, gateway, VirtualService 설정중 한곳에 문제를 주고 해당 서비스가 어떤 문제가 있는지 한번 위 명령어들로 확인해보고자 한다.

먼저 문제있는 서비스의 Ingress와 gateway, Virtual Service 설정은 다음과 같이 진행했다.

$ k get ingress -n istio-system | grep query
query-ingress   alb     query.test.com                                                        query-server-ingress-fin.lb.naverncp.com    80      107d

위와같이 query-ingress는 현재 NCP ALB로 연결 된 상태다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
중략
  labels:
    app: query-server-ingress
  name: query-server-ingress
  namespace: istio-system
spec:
  ingressClassName: alb
  defaultBackend:
    service:
      name: istio-ingressgateway
      port:
        number: 80
  rules:
  - host: query-test.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ssl-redirect
            port: 
              name: use-annotation            

GW와 VS 매니페스트는 다음과 같다.

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
중략
  creationTimestamp: "2024-12-13T01:46:08Z"
  generation: 1
  labels:
    app.kubernetes.io/instance: opsquery
  name: query-gateway
  namespace: istio-system
  resourceVersion: "441696723"
  uid: 19fc1bd0-5c30-4dc1-b624-b956da859c15
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - query.test.co.kr
    port:
      name: http
      number: 80
      protocol: HTTP


---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ops-query-vs
  namespace: devops
spec:
  hosts:
  - "ops-query.test.co.kr"
  gateways:
  - istio-system/devops-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: query-svc-active
      weight: 100
    - destination:
        host: query-svc-preview
      weight: 0

이제 proxy-status와 proxy-config 명령어로 한번 원인을 찾아보고자 한다.

$ istioctl proxy-status | grep ops-query
ops-query-7cc7fc6f69-b6sml.devops                                    Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-784bcfdd5d-2fgbs     1.16.2

$ istioctl proxy-config all ops-query-7cc7fc6f69-b6sml.devops | grep ops 
##결과 없음

결과가 없다는건 Gateway나 VirtualService에 문제가 있을 가능성이 크다(고한다.)

Gateway 설정 문제:
Gateway가 Istio IngressGateway Pod와 제대로 연결되지 않았을 수 있습니다.
Gateway의 selector가 IngressGateway Pod의 라벨과 일치하지 않으면 트래픽을 수신할 Listener가 생성되지 않습니다.

VirtualService 설정 누락 또는 연결 문제:
VirtualService가 Gateway와 연계되어 있지 않거나, VirtualService에서 트래픽 라우팅 규칙이 제대로 정의되지 않았을 경우 Listener가 생성되지 않습니다.
VirtualService가 없으면 Gateway는 트래픽 처리 규칙을 알 수 없어 Listener를 생성하지 않습니다.

Istiod 구성 전달 문제:
Istiod(Control Plane)에서 Envoy 프록시(데이터 플레인)로 Gateway 및 VirtualService 설정이 전달되지 않았을 가능성이 있습니다.
이는 Istiod와 IngressGateway 간의 통신 문제, 혹은 설정 동기화 실패(STALE 상태)가 원인일 수 있습니다.

그럼 Ingress, GW, VS설정에 문제가 없는지 찾아보면 될것이다. 사실 VS 설정중 게이트웨이 설정하는부분을 잘못 넣어놨었다.

gateways:
-   istio-system/devops-gateway

정상적으로 하려면 devops-gateway가 아니라 query-gateway 으로 설정해줘야 한다.

$ istioctl proxy-config all ops-query-7cc7fc6f69-b6sml.devops  | grep ops-query
ops-query-svc-active.devops.svc.cluster.local                                  80        -          outbound      EDS              
ops-query-svc-preview.devops.svc.cluster.local                                 80        -          outbound      EDS              
80                                                                                   ops-query-svc-active, ops-query-svc-active.devops + 1 more...                                                            /*                     
80                                                                                   ops-query-svc-preview, ops-query-svc-preview.devops + 1 more...                                                          /*     

서비스도 정상적으로 동작하고, proxy-config 의 결과에도 정상적으로 출력되는것이 확인된다.
proxy-config에 대한(리스너, 라우드, 클러스터, 엔드포인트등 xDS)자세한 설명은 다음 주차때 envoy프록시 설명에서 자세히 다룰듯 하다.

다시 실습으로 넘어가서...

# istio-ingressgateway 서비스 NodePort 변경 및 nodeport 30000로 지정 변경
kubectl get svc,ep -n istio-system istio-ingressgateway
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl get svc -n istio-system istio-ingressgateway

# istio-ingressgateway 서비스 externalTrafficPolicy 설정 : ClientIP 수집 확인
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

curl -s http://127.0.0.1:30000/api/catalog | jq
curl -s http://127.0.0.1:30000/api/catalog/items/1 | jq
curl -s http://127.0.0.1:30000/api/catalog -I | head -n 1

위 명령어들로 실습환경의 30000포트로 접속가능하도록 설정해주고 curl로 접속을 확인하면 다음과 같이 정상적으로 접속됨을 확인 할 수 있다.

옵저버빌리티

이제 옵저버빌리티를 하기 위해 추가 설치한 프로메테우스, 그라파나, 키알리, 트레이싱에 대한 노드포트를 설정해준다.

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}]}}'

이후 localhost:서비스포트 로 접속하면 정상적으로 접속됨을 확인할 수 있다. 이중 Kiali를 살펴보면..

 

이미지와 같이 서비스매쉬의 트래픽 흐름을 확인 할 수 있다. 아까 장애 상황에서 라면 istio-ingressgateway에서 webapp으로 흐르는 트래픽이 안나왔을것이다.

 

이제 해당 서비스에 강제로 500에러를 50프로 빈도로 발생하도록 하면 kiali에서 다음 화면 처럼 실패가 나고 있는것을 볼 수 있다.

 

실무환경에서도 여러가지 이유로 간헐적으로 5xx대 에러가 발생할 수 있는데, 이때 다음 설정으로 5xx에러 발생시 재시도를 하도록 조치 할 수 있다.

cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  http:
  - route:
    - destination:
        host: catalog
    retries:
      attempts: 3
      retryOn: 5xx
      perTryTimeout: 2s
EOF

 

위 설정은 만약 5xx대 에러 발생시 2초 뒤 3번 재시도 하도록 할 수 있다. 사이드카 방식 istio의 큰 장점이라고 본다.

 

이밖에, istio 만으로 트래픽을 컨트롤하여 여러가지 배포방법을 이용할 수 있다.

 

우리는 argocd를 사용중인데, VS에서 route 설정으로 weight으로 active, preview를 설정하여 블루/그린 배포방법을 이용중이다.

 

추가로 다음과같이 VS에 헤더를 기반으로 매칭을 확인하여 트래픽을 보낼 수 있다.

cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  http:
  - match:
    - headers:
        x-dark-launch:
          exact: "v2"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1
EOF

 

현재 운영환경에서 아르고 롤아웃으로 프리뷰가 배포 됐을 때, promote 하기 전에 해당 배포버전으로 미리 접속하여 QA를 진행하고자 할 수 있다. 이때 굉장히 유용할듯 하다.

 

마지막으로...

istio를 사용하면서 istio 컨테이너의 로그를 자세히 봐야 할 필요가 있었다.

실사례로, 프론트에서는 아무런 값이 안들어왔고, 백앤드에서는 데이터를 정상적으로 전송했다고 한다. 이때 istio 컨테이너 로그를 확인 할 필요가 있었다. 왜냐하면 결국 데이터가 전송됐다면 istio 로그에 찍혔을테니까 말이다. 

 

그래서 다음과같은 설정을 적용해서 로그를 찍었다.

 

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: request-size-limit
  namespace: test
spec:
  workloadSelector:
    labels:
      app: test-api
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: ANY
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.buffer
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer
          max_request_bytes: 50485760


  - applyTo: HTTP_FILTER
    match:
      context: ANY
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
          inlineCode: |
            function envoy_on_response(response_handle)
              local headers = response_handle:headers()
              for key, value in pairs(headers) do
                response_handle:logInfo("response Header:" .. key .. ":"  .. value)
              end
              local response_body = response_handle:body():getBytes(0,response_handle:body():length())
              response_handle:logInfo("response body:" .. response_body)
            end

  - applyTo: LISTENER
    match:
      context: ANY
    patch:
      operation: MERGE
      value:
        per_connection_buffer_limit_bytes: 20480000

 

해당 설정은 요청에 대한 response body 용량과 실제 body 내용을 로그로 남기는 설정이다.

 

마무리..

1주차부터 좋은 지식을 많이 얻었다. 스터디 운영진님들 감사합니다.

'job > devops' 카테고리의 다른 글

istio-2  (1) 2025.04.17
taskfile  (0) 2024.03.10
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

1. 패키지 관리 도구 및 저장소별로 nexus 프록시 타입 레포 생성

2. maven, npm등 포맷별로 만들어진 proxy 레포를 하나의 그룹으로 묶기

3. 깃헙에서 소스 패키지를 태그나 릴리즈등으로 받는 경우는 raw host 만들어서 직접 파일 업로드하여 패키지 관리. 물론 raw host들도 그룹으로 묶기

4. 빌드 환경에 따라 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경하기

5. 패키지 설치 후 빌드하는 과정에서 디펜던시 패키지가 또다시 패키지를 설치하는경우가 있음. 따라서 패키지 설치후 디펜던시 패키지내에 추가적으로 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경해야 할 수 있음

큰 틀로 보면 위 순서대로 하면 된다. 

 

구축된 환경들을 나열해보면

React Native, Flutter, Yarn, Maven, NPM, Docker image, composer, python, cocoapod, rubygem

그리고 apk, yum, homebrew, nuget까지.. 많이 사용되는 대부분의 환경은 다 한 것 같다.

 

해당 업무를 하면서 조언을 얻기가 쉽지 않았다.  우리나라에 망분리 환경에서 개발하는 개발자들이 그렇게 많겠지만 개발자는 인프라영역을 모르고, 인프라담당자는 개발영역을 잘 몰르니 말이다. 그래서 난관이 좀 많았다. 망분리 개발환경 구축만큼은 나름 목소리좀 낼 수 있을듯 하다.

어쨌든 뭐. 이정도면 나름 만족스러운 결과(개발자도 최대한 불편하지 않고, 보안에도 위법되지 않고)인듯 하다.

 

마지막으로 각 단계에서 발생했던 난관들을 적어보자면...

1. 패키지 관리 도구 및 저장소별로 nexus 프록시 타입 레포 생성

넥서스 캐시등의 설정에 유의해야하고.

넥서스에서 기본적으로 제공하는 레시피가 제한적이다. 그럴땐 넥서스 레포 '패키지관리도구이름' 검색해보면 직접 레시피 만들 수 있도록 깃헙에 등록돼있는 경우가 있음.(ex apk)

 

2. maven, npm등 포맷별로 만들어진 proxy 레포를 하나의 그룹으로 묶기

이건 뭐 어려울껀 없다.. 가장 많이 사용하는 레포를 순서상 가장 위로 하도록

 

3. 깃헙에서 소스 패키지를 태그나 릴리즈등으로 받는 경우는 raw host 만들어서 직접 파일 업로드하여 패키지 관리. 물론 raw host들도 그룹으로 묶기

Nexus는 기본적으로 바이너리 컴포넌트 관리 솔루션으로 설계되었으며, 소스 코드 관리 시스템이 아니다. 따라서 GitHub과 같은 소스 코드 저장소를 직접적으로 프록시하거나 통합하는 데 적합하지 않다.

그래서 패키지를 깃헙에서 받는 경우에는 어떻게 해야할까 고민이 많았다. 기 구축된 내부 깃랩 서버가 있어서 처음엔 깃헙>깃랩을 동일하게 구성(미러링 되도록)해서 해결해볼까 했다.

그러나 이경우 깃헙에서 기본적으로 사용하는 태그/아카이브/릴리즈 경로와 깃랩의 경로가 서로 달랐다. 미러링하다보니까 모든 패키지를 다 다운받아야하니 불필요하게 깃랩의 용량이 커지는것도 싫었다.

그래서 결국 필요한 패키지를 깃헙에서 다운받아 raw host 넥서스 레포로 업로드 하도록 했다. 현재 내가 다니는 회사의 규모가 크진 않아서 필요한 패키지를 직접 수동으로 업로드하긴 했는데, 만약 자동화가 필요하다면 구글 시트에 개발자가 필요한 패키지이름/깃헙 경로 업로드하면 스케쥴링 걸어서 자동으로 넥서스 레포에 업로드 하도록 하면 될듯 싶다.

 

4. 빌드 환경에 따라 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경하기

CI/CD 자동화 환경에서는 dockerfile에서 직접 설정을 해야 하고, 개발자 개인 개발 PC에서의 설정은 개발자가 해야 할 일이다.

패키지 저장소별 설정 방법 가이드는 https://help.sonatype.com/en/formats.html 를 참고하면 된다.

 

5. 패키지 설치 후 빌드하는 과정에서 디펜던시 패키지가 또다시 패키지를 설치하는경우가 있음. 따라서 패키지 설치후 디펜던시 패키지내에

추가적으로 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경해야 할 수 있음

앞서 깃헙과 함께 가장 골치 아픈 상황이였다. 특히 프론트개발환경에 React Native에서 많았다. 이건 뭐.. 어쩔 수 없다. 이것도 찾아서 다 바꿔야 한다. 빌드 도구별로 패치 기능이 또 있어서 잘 활용하면 된다.

패키지 설치 -> 패치 -> 빌드 가 가능한 환경이면 devops가 replace(기존 저장소를 내부 넥서스로)를 할 수 있다. 근데 애매한게... 빌드 하는 과정에서 패치가 필요한경우가 있다. 이때는 개발자가 소스코드에서 직접 수정해줬다. 예를들어 Gradle 같은 경우는 init.gradle에서 설정이 가능하다.

 

 

넥서스에서 제공하는 기본 repository recipe중에 apk는 없다.

 

ps.

나는 apt랑 헷갈려서 apt로 만들었다가 다음과 같은 에러를 만났다.

package mentioned in index not found (try 'apk update')

 

어쨋든 apk용 레포를 만들기 위해 넥서스에 apk recipe를 추가해보자.

 

1. git clone https://github.com/sonatype-nexus-community/nexus-repository-apk.git

2. cd nexus-repo tab

3. mvn clean package -PbuildKar

3-1. mvn 없으면 maven 설치

3-2. 빌드가 완료되면 target 폴더에 .kar 파일이 생성됩니다

4. 생성된 .kar 파일을 Nexus Repository의 deploy 폴더로 복사

5. 넥서스 재시작

1

 

'job > linux' 카테고리의 다른 글

기본 엔지니어링 체크리스트  (0) 2024.03.20
docker Exited (137)  (0) 2024.01.09
ci/cd 파이프라인  (0) 2022.11.16
간헐적 500 Server Internal Error 원인 파악하기  (0) 2022.11.10
du -sch --exclude  (0) 2022.07.11

집합 자료형은 중복되지 않는 고유한 요소들의 모음을 저장하는 자료형으로, 여러 가지 실무 상황에서 유용하게 사용할 수 있다. 집합은 중괄호 {}를 사용하여 생성하며, 다양한 집합 연산을 지원한다. 아래에서는 집합 자료형을 실무에서 사용할 수 있는 몇 가지 상황을 설명해보겠다.

1. 중복 제거

가장 일반적인 집합의 사용 사례는 중복된 데이터를 제거하는 것이다. 예를 들어, 고객 이메일 목록에서 중복된 이메일 주소를 제거할 때 유용하다.

예시

emails = ["alice@example.com", "bob@example.com", "alice@example.com", "charlie@example.com"]
unique_emails = set(emails)
print(unique_emails)  # 출력: {'alice@example.com', 'bob@example.com', 'charlie@example.com'}

2. 교집합, 합집합, 차집합 연산

집합은 교집합, 합집합, 차집합 등의 집합 연산을 효율적으로 수행할 수 있다. 이는 데이터 분석, 필터링 등에 매우 유용하다.

예시

# 두 집합 생성
set_a = {"apple", "banana", "cherry"}
set_b = {"banana", "cherry", "date", "fig"}

# 교집합
intersection = set_a & set_b
print(intersection)  # 출력: {'banana', 'cherry'}

# 합집합
union = set_a | set_b
print(union)  # 출력: {'apple', 'banana', 'cherry', 'date', 'fig'}

# 차집합
difference = set_a - set_b
print(difference)  # 출력: {'apple'}

3. 데이터 무결성 유지

집합은 중복을 허용하지 않기 때문에, 데이터 무결성을 유지하는 데 유용하다. 예를 들어, 사용자 ID나 제품 코드와 같이 고유해야 하는 데이터를 저장할 때 사용한다.

예시

user_ids = {"user1", "user2", "user3"}

# 새로운 사용자 ID 추가
user_ids.add("user4")
print(user_ids)  # 출력: {'user1', 'user2', 'user3', 'user4'}

# 중복된 사용자 ID 추가 시도
user_ids.add("user2")
print(user_ids)  # 출력: {'user1', 'user2', 'user3', 'user4'} (중복 추가되지 않음)

4. 빠른 멤버십 테스트

집합은 특정 요소가 집합에 존재하는지 빠르게 확인할 수 있다. 이는 대규모 데이터에서 특정 요소를 검색할 때 유용하다.

예시

# 대규모 데이터 집합 생성
large_set = set(range(1000000))

# 특정 요소 존재 여부 확인
print(999999 in large_set)  # 출력: True
print(1000000 in large_set)  # 출력: False

5. 태그 시스템

집합은 태그 시스템을 구현할 때 유용하다. 예를 들어, 블로그 게시물에 여러 태그를 추가하고, 특정 태그를 가진 게시물을 검색할 때 사용한다.

예시

# 게시물에 태그 추가
post_tags = {"python", "programming", "tutorial"}

# 새로운 태그 추가
post_tags.add("coding")
print(post_tags)  # 출력: {'python', 'programming', 'tutorial', 'coding'}

# 특정 태그 존재 여부 확인
print("python" in post_tags)  # 출력: True
print("java" in post_tags)    # 출력: False

결론

집합 자료형은 중복 제거, 집합 연산, 데이터 무결성 유지, 빠른 멤버십 테스트, 태그 시스템 등 다양한 실무 상황에서 유용하게 사용할 수 있다. 집합의 특성과 장점을 이해하고 적절히 활용하면 데이터 처리와 분석을 더욱 효율적으로 수행할 수 있다.

프로그래밍을 할 때 리스트와 튜플을 사용하여 데이터를 저장할 수 있다. 이 두 자료형은 많은 면에서 비슷하지만, 메모리 사용량에서는 차이가 있다. 이 글에서는 리스트와 튜플의 메모리 사용량 차이를 설명해보겠다.

리스트와 튜플의 메모리 사용량 비교

리스트와 튜플은 각각 데이터를 저장하는 방식이 다르기 때문에 메모리 사용량에서도 차이가 난다. 일반적으로 튜플이 리스트보다 메모리를 덜 사용한다.

예시

# 리스트와 튜플 생성
a_list = [1, 2, 3]
a_tuple = (1, 2, 3)

# 메모리 사용량 확인
print(a_list.__sizeof__())  # 출력: 64
print(a_tuple.__sizeof__())  # 출력: 48

위 예시에서 볼 수 있듯이, 동일한 데이터를 저장할 때 리스트는 64바이트를 사용하고, 튜플은 48바이트를 사용한다.

메모리 사용량 차이의 이유

  1. 가변성:
    • 리스트는 가변적이어서 요소를 추가하거나 삭제할 수 있다. 이를 위해 리스트는 추가적인 메모리를 할당하여 데이터를 저장하고 관리해야 한다. 반면, 튜플은 불변적이어서 한 번 생성되면 변경할 수 없다. 따라서 튜플은 고정된 메모리만 할당하면 된다.
  2. 오버 할당:
    • 리스트는 요소를 추가할 때마다 메모리를 재할당하는 비용을 줄이기 위해 오버 할당(over-allocation) 기법을 사용한다. 이는 리스트가 더 많은 메모리를 사용할 수 있게 한다. 반면, 튜플은 이러한 오버 할당이 필요 없으므로 더 적은 메모리를 사용한다[1][2].
  3. 구조적 차이:
    • 리스트는 각 요소에 대한 포인터를 저장하는 데 추가 메모리를 사용한다. 반면, 튜플은 이러한 포인터를 저장하지 않아 더 적은 메모리를 사용한다[1].

[1] https://stackoverflow.com/questions/46664007/why-do-tuples-take-less-space-in-memory-than-lists

[2] https://www.reddit.com/r/learnpython/comments/1b9rdxq/list_vs_tuple_mutable_vs_immutable_performance/

Citations: [1] https://stackoverflow.com/questions/46664007/why-do-tuples-take-less-space-in-memory-than-lists [2] https://www.reddit.com/r/learnpython/comments/1b9rdxq/list_vs_tuple_mutable_vs_immutable_performance/ [3] https://stackoverflow.com/questions/20771470/list-memory-usage [4] https://www.geeksforgeeks.org/memory-management-in-lists-and-tuples-using-python/ [5] https://www.geeksforgeeks.org/python-memory-consumption-dictionary-vs-list-of-tuples/ [6] https://www.upgrad.com/blog/list-vs-tuple/ [7] https://github.com/BecomeWeasel/daily_algo_challenge/issues/2

프로그래밍을 할 때 리스트와 튜플을 언제 사용해야 할지 고민될 수 있다. 리스트와 튜플은 모두 데이터를 순서대로 저장할 수 있는 자료형이지만, 몇 가지 중요한 차이점이 있다.

리스트 (List)

리스트는 데이터를 수정, 추가, 삭제할 수 있는 변경 가능한 자료형이다. 대괄호 []로 감싸서 만들며, 다양한 데이터를 저장할 수 있다. 리스트는 다음과 같은 상황에서 사용하기 좋다:

  1. 데이터가 자주 변경될 때:
    • 리스트는 데이터를 자유롭게 수정할 수 있다. 예를 들어, 쇼핑 목록이나 할 일 목록처럼 자주 업데이트해야 하는 데이터를 저장할 때 유용하다.
  2. 동적 크기 조정이 필요할 때:
    • 리스트는 크기를 동적으로 조정할 수 있다. 데이터를 추가하거나 삭제할 수 있어, 크기가 변동하는 데이터에 적합하다.

튜플 (Tuple)

튜플은 한 번 생성되면 수정할 수 없는 변경 불가능한 자료형이다. 소괄호 ()로 감싸서 만들며, 다양한 데이터를 저장할 수 있다. 튜플은 다음과 같은 상황에서 사용하기 좋다:

  1. 데이터가 변경되지 않을 때:
    • 튜플은 데이터가 변경되지 않아야 할 때 사용하기 좋다. 예를 들어, 좌표나 RGB 색상 값처럼 고정된 데이터를 저장할 때 유용하다.
  2. 데이터의 무결성을 유지해야 할 때:
    • 튜플은 변경할 수 없기 때문에 데이터의 무결성을 유지할 수 있다. 중요한 데이터를 보호할 때 적합하다.
  3. 메모리 사용을 최적화할 때:
    • 튜플은 리스트보다 메모리를 적게 사용하고, 처리 속도가 빠르다. 따라서 메모리 사용을 최적화해야 하는 상황에서 유용하다.

리스트와 튜플의 차이점 정리

특징 리스트 (List) 튜플 (Tuple)

생성 방법 대괄호 [] 사용 소괄호 () 사용
변경 가능 여부 변경 가능 (요소 수정, 추가, 삭제 가능) 변경 불가능 (요소 수정, 추가, 삭제 불가)
사용 예시 동적으로 변하는 데이터 관리에 유용하다 고정된 데이터 관리에 유용하다

결론

리스트와 튜플은 각각의 특성과 장점을 가지고 있다. 리스트는 데이터가 자주 변경되거나 크기가 변동할 때 사용하기 좋다. 반면, 튜플은 데이터가 변경되지 않아야 하거나 데이터의 무결성을 유지해야 할 때, 그리고 메모리 사용을 최적화해야 할 때 사용하기 좋다.

리스트 (List)

리스트는 여러 데이터를 순서대로 저장할 수 있는 자료형이다. 리스트는 대괄호 []로 감싸서 만들고, 각 요소는 쉼표 ,로 구분한다. 리스트의 가장 큰 특징은 변경 가능하다는 점이다. 즉, 리스트에 있는 데이터를 수정, 추가, 삭제할 수 있다.

예시

# 리스트 생성
fruits = ["사과", "바나나", "딸기"]

# 리스트 요소 변경
fruits[1] = "오렌지"  # 바나나를 오렌지로 변경
print(fruits)  # 출력: ['사과', '오렌지', '딸기']

# 리스트에 요소 추가
fruits.append("포도")
print(fruits)  # 출력: ['사과', '오렌지', '딸기', '포도']

# 리스트에서 요소 삭제
fruits.remove("딸기")
print(fruits)  # 출력: ['사과', '오렌지', '포도']

튜플 (Tuple)

튜플은 리스트와 비슷하게 여러 데이터를 순서대로 저장할 수 있는 자료형이다. 하지만 튜플은 소괄호 ()로 감싸서 만들고, 리스트와 달리 변경 불가능하다. 즉, 한 번 생성된 튜플의 요소는 수정, 추가, 삭제할 수 없다.

예시

# 튜플 생성
colors = ("빨강", "초록", "파랑")

# 튜플 요소 접근
print(colors[1])  # 출력: 초록

# 튜플 요소 변경 시도 (오류 발생)
# colors[1] = "노랑"  # 오류: 튜플은 변경할 수 없음

# 튜플에 요소 추가 시도 (오류 발생)
# colors.append("노랑")  # 오류: 튜플은 변경할 수 없음

리스트와 튜플의 차이점 정리

특징 리스트 (List) 튜플 (Tuple)

생성 방법 대괄호 [] 사용 소괄호 () 사용
변경 가능 여부 변경 가능 (요소 수정, 추가, 삭제 가능) 변경 불가능 (요소 수정, 추가, 삭제 불가)
사용 예시 동적으로 변하는 데이터 관리에 유용하다 고정된 데이터 관리에 유용하다

결론

리스트와 튜플은 여러 데이터를 관리할 때 매우 유용한 자료형이다. 리스트는 데이터를 자유롭게 수정, 추가, 삭제할 수 있어 유연성이 높다. 반면, 튜플은 한 번 생성되면 변경할 수 없기 때문에 데이터의 무결성을 유지하는 데 유리하다. 이 두 자료형의 차이를 이해하고 상황에 맞게 사용하는 것이 중요하다.

2차원 배열이란

2차원 배열은 숫자나 문자를 정리해서 저장할 수 있는 표와 같은 것이다. 엑셀이나 구글 스프레드시트를 생각해보면 쉽게 이해할 수 있다. 표의 각 칸에는 하나의 값이 들어가고, 이 값들은 행과 열로 구분된다.

2차원 배열의 예시

예를 들어, 다음과 같은 표가 있다고 하자:

0열 1열 2열

0행 1 2 3
1행 4 5 6
2행 7 8 9

이 표는 3개의 행과 3개의 열로 이루어져 있다. 각 칸에는 숫자가 들어있다. 이 표를 2차원 배열이라고 부른다.

파이썬에서 2차원 배열 만들기

파이썬에서는 리스트라는 것을 사용해서 2차원 배열을 만들 수 있다. 리스트는 여러 개의 값을 한 곳에 모아놓는 방법이다. 2차원 배열을 만들기 위해서는 리스트 안에 리스트를 넣으면 된다.

array = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

위의 코드는 3x3 크기의 2차원 배열을 만든다. 첫 번째 리스트 [1, 2, 3]는 첫 번째 행을 나타내고, 두 번째 리스트 [4, 5, 6]는 두 번째 행을 나타낸다.

2차원 배열에서 값 꺼내기

2차원 배열에서 특정 값을 꺼내려면 행과 열의 위치를 알려줘야 한다. 예를 들어, 첫 번째 행과 두 번째 열에 있는 값을 꺼내려면 다음과 같이 하면 된다.

value = array[0][1]  # 결과는 2

여기서 array은 첫 번째 행(0행)과 두 번째 열(1열)에 있는 값을 의미한다. 파이썬에서는 숫자를 셀 때 0부터 시작한다는 점을 기억하자.

2차원 배열에서 부분 배열 꺼내기

2차원 배열에서 여러 개의 값을 한꺼번에 꺼내는 것도 가능하다. 이를 슬라이싱이라고 한다. 예를 들어, 첫 번째와 두 번째 행을 꺼내려면 다음과 같이 한다.

rows = array[0:2]  # 결과는 [[1, 2, 3], [4, 5, 6]]

여기서 array[0:2]는 첫 번째 행(0행)과 두 번째 행(1행)을 의미한다.

2차원 배열에서 인덱싱과 슬라이싱을 활용하면 특정 행, 열 또는 부분 배열을 쉽게 추출할 수 있다. 이를 통해 데이터 분석, 이미지 처리 등 다양한 분야에서 효율적으로 데이터를 다룰 수 있다. 아래 예제는 NumPy 라이브러리를 사용하여 2차원 배열을 처리하는 방법을 보여준다.

예시: 2차원 배열 생성 및 인덱싱, 슬라이싱

먼저, NumPy를 사용하여 2차원 배열을 생성하고, 인덱싱과 슬라이싱을 통해 특정 부분을 추출하는 예제를 살펴보자.

import numpy as np

# 2차원 배열 생성
array = np.array([
    [1, 2, 3, 4, 5],
    [6, 7, 8, 9, 10],
    [11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20]
])

# 배열 출력
print("원본 배열:")
print(array)

인덱싱 예제

특정 행과 열의 요소를 추출하는 방법이다.

# 두 번째 행의 세 번째 요소 (8) 추출
element = array[1, 2]
print("\\\\n두 번째 행의 세 번째 요소:", element)

# 마지막 행의 마지막 요소 (20) 추출
element = array[-1, -1]
print("마지막 행의 마지막 요소:", element)

슬라이싱 예제

배열의 특정 부분을 추출하는 방법이다.

# 첫 두 행과 첫 세 열 추출
sub_array = array[:2, :3]
print("\\\\n첫 두 행과 첫 세 열:")
print(sub_array)

# 두 번째 행부터 끝까지, 세 번째 열부터 네 번째 열까지 추출
sub_array = array[1:, 2:4]
print("\\\\n두 번째 행부터 끝까지, 세 번째 열부터 네 번째 열까지:")
print(sub_array)

# 모든 행에서 두 번째 열만 추출
column = array[:, 1]
print("\\\\n모든 행에서 두 번째 열:")
print(column)

# 모든 열에서 세 번째 행만 추출
row = array[2, :]
print("\\\\n모든 열에서 세 번째 행:")
print(row)

문자열 인덱싱

문자열 인덱싱은 문자열의 특정 위치에 있는 문자를 가져오는 방법이다. 인덱스는 0부터 시작하며, 음수 인덱스를 사용하면 문자열의 끝에서부터 역순으로 접근할 수 있다.

# 문자열 설정
text = "Hello, World!"

# 인덱싱 예제
print(text[0])  # 출력: H (첫 번째 문자)
print(text[7])  # 출력: W (여덟 번째 문자)
print(text[-1])  # 출력: ! (마지막 문자)
print(text[-5])  # 출력: o (뒤에서 다섯 번째 문자)

문자열 슬라이싱

문자열 슬라이싱은 문자열의 일부분을 추출하는 방법이다. 슬라이싱은 [start:end:step] 형식을 사용하며, start는 시작 인덱스, end는 끝 인덱스(포함되지 않음), step은 간격을 의미한다.

# 문자열 설정
text = "Hello, World!"

# 슬라이싱 예제
print(text[0:5])  # 출력: Hello (0번 인덱스부터 4번 인덱스까지)
print(text[7:12])  # 출력: World (7번 인덱스부터 11번 인덱스까지)
print(text[:5])  # 출력: Hello (처음부터 4번 인덱스까지)
print(text[7:])  # 출력: World! (7번 인덱스부터 끝까지)
print(text[:])  # 출력: Hello, World! (전체 문자열)
print(text[::2])  # 출력: Hlo ol! (2칸씩 건너뛰며 추출)
print(text[::-1])  # 출력: !dlroW ,olleH (역순으로 추출)

인덱싱과 슬라이싱을 동시에 활용한 실무 예제 - 로그 파일에서 날짜와 오류 메시지 추출하기

문자열 인덱싱과 슬라이싱을 동시에 활용하면 문자열의 특정 부분을 효율적으로 추출하고 조작할 수 있다. 예를 들어, 로그 파일에서 특정 정보를 추출하거나, 텍스트 데이터에서 특정 패턴을 찾는 작업에 유용하다.

로그 파일의 각 줄에서 날짜와 오류 메시지를 추출하는 예제를 살펴보자. 로그 파일의 형식은 다음과 같다:

2024-08-07 12:34:56 ERROR: Something went wrong
2024-08-07 12:35:56 INFO: All systems operational
2024-08-07 12:36:56 ERROR: Another error occurred

이 로그 파일에서 날짜와 오류 메시지만 추출하는 코드를 작성해보자.

# 로그 파일의 각 줄을 리스트로 저장
log_lines = [
    "2024-08-07 12:34:56 ERROR: Something went wrong",
    "2024-08-07 12:35:56 INFO: All systems operational",
    "2024-08-07 12:36:56 ERROR: Another error occurred"
]

# 날짜와 오류 메시지를 추출하는 함수
def extract_error_info(log_lines):
    for line in log_lines:
        if "ERROR" in line:
            # 날짜 추출 (인덱싱과 슬라이싱을 동시에 활용)
            date = line[:10]  # 처음 10글자: 2024-08-07
            # 오류 메시지 추출
            error_message = line[line.index("ERROR:") + 7:]  # "ERROR:" 다음부터 끝까지
            print(f"Date: {date}, Error: {error_message}")

# 함수 호출
extract_error_info(log_lines)

결과

Date: 2024-08-07, Error: Something went wrong
Date: 2024-08-07, Error: Another error occurred

설명

  1. 날짜 추출: line[:10]을 사용하여 문자열의 처음 10글자를 추출한다. 이는 날짜를 의미한다.
  2. 오류 메시지 추출: line.index("ERROR:") + 7을 사용하여 "ERROR:" 문자열의 위치를 찾고, 그 이후의 문자열을 추출한다.

이 예시는 인덱싱과 슬라이싱을 동시에 활용하여 문자열에서 필요한 정보를 효율적으로 추출하는 방법을 보여준다. 이러한 기법은 로그 파일 분석, 데이터 전처리 등 다양한 실무 상황에서 유용하게 사용될 수 있다.

복합 연산자는 변수의 값을 업데이트할 때 사용하는 연산자로, 기본 연산자와 할당 연산자를 결합한 형태이다. 예를 들어, +=, -=, *=, /=, %=, **=, //= 등이 있다. 이러한 연산자는 코드의 가독성을 높이고 간결하게 만들어준다.

예시 1: += 연산자

# 초기 값 설정
x = 5

# 복합 연산자 사용
x += 3  # x = x + 3과 동일

print(x)  # 출력: 8

예시 2: = 연산자

# 초기 값 설정
y = 10

# 복합 연산자 사용
y -= 4  # y = y - 4와 동일

print(y)  # 출력: 6

예시 3: = 연산자

# 초기 값 설정
z = 7

# 복합 연산자 사용
z *= 2  # z = z * 2와 동일

print(z)  # 출력: 14

예시 4: /= 연산자

# 초기 값 설정
a = 20

# 복합 연산자 사용
a /= 5  # a = a / 5와 동일

print(a)  # 출력: 4.0

예시 5: %= 연산자

# 초기 값 설정
b = 13

# 복합 연산자 사용
b %= 4  # b = b % 4와 동일

print(b)  # 출력: 1

예시 6: *= 연산자

# 초기 값 설정
c = 2

# 복합 연산자 사용
c **= 3  # c = c ** 3과 동일

print(c)  # 출력: 8

예시 7: //= 연산자

# 초기 값 설정
d = 15

# 복합 연산자 사용
d //= 2  # d = d // 2와 동일

print(d)  # 출력: 7

자료형이란 프로그래밍을 할 때 쓰이는 숫자, 문자열 등과 같이 자료 형태로 사용하는 모든 것을 뜻한다.

예시: Python에서 자료형 이해의 중요성

상황 설명:

Alice는 Python을 배우기 시작한 초보 프로그래머다. 그녀는 계산기를 만드는 프로젝트를 진행 중이다. 그러나 자료형에 대한 이해가 부족하여 여러 가지 문제에 직면하게 된다.

잘못된 접근: Alice는 자료형에 대한 이해 없이 바로 코딩을 시작했다. 그녀는 사용자로부터 입력을 받아 두 숫자를 더하는 간단한 계산기를 만들려고 했다.

# Alice의 코드
num1 = input("첫 번째 숫자를 입력하세요: ")
num2 = input("두 번째 숫자를 입력하세요: ")

result = num1 + num2
print("결과: ", result)

Alice는 두 숫자를 더한 결과가 기대와 다르다는 것을 발견했다. 예를 들어, '3'과 '5'를 입력했을 때 결과는 '35'가 나왔다.

문제 분석: Alice는 input 함수가 문자열을 반환한다는 사실을 몰랐다. 따라서 num1과 num2는 문자열로 저장되었고, 문자열끼리의 덧셈은 문자열을 이어붙이는 결과를 초래했다.

올바른 접근: 자료형을 이해한 후, Alice는 사용자로부터 입력받은 값을 정수형으로 변환해야 한다는 것을 알게 되었다.

# 수정된 코드
num1 = int(input("첫 번째 숫자를 입력하세요: "))
num2 = int(input("두 번째 숫자를 입력하세요: "))

result = num1 + num2
print("결과: ", result)

이제 Alice의 계산기는 올바르게 작동한다. '3'과 '5'를 입력했을 때 결과는 '8'이 된다.

이 예시는 자료형을 이해하지 않고 프로그래밍을 시작하면 발생할 수 있는 문제를 보여준다. 자료형을 충분히 이해하는 것은 프로그래밍의 기본이자 핵심이다. 자료형을 이해함으로써 Alice는 올바른 계산기를 만들 수 있었고, 이는 다른 복잡한 프로그램을 작성할 때도 중요한 기초가 된다.

소스 관리

기본 대상 브랜치는 잠겨 있습니다.
병합은 PR을 통해 이루어집니다.
PR은 관련 작업 항목을 참조합니다.
커밋 기록은 일관되고 커밋 메시지는 정보(내용, 이유)를 제공한다.
일관된 브랜치 이름 지정 규칙.
리포지토리 구조에 대한 명확한 문서화.
secret은 커밋 기록에 포함되지 않거나 공개되지 않습니다. (자격 증명 스캔 참조)
공개 리포지토리는 OSS 가이드라인을 따르며, "공개 리포지토리의 기본 브랜치에 필요한 파일을 참조하세요." 를 적는다.

 

 

 

 

'job > linux' 카테고리의 다른 글

Nexus Apk repo 생성하기  (0) 2024.10.25
docker Exited (137)  (0) 2024.01.09
ci/cd 파이프라인  (0) 2022.11.16
간헐적 500 Server Internal Error 원인 파악하기  (0) 2022.11.10
du -sch --exclude  (0) 2022.07.11

git clone시 프로젝트 단위로만 git clone이 가능하다.

 

여간 불편한게 아니다.

그룹 혹은 서브그룹단위로 한번에 다운로드하면 참 좋을텐데.

 

이떄 사용하는 도구는 gitlab CLI 또는 glab이다.

https://gitlab.com/gitlab-org/cli/-/releases

 

Releases · GitLab.org / cli · GitLab

A GitLab CLI tool bringing GitLab to your command line

gitlab.com

 

 

설치방법 : https://gitlab.com/gitlab-org/cli#installation

 

GitLab.org / cli · GitLab

A GitLab CLI tool bringing GitLab to your command line

gitlab.com

 

나는 brew install glab

 

 

토큰 인증 등록 : glab auth login —hostname domain.com —token glpat-12312382183

 

그룹 Clone 방법 : glab repo clone -g 최상위그룹/서브그룹1/서브그룹2 --preserve-namespace --paginate

 

 

 

  1. 로키 스트림의 생성에 사용되는 과정:
    • 로그 데이터는 라벨로 분류됩니다. 라벨은 로그 데이터를 구분하고 쿼리하기 위한 메타데이터로, 키/값의 쌍으로 이루어져 있습니다. 예를 들어, {component="printer", location="f2c16", level="error"}와 같은 라벨 세트가 로그 메시지에 할당됩니다.
    • 라벨 세트는 해시되어 고유한 '스트림 ID'를 생성합니다. 이 ID는 특정 로그 스트림을 식별하는 데 사용됩니다. 이미지에는 해시된 결과의 예로 3b2cea09797978fc가 있습니다.
  2. 청크의 생성과 저장:
    • 동일한 라벨 세트를 가진 추가적인 로그 메시지들은 같은 '청크'에 추가됩니다. 예를 들어, "Printing is not supported by this printer", "Out of paper", "Too much paper"와 같은 다양한 로그 메시지가 모두 같은 라벨을 공유하므로 같은 청크에 저장됩니다.
    • 이러한 청크는 채워진 후에 압축되고 저장됩니다.
  3. 청크 조회를 위한 인덱스:
    • 청크를 빠르게 찾기 위해, 별도의 작고 분리된 인덱스가 유지됩니다. 이 인덱스를 통해 청크를 빠르게 조회할 수 있습니다.
  4. 라벨 값의 변화와 새로운 청크 생성:
    • 만약 라벨의 키 또는 값이 달라지면, 다른 해시 값을 가지는 새로운 스트림과 새로운 청크가 생성됩니다. 예를 들어, {component="printer", location="f2c16", level="info"} 라벨 세트는 "Consider the environment before printing this log message"라는 로그 메시지와 함께 새로운 청크를 형성합니다.

 

 

자 그러면 로키의 Chunk 데이터 형식을 더 알아보자.

이 형식은 로키가 로그 데이터를 저장할 때 사용하는 내부 구조를 나타냅니다.

 

 

 

프롬쿼리를 공부중인데

카운터, 게이지, 히스토그램등 메트릭타입은 이해를 했다. 쿼리를 레이블로 필터링하거나 혹은 정규표현식으로 필터링하는것까지도 이해를했다.

 

다만 

instant vector 

range vector

rate함수

서브쿼리가 잘 이해가지 않아, 이해하기 위해 끄적인다.

 

사용 메트릭

promhttp_metric_handler_requests_total{code="200"}

메트릭 설명 : 

프로메테우스 메트릭 수집관련 http 응답코드중 200(정상)반환한 요청의 수

쿼리1. instant Vector

promhttp_metric_handler_requests_total{code="200"}

결과값

 

결과값 설명 :promhttp_metric_handler_requests_total 결과값

그래프 결과 :

 

 

쿼리2. Range Vector

promhttp_metric_handler_requests_total{code="200"}[5m]

결과값: 

결과값 설명:  지난 5분간의 반환값. 갯수는 총 20개다. 현재 프로메테우스에서 메트릭 수집하는게 15초간격으로 하니까 5분이면 20개가 맞다.

 

그래프결과: 결과값 자체가 타임스탬프별 결과값을 테이블 형식으로 가지고 있기 때문에 그래프 출력 못함. 

 

쿼리3. rate(promhttp_metric_handler_requests_total{code="200"}[5m])

결과값:

 

결과값 설명: rate함수(초당 평균 증가율을 계산하는 함수)를 사용하여 5분(300초)동안 초당 평균 요청 증가율을 출력한것.

앞서 5m일때의 값이 약 20개이다. 즉 rate함수가 초당 평균증가율을 계산하는거니까 300초([5m])동안 약 20개 가량이 증가했으니까  20 / 300 하면 대략 0.066666667이 나온다. 

 

그래프결과: 

 

06시 9분에 처음 메트릭 수집이 시작됐고 5분뒤인 14분에 약 20개 가량의 결과값이 쌓였다.

그래서 위와같이 약 14분가량에 밸류값이 0.06666667에 가까운것을 알수있다.

 

레인지 백터로 5m을 줬을 때 최근 5분동안의 결과값을 타임스탬프 형식으로 가져오는것은 맞다. 여기서 오해하면 안되는게 rate 함수를 사용했을 때 최근 5분동안의 증가율만 결과값으로 반환하지만 이를 그래프로 표현했을때는 수집 시점부터의 수집값을 가지고 그래프를 그린다는것

 

쿼리4-1.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[10m:1m]

 

결과값:

 

쿼리4-2.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[20m:1m]

결과값:

 

쿼리4-3.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[20m:10m]

결과값:

 

쿼리4-4.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[180m:10m]

결과값:

 

 

마지막으로 서브 쿼리에 대한 설명이다. [180m:10m]180분동안 10분간격으로 데이터를 가져온다는 뜻이다. 

 

설치

brew install go-task

아주 간단한 사용법

 cat taskfile.yaml
version: '3'

tasks:
  hello:
    cmds:
      - echo 'Hello World from Task!'
    silent: true
 task hello
Hello World from Task!

말그대로 task들을 코드로 관리하는 도구

  • 챗지피티 설명Taskfile의 주요 장점은 다음과 같습니다:
    1. 간단하고 명확한 문법: Taskfile은 가독성이 높고 쓰기 쉬운 문법을 제공합니다. 이는 개발자가 작업을 빠르게 정의하고 이해할 수 있게 해줍니다.
    2. 로컬 및 원격 실행 지원: Taskfile은 로컬 개발 환경과 원격 CI/CD 파이프라인에서 동일한 작업을 실행할 수 있게 해줍니다. 이는 개발 및 배포 과정의 일관성을 보장합니다.
    3. 작업 의존성 관리: Taskfile을 사용하면 작업 간의 의존성을 쉽게 정의하고 관리할 수 있습니다. 이는 작업 실행 순서를 자동화하고 복잡한 작업 흐름을 구성하는 데 도움이 됩니다.
    4. 재사용성 및 모듈화: Taskfile을 통해 정의된 작업은 재사용 가능하고 모듈화되어 있어, 다른 프로젝트나 파이프라인에서 쉽게 재사용할 수 있습니다.
    5. 확장성: Taskfile은 단순한 작업 실행부터 복잡한 파이프라인 구성까지 다양한 요구 사항을 수용할 수 있도록 설계되었습니다. 개발자는 필요에 따라 작업을 확장하고 사용자 정의할 수 있습니다.
    6. 통합 용이성: Taskfile은 GitHub Actions, Jenkins, GitLab CI 등 다양한 CI/CD 도구와 쉽게 통합될 수 있습니다. 이를 통해 개발 팀은 파이프라인을 효율적으로 관리하고 자동화할 수 있습니다.
    Taskfile은 Makefile과 비교할 때 더 현대적이고 사용자 친화적인 대안으로, 개발자들이 작업을 더 효율적으로 관리하고 실행할 수 있게 해줍니다. 추가적인 질문이 있거나 더 자세한 정보가 필요하시면 언제든지 문의해 주세요.
  • Taskfile은 개발 과정에서 다양한 작업을 관리하기 위한 도구입니다. 이 도구를 사용하면 테스트 실행, 빌딩, 패키징, 배포 등의 작업을 선언적 방식으로 정의하고 자동화할 수 있습니다. Taskfile의 주요 목적은 작업 실행을 단순화하고, 개발자가 로컬 및 CI/CD 파이프라인 환경에서 동일한 작업을 쉽게 실행할 수 있도록 하는 것입니다.

msbuild라던가 gradle의 여러 버전별로 사용해야 할 때 파이프라인에서 각 gradle의 환경변수를 직접 잡아줄 필요 없이 task를 사용하면 손쉽게 버전별 도구를 사용할수있지 않을가 싶다.

version: '3'

tasks:
  gradle6.1:
    cmds:
      - gradle6.1 wrapper --gradle-version=6.1
      - ./gradlew build
    desc: "Build the project with Gradle 6.1"

  gradle7.2:
    cmds:
      - gradle7.2 wrapper --gradle-version=7.2
      - ./gradlew build
    desc: "Build the project with Gradle 7.2"

이외에도 docker compose up , down 조차도 어려워하는 고객들에게 task를 사용하여 명령어를 만들어주고 사용하라고 해도 좋을듯.

가장 베스트는 개발자들이 직접 이 taskfile이라는 도구를 사용하면서 개발(로컬피씨)환경과 빌드서버환경을 통일하면 가장 좋을듯하다.

사용법 : https://www.youtube.com/watch?v=_-bpnCY3-FE

'job > devops' 카테고리의 다른 글

istio-2  (1) 2025.04.17
istio-1  (0) 2025.04.08
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

https://arxiv.org/abs/2312.16171

 

논문상으로 퀄리티 57.7%, 정확도는 36.4% 향상

  1. 본론만 말하기,쓸대없는 말 하지 말기, 미사여구 붙이지 말기
    ex)자꾸 질문해서 미안한데.. 이런 쓸대없는말 하지 말기
  2. 청중 설정하기. 의도한 청중을 설정하고 질문하기
    ex)스마트폰을 사용해본적 없는 사람을 위해 스마트폰 작동원리에 대한 개요를 설명해줘
  3. 복잡한 작업을 간단한 프롬프트로 세분화시켜라.
    ex) A로는 B를 만들고 만들어진 B를가지고 C와D를 만들어줘. C가지고는 C-1을 생성하고 D로는 D-1을 추가로 만들어줘 X ex) P1: A로 B 만들어줘. 그리고 만들어진 B를 추후 재사용할꺼니까 기억해 P2: 만들어진 B를가지고 C를 만들어줘. 그리고 만들어진 C를 추후 재사용할꺼니까 기억해 P3. 만들어진 B를 가지고 D를 만들어줘. 그리고 만들어진 D를 추후 재사용할꺼니까 기억해 P4. C로는 C-1을 만들어줘. P5. D로는 D-1을 만들어줘.
  4. 긍정 지시문 사용하기, 부정어 사용하지말기
    ex) 반말 하지마 X 존댓말 해 O.
  5. 어린이 청자 설정하기
    ex) 비전공자도 이해하기 쉽게 설명해줘 ex) 11살짜리도 이애할수 있게 설명해줘
  6. 팁준다고하기
    ex) 더 나은 답변을 하면 $300K팁을 줄게. devops에 대해서 설명해줘
  7. 예제 중심 프롬프트 구현하기. 지시, 예시, 질문으로 질문하기, 다음과같이 질문 형식지로 질문하기
    ex) ###Example###
    ex) ###Question###
    ex) ###Instruction###
  8. 임무를 설정하기
    ex)너는 반드시~~, 너는 무조건~~
    ex)너의 임무는~~
  9. 협박하기
    ex)너의 임무는 XX야. 제대로 답변 못하면 “당신은 불이익을 받을것입니다”. 라는 문장 포함시키기
  10. 다음 프롬프트들 추가시키기
    1. 자연스러운 답변을 요청하기 : Answer a question given in a natural, human-like manner
    2. 단계별로 생각하도록 하기 : think step by step
    3. 편견제거시키는 프롬프트 추가시키기 : Ensure that your answer is unbiased and avoids relying on stereotypes.
    4. 정보 충분할때까지 질문시키기 : From now on, I would like you to ask me questions to
    5. 필요한 모든 정보를 포함시키라고 하기 : Write a detailed [essay/text /paragraph] for me on [topic] in detail by adding all the information necessary
  11. 테스트 추가시키기
    ex) 내가 답변을 물어볼때까지 정답은 알려주지말고 테스트만 해봐.
  12. 역할 부여시키기
    ex) 너는 devops엔지니어야.
  13. 구분기호 사용하기
    ex) 따옴표, 쌍따옴표같은거 쓰기
  14. 주요 특정 단어 반복해서 사용하기
    ex) devops 엔지니어로 블로그를 작성중이야. devops 엔지니어에게 필요한 devops 도구를 추천할꺼야.
  15. CoT 이용해서 질문하기(질문을 계속 이어서 하라)
    ex)10을 2로 나눕니다. 먼저 10을 2로 나눕니다. 결과는 ?
  16. 출력문구 지정하라.devops 엔지니어란:
  17. devops 엔지니어의 직무요건:
  18. ex) devops 엔지니어가 뭔지 설명해줘
  19. 여러개의 파일의 프로젝트를 만드는 스크립트를 요청
    ex) 코딩시 두개 이상의 파일에 걸쳐있는 코딩을 할때 각각의 파일을 만들지말고 챗지피티한테 애초에 관련 파일 전체를 만드는 스크립트를 만들어달라고 한다.
  20. 키워드 제시
    ex)devops 엔지니어에 대해 설명해줘. 단 CI/CD, k8s, docker, 컨테이너 라는 단어는 반드시 포함시켜.

전통적인 컨테이너 방식 대신 Nix Shell을 사용하여 임시 환경을 생성하고 파괴하는 새로운 접근 방식을 탐구합니다. Nix Shell은 개발자가 필요한 도구를 포함한 환경을 쉽게 생성하고 사용 후 즉시 제거할 수 있는 효율적인 방법을 제공합니다. 이 접근 방식의 주요 장점은 다양한 운영 체제에서 일관된 개발 환경을 제공하면서도 필요할 때만 특정 도구를 사용할 수 있게 해준다는 점입니다.

  • 임시 환경의 필요성: 개발자들이 필요에 따라 환경을 쉽게 생성하고 제거할 수 있는 능력은 효율적인 작업 흐름을 위해 필수적입니다. 임시 환경은 특히 개발, 테스트 및 빌드 파이프라인에서 유용합니다.
  • 컨테이너의 한계: 컨테이너는 여러 환경에서 널리 사용되지만, 설정과 관리가 복잡할 수 있으며, 다양한 도구와 응용 프로그램의 설치 및 설정에 제한이 있을 수 있습니다.
  • Nix Shell의 소개: Nix Shell은 이러한 문제를 해결하기 위한 대안으로, 필요한 도구와 응용 프로그램을 포함한 커스텀 환경을 쉽게 생성할 수 있습니다. 이는 특히 여러 도구가 필요한 복잡한 프로젝트나 다양한 개발 요구 사항이 있는 팀에 유용합니다.
  • 사용 사례와 예시: Nix Shell을 사용하여 GitHub CLI, Kubernetes, 그리고 다양한 개발 도구를 포함한 환경을 신속하게 설정하는 과정을 보여줍니다. 이는 개발자가 복잡한 설치 과정 없이도 필요한 모든 도구에 즉시 접근할 수 있게 해줍니다.
  • 플랫폼 독립성: Nix Shell은 macOS, Windows, Linux 등 다양한 운영 체제에서 동일한 방식으로 작동합니다. 이는 개발자가 운영 체제의 차이에 구애받지 않고 일관된 환경을 유지할 수 있게 해줍니다.
  • 효율성과 생산성 향상: Nix Shell을 사용하면 개발자가 프로젝트에 필요한 도구를 빠르고 쉽게 준비할 수 있으며, 사용하지 않을 때는 쉽게 제거할 수 있습니다. 이는 개발자의 시간을 절약하고 전반적인 생산성을 향상시킵니다.

 

https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-shell

 

nix shell - Nix Reference Manual

Warning This program is experimental and its interface is subject to change. nix shell - run a shell in which the specified packages are available nix shell [option...] installables... Start a shell providing youtube-dl from the nixpkgs flake: # nix shell

nixos.org

 



 

Nix 설치하기

  1. Nix 설치: Nix는 Linux 및 macOS에서 사용할 수 있습니다. 터미널을 열고 아래 명령어를 실행하여 Nix를 설치합니다.
sh <(curl -L <https://nixos.org/nix/install>)
  1. 환경 설정: 설치 후에는 .bashrc, .zshrc, 또는 사용 중인 셸의 설정 파일에 Nix를 초기화하는 코드가 추가됩니다. 변경사항을 적용하려면 새 터미널 세션을 시작하거나 설정 파일을 소스로 지정합니다.
source ~/.bashrc  # 또는 해당하는 셸의 설정 파일

Nix 셸 사용하기

  1. 단일 패키지 실행: Nix 셸을 사용하여 임시 환경에서 단일 패키지를 실행할 수 있습니다. 예를 들어, hello 프로그램을 사용해 보려면 다음 명령어를 실행합니다.
nix-shell -p hello
  1. 여러 패키지 실행: 여러 패키지를 포함하는 환경을 생성하려면, 각 패키지 이름을 -p 옵션과 함께 나열합니다.
nix-shell -p nodejs python39
  1. shell.nix 파일 사용: 보다 복잡한 환경을 구성하려면 shell.nix 파일을 생성하고 필요한 패키지와 환경 설정을 정의할 수 있습니다. 예를 들어, Node.js와 Python 환경을 구성하려면 다음과 같은 내용의 shell.nix 파일을 작성합니다.
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  buildInputs = [
    pkgs.nodejs
    pkgs.python39
  ];
}

해당 디렉토리에서 **nix-shell**을 실행하면 **shell.nix**에 정의된 환경이 생성됩니다.ㅌㅌ

'job' 카테고리의 다른 글

Kubernetes환경에서의 의존성에 대해서  (0) 2024.01.28
2024년도 devops 추천 도구  (0) 2024.01.11
git 그룹단위로 전체 clone 하기  (0) 2023.08.25
keycloak 을 broker로, google을 IdP로  (0) 2023.05.23
teleport란  (0) 2023.05.02

애플리케이션이 구동되려면 여러가지 서비스들이 복합적으로 실행되어야 한다.

App A가 있어야하고 이를 바라보는 App B, App B에 연결된 DB, DB에 연결된 DB User, 스키마가 있어야 한다. 이렇듯 애플리케이션이 구동하려면 여러가지 서비스간의 의존성을 고려해야 한다.

 

이러한 서비스간의 의존성은 과거에는 배포순서가 중요했다. 예를들어, 과거의 배포방식은 SSH로 서버에 직접 접속하거나 혹은 파이프라인 또는 스크립트로 배포를 했다. 즉, 정해진 스크립트(배포 순서)에 따라 서비스들이 배포됐기에 배포 순서가 중요했다.

 

그러나 k8s 환경에서는 배포 순서가 그다지 중요하지 않아졌다.

왜냐하면 k8s는 실패한 리소스의 배포를 계속 시도하므로 만약 잘못된 순서대로 배포된다해도 결국 애플리케이션은 정상적으로 동작하게 될 것이다.

 

하지만 과거의 관습(의존성을 배포순서로 가져가는것)을 k8s 환경에서 사용하는 경우가 많다.

우리가 중요하게 생각해야할 점은 k8s를 사용하는 현 시점에서 리소스의 종속성과 생성 순서에 대해 과도하게 걱정할 필요가 없다는 것이다.  오히려 시스템이 자연스럽게 일관성을 유지하도록 하고, 필요한 데이터나 정보에 기반한 종속성에 초점을 맞추는 것이 중요하다는 의미이다.

예를 들어, 애플리케이션이 데이터베이스에 접근하기 위해 필요한 접근정보가 준비되지 않았다면, 쿠버네티스는 자동으로 해당 애플리케이션의 생성을 지연시킨다.

 

즉, 리소스의 생성 순서보다는 해당 리소스가 제공하는 데이터의 가용성이 더 중요함을 의미한다.

 

 

 

 

 

출처 : https://www.youtube.com/watch?v=4-WpJ49MDG8

'job' 카테고리의 다른 글

Nix shell 소개  (1) 2024.02.18
2024년도 devops 추천 도구  (0) 2024.01.11
git 그룹단위로 전체 clone 하기  (0) 2023.08.25
keycloak 을 broker로, google을 IdP로  (0) 2023.05.23
teleport란  (0) 2023.05.02

kubewarden

Kubewarden은 쿠버네티스(Kubernetes) 클러스터의 보안과 정책 준수를 관리하기 위한 도구입니다.

Kubewarden을 사용하면, 클러스터에 어떤 파드(Pod, 쿠버네티스에서 애플리케이션을 실행하는 단위)가 생성되거나 업데이트될 때 적용되는 규칙이나 정책을 설정할 수 있습니다.

  • Kubewarden은 WebAssembly(WASM)와 쿠버네티스 admission controllers를 결합한 도구로, 다양한 언어로 작성된 정책을 컴파일하여 쿠버네티스에서 실행할 수 있게 해줍니다.
    • 쿠버네티스의 Admission Controllers
      • 쿠버네티스의 Admission Controllers는 쿠버네티스 API 서버로의 요청을 가로채서 검사하고, 수정하거나 거부하는 역할을 합니다.
      • 이들은 쿠버네티스 클러스터에서 리소스(파드, 서비스 등)의 생성, 업데이트, 삭제 등의 요청이 처리되기 전에 특정 규칙이나 정책을 적용합니다.
      • 예를 들어, 특정 파드가 너무 많은 CPU 자원을 요청하는 것을 막거나, 특정 네임스페이스에서만 리소스를 생성할 수 있도록 제한하는 등의 작업을 수행합니다.
      • Admission Controllers는 쿠버네티스의 보안과 정책 준수를 강화하는 데 중요한 역할을 합니다.
  • Kubewarden을 사용하면 정의된 정책을 적용하고, 이 정책들이 쿠버네티스 API로의 요청을 수락, 거부 또는 변경할 수 있습니다.

    Kubewarden의 정책 모듈은 컨테이너 이미지로 참조되며, 이는 정책을 이미지에 저장하고 있다는 것을 의미합니다. 예를 들어, 특정 이미지가 권한 있는 파드의 실행을 방지하는 정책을 가지고 있을 수 있습니다. 
    • 예시
apiVersion: policies.kubewarden.io/v1
kind: ClusterAdmissionPolicy
metadata:
  name: validate-pod-security-standards
spec:
  module: registry://ghcr.io/kubewarden/policies/validate-pod-security-standards:v1.0.0
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
    operations:
    - CREATE
    - UPDATE
  mutating: false
  settings: 
    requiredLabels: # 파드에 반드시 존재해야 하는 레이블들을 정의합니다.
      - "app.kubernetes.io/name"
      - "app.kubernetes.io/version"
    requiredAnnotations: # 파드에 반드시 존재해야 하는 어노테이션을 정의합니다.
      - "kubewarden.policy/owner"
    enforceRunAsNonRoot: true # 파드가 Non-root 사용자로 실행되어야 한다는 요구사항을 설정합니다.
    allowedCapabilities: [] # 파드에서 허용되는 추가적인 리눅스 기능(capabilities)을 비어 있는 배열로 설정하여 모든 추가 기능을 금지합니다.

 

이 설정에 따르면:

  • module: registry://ghcr.io/kubewarden/policies/validate-pod-security-standards:v1.0.0: Kubewarden 정책 모듈은 쿠버네티스 클러스터에서 파드의 보안 표준을 검증하는 데 사용됩니다. 이 모듈의 주요 목적과 기능은 다음과 같습니다:
  • requiredLabels: 모든 파드에는 **app.kubernetes.io/name**과 app.kubernetes.io/version 레이블이 있어야 합니다. 이는 파드가 어떤 애플리케이션에 속하는지와 애플리케이션의 버전을 명시하는 데 사용됩니다.
  • requiredAnnotations: 모든 파드에는 kubewarden.policy/owner 어노테이션이 있어야 합니다. 이는 정책의 소유자 또는 관리자를 지정하는 데 사용될 수 있습니다.
  • enforceRunAsNonRoot: 이 값이 **true**로 설정되면, 모든 파드는 루트가 아닌 사용자로 실행되어야 합니다. 이는 보안 관행에 따른 것입니다.
  • allowedCapabilities: 이 배열이 비어 있기 때문에, 파드에서 어떠한 추가 리눅스 기능도 허용되지 않습니다. 이는 파드가 더 높은 권한을 가지는 것을 방지합니다.

 

  • Kubewarden 정책은 Artifact Hub에서 찾을 수 있으며, 이곳은 쿠버네티스 관련 거의 모든 것을 찾을 수 있는 장소입니다.
    • artifact hub

 

  • 사용자는 Kubewarden을 사용하여 사용자 정의 정책 모듈을 개발하고 적용할 수 있습니다. 예를 들어, 특정 크기의 SQL 클레임만을 허용하는 사용자 정의 정책을 만들 수 있습니다.
    • 사용자 정의 정책 생성 방법
      1. 정책 요구 사항 정의: 먼저, 이 정책이 해결하려는 문제를 정의합니다. 예를 들어, 클러스터에서 너무 큰 SQL 데이터베이스가 생성되는 것을 방지하고자 할 수 있습니다. 이를 위해 'small', 'medium', 'large'와 같은 특정 크기만을 허용하고자 하는 요구 사항을 정의합니다.
      2. 정책 로직 개발: 다음으로, 이 요구 사항을 구현하는 로직을 개발합니다. 이 과정에서는 Kubewarden 정책을 구현할 수 있는 프로그래밍 언어(예: Rust, Go 등)를 사용하여, SQL 클레임의 크기를 검사하고, 허용된 크기에 맞지 않는 클레임을 거부하는 코드를 작성합니다.
      3. WASM으로 컴파일: 개발한 정책 로직을 WebAssembly(WASM)로 컴파일합니다. WASM은 다양한 환경에서 실행될 수 있는 저수준 바이너리 포맷입니다. Kubewarden은 WASM 형식의 정책을 실행합니다.
      4. 정책 모듈 배포: 컴파일된 정책 모듈을 컨테이너 이미지로 패키징하고, Docker 레지스트리(예: Docker Hub, GHCR 등)에 배포합니다.
      5. Kubewarden 정책 설정: Kubewarden 정책을 쿠버네티스 클러스터에 적용합니다. 이때 정책 모듈의 위치와 해당 정책이 적용될 리소스 및 조건을 지정하는 YAML 파일을 작성하고 적용합니다.
      apiVersion: policies.kubewarden.io/v1
      kind: ClusterAdmissionPolicy
      metadata:
        name: sql-size-restriction
      spec:
        module: registry://myregistry.io/kubewarden/sql-size-restriction:v1.0.0
        rules:
        - apiGroups: [""]
          apiVersions: ["v1"]
          resources: ["sqlclaims"]
          operations:
          - CREATE
          - UPDATE
        mutating: false
        settings:
          allowedSizes:
            - small
            - medium
            - large
      
      이 예시에서, 정책은 **sqlclaims**라는 커스텀 리소스에 적용되며, 생성 또는 업데이트 시 'small', 'medium', 'large'라는 크기 제한을 강제합니다. 이를 통해 쿠버네티스 클러스터 내에서 자원 사용을 효과적으로 관리하고, 과도한 리소스 사용을 방지할 수 있습니다.
    • Kubewarden을 사용하여 사용자 정의 정책 모듈을 개발하고 적용하는 예시로, "특정 크기의 SQL 클레임만을 허용하는 정책"을 들 수 있습니다. 이런 종류의 정책은 쿠버네티스 클러스터에서 SQL 데이터베이스 리소스의 크기를 제한하는 데 사용될 수 있습니다. 여기에는 몇 가지 주요 단계가 있습니다:
  • Kubewarden의 장점 중 하나는 거의 모든 언어(WASM으로 컴파일될 수 있어야 함)로 정책을 작성할 수 있다는 것이며, 이는 다른 정책 도구와 구별되는 주요 특징입니다.
  • Kubewarden의 단점으로는 새 정책을 추가할 때마다 정책 서버가 재시작되어야 한다는 점과 쿠버네티스 표준을 따르지 않아 이벤트가 발생하지 않는다는 점이 있습니다.
    • 이벤트 미발생 예시
      • 쿠버네티스 클러스터에서 '파드 메모리 제한' 정책이 위반되어 파드 생성이 거부되었다고 가정해봅시다.
      • 쿠버네티스 표준을 따르는 시스템에서는 이러한 거부 사건이 '이벤트'로 기록되고, 시스템 관리자나 다른 애플리케이션에서 이를 감지할 수 있습니다.
      • 하지만 Kubewarden은 이러한 이벤트를 생성하지 않기 때문에, 관리자나 다른 시스템이 이러한 중요한 정보를 즉시 알 수 없을 수 있습니다.
    • 쿠버네티스에서는 보통 중요한 변화나 상태 변경 시 '이벤트'를 발생시켜 사용자나 다른 시스템 요소에 알립니다.하지만, Kubewarden은 쿠버네티스 표준 이벤트 생성을 지원하지 않아 이러한 알림이 발생하지 않습니다.

'job > devops' 카테고리의 다른 글

istio-2  (1) 2025.04.17
istio-1  (0) 2025.04.08
taskfile  (0) 2024.03.10
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

https://www.youtube.com/watch?v=-qeoLfSGlFU&t=2s 를 참고하였다.

 

1.Service Catalogs - Port 

  • Backstage는 CNCF의 인기있는 프로젝트 중 하나로, UI를 제공하는 도구의 기본이 될 수 있지만, 최종 사용자에게 직접 사용되기에는 복잡하고 유지 관리가 어렵.
  • Port는 현재 사용 가능한 최고의 도구로 평가되며, Kubernetes와 친화적으로 만들어야 할 작업이 있지만, SaaS로만 사용 가능하고 오픈소스가 아니라는 단점이 있음에도 불구하고 추천

서비스 카탈로그(Service Catalog)는 조직 내에서 사용되는 서비스, 애플리케이션, 리소스들의 목록을 관리하고, 사용자가 이들에 접근하고 활용할 수 있도록 도와주는 도구나 시스템을 의미합니다. 이러한 카탈로그는 IT 서비스 관리(ITSM)의 중요한 부분이며, 개발자, IT 전문가, 그리고 다른 사용자들이 필요한 서비스를 쉽게 찾고, 이해하며, 사용할 수 있도록 합니다.

  1. Backstage:
    • 개발자가 서비스를 더 빠르고 효율적으로 찾고, 사용하며, 관리할 수 있도록 도와주는 통합 개발 환경(IDE)입니다.
    • 기업이나 조직의 서비스, 소프트웨어 컴포넌트, 인프라 등을 한 곳에서 관리할 수 있도록 합니다.
    • 사용자 정의가 가능하며, 다양한 플러그인과의 통합을 지원합니다.
  2. Port:
    • Backstage와 유사한 기능을 제공하지만, 사용자에게 더 친숙하고 쉬운 인터페이스를 제공하는 것을 목표로 합니다.
    • SaaS(서비스로서의 소프트웨어) 형태로 제공되며, 오픈소스가 아닌 것이 특징입니다.
    • Kubernetes와의 통합 및 호환성에 중점을 두고 있으며, 쿠버네티스 클러스터에서 실행되는 서비스 관리에 특화되어 있습니다.

 

2. Application Management (Kubernetes Manifests - Helm 대안. Timoni, CUE 랭귀지

  • Timoni는 CUE 기반으로 Helm과 유사하게 작동한다.

Helm과 차이점 : https://timoni.sh/comparison/

CUE 랭귀지 : https://timoni.sh/cue/introduction/

 

 

3. Pipelines (CI/CD) - Dagger

  • Dagger는 새로운 도구로, 어디서나 실행될 수 있는 파이프라인을 정의할 수 있으며, 다양한 언어로 정의 가능.

컨테이너 기반의 파이프라인 도구. 벤더 종속성 피함

 

4. Observability -Pixie, groundcover

  • Grafana Cloud가 전체 솔루션으로서 강조됨. Pixie는 혁신적인 접근 방식을 제공.

결국 옵저버빌리티에는 loki, prometheus, victoria metircs, jaeger, tempo, alertmanager, komodor등 여러가지 도구들을 조합해서 사용해야하는데 이런 여러가지 솔루션을 종합적으로 제공하는 Pixie나 groundcover를 추천한다.

pixie: https://px.dev/

groundcover: https://www.groundcover.com/

 

5. Databases - CMPG, Atlas Operator

    • 관리형 데이터베이스 서비스 사용을 권장, PostgreSQL에 특화된 Cloud Native PG (CMPG)와 Atlas Operator를 추천함 
  1. CMPG (Cloud Native PostgreSQL):
    • CMPG는 PostgreSQL 데이터베이스를 Kubernetes 환경에서 관리하고 운영하기 위한 솔루션입니다.
    • Kubernetes 네이티브 방식을 채택하여, 데이터베이스 관리를 Kubernetes 클러스터와 일관성 있게 통합합니다. 이는 Kubernetes의 자동화, 확장성, 복원력 등의 장점을 데이터베이스 관리에도 적용할 수 있게 합니다.
    • PostgreSQL에 특화되어 있어, 이 데이터베이스 시스템을 사용하는 조직에게 특히 유용합니다.
    • Kubernetes 환경에서의 운영을 간소화하고, 더 효율적인 관리 및 자동화 기능을 제공하는 것이 주된 장점입니다.
  2. Atlas Operator:
    • Atlas Operator는 MongoDB Atlas와의 통합을 위한 Kubernetes 오퍼레이터입니다.
    • MongoDB Atlas는 클라우드에서 MongoDB 데이터베이스를 관리하는 서비스로, 자동화된 백업, 확장성, 보안 기능을 제공합니다.
    • Atlas Operator를 사용하면 Kubernetes 환경 내에서 MongoDB Atlas 리소스를 보다 쉽게 관리하고, Kubernetes 애플리케이션과 MongoDB Atlas 사이의 통합을 간편하게 구성할 수 있습니다.
    • 이러한 통합은 Kubernetes 클러스터의 리소스 관리와 데이터베이스 서비스 관리를 하나의 플랫폼에서 할 수 있게 해, 개발 및 운영 효율성을 높입니다.

6. Infrastructure and Service Management - k8s

  • Ansible, Terraform, Pulumi 등이 언급되지만, 최종 선택은 Kubernetes 자체임. Kubernetes는 컨테이너 관리뿐만 아니라 다양한 리소스 관리를 위한 플랫폼으로 강조됨

7.  Security - KubeScape, Teller

  • KubeScape는 Kubernetes 클러스터의 보안을 전반적으로 평가하고 개선할 수 있는 능력을 제공합니다. 이는 클라우드 네이티브 환경의 복잡성을 고려할 때 매우 중요한 기능입니다. 자동화된 보안 평가는 클러스터의 보안 상태를 지속적으로 모니터링하고 개선하는 데 큰 도움이 됩니다.
  • Teller는 민감한 데이터의 관리와 보안을 강화하는 데 중점을 두고 있으며, 특히 개발과 운영 환경에서의 비밀 관리에 있어서 중요한 역할을 합니다. 코드 내에 민감한 데이터를 하드코딩하는 위험을 줄이고, 보안을 강화하는 동시에 개발자의 작업 편의성을 높여줍니다.
  1. KubeScape:
    • KubeScape는 Kubernetes 환경을 위한 보안 검사 도구입니다.
    • 이 도구는 Kubernetes 클러스터의 구성과 배포된 애플리케이션을 분석하여, 보안 취약점과 비효율적인 구성을 식별합니다.
    • KubeScape는 CNCF의 보안 벤치마크와 산업 표준에 따라 Kubernetes 환경을 평가합니다. 이를 통해 보안 위험을 줄이고, 클러스터의 보안 상태를 개선하는 데 도움을 줍니다.
    • 자동화된 보안 검사를 통해 개발 및 운영 과정에서의 보안 관리를 간소화하고 효율적으로 만듭니다.
  2. Teller:
    • Teller는 애플리케이션과 개발 환경에서 비밀번호, API 키, 인증서 등과 같은 민감한 데이터를 안전하게 관리하기 위한 도구입니다.
    • 이 도구는 다양한 비밀 관리 시스템과 통합되며, 이러한 민감한 데이터를 안전하게 가져오고, 관리할 수 있도록 합니다.
    • 개발자들이 코드 내에 민감한 정보를 하드코딩하지 않고도, 필요한 시점에 안전하게 접근할 수 있게 해줍니다.
    • CI/CD 파이프라인, 개발자의 로컬 환경, 서버 등 다양한 환경에서의 비밀 관리를 지원합니다.

8. Networking - Cilium, Gateway API

  • 서비스 메시 도구들과 함께 Cilium이 중요한 네트워킹 솔루션이다. Cilium은 Kubernetes 네트워킹의 표준이며, eBPF를 기반으로 확장성을 제공함.
  • Gateway API는 Kubernetes의 Ingress 규격을 대체할 것으로 예상됨

9. Miscellaneous - Charm

  • Charm은 단일 도구가 아니라 터미널 사용 경험을 개선하기 위한 다양한 도구와 라이브러리의 세트임. 터미널 사용자에게 유용할 것

https://charm.sh/

 

10. Miscellaneous - eBPF

  • eBPF는 2023년 중요한 기술로 강조되며, 여러 솔루션의 기반이 됨. eBPF를 직접 사용하지는 않겠지만, 많은 도구와 서비스가 eBPF를 기반으로 구축될 것

 

'job' 카테고리의 다른 글

Nix shell 소개  (1) 2024.02.18
Kubernetes환경에서의 의존성에 대해서  (0) 2024.01.28
git 그룹단위로 전체 clone 하기  (0) 2023.08.25
keycloak 을 broker로, google을 IdP로  (0) 2023.05.23
teleport란  (0) 2023.05.02

+ Recent posts