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 등과 연동해 서비스 간 요청의 전체 경로와 지연 구간을 추적할 수 있다.

+ Recent posts