Kubernetes

Istio in Action 5장 - 트래픽 제어 : 세밀한 트래픽 라우팅

mokpolar 2025. 8. 28. 10:05
반응형

안녕하세요?
Istio in Action 책을 공부하면서 내용을 조금씩 정리해보려고 합니다.

5장은 트래픽 제어: 세밀한 트래픽 라우팅 입니다.

 

실습 환경 준비

  • MacOS, OrbStack으로 Container 구동
  • Kind, k8s 1.33.1
  • istioctl 1.27.0
curl -L https://istio.io/downloadIstio | sh -

istioctl install --set profile=demo -y
        |\
        | \
        |  \
        |   \
      /||    \
     / ||     \
    /  ||      \
   /   ||       \
  /    ||        \
 /     ||         \
/______||__________\
____________________
  \__       _____/
     \_____/

WARNING: Istio is being upgraded from 1.13.0 to 1.27.0.
         Running this command will overwrite it; use revisions to upgrade alongside the existing version.
         Before upgrading, you may wish to use 'istioctl x precheck' to check for upgrade warnings.
✔ Istio core installed ⛵️
✔ Istiod installed 🧠
✔ Egress gateways installed 🛫
✔ Ingress gateways installed 🛬
✔ Installation complete

 

 

5.1 새로운 코드 배포의 위험 줄이기

5.1.1 배포 vs. 릴리스

  • 배포는 운영 환경에 설치되지만 실제 운영 환경 트래픽을 받지는 않는 코드.
  • 배포가 운영 환경에 설치되면 스모크 테스트를 수행하고 검증
  • 릴리즈는 실제 트래픽을 새 배포로 가져오는 것, 그러니까 운영 환경 트래픽을 배포로 이관하는 순간.
  • 배포와 릴리즈를 구분해야 새로운 코드를 안전하게 운영 환경에 도입할 수 있다.

  • 위와 같이 트래픽을 제어해 내부 직원들에게 보낼 수 있다.
  • 실 트래픽의 대부분은 구 버젼이 받고, 신 버젼은 일부만을 받고 있는 이 방식을 "카나리한다"고 말한다.
  • 그리고 이걸 "카나리 릴리즈" 라고 부른다.
  • 새 코드가 예상, 검증했던 기능이나 동작, 성능을 전달하지 않는다면? 트래픽을 이전 버젼으로 릴리즈를 롤백할 수 있다.

 

5.2 이스티오로 요청 라우팅하기

  • 다크 런치 : 대다수의 사용자는 잘 작동한다고 알려진 버젼으로 보내고, 특정 사용자 집단만 신 버젼으로 보내기

 

5.2.2 catalog 서비스 v1 배포하기

apiVersion: v1
kind: ServiceAccount
metadata:
  name: catalog
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: catalog
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: catalog
---
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:
      serviceAccountName: catalog
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: istioinaction/catalog:latest
        imagePullPolicy: IfNotPresent
        name: catalog
        ports:
        - containerPort: 3000
          name: http
          protocol: TCP
        securityContext:
          privileged: false
k apply -f services/catalog/kubernetes/catalog.yaml
serviceaccount/catalog unchanged
service/catalog created
deployment.apps/catalog created

 

 

catalog 서비스가 동작하는지 확인

k run -i -n default --rm --restart=Never dummy --image=curlimages/curl --command -- sh -c 'curl -s http://catalog.istioinaction/items'
[
  {
    "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"
  }
]

gateway, virtualservice 배포

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

 

 

호출 테스트

curl http://192.168.97.2:31733/items -H "Host: catalog.istioinaction.io"
[
  {
    "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"
  }
]

 

5.2.3 catalog 서비스 v2 배포하기

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: catalog
    version: v2
  name: catalog-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: catalog
      version: v2
  template:
    metadata:
      labels:
        app: catalog
        version: v2
    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

 

5.2.4 모든 트래픽 catalog 서비스 v1으로 라우팅하기

  • 이스티오가 v1, v2를 식별하려면 subset을 지정하는 DestinationRule을 만들어야한다.
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

 

 

그리고 virtualservice를 v1으로만 향하도록 업데이트

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

 

 

 

10번 호출해서 v1으로만 향하는지 확인

for i in {1..10}; do curl http://192.168.97.2:31733/items -H "Host: catalog.istioinaction.io"; printf "\n\n"; done
[
  {
    "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"
  }
]

[
  {
    "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"
  }
]...

 

5.2.5 특정 요청을 v2로 라우팅하기

  • HTTP 헤더 x-istio-cohort: internal 을 포함한 트래픽은 catalog v2로 보내고 싶은 경우
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

 

 

위와 같이 수정한뒤, x-istio-cohort: internal 헤더를 추가해서 라우팅이 v2로 잘 되는 모습을 볼 수 있음

curl http://192.168.97.2:31733/items -H "Host: catalog.istioinaction.io" -H "x-istio-cohort: internal"
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
  {
    "id": 2,
    "color": "cyan",
    "department": "Clothing",
    "name": "Atlas Shirt",
    "price": "127.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
  {
    "id": 3,
    "color": "teal",
    "department": "Clothing",
    "name": "Small Metal Shoes",
    "price": "232.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
  {
    "id": 4,
    "color": "red",
    "department": "Watches",
    "name": "Red Dragon Watch",
    "price": "232.00",
    "imageUrl": "http://lorempixel.com/640/480"
  }
]%

 

5.2.6 호출 그래프 내 깊은 위치에서 라우팅

  • 이스티오의 라우팅 기능은 엔보이의 기능에서 나온다.
  • 요청별 라우팅의 경우, 애플리케이션이 주입한 헤더를 사용하거나
  • Agent와 같이 알려진 헤더 또는 쿠키 값에 의존할 수도 있다.

아래는 지금까지와 달리 메시의 모든 사이드카에 적용하는 방법

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
    - mesh <----- 이 virtualservice는 메시의 모든 사이드카에 적용된다. 
  http:
  - match:
    - headers:
        x-istio-cohort:
          exact: "internal"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1

5.3 트래픽 전환

 

 

아래의 경우 90%의 트래픽은 v1, 10%는 v2로 이동하게 된다.

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 <-----------v1에 대부분
    - destination:
        host: catalog
        subset: version-v2
      weight: 10 <-----------일부 v2로 

 

 

트래픽을 반반으로 나누고 싶다면 가중치만 업데이트하면 된다.
만약 v1, v2외 다른 버젼이 있을경우 DestinationRule에서 subset으로 선언해야 한다.

 

5.3.1 Flagger로 카나리 릴리스하기

  • 라우팅 변경을 수동으로 하는건 실수 등의 위험이 있으므로
  • Flagger를 사용해서 서비스 릴리스를 자동화할 수 있다.
  • Flagger는 서비스 상태를 판단할 때 메트릭에 의존하며
  • 성공 메트릭을 사용하려면 프로메테우스를 설치해 이스티오 데이터 플레인을 수집하게 해야 한다.
helm repo add flagger https://flagger.app
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/juyoungjung/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /Users/juyoungjung/.kube/config
"flagger" has been added to your repositories
k apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
customresourcedefinition.apiextensions.k8s.io/canaries.flagger.app created
customresourcedefinition.apiextensions.k8s.io/metrictemplates.flagger.app created
customresourcedefinition.apiextensions.k8s.io/alertproviders.flagger.app created
helm install flagger flagger/flagger --namespace=istio-system --set crd.create=false --set meshProvider=i
stio --set metricsServer=http://prometheus:9090
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/juyoungjung/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /Users/juyoungjung/.kube/config
NAME: flagger
LAST DEPLOYED: Thu Aug 28 09:42:24 2025
NAMESPACE: istio-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Flagger installed

 

 

Flagger는 아래와 같은 Canary 리소스를 사용해서 카나리 릴리스의 파라미터를 지정할 수 있다.
그리고 Flagger가 알아서 리소스를 만들어서 배포한다.

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: catalog-release
  namespace: istioinaction
spec:
  targetRef: <----------------------- 카나리 대상 디플로이먼트
    apiVersion: apps/v1
    kind: Deployment
    name: catalog
  progressDeadlineSeconds: 60
  # Service / VirtualService Config
  service: <------------------------- 서비스용 설정
    name: catalog
    port: 80
    targetPort: 3000
    gateways:
    - mesh
    hosts:
    - catalog
  analysis: <------------------------ 카나리 진행 파라미터
    interval: 45s <------ 45초마다 카나리의 각 단계를 평가
    threshold: 5  <------ 5회 초과 지정범위와 다르면 카나리를 중단하고 롤백한다
    maxWeight: 50 <------ 트래픽이 50%에 도달하면 100%로 바꾼다.
    stepWeight: 10 <----- 단계별로 10%씩 늘린다. 
    match:
    - sourceLabels:
        app: webapp
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99 <-------- 1분 동안의 성공률이 99%이상이어야 한다. 
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500 <------- p99의 요청시간은 500ms까지 허용한다.
      interval: 30s
k get canary catalog-release -w
NAME              STATUS         WEIGHT   LASTTRANSITIONTIME
catalog-release   Initializing   0        2025-08-28T00:53:05Z
catalog-release   Initialized    0        2025-08-28T00:53:57Z

 

 

Flagger가 자동 생성한 VirtualService는 아래와 같다.

k get virtualservice catalog -o yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  annotations:
    helm.toolkit.fluxcd.io/driftDetection: disabled
    kustomize.toolkit.fluxcd.io/reconcile: disabled
  creationTimestamp: "2025-08-28T00:53:57Z"
  generation: 1
  name: catalog
  namespace: istioinaction
  ownerReferences:
  - apiVersion: flagger.app/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: Canary
    name: catalog-release
    uid: 939a05a4-5492-454f-89f0-fb959d7318c2
  resourceVersion: "40620"
  uid: 4a8de726-0981-4e4f-b626-4194dba1a914
spec:
  gateways:
  - mesh
  hosts:
  - catalog
  http:
  - match:
    - sourceLabels:
        app: webapp
    route:
    - destination:
        host: catalog-primary
      weight: 100 <------------ 100으로 갈것임을 알 수 있다. 
    - destination:
        host: catalog-canary
      weight: 0
  - route:
    - destination:
        host: catalog-primary
      weight: 100
curl http://192.168.97.2:31733/api/catalog -H "Host: webapp.istioinaction.io"
[{"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"}]

 

 

이제 catalog-v2를 배포해서 카나리 진척과정을 볼 수 있다.

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
k apply -f  ch5/flagger/catalog-deployment-v2.yaml

 

 

배포하면 자동으로로 카나리가 진행된다.

k get canary catalog-release -w
NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
catalog-release   Initialized   0        2025-08-28T00:53:57Z
catalog-release   Progressing   0        2025-08-28T01:01:27Z
catalog-release   Progressing   10       2025-08-28T01:02:12Z
catalog-release   Progressing   10       2025-08-28T01:02:57Z

 

 

Flagger가 자동으로 VirtualService의 가중치를 제어한 것을 볼 수 있다.

k get vs catalog -o yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  annotations:
    helm.toolkit.fluxcd.io/driftDetection: disabled
    kustomize.toolkit.fluxcd.io/reconcile: disabled
  creationTimestamp: "2025-08-28T00:53:57Z"
  generation: 2
  name: catalog
  namespace: istioinaction
  ownerReferences:
  - apiVersion: flagger.app/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: Canary
    name: catalog-release
    uid: 939a05a4-5492-454f-89f0-fb959d7318c2
  resourceVersion: "41686"
  uid: 4a8de726-0981-4e4f-b626-4194dba1a914
spec:
  gateways:
  - mesh
  hosts:
  - catalog
  http:
  - match:
    - sourceLabels:
        app: webapp
    route:
    - destination:
        host: catalog-primary
      weight: 90
    - destination:
        host: catalog-canary
      weight: 10
  - route:
    - destination:
        host: catalog-primary
      weight: 90
반응형