istio 3주차 - Traffic control, Resilience

bocopile·2025년 4월 26일

istio

목록 보기
3/5
post-thumbnail

지난 포스팅에선 Envoy, Istio Gateway에 대해 다뤄봤습니다.

이번 포스팅에선 5장 Traffic Control, 6장 Resilience을 다뤄보도록 하겠습니다.

(공통)실습 환경 구성

k8s - 1.23.17 : NodePort (30000/http, 30005/https)

  • 프로젝트 클론
    git clone https://github.com/AcornPublishing/istio-in-action
    cd istio-in-action/book-source-code-master
  • myk8s cluster 배포
    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/bkshin/IdeaProjects/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
  • 사이트 접근
    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

istio - 1.17.8 (addon 포함)

  • myk8s-control-plane 진입
    docker exec -it myk8s-control-plane bash
  • 마운트 확인
    tree /istiobook/ -L 1
  • istioctl 설치
    # 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-ingresgateway, crd 설치 확인
    kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
    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
  • 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
    
    # 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

5장 Traffic Control

1. Reducing the risk of deploying new code

소프트웨어를 운영하면서 자주 듣는 두 용어가 있습니다. 바로 "배포(Deployment)" 와 "릴리스(Release)" 입니다.

많은 팀이 이 둘을 혼용해 사용하지만, 실제로는 아주 중요한 차이가 존재합니다.

이번 글에서는 가상의 catalog 서비스를 예로 들어, 배포와 릴리스의 차이를 명확하게 설명해 보겠습니다.

현재 catalog 서비스 v1이 운영되고 있다고 가정해봅시다.

이제 여기에 코드 변경을 적용해 v2.0 버전을 출시하려고 합니다.

일반적인 과정은 다음과 같습니다:

  1. CI 시스템으로 빌드
  2. v2.0로 태그
  3. 스테이징 환경에 배포 및 테스트
  4. 문제가 없으면 운영 환경에 배포

여기까지는 일반적인 소프트웨어 배포 과정처럼 보이죠.

하지만 이 시점에서 중요한 포인트가 있습니다.

배포 (Deployment)

배포(Deployment)란 운영 환경에 설치되지만, 아직 트래픽을 받지 않는 상태입니다.

catalog v2.0을 운영 서버에 설치한다고 해서, 모든 사용자가 바로 새로운 코드를 사용하는 것은 아닙니다.

  • 배포 후, 우리는 Smoke 테스트를 통해 운영 서버에 설치된 v2.0이 정상적으로 동작하는지 확인할 수 있습니다.
  • 운영 환경 메트릭과 로그를 통해 새로운 버전의 상태를 관찰할 수 있습니다.
  • 이때까지는 아무 사용자 트래픽도 새로운 버전으로 넘어오지 않습니다.

정리: 배포는 설치(Deployment)일 뿐, 사용자 영향은 없습니다.

릴리즈 (Release)

릴리스(Release)란 새 버전으로 실제 트래픽을 이동시키는 행위입니다.

운영 서버에 배포만 되어 있다면 아직 아무 일도 일어나지 않았습니다.

진짜 변화는 "사용자 요청"을 새로운 catalog v2.0로 보내기 시작할 때 발생합니다.

릴리스 방법은 여러 가지가 있습니다:

  • 내부 직원만 릴리스 (Internal Canary)
  • 일부 고객만 릴리스 (Canary Release)
  • 고객 등급별 점진적 릴리스 (예: 무과금 → 실버 → 골드 고객)
  • 전체 릴리스 (Full Release)

릴리스를 통해 트래픽을 점진적으로 v2.0로 넘기고, 문제가 없으면 범위를 확장합니다.

카나리 릴리스(Canary Release)

초기에 일부 사용자만 새로운 버전을 사용하게 합니다.

  • 트래픽의 일부만 v2.0으로 전송합니다.
  • 내부 직원이나 특정 고객 그룹을 대상으로 테스트합니다.
  • 문제가 생기면 즉시 트래픽을 다시 v1.0로 롤백할 수 있습니다.

이 과정을 통해, 신버전에 대한 위험을 최소화할 수 있습니다.

롤백은 어떻게?

만약 v2.0 버전에서 문제가 발생한다면?

  • 바로 트래픽을 기존 v1/-으로 되돌립니다.
  • 고객은 문제를 거의 느끼지 못한 채 서비스를 계속 이용할 수 있습니다.

릴리스를 점진적으로 하고, 항상 롤백 경로를 준비해두는 것은 안정적인 운영을 위한 핵심 전략입니다.

과거의 실수: 배포와 릴리스를 동시에?

한때 ACME라는 회사는 새 버전을 배포하자마자 바로 전체 릴리스를 했습니다.

  • 서버에 새 버전을 설치 → 즉시 전체 트래픽 이동
  • 신버전에 있던 숨겨진 버그까지 사용자들에게 바로 노출

이런 방식은 위험합니다.

배포와 릴리스를 분리하지 않으면, 문제가 발생했을 때 피해 범위가 커지기 때문입니다.

Istio를 통한 트래픽 제어

Istio 같은 서비스 메시를 사용하면, 릴리스를 훨씬 세밀하게 제어할 수 있습니다.

  • 트래픽의 비율을 조정하거나
  • 특정 사용자 그룹만 신버전으로 라우팅하거나
  • 자동화된 롤백 전략을 적용할 수 있습니다.

운영 리스크를 줄이고, 훨씬 유연한 릴리스를 할 수 있게 됩니다.

2. Routing Request with istio

1) catalog Service v1.0 배포

  • catalog 1.0 배포
    kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
  • 배포 확인
    ```bash
    kubectl get pod -n istioinaction -owide
    kubectl describe pod -n istioinaction catalog-6cf4b97d-h9xsd
    ```
    
    ![](https://velog.velcdn.com/images/gjrjr4545/post/a19b503e-3466-4d98-941d-811822d82546/image.png)
  • 도메인 질의를 위한 임시 설정
    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
    kubectl apply -f ch5/catalog-gateway.yaml -n istioinaction
  • VirtualService 리소스 설정
    cat ch5/catalog-vs.yaml
    kubectl apply -f ch5/catalog-vs.yaml -n istioinaction
  • 배포 확인
    kubectl get gw,vs -n istioinaction
  • istio-ingressgateway Service(NodePort)에 포트 정보 확인
    kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
  • 접속 확인
    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
  • tracing 확인
  • 특정 파드의 istio-proxy 에 Envoy 에 Admin 웹 접속
    kubectl port-forward deploy/istio-ingressgateway -n istio-system 15000:15000
    open http://127.0.0.1:15000

2) catalog Service v2.0 배포

  • catalog v2.0 배포
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
  • 배포 확인
    kubectl get deploy -n istioinaction --show-labels
    kubectl get pod -n istioinaction -o wide
    docker exec -it myk8s-control-plane istioctl proxy-status
  • 호출 테스트
    for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
    • v1 접속 확인
- v2 접속 확인

    ![](https://velog.velcdn.com/images/gjrjr4545/post/2c4c4d9e-f703-4e8b-928d-371cac081dc3/image.png)
  • 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 -o json
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
    
  • endpoint 상세 확인
    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
  • kiali 확인

3) catalog Service v1.0에 모든 트래픽이 가도록 설정

  • label 확인
    • v1.0 : version=v1 로 설정

    • v2.0 : version=v2 로 설정

      kubectl get pod -l app=catalog -n istioinaction --show-labels

  • catalog-dest-rule 적용
    ```bash
    cat ch5/catalog-dest-rule.yaml
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
    ```
    
    ```
  • 적용 확인
    kubectl get destinationrule -n istioinaction
    kubectl describe destinationrule -n istioinaction catalog
  • catalog 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 --subset version-v1 -o json
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v2 -o json
    • v1.0
    • v2.0
  • endpoint 확인

    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'

  • deploy catalog endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
  • VirtualService 수정 (subnet 추가)
    cat ch5/catalog-vs-v1.yaml
    kubectl apply -f ch5/catalog-vs-v1.yaml -n istioinaction
  • 호출 테스트 - v1만 호출
    for i in {1..100}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
  • 세부 정보 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
  • cluster 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1
  • cluster 상세 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
  • endpoint 확인
    ```bash
    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'
    ```
    
    ![](https://velog.velcdn.com/images/gjrjr4545/post/a2b55d5f-ab1c-428e-a1c5-478e8128917e/image.png)
    )

4) catalog Service v2.0에 대한 특정 요청 설정

http 요청 헤더에 x-istio-cohort: internal 가 포함된 경우 catalog v2.0으로 가도록 설정

  • VirtualService 배포
    cat ch5/catalog-vs-v2-request.yaml
    kubectl apply -f ch5/catalog-vs-v2-request.yaml -n istioinaction
  • 호출 테스트
    for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
    • 요청 헤더에 x-istio-cohort: internal 포함되지 않아 v1.0으로만 통신
  • 요청 헤더 포함 호출 테스트 진행
    curl http://catalog.istioinaction.io:30000/items -H "x-istio-cohort: internal"
    • v2.0 호출 되는 것을 확인
  • 반복 접속 테스트
    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
    • v2.0으로만 요청이 가는 것을 확인
  • ingressgateway 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080
  • 상세 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json

5) 호출 그래프 내부의 깊은 라우팅

기존 실습에 라우팅 수행위치가 edge, gateway에서만 이루어졌다.

이번 트래픽 규칙은 호출 그래프 내 깊은 곳에도 적용하는 것을 실습한다.

  • 초기화
    kubectl delete gateway,virtualservice,destinationrule --all -n istioinaction
  • webapp 기동
    kubectl apply -n istioinaction -f services/webapp/kubernetes/webapp.yaml
  • 배포 확인
    kubectl get deploy,pod,svc,ep -n istioinactionkubectl get deploy,pod,svc,ep -n istioinaction
  • Gateway, VirtualService 호출 후 테스트 진행
    • webapp → catalog (k8s service (ClusterIP) 라우팅 ) 적용
      cat services/webapp/istio/webapp-catalog-gw-vs.yaml
      kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
  • 도메인 질의를 위한 임시 설정
    echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 3
  • 호출 테스트
    curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
  • 반복 호출 테스트
    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
  • istio-ingressgateway 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 
  • outbound 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system | egrep 'webapp|catalog'
  • outbound 상세 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||webapp.istioinaction.svc.cluster.local'
  • 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
  • 카탈로그 서비스의 v1.0로 모든 트래픽을 라우팅하도록 VirtualService, DestinationRule 생성
    ch5/catalog-dest-rule.yaml
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
  • ingressgateway 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
  • VirtualService - service mesh v1.0 적용
    cat ch5/catalog-vs-v1-mesh.yaml
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
  • VirtualService 확인
    kubectl get vs -n istioinaction
  • 반복 호출 테스트 진행
    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
  • webapp 파드 로그 확인
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
  • proxy-config 확인

    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
    • webapp 에서 catalog 로 VirtualService 정보는 없었는데, 추가됨을 확인
  • 상세 확인
    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
  • catalog cluster 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local

  • catalog cluster v1.0 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --subset version-v1 -o json
  • catalog endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'
  • kiali 확인
  • VirtualService - service mesh v2.0 적용
    cat ch5/catalog-vs-v2-request-mesh.yaml
    kubectl apply -f ch5/catalog-vs-v2-request-mesh.yaml -n istioinaction
  • 반복 접속 테스트 진행
    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
  • Virtual Service 확인 - 1개 증가
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
  • 상세 확인
    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
  • kiali 확인

3. 트래픽 변환

istio로 가중치 기반의 트래픽 전환을 수행하는 방법에 대한 실습을 진행

1) 기본 테스트

  • webapp 배포
    kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction # 이미 배포 상태
  • label 확인
    kubectl get deploy,rs,pod -n istioinaction --show-labels
  • 반복 테스트 진행
    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.0으로 재설정
    cat ch5/catalog-vs-v1-mesh.yaml
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
  • 호출 테스트
    curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
  • 전체 트래픽의 10%만 catalog v2.0으로 라우팅 설정
    cat ch5/catalog-vs-v2-10-90-mesh.yaml
    kubectl apply -f ch5/catalog-vs-v2-10-90-mesh.yaml -n istioinaction
  • VirtualService 적용
    kubectl get vs -n istioinaction catalog
  • 호출 테스트 - v2.0에 약 10.8% 라우팅 되는것을 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
  • route 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
  • 트래픽을 50:50 으로 라우팅 설정
    cat ch5/catalog-vs-v2-50-50-mesh.yaml
    kubectl apply -f ch5/catalog-vs-v2-50-50-mesh.yaml -n istioinaction
  • 호출 테스트 - v2.0에 약 52% 라우팅 되는것을 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l

2) Kubernete용 Flagger 사용한 카나리아 릴리스

Flagger를 이용하면 릴리스를 어떻게 수행할지, 언제 더 많은 사용자에게 릴리스를 개방할지, 릴리스가 문제를 일으킬 경우 언제 롤백할지 등에 관련된 파라미터를 지정할 수 있다.

Flagger는 릴리스를 수행하는 데 필요한 작절한 설정을 모두 만든다.

출처 : https://github.com/stefanprodan/gitops-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
    kubectl get gw,vs -n istioinaction
  • Flagger CRD 설치
    kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
    kubectl get crd | grep flagger
  • 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
  • secrets 확인
    kubectl get secret -n istio-system | grep flagger-token
  • secrets 상세 확인
    kubectl view-secret -n istio-system flagger-token-65shq --all
  • Flagger Canary 리소스를 이용한 배포를 위한 yaml 파일 확인

    cat ch5/flagger/catalog-release.yaml 
    apiVersion: flagger.app/v1beta1
    kind: Canary
    metadata:
      name: catalog-release
      namespace: istioinaction
    spec:
      targetRef: #1
        apiVersion: apps/v1
        kind: Deployment
        name: catalog
      progressDeadlineSeconds: 60
      # Service / VirtualService Config
      service: #2
        name: catalog
        port: 80
        targetPort: 3000
        gateways:
        - mesh
        hosts:
        - catalog
      analysis: #3
        interval: 45s
        threshold: 5
        maxWeight: 50
        stepWeight: 10
        match:
        - sourceLabels:
            app: webapp
        metrics:
        - name: request-success-rate
          thresholdRange:
            min: 99
          interval: 1m
        - name: request-duration
          thresholdRange:
            max: 500
          interval: 30s
    • spec.targetRef : Canary 대상-: Deployment → catalog
    • service : k8s service, Gateway 설정
    • analysis
      • interval : 45초 간격으로 실행
      • threshold : 5번 배포 실패시 롤백 작업
      • maxWeight : Canary로 보낼 수 있는 최대 트래픽 비율은 50%
      • stepWeight : 트래픽을 한 번 분석할 때마다 10%씩 Canary로 증가
      • match : sourceLabelsapp=webapp인 트래픽만 고려
      • metrics : Canary 배포시 모니터링 할 내용
        이름내용
        request-success-rate성공률이 99% 미만이면 Canary 롤백
        request-duration응답 시간이 500ms 이상이면 Canary 롤백
  • 반복 호출 테스트

    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
  • catalog flagger 배포
    kubectl apply -f ch5/flagger/catalog-release.yaml -n istioinaction
  • catalog flagger 배포 로그 확인
    kubectl logs -f deploy/flagger -n istio-system
  • 배포 확인
    kubectl get canary -n istioinaction -owide
  • flagger Initialized 동작 확인
    kubectl get deploy,svc,ep -n istioinaction -o wide
  • VS catalog 상세 확인
    kubectl get vs -n istioinaction catalog -o yaml
  • destinationrule 확인
    kubectl get destinationrule -n istioinaction
  • destinationrule 상세 확인
    kubectl get destinationrule -n istioinaction catalog-primary -o yaml 
    kubectl get destinationrule -n istioinaction catalog-canary -o yaml 
  • route 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
  • kiali 확인 - catalog-primary 서비스로만 100% 전달

이제 catalog v2 를 도입하고 Flagger가 어떻게 릴리스에서 이를 자동화하는지, 어떻게 메트릭에 기반해 의사결정을 내리는지 살펴보자.

https://docs.flagger.app/tutorials/istio-progressive-delivery

  • 정상 메트릭 수집을 위한 부하 테스트 실행
    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 로그 확인
    kubectl logs -f deploy/flagger -n istio-system
  • flagger 상태확인
    kubectl get canary -n istioinaction -w
  • imageUrl 출력 (v2)을 포함하는 catalog deployment v2 배포
    cat ch5/flagger/catalog-deployment-v2.yaml
    kubectl apply -f ch5/flagger/catalog-deployment-v2.yaml -n istioinaction
  • 배포 확인
    kubectl get vs -n istioinaction catalog -o yaml -w 
    • Canary 0% 배포
  • Canary 30% 배포
  • Canary 50% 배포 후 완료
  • canary CRD 이벤트 확인
    kubectl describe canary -n istioinaction catalog-release | grep Events: -A20
  • kiali 확인
  • promtheus 확인

    • flagger_canary_weight
    • flagger_canary_metric_analysis{metric="request-duration"}
    • flagger_canary_metric_analysis{metric="request-success-rate"}
  • 최종 접속 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l

3) 실습 리소스 정리

# Canary 삭제 : Flagger가 만든 service (catalog, catalog-canary, catalog-primary), destinationrule (catalog-canary, catalog-primary), deployment (catalog-primary) 를 제거함
kubectl delete canary catalog-release -n istioinaction

# catalog 삭제
kubectl delete deploy catalog -n istioinaction

# Flagger 삭제
helm uninstall flagger -n istio-system

4. 트래픽 미러링

1) 트래픽 미러링이란?

서비스를 운영하다 보면 새 버전을 배포할 때 항상 따라오는 고민이 있습니다.

"새 버전이 실제 트래픽에서 문제를 일으키지는 않을까?"

이를 위해 우리는 보통 요청 수준 라우팅(request-level routing)이나 트래픽 전환(traffic shifting) 같은 기술을 사용해 왔습니다.

예를 들어 일부 요청만 새 버전으로 보내거나, 트래픽 비율을 조절하면서 점진적으로 전환하는 방식이죠.

하지만 이 방식들 모두 실제 사용자 트래픽을 직접 건드린다는 점에서 완벽하진 않습니다.

아무리 신중하게 제어하더라도 예상치 못한 문제가 발생할 수 있고, 그 여파는 바로 사용자에게 전해질 수 있습니다.

그래서 등장한 또 다른 접근 방식이 있습니다.

바로 운영 트래픽을 복사(Mirroring) 해서 요청 경로 외부의 새로운 디플로이먼트로 보내는 것입니다.

위 그림처럼 실제 고객 요청은 여전히 기존 catalog 서비스로 정상 처리되고, 동시에 이 요청이 복제되어 테스트용 catalog-v2.0 서비스에도 전달됩니다.

즉, 진짜 트래픽으로 새 버전을 실험해보지만, 사용자 경험에는 전혀 영향을 주지 않는 것입니다.

[장점]

  • 실제 트래픽 기반으로 테스트할 수 있어 QA 환경보다 훨씬 정확한 피드백을 얻을 수 있다.
  • 실제 사용자에게 영향이 없기 때문에 실패하더라도 서비스 장애로 이어지지 않는다.
  • 사전 검증을 통해 안정성을 확보한 후에만 본격적으로 트래픽을 전환할 수 있다.

2) 실습 환경 초기화

  • catalog 디플로이먼트를 초기 상태로 돌리고, catalog-v2 를 별도의 디플로이먼트로 배포
    kubectl apply -f services/catalog/kubernetes/catalog-svc.yaml -n istioinaction
    kubectl apply -f services/catalog/kubernetes/catalog-deployment.yaml -n istioinaction
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
  • 배포 확인
    kubectl get deploy,svc,ep,gw,vs -n istioinaction -o wide
  • 반복 접속 테스트
    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
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l

3) 트래픽 미러링

  • 미러링 수행이 필요한 Virtual Service 확인

    cat ch5/catalog-vs-v2-mirror.yaml
    # 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
    • 기본 종착지 : version-v1
    • 미러링 : 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; done
  • 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
  • istio-proxy 로그 확인
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    kubectl logs -n istioinaction -l app=catalog -l version=v2 -c istio-proxy -f
  • 미러링 설정
    kubectl apply -f ch5/catalog-vs-v2-mirror.yaml -n istioinaction
  • v1.0 으로만 호출 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    
  • v1.0 로그 확인
    kubectl logs -n istioinaction -l app=catalog -l version=v1 -c catalog -f
  • v2.0 로그확인
    kubectl logs -n istioinaction -l app=catalog -l version=v2 -c catalog -f
  • kiali 확인

5. Istio의 서비스 검색을 사용하여 클러스터 외부 서비스로 라우팅

1) 설명

Istio는 기본적으로 애플리케이션이 메시 외부(예: 외부 웹사이트나 서비스)로 트래픽을 보낼 수 있도록 허용합니다.

모든 트래픽은 사이드카 프록시를 거치기 때문에, 어느 정도 라우팅 제어는 가능하지만 기본 설정은 '허용'입니다.

[왜 외부 트래픽을 막아야 할까?]

만약 메시 내 서비스나 애플리케이션이 손상된다면?

악의적인 공격자가 외부 서버와 통신하며 추가적인 침해를 시도할 수 있습니다.

이를 막기 위해 "어떤 트래픽도 메시를 떠날 수 없게" 차단하는 정책이 필요합니다.

이것은 일종의 심층 방어(Defense in Depth) 전략입니다.

외부 통신을 차단함으로써 손상된 서비스가 공격자에게 "집으로 연락"하는 것을 원천적으로 막을 수 있습니다.

[주의점]

주의할 점은, 사이드카 프록시를 우회하는 방법도 존재한다는 것입니다.

따라서 추가적으로 3계층, 4계층 네트워크 수준 보호를 병행해야 합니다.

(예: VPC 보안 그룹, 네트워크 ACL, 방화벽 등으로 IP·포트 레벨 차단)

[요약]

  • Istio는 기본적으로 외부 트래픽을 허용한다.
  • 보안을 강화하려면 외부 트래픽을 차단해 심층 방어를 구축해야 한다.
  • 사이드카 우회 가능성도 있으므로, 네트워크 레벨 추가 보호가 필요하다.

2) istio 설정을 통한 간단한 보호계층 추가

  • 현재 istiooperators meshConfig 설정 확인
    kubectl get istiooperators -n istio-system -o json
  • 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
  • myk8s-control-plane 접근
    docker exec -it myk8s-control-plane bash
  • outbound 설정 변경 : ALLOW_ANY → REGISTRY_ONLY 변경
    istioctl install --set profile=default --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
  • 컨테이너 빠져 나오기
    exit
  • 배포 확인
    docker exec -it myk8s-control-plane istioctl proxy-status
  • webapp 파드에서 외부 다운로드 실행 - 에러 발생
    kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yamlkubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
  • webapp 로그 확인 - BlackHoleCluster 차단
  • proxy-config : webapp 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn BlackHoleCluster -o json
  • 현재 istiooperators meshConfig 설정 확인
    kubectl get istiooperators -n istio-system -o json

3) ServiceEntry 설명

[Istio 내부 서비스 저장소]

Istio는 메쉬 내부에서 사용할 수 있도록 자체 서비스 저장소(internal service registry) 를 운영합니다.

이 저장소는 메쉬 내부 서비스들이 서로를 찾기 위한 공식 서비스 디스커버리 역할을 합니다.

  • 기본적으로 Istio는 컨트롤 플레인이 설치된 플랫폼(예: Kubernetes)을 참고해서 이 저장소를 자동으로 구축합니다.
  • Kubernetes에서는 Service 객체를 읽어와서 서비스 카탈로그를 생성하죠.

⇒ istiod는 k8s API(Service 리소스)를 통해 서비스/엔드포인트 정보를 동적으로 발견하고 저장합니다.

하지만 이 기본 저장소에는 외부 서비스 정보가 없습니다.

외부 서비스를 인식시키려면 별도로 등록해주어야 합니다.
이때 사용하는 것이 바로 ServiceEntry 입니다.

[외부 서비스 연결 예시: JSONPlaceholder]

가상의 온라인 포럼 서비스를 운영한다고 가정해봅시다.

외부의 jsonplaceholder.typicode.com API를 사용해서 사용자 피드백을 받고 싶다고 할 때, Istio에 이 외부 API를 등록해야 합니다.

JSONPlaceholder는 테스트용 가짜 데이터 API를 무료로 제공하는 서비스입니다.

다음과 같은 리소스를 제공합니다:

리소스설명예시 요청
/posts게시글 목록GET /posts
/comments댓글 목록GET /comments
/albums앨범 목록GET /albums
/photos사진 정보GET /photos
/todos할 일 목록GET /todos
/users사용자 정보GET /users

[ServiceEntry 리소스 등록 방법]

Istio에 외부 서비스를 등록하려면 다음과 같은 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

[주요 설정]

  • hosts: 연결할 외부 서비스 도메인 (여기서는 jsonplaceholder.typicode.com)
  • ports: 통신할 포트와 프로토콜 (HTTP/80)
  • resolution: DNS: 도메인 이름 기반으로 주소를 찾겠다는 의미
  • location: MESH_EXTERNAL: 외부에 위치한 서비스임을 명시

이렇게 하면 메쉬 내부 서비스가 이 외부 API를 향해 트래픽을 보낼 수 있게 됩니다.

4) ServiceEntry 실습

  • 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/
  • webapp 반복 접속
    for i in {1..100}; do curl http://webapp.istioinaction.io:30000/ ; done
  • kiali 확인
  • 메시 안에서 새로운 포럼 서비스 호출
    curl -s http://webapp.istioinaction.io:30000/api/users
  • ServiceEntry 생성
    cat ch5/forum-serviceentry.yaml
    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
    cat forum-2.json | grep jsonplaceholder.typicode.com
  • 도메인 질의 응답 IP로 확인된 endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction --cluster 'outbound|80||jsonplaceholder.typicode.com'
  • 메시 안에서 새로운 forum 서비스 호출
    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 접속 - forum이 추가 된것을 확인
    open http://webapp.istioinaction.io:30000/

5) 실습 후 리소스 삭제

kind delete cluster --name myk8s

6장 Resilience

1. 애플리케이션에 복원력 구축

1) 복원력 패턴의 필요성

마이크로서비스를 구축할 때 가장 최우선으로 고려해야 할 것은 복원력(Resilience) 입니다.

"장애가 발생하지 않도록 구축하면 된다"는 말은 현실을 무시한 이야기입니다.

실제로는 언제든 장애가 발생할 수 있고, 단일 장애가 전체 시스템의 중단으로 이어질 위험이 존재합니다.

[복잡한 분산 시스템, 그리고 장애의 위험]

마이크로서비스는 네트워크를 통해 서로 통신하는 수많은 작은 서비스들로 구성됩니다.

이 과정에서 장애 지점(Failure Point) 은 오히려 더 많이 생길 수 있습니다.

특히 다음과 같은 문제가 발생할 수 있습니다:

  • 한 서비스가 다른 서비스 호출 시 네트워크 지연(latency) 발생
  • 특정 엔드포인트가 불안정하게 응답
  • 오류 발생 시 잘못된 재시도로 부하 증폭
  • 한 서비스 장애가 다른 서비스로 연쇄 전파(cascading failure)

이러한 장애들은 시간이 지나면서 전체 시스템의 안정성에 큰 위협이 됩니다.

[서비스 소유자가 준비해야 할 것]

서비스 소유자(Service Owner) 는 이를 대비해,

애플리케이션과 서비스 전반에 걸쳐 복원력 패턴을 일관성 있게 채택해야 합니다.

대표적인 복원력 패턴은 다음과 같습니다:

  • Retry (재시도): 일시적 오류 발생 시 요청을 다시 보내 복구를 시도
  • Timeout (타임아웃): 응답이 지연될 경우 요청을 자동으로 취소
  • Circuit Breaker (서킷 브레이커): 장애가 반복될 경우 호출 자체를 중단해 시스템 보호
  • Fallback (대체 경로 전환): 문제가 있는 서비스 대신 다른 경로로 트래픽 라우팅

[예시: 서비스 A → 서비스 B 호출 시]

  • 서비스 B의 특정 엔드포인트가 지연된다면, 서비스 A는 다른 엔드포인트, 다른 가용 영역(Availability Zone), 심지어 다른 리전으로 트래픽을 전환할 수 있어야 합니다.
  • 서비스 B에서 오류가 발생하면, 서비스 A는 재시도를 시도하거나, 일정 시간이 지나면 요청을 중단하고 물러나야 합니다.
  • 만약 무한 재시도를 하거나 계속 부하를 주면, 서비스 B는 과부하되어 더 큰 장애를 초래할 수 있습니다.

[장애를 예상하고 설계하라]

복원력 있는 시스템을 만든다는 것은 장애를 '없애는' 것이 아니라 '예상하고 대응하는' 것을 의미합니다.

  • 요청 실패를 감지하고 자동으로 복구를 시도하거나
  • 상황에 맞게 대체 경로를 선택하거나
  • 필요한 경우 호출을 중단하고 시스템을 보호하는 설계를 해야 합니다.

2) 기존 인프라의 한계 - 언어 마다 구현 상이, 운영 부담

마이크로서비스 아키텍처에서 복원력(Resilience)을 확보하는 방법은 오랫동안 개발자들의 주요 고민거리였습니다.

서비스 메시 기술이 등장하기 전까지는, 복원력 패턴(재시도, 타임아웃, 서킷 브레이커 등)을 직접 애플리케이션 코드에 작성해야 했습니다.

하지만 이 방식에는 여러 한계가 있었습니다.

[오픈소스 프레임워크의 등장]

개발자들의 부담을 줄이기 위해, 몇몇 오픈소스 프레임워크가 등장했습니다.

  1. Twitter Finagle
  • 2011년 트위터는 자사의 복원력 프레임워크 Finagle을 오픈소스화했습니다.
  • Finagle 소개 블로그
  • 스칼라/자바 기반 애플리케이션용으로 개발되었으며, 타임아웃, 재시도, 서킷 브레이킹과 같은 다양한 RPC 복원력 패턴을 제공합니다.
  1. Netflix OSS: Hystrix와 Ribbon
  • 넷플릭스도 자체 복원력 컴포넌트를 오픈소스화했습니다.
  • Hystrix : 서킷 브레이커 기능 제공 (Hystrix 소개)
  • Ribbon : 클라이언트 측 로드밸런싱 기능 제공 (Ribbon 소개)
  • 자바 커뮤니티에서 매우 인기가 높았으며, 이후 Spring Cloud Netflix 프로젝트에 통합되어 스프링 개발자들이 쉽게 활용할 수 있게 되었습니다. (Spring Cloud Netflix)

[문제점: 언어와 프레임워크 종속성]

하지만 이런 오픈소스 복원력 라이브러리들은 몇 가지 중요한 문제를 가지고 있었습니다.

  • 대부분 JVM(Java 기반) 언어에 최적화되어 있음
  • Node.js, Go, Python과 같은 다른 언어 사용자들은
    • 유사한 기능을 가진 별도 라이브러리를 찾아야 하거나
    • 아예 직접 복원력 코드를 구현해야 했음
  • 이로 인해 시스템 내 언어/프레임워크별 복원력 구현이 일관되지 않게 됨

[운영 부담 증가]

또한, 복원력 코드가 애플리케이션 내부에 깊이 침투하는 문제가 있었습니다.

  • 네트워킹 관련 코드가 비즈니스 로직 사이사이에 흩어지게 됨
  • 결과적으로 코드 가독성과 유지보수성이 저하
  • 여러 언어와 프레임워크를 사용하는 대규모 마이크로서비스 환경에서는
    • 모든 조합을 동일하게 패치하고
    • 복원력 기능을 일관성 있게 유지하는 것이 큰 부담이 됨

3) 이스티오 서비스 프록시가 복원력 기능 제공

이스티오 서비스 프록시는 애플리케이션 옆에 위치하며 애클리케이션에 모든 I/O 트래픽을 처리합니다.

또한 애플리케이션 수준 요청과 메시지(HTTP 요청 등)를 이해하므로 프록시 안에서 복원력 기능을 구현할 수 있습니다.

이스티오의 서비스 프록시는 기본적으로 다음과 같은 복원력 패턴을 구현합니다.

  • 클라이언트 측 로드 밸런싱 Client-side load balancing
  • 지역 인식 로드 밸런싱 Locality-aware load balancing
  • 타임아웃 및 재시도 Timeouts and retries
  • 서킷 브레이킹 Circuit breaking

4) 복원력 패턴을 분산 구현

마이크로서비스 환경에서는 복원력을 고려할 때 어디에서 이를 처리할지에 대한 선택이 매우 중요합니다.

이스티오(Istio)를 사용하면, 애플리케이션 인스턴스 옆에 배치된 데이터 플레인 프록시가 복원력 패턴을 처리합니다.

따라서 별도의 중앙집중식 게이트웨이 없이도 복원력 있는 통신을 구현할 수 있습니다.

[과거의 방식: 중앙집중식 장치]

예전에는 복잡한 네트워크 문제를 해결하기 위해

하드웨어 로드 밸런서, 메시징 시스템, 엔터프라이즈 서비스 버스(ESB), API 게이트웨이 같은

중앙 집중식 미들웨어를 사용했습니다.

하지만 문제는 다음과 같았습니다:

  • 고가의 장비 의존
  • 변경 및 확장 어려움
  • 정적인 환경에 최적화되어 있음
  • 클라우드와 같은 동적이고 탄력적인 인프라에 부적합

이런 이유로, 고도로 분산된 방식으로 복원력을 구현하는 것이 필수적이 되었습니다.

[이스티오가 제공하는 접근법]

이스티오는 복원력 패턴을 서비스 코드와 분리하고, 애플리케이션 인스턴스 옆(sidecar 프록시) 에 배치하여

언어와 프레임워크에 상관없이 일관되게 관리할 수 있게 해줍니다.

이는 과거의 중앙집중식 모델과 명확히 대비되는, 현대적인 분산 클라우드 아키텍처에 최적화된 방법입니다.

2. Client-side load balancing

1) 설명

이스티오는 서비스 및 엔드포인트 디스커버리를 사용하여 위와 같이 서비스 간 통신의 클라이언트 측 프록시에 올바른 최신 정보를 제공합니다.

서비스 운영자와 개발자는 DestinationRule 리소스를 이용하여 클라이언트가 어떤 로드 벨런싱 알고리즘을 사용할지 설정할수 있다.

  • 라운드 로빈(기본값)
  • 랜덤
  • 가중치를 적용한 최소 요청

2) DestinationRule 실습

  • 실습 전 초기화
    kubectl delete gw,vs,deploy,svc,destinationrule --all -n istioinaction
  • 예제서비스 2개 배포
    cat ch6/simple-backend.yaml
    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
  • Gateway, VirtualService 배포
    cat ch6/simple-web-gateway.yaml
    kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction
  • 배포 확인
    kubectl get gw,vs -n istioinaction
  • 도메인 질의를 위한 hosts 설정
    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 -c simple-web
    kubectl stern -l app=simple-backend -n istioinaction -c simple-backend
  • simple-web route 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
  • kiali 확인
  • DestinationRule 적용 (ROUND_ROBIN)
    cat ch6/simple-backend-dr-rr.yaml
    kubectl apply -f ch6/simple-backend-dr-rr.yaml -n istioinaction
  • 적용 확인
    kubectl get dr -n istioinaction
  • DestinationRule 상세 확인
    kubectl get destinationrule simple-backend-dr -n istioinaction \
     -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'
  • 호출 테스트 진행
    curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"
  • 로그 확인
    kubectl stern -l app=simple-web -n istioinaction -c simple-web
    kubectl stern -l app=simple-backend -n istioinaction -c simple-backend
  • endpoint 상세 확인 - 기본값이라 별도로 표기 되지 않음
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json

3) Fortio 설치

현실적인 환경에서 서비스가 요청을 처리하는데 시간이 소요됩니다.
소요 시간은 여러 이유로 달라질 수 있습니다.

  • 요청 크기 Request size
  • 처리 복잡도 Processing complexity
  • 데이터베이스 사용량 Database usage
  • 시간이 걸리는 다른 서비스 호출 Calling other services that take time

또한 서비스 외적인 이유로도 응답시간에 영향을 미칠수 있습니다.

  • 예기치 못한, 모든 작업을 멈추는 stop-the-world 가비지 컬렉션 Unexpected, stop-the-world garbage collections
  • 리소스 경합 Resource contention (CPU, 네트워크 등)
  • 네트워크 혼잡 Network congestion

예제 서비스를 다시 호출하고 최초 설정한 서비스 응답시간에 따라 차이가 발생합니다.

kubectl exec -it netshoot -- time curl -s -o /dev/null http://simple-web.istioinaction

이떄 Fortio 라는 CLI 부하 생성 도구를 사용해 서비스를 실행하고 클라이언트 측 로드 밸런싱의 차이를 관찰할 예정입니다. → Link

  • mac 설치
    brew install fortio
    fortio -h
    fortio server
    open http://127.0.0.1:8080/fortio
  • Fortio가 예제 서비스 호출할수 있는지 확인
    fortio curl http://simple-web.istioinaction.io:30000

4) LB 알고리즘에 따른 지연 시간 성능 측정

[테스트 환경]

  • 60초 동안 10개의 커넥션으로 초당 1000개 요청을 보냅니다.
  • 엔드포인트 중 하나에 긴 가비지 컬렉션 이벤트 또는 기타 애플리케이션 지연 시간이 발생한 상황을 시뮬레이션합니다.
  • 로드 밸런싱 전략을 라운드 로빈, 랜덤, 최소 커넥션으로 바꿔가면서 차이점을 관찰할 예정입니다.

[배포]

  • 지연된 simple-backend-1 서비스를 배포
    cat ch6/simple-backend-delayed.yaml
    kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
  • Rollout 진행
    kubectl rollout restart deployment -n istioinaction simple-backend-1
  • simple-backend-1 배포 확인
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
  • simple-backend-2 배포 확인
    kubectl exec -it deploy/simple-backend-2 -n istioinaction -- env | grep TIMING
  • deploy 편집
    KUBE_EDITOR="vi" kubectl edit deploy/simple-backend-1 -n istioinaction
    ...
          - name: /
              value: 1000ms
    ...
    
  • 확인
    kubectl describe pod -n istioinaction -l app=simple-backend | grep TIMING_50_PERCENTILE:
  • 테스트 진행
    curl -s http://simple-web.istioinaction.io:30000 | grep duration 
  • tracing 확인
  • fortio 접근하여 다음과 같이 세팅 - Link

  • 테스트 결과

    - target 50% 0.188919
    - target 75% 1.0329
    - target 90% 1.09842
    - target 99% 1.13774
    - target 99.9% 1.14167
  • 로드 밸런싱 알고리듬을 RANDOM 으로 변경하고 다시 로드 테스트 진행
    cat ch6/simple-backend-dr-random.yaml
    kubectl apply -f ch6/simple-backend-dr-random.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
  • 동일한 환경에서 fortio 테스트 진행

    - target 50% 0.189739
    - target 75% 1.05903
    - target 90% 1.15788
    - target 99% 1.21719
    - target 99.9% 1.22312
    • 75분위수에서 응답이 1초 이상 으로 RoundRobin 과 비슷함
  • 로드 밸런싱 알고리듬을 Least connection 으로 변경하고 다시 로드 테스트 진행

    cat ch6/simple-backend-dr-least-conn.yaml
    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
  • fortio 동일하게 테스트 수행

    - target 50% 0.181418
    - target 75% 0.194569
    - target 90% 1.02382
    - target 99% 1.0772
    - target 99.9% 1.08254
    • 75분위수에서 응답이 200ms(0.19..) 이내 응답성능으로, RR, Random 보다 좋음!

3. Locality-aware load balancing

1) 설명

[서비스 메시와 서비스 토폴로지 이해]

Istio 같은 컨트롤 플레인의 핵심 역할 중 하나는 서비스 토폴로지(서비스들의 위치와 관계) 를 이해하고,

그 토폴로지가 어떻게 변화할 수 있는지 예측하는 것입니다.

서비스 메시에서 전체 토폴로지를 파악하면, 서비스와 피어 서비스 간 위치 정보를 기반으로

자동으로 라우팅과 로드 밸런싱 결정을 내릴 수 있습니다.

[Istio가 지원하는 로드 밸런싱 최적화 방식]

Istio는 다음과 같은 방법으로 로드 밸런싱을 최적화합니다.

  • 워크로드 위치 기반 가중치 부여 → 요청자가 위치한 리전/가용 영역에 있는 서비스로 우선적으로 트래픽을 보냅니다.
  • 예시
    • simple-backend 서비스가 us-west, us-east, europe-west 리전에 배포되어 있다고 가정합니다.
    • simple-webus-west 리전에 있다면, simple-backend 호출은 us-west에 배포된 인스턴스를 우선적으로 선택합니다.
  • 이점
    • 지연 시간(Latency) 감소
    • 네트워크 비용 절감
    • 성능 최적화

만약 모든 엔드포인트를 동등하게 취급한다면, 리전 간 트래픽이 오가며 지연이 발생하고 추가 비용이 발생할 수 있습니다.

2) Hands-on with locality load balancing

지역 인식 로드벨런싱이 잘 동작하는지 테스트를 진행합니다.

  • 배포 파일 확인
    vi 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 # 추가해두자!
    ...
  • 서비스 배포
    kubectl apply -f ch6/simple-service-locality.yaml -n istioinaction
  • 배포 확인 - simple-backend-1
    kubectl get deployment.apps/simple-backend-1 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
    
    kubectl get deployment.apps/simple-backend-1 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.version}{"\n"}'
  • 배포 확인 - simple-backend-2
    kubectl get deployment.apps/simple-backend-2 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
    
    kubectl get deployment.apps/simple-backend-2 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.version}{"\n"}'
  • DestinationRule 적용
    cat ch6/simple-backend-dr-outlier.yaml
    kubectl apply -f ch6/simple-backend-dr-outlier.yaml -n istioinaction
  • 배포 확인
    kubectl get dr -n istioinaction simple-backend-dr -o jsonpath='{.spec}' | jq
  • 반복 호출 확인 - backend-1 로만 호출
    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
  • 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 -o json
  • outbound 확인
    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
  • 로그 확인
    kubectl stern -l app=simple-backend -n istioinaction
  • tracing 확인
  • 호출 테스트 2 ⇒ 오동작 주입 후 확인
    cat ch6/simple-service-locality-failure.yaml
  • 배포
    kubectl apply -f ch6/simple-service-locality-failure.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
  • 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'        
  • 세부 확인
    ```bash
    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
    ```
    
    ![](https://velog.velcdn.com/images/gjrjr4545/post/69a397b7-33bd-4e27-bc1d-88e16e81b709/image.png)
    )
  • simple-backend-1 을 정상화
    kubectl apply -f ch6/simple-service-locality.yaml -n istioinaction

3) 가중치 분포로 지역 인식 LB 제어 강화

가중치에 따른 동작 방식 테스트를 진행한다.

  • 70% 가 최인접 지역, 30%가 인접지역으로 세팅 예정
  • 배포 파일 확인
    cat ch6/simple-backend-dr-outlier-locality.yaml
  • DestinationRule 적용
    kubectl apply -f ch6/simple-backend-dr-outlier-locality.yaml -n istioinaction
  • 반복 호출
    for in in {1..200}; 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'
  • kiali 확인

4. Transparent timeouts and retries

1) timeout : 지연시간

이스티오를 사용해 타임아웃 정책을 제어하는지 살펴보도록 하겠습니다.

  • 환경 재설정
    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
  • 호출 테스트 - 평균 0.2ms 소요
    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
  • 동작중 파드에 env 직접 수정
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    
    -----------------------------------
    export TIMING_50_PERCENTILE=1000ms
    exit
    -----------------------------------
  • 호춣 테스트
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
  • simple-backend 로 향하는 호출의 타임아웃을 0.5초로 지정
    cat ch6/simple-backend-vs-timeout.yaml
    kubectl apply -f ch6/simple-backend-vs-timeout.yaml -n istioinaction
  • 배포 확인
    kubectl get vs -n istioinaction
    !
  • 호출 테스트
    for in in {1..100}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
  • kiali 확인

2) 재시도

이스티오에서는 재시도가 기본적으로 활성화돼 있고, 두 번까지 재시도한다.

  • 설정 초기화
    kubectl apply -f ch6/simple-web.yaml -n istioinaction
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction
  • VirtualService 리소스 변경 → 최대 재시도 0번
    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
  • 에러 발생 시 재시도 실습 - 호출 중 75%에 HTTP 503 반환
  • 배포 파일 확인
    cat ch6/simple-backend-periodic-failure-503.yaml
  • 배포 진행
    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
    ---------------------------------------------------------------
  • 호출 테스트 진행
    for in in {1..100}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
  • kiali 확인
    ![](https://velog.velcdn.com/images/gjrjr4545/post/78b7d72b-da8b-4967-b4c4-6757ee5761ff/image.png)
  • simple-backend 로 향하는 호출에 재시도를 2회로 명시적으로 설정
    cat ch6/simple-backend-enable-retry.yaml
    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 -o json
  • 호출 테스트
    for in in {1..100}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
  • kiali 확인
  • 실패는 존재하나 호출자에게 드러나진 않는다 (재시도 정의가 되어 있어 이런 오류를 우회 하게끔 반영)

5. 서킷 브레이커 (with 이스티오)

서킷 브레이커 기능을 사용하면 부분적이거나 연쇄적인 장애를 방지할수 있습니다.

이스티오에는 서킷브레이커 라는 명시적인 설정은 존재하지 않으나, 백엔드 서비스, 특히 문제가 있는 서비스로의 부하를 제한할 수 있는 방법이 두 가지 있어 서킷 브레이커를 효과적으로 시행할 수 있습니다.

  1. 특정 서비스로의 커넥션 및 미해결 요청 개수를 얼마나 허용할지 관리하는 방법
    • 이스티오에서는 DestinationRule 의 connectionPool 설정을 사용해 서비스 호출 시 누적될 수 있는 커넥션 및 요청 개수를 제한할 수 있습니다.

  1. 로드 밸런싱 풀의 엔드포인트에 상태를 관찰해 오동작하는 엔드포인트를 잠시 퇴출시키는 방법
    • 서비스 풀의 특정 호스트에 문제가 발생하면 그 호스트로의 트래픽 전송을 건너뛸 수 있습니다.
    • 모든 호스트를 소진하면 회로는 한동안 사실상 ‘개방’됩니다.

1) 커넥션 풀 제어로 느린 서비스에 대응하기

  • tracing 샘플링을 기본 1% → 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
  • 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
  • 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=3000ms
    exit
    -----------------------------------
  • 테스트 진행
    curl -s http://simple-web.istioinaction.io:30000 | grep duration              
  • fortio 테스트 진행
    fortio load -quiet -jitter -t 30s -c 1 -qps 1 http://simple-web.istioinaction.io:30000
  • 설정 변경

    cat ch6/simple-backend-dr-conn-limit.yaml

    • maxConnections : 총 커넥션 수
    • http1MaxPendingRequests : 대기 중인 요청 (사용할 커넥션이 없어 보류 중인 요청을 얼마나 허용할지)
    • http2MaxRequests : 모든 호스트에 대한 최대 동시 요청 개수
  • 배포 진행

    kubectl apply -f ch6/simple-backend-dr-conn-limit.yaml -n istioinaction
  • 배포 확인
    kubectl get dr -n istioinaction
  • I/O 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction | egrep 'RULE|backend'
  • 설정 적용 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
  • 테스트 재실행

    fortio load -quiet -jitter -t 30s -c 1 -qps 1 --allow-initial-errors http://simple-web.istioinaction.io:30000

  • 정확한 확인이 필요(서킷 브레이크 영향인지 vs 업스트림의 장애인지 확인)

    • 기본적으로 이스티오 서비스 프록시(엔보이)에는 각 클러스터에 대한 통계가 많지만 이스티오가 통계를 잘라낸다.
    • 이는 수집 에이전트(프로메테우스 등)가 통계의 큰 카디널리티 cardinality 에 압도되지 않게 하기 위해서다.
  • sample-web 서비스에 통계 수집을 활성화

    cat ch6/simple-web-stats-incl.yaml | grep statsInclusionPrefixes 
    kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction

  • istio-proxy status 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
  • status 조회
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
     
     kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
    
  • 테스트 진행
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
  • 로그 확인
    kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
    

  • kiali 확인
    • 업스트림 서버가 느리게 응답하거나, 다운됨

    • 트래픽 급증으로 인해 큐에 쌓인 요청이 너무 많음

    • Envoy의 circuit breaker 설정 (예: max_requests, max_connections)을 초과함

  • Jaeger 확인
  • 프로메테우스 확인

    • envoy_cluster_upstream_cx_overflow - Link
    • envoy_cluster_upstream_rq_pending_overflow - Link
  • http2MaxRequests 증가
    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 
    
  • istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
  • 로그 모니터링
    kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
  • 테스트 진행 → 기존 대비 500 에러가 현저하게 줄어듬 (22 → 5)
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
  • overflow 확인 - cx_over_flow : 37 / upstream_rq_pending_overflow :6
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
  • cluster upstream 전체 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
  • kiali 확인 : simple-web 에선 5xx 에러가 발생하였지만, simple-backend에선 미발생
  • jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13 , guid:x-request-id=304fd07c-0d09-9749-8e36-c0758c7464e3

    • simple-backend 에서는 정상 301 응답을 주었고, simple-web 에서 503 (UO)발생 되었다
  • 보류 대기열 깊이 2로 변경

    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-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests           
  • istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
  • 2개의 커넥션에서 요청을 초당 1개씩 보내기 - 요청 전부 200 OK
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
  • overflow 확인 - cx_overflow : 45 /upstream_rq_pending_overflow : 0
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
  • cluster upstream 전체 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
    
  • kiali 확인
  • jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13 , guid:x-request-id=304fd07c-0d09-9749-8e36-c0758c7464e3

2) 이상값 감지로 비정상 서비스에 대응하기

이번엔 오동작 misbehaving 하는 특정 호스트를 서비스에서 제거하는 이스티오의 접근법을 다룹니다.

이스티오는 이를 위해 엔보이의 이상값 감지 가능을 사용합니다.

  • 실습 환경 초기화 - 예제에서는 이상값 감지 기능을 고립, 이스티오의 기본 재시도 메커니즘도 비활성화
    kubectl delete destinationrule --all -n istioinaction
    kubectl delete vs simple-backend-vs -n istioinaction
    
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction
    kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction
  • istio-proxy status 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
  • 호출 테스트 - 전체 200 OK
    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
  • kiali 확인
  • simple-backend-1 엔드포인트는 호출 중 75%가 HTTP 500 실패 설정 배포
    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
  • 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
  • kiali 확인
  • 이상 값 감지 설정

    이름설명
    consecutive5xxErrors잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5, 연속적인 에러 횟수
    interval이스티오 서비스 프록시가 체크하는 주기. 기본값 10초
    baseEjectionTime서비스 엔드포인트에서 제거된다면, 제거 시간은 n / 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초
    maxEjectionPercent로드 밸런싱 풀에서 제거 가능한 호스트 개수(%) , 기본값 10%
  • 배포 파일 확인

    cat ch6/simple-backend-dr-outlier-5s.yaml

  • 이상 값 감지 설정
    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
  • endpoint 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster '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
  • endpoint 모니터링 진행
    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
  • 로드 테스트 진행 - 500 에러가 극적으로 감소
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
  • 통계 확인 - 500 에러 발생 5건
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
  • 이상 감지 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep outlier
  • 5초후 다시 엔드포인트 모니터링 - 정상화
  • kiali 확인
  • jaeger 확인
  • 오류율 개선을 위한 기본 재시도 설정 추가
    cat ch6/simple-backend-vs-retry-500.yaml
    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
  • endpoint 모니터링
    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
  • 로드 테스트 진행 - 전체 200 OK
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
  • 이상 감지 확인 - 엔드포인트 이상 감지 전에 3번 실패
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep outlier
  • 통계 확인 - 재시도 retry 덕분에 결과적으로 모두 성공
    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
    
  • jaeger 확인

6. 리소스 삭제

kind delete cluster --name myk8s
/etc/hosts # 설정된 host 삭제
profile
DevOps Engineer

0개의 댓글