Istio Ambient Mesh는 사이드카 프록시 없이 인프라에 통합된 데이터플레인 방식을 제공한다. 기존 Istio는 각 파드마다 사이드카 프록시를 배치하여 트래픽을 제어했다.


Ambient Mesh에서는 노드 단위로 공유되는 ztunnel 파드를 통해 파드 간 안전한 통신을 보장한다. ztunnel은 L4(전송 계층) 보안, 인증, 원격 측정, L4 정책을 제공한다. L7(애플리케이션 계층) 트래픽 제어나 정책이 필요할 때는 Waypoint Proxy 파드를 통해 처리한다.

 

Waypoint Proxy는 특정 네임스페이스 또는 서비스 계정 단위로 배포할 수 있다.

HBONE(HTTP Based Overlay Network Encapsulation) 프로토콜을 활용해 HTTP CONNECT 방식으로 트래픽을 터널링한다. HTTP/2와 mTLS를 사용하여 암호화 및 상호 인증을 제공한다.

ztunnel은 Istiod에서 xDS Config를 받아 네트워크 구성을 동적으로 적용한다.

ztunnel과 Waypoint Proxy는 서로 역할이 분리되어 보안성과 확장성을 높인다.

Ambient Mesh는 파드 스펙 변경이나 재시작 없이 메시 기능을 적용할 수 있다. 리소스 오버헤드가 기존 사이드카 방식보다 적고, 클러스터 자원 활용 효율이 높아진다. 또한 보안 측면에서 ztunnel은 노드 단위 키만 접근하므로 공격 범위가 제한된다.

기존 사이드카 방식과 Ambient Mesh 방식을 혼용해 사용할 수 있다. Ambient Mesh는 운영 단순화, 비용 절감, 확장성 개선 등 다양한 이점을 제공한다.

 

Istio의 사이드카 방식은 애플리케이션 파드의 사양을 수정하고 트래픽을 리디렉션해야 하므로, 사이드카 설치나 업그레이드 시 파드를 재시작해야 하는 부담이 있다. 각 파드별로 프록시 리소스를 최악의 상황에 맞춰 할당해야 하므로 클러스터 전체의 리소스 활용도가 저하된다. 사이드카가 트래픽 캡처와 HTTP 처리를 담당하면서 일부 비표준 HTTP 구현 애플리케이션에서는 트래픽이 차단되는 문제가 발생한다. 이러한 제약으로 인해 덜 침입적이고 사용하기 쉬운 서비스 메시 옵션의 필요성이 대두된다.

 

 

실습 환경 구성

```bash
# 
kind create cluster --name **myk8s** --image kindest/node:**v1.32.2** --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: **control-plane**
  extraPortMappings:
  - containerPort: 30000 # Sample Application
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # kube-ops-view
    hostPort: 30005
- role: **worker**
- role: **worker**
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'apt update && apt install tree psmisc lsof ipset wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'; echo; done
~~for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'DEBIAN_FRONTEND=noninteractive **apt install termshark -y**'; echo; done~~

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=**NodePort**,service.main.ports.http.nodePort=**30005** --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://127.0.0.1:**30005**/#scale=1.5"
open "http://127.0.0.1:30005/#scale=1.3"
```
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind


# '테스트용 PC(mypc)' 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 혹은 지정 없이 배포
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps


# '웹서버(myweb1, myweb2)' 컨테이너 기동 : kind 도커 브리지를 사용
# https://hub.docker.com/r/hashicorp/http-echo
docker run -d --rm --name myweb1 --network kind --ip 172.18.0.101 hashicorp/http-echo -listen=:80 -text="myweb1 server"
docker run -d --rm --name myweb2 --network kind --ip 172.18.0.102 hashicorp/http-echo -listen=:80 -text="myweb2 server"
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name myweb1 --network kind hashicorp/http-echo -listen=:80 -text="myweb1 server"
docker run -d --rm --name myweb2 --network kind hashicorp/http-echo -listen=:80 -text="myweb2 server"
docker ps


# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/myweb2 172.18.0.102
/myweb1 172.18.0.101
/mypc 172.18.0.100
/myk8s-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc curl myweb1
docker exec -it mypc curl myweb2

docker exec -it mypc curl 172.18.0.101
docker exec -it mypc curl 172.18.0.102
# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml


# 확인
kubectl get crd
kubectl get pod -n metallb-system


# IPAddressPool, L2Advertisement 설정
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.201-172.18.255.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A
# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.26.0
echo 'export ISTIOV=1.26.0' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false
client version: 1.26.0

# ambient 프로파일 컨트롤 플레인 배포
istioctl install --set profile=ambient --set meshConfig.accessLogFile=/dev/stdout --skip-confirmation
istioctl install --set profile=ambient --set meshConfig.enableTracing=true -y

# Install the Kubernetes Gateway API CRDs
kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
kubectl apply -f istio-$ISTIOV/samples/addons # nodePort 충돌 시 한번 더 입력

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get crd | grep istio.io
kubectl get crd | grep -v istio | grep -v metallb
kubectl get crd  | grep gateways
gateways.gateway.networking.k8s.io          2025-06-01T04:54:23Z
gateways.networking.istio.io                2025-06-01T04:53:51Z

kubectl api-resources | grep Gateway
gatewayclasses                      gc           gateway.networking.k8s.io/v1        false        GatewayClass
gateways                            gtw          gateway.networking.k8s.io/v1        true         Gateway
gateways                            gw           networking.istio.io/v1              true         Gateway

kubectl describe cm -n istio-system istio
...
Data
====
mesh:
----
accessLogFile: /dev/stdout
defaultConfig:
  discoveryAddress: istiod.istio-system.svc:15012
defaultProviders:
  metrics:
  - prometheus
enablePrometheusMerge: true
...

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                           CLUSTER        CDS         LDS         EDS         RDS         ECDS        ISTIOD                     VERSION
ztunnel-25hpt.istio-system     Kubernetes     IGNORED     IGNORED     IGNORED     IGNORED     IGNORED     istiod-86b6b7ff7-x4787     1.26.0
ztunnel-4r4d4.istio-system     Kubernetes     IGNORED     IGNORED     IGNORED     IGNORED     IGNORED     istiod-86b6b7ff7-x4787     1.26.0
ztunnel-9rzzt.istio-system     Kubernetes     IGNORED     IGNORED     IGNORED     IGNORED     IGNORED     istiod-86b6b7ff7-x4787     1.26.0

docker exec -it myk8s-control-plane istioctl ztunnel-config workload
docker exec -it myk8s-control-plane istioctl ztunnel-config service

# iptables 규칙 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'iptables-save'; echo; done


# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

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

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004
# ztunnel 파드 확인 : 파드 이름 변수 지정
kubectl get pod -n istio-system -l app=ztunnel -owide
kubectl get pod -n istio-system -l app=ztunnel
ZPOD1NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[0].metadata.name}")
ZPOD2NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[1].metadata.name}")
ZPOD3NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[2].metadata.name}")
echo $ZPOD1NAME $ZPOD2NAME $ZPOD3NAME

#
kubectl describe pod -n istio-system -l app=ztunnel
...
Containers:
  istio-proxy:
    Container ID:  containerd://d81ca867bfd0c505f062ea181a070a8ab313df3591e599a22706a7f4f537ffc5
    Image:         docker.io/istio/ztunnel:1.26.0-distroless
    Image ID:      docker.io/istio/ztunnel@sha256:d711b5891822f4061c0849b886b4786f96b1728055333cbe42a99d0aeff36dbe
    Port:          15020/TCP
    ...
    Requests:
      cpu:      200m
      memory:   512Mi
    Readiness:  http-get http://:15021/healthz/ready delay=0s timeout=1s period=10s #success=1 #failure=3
    Environment:
      CA_ADDRESS:                        istiod.istio-system.svc:15012
      XDS_ADDRESS:                       istiod.istio-system.svc:15012
      RUST_LOG:                          info
      RUST_BACKTRACE:                    1
      ISTIO_META_CLUSTER_ID:             Kubernetes
      INPOD_ENABLED:                     true
      TERMINATION_GRACE_PERIOD_SECONDS:  30
      POD_NAME:                          ztunnel-9rzzt (v1:metadata.name)
      POD_NAMESPACE:                     istio-system (v1:metadata.namespace)
      NODE_NAME:                          (v1:spec.nodeName)
      INSTANCE_IP:                        (v1:status.podIP)
      SERVICE_ACCOUNT:                    (v1:spec.serviceAccountName)
      ISTIO_META_ENABLE_HBONE:           true
    Mounts:
      /tmp from tmp (rw)
      /var/run/secrets/istio from istiod-ca-cert (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-42r88 (ro)
      /var/run/secrets/tokens from istio-token (rw)
      /var/run/ztunnel from cni-ztunnel-sock-dir (rw)
    ...
Volumes:
  istio-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  43200
  istiod-ca-cert:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      istio-ca-root-cert
    Optional:  false
  cni-ztunnel-sock-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/ztunnel
    HostPathType:  DirectoryOrCreate
...


#
kubectl krew install pexec
kubectl pexec $ZPOD1NAME -it -T -n istio-system -- bash
-------------------------------------------------------
whoami

ip -c addr
ifconfig

iptables -t mangle -S
iptables -t nat -S

ss -tnlp
ss -tnp

ss -xnp
Netid       State        Recv-Q        Send-Q                               Local Address:Port                Peer Address:Port        Process                                                                              
u_str       ESTAB        0             0                                                * 44981                          * 44982        users:(("ztunnel",pid=1,fd=13),("ztunnel",pid=1,fd=8),("ztunnel",pid=1,fd=6))       
u_seq       ESTAB        0             0                    /var/run/ztunnel/ztunnel.sock 47646                          * 46988                                                                                            
u_str       ESTAB        0             0                                                * 44982                          * 44981        users:(("ztunnel",pid=1,fd=7))                                                      
u_seq       ESTAB        0             0                                                * 46988                          * 47646        users:(("ztunnel",pid=1,fd=19)) 

ls -l  /var/run/ztunnel
total 0
srwxr-xr-x    1 root     root             0 Jun  1 04:54 ztunnel.sock

# 메트릭 정보 확인
curl -s http://localhost:15020/metrics

# Viewing Istiod state for ztunnel xDS resources
curl -s http://localhost:15000/config_dump

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

# 아래 ztunnel 파드도 확인해보자
kubectl pexec $ZPOD2NAME -it -T -n istio-system -- bash
kubectl pexec $ZPOD3NAME -it -T -n istio-system -- bash


# 노드에서 기본 정보 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /var/run/ztunnel'; echo; done


# ztunnel 데몬셋 파드 로그 확인
kubectl logs -n istio-system -l app=ztunnel -f
...
# ztunnel 파드 확인 : 파드 이름 변수 지정
kubectl get pod -n istio-system -l app=ztunnel -owide
kubectl get pod -n istio-system -l app=ztunnel
ZPOD1NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[0].metadata.name}")
ZPOD2NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[1].metadata.name}")
ZPOD3NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[2].metadata.name}")
echo $ZPOD1NAME $ZPOD2NAME $ZPOD3NAME

#
kubectl describe pod -n istio-system -l app=ztunnel
...
Containers:
  istio-proxy:
    Container ID:  containerd://d81ca867bfd0c505f062ea181a070a8ab313df3591e599a22706a7f4f537ffc5
    Image:         docker.io/istio/ztunnel:1.26.0-distroless
    Image ID:      docker.io/istio/ztunnel@sha256:d711b5891822f4061c0849b886b4786f96b1728055333cbe42a99d0aeff36dbe
    Port:          15020/TCP
    ...
    Requests:
      cpu:      200m
      memory:   512Mi
    Readiness:  http-get http://:15021/healthz/ready delay=0s timeout=1s period=10s #success=1 #failure=3
    Environment:
      CA_ADDRESS:                        istiod.istio-system.svc:15012
      XDS_ADDRESS:                       istiod.istio-system.svc:15012
      RUST_LOG:                          info
      RUST_BACKTRACE:                    1
      ISTIO_META_CLUSTER_ID:             Kubernetes
      INPOD_ENABLED:                     true
      TERMINATION_GRACE_PERIOD_SECONDS:  30
      POD_NAME:                          ztunnel-9rzzt (v1:metadata.name)
      POD_NAMESPACE:                     istio-system (v1:metadata.namespace)
      NODE_NAME:                          (v1:spec.nodeName)
      INSTANCE_IP:                        (v1:status.podIP)
      SERVICE_ACCOUNT:                    (v1:spec.serviceAccountName)
      ISTIO_META_ENABLE_HBONE:           true
    Mounts:
      /tmp from tmp (rw)
      /var/run/secrets/istio from istiod-ca-cert (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-42r88 (ro)
      /var/run/secrets/tokens from istio-token (rw)
      /var/run/ztunnel from cni-ztunnel-sock-dir (rw)
    ...
Volumes:
  istio-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  43200
  istiod-ca-cert:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      istio-ca-root-cert
    Optional:  false
  cni-ztunnel-sock-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/ztunnel
    HostPathType:  DirectoryOrCreate
...


#
kubectl krew install pexec
kubectl pexec $ZPOD1NAME -it -T -n istio-system -- bash
-------------------------------------------------------
whoami

ip -c addr
ifconfig

iptables -t mangle -S
iptables -t nat -S

ss -tnlp
ss -tnp

ss -xnp
Netid       State        Recv-Q        Send-Q                               Local Address:Port                Peer Address:Port        Process                                                                              
u_str       ESTAB        0             0                                                * 44981                          * 44982        users:(("ztunnel",pid=1,fd=13),("ztunnel",pid=1,fd=8),("ztunnel",pid=1,fd=6))       
u_seq       ESTAB        0             0                    /var/run/ztunnel/ztunnel.sock 47646                          * 46988                                                                                            
u_str       ESTAB        0             0                                                * 44982                          * 44981        users:(("ztunnel",pid=1,fd=7))                                                      
u_seq       ESTAB        0             0                                                * 46988                          * 47646        users:(("ztunnel",pid=1,fd=19)) 

ls -l  /var/run/ztunnel
total 0
srwxr-xr-x    1 root     root             0 Jun  1 04:54 ztunnel.sock

# 메트릭 정보 확인
curl -s http://localhost:15020/metrics

# Viewing Istiod state for ztunnel xDS resources
curl -s http://localhost:15000/config_dump

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

# 아래 ztunnel 파드도 확인해보자
kubectl pexec $ZPOD2NAME -it -T -n istio-system -- bash
kubectl pexec $ZPOD3NAME -it -T -n istio-system -- bash


# 노드에서 기본 정보 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /var/run/ztunnel'; echo; done


# ztunnel 데몬셋 파드 로그 확인
kubectl logs -n istio-system -l app=ztunnel -f
...

 

 

#
docker exec -it myk8s-control-plane ls -l istio-1.26.0
total 40
-rw-r--r--  1 root root 11357 May  7 11:05 LICENSE
-rw-r--r--  1 root root  6927 May  7 11:05 README.md
drwxr-x---  2 root root  4096 May  7 11:05 bin
-rw-r-----  1 root root   983 May  7 11:05 manifest.yaml
drwxr-xr-x  4 root root  4096 May  7 11:05 manifests
drwxr-xr-x 27 root root  4096 May  7 11:05 samples
drwxr-xr-x  3 root root  4096 May  7 11:05 tools

# Deploy the Bookinfo sample application:
docker exec -it myk8s-control-plane kubectl apply -f istio-1.26.0/samples/bookinfo/platform/kube/bookinfo.yaml

# 확인
kubectl get deploy,pod,svc,ep
docker exec -it myk8s-control-plane istioctl ztunnel-config service
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
docker exec -it myk8s-control-plane istioctl proxy-status


# 통신 확인 : ratings 에서 productpage 페이지
kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"


# 요청 테스트용 파드 생성 : netshoot
kubectl create sa netshoot

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot
spec:
  serviceAccountName: netshoot
  nodeName: myk8s-control-plane
  containers:
  - name: netshoot
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

# 요청 확인
kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title

# 반복 요청
while true; do kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done

 

#
docker exec -it myk8s-control-plane cat istio-1.26.0/samples/bookinfo/gateway-api/bookinfo-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  gatewayClassName: istio
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: bookinfo
spec:
  parentRefs:
  - name: bookinfo-gateway
  rules:
  - matches:
    - path:
        type: Exact
        value: /productpage
    - path:
        type: PathPrefix
        value: /static
    - path:
        type: Exact
        value: /login
    - path:
        type: Exact
        value: /logout
    - path:
        type: PathPrefix
        value: /api/v1/products
    backendRefs:
    - name: productpage
      port: 9080

docker exec -it myk8s-control-plane kubectl apply -f istio-1.26.0/samples/bookinfo/gateway-api/bookinfo-gateway.yaml

# 확인
kubectl get gateway
NAME               CLASS   ADDRESS          PROGRAMMED   AGE
bookinfo-gateway   istio   172.18.255.201   True         75s

kubectl get HTTPRoute
NAME       HOSTNAMES   AGE
bookinfo               101s

kubectl get svc,ep bookinfo-gateway-istio
NAME                             TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                        AGE
service/bookinfo-gateway-istio   LoadBalancer   10.200.1.122   172.18.255.201   15021:30870/TCP,80:31570/TCP   2m37s

NAME                               ENDPOINTS                      AGE
endpoints/bookinfo-gateway-istio   10.10.1.6:15021,10.10.1.6:80   2m37s

kubectl get pod -l gateway.istio.io/managed=istio.io-gateway-controller -owide
NAME                                      READY   STATUS    RESTARTS   AGE     IP          NODE            NOMINATED NODE   READINESS GATES
bookinfo-gateway-istio-6cbd9bcd49-fwqqp   1/1     Running   0          3m45s   10.10.1.6   myk8s-worker2   <none>           <none>

# 접속 확인
docker ps

kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl $GWLB/productpage -v
docker exec -it mypc curl $GWLB/productpage -I

# 반복 요청 : 아래 mypc 컨테이너에서 반복 요청 계속 해두기!
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done


# 자신의 로컬 PC에서 접속 시도
kubectl patch svc bookinfo-gateway-istio -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 80, "nodePort": 30000}]}}'
kubectl get svc bookinfo-gateway-istio

open "http://127.0.0.1:30000/productpage"

# 반복 요청
while true; do curl -s http://127.0.0.1:30000/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done

 

Adding your application to ambient- Docs

  • The namespace or pod has the label istio.io/dataplane-mode=ambient
  • The pod does not have the opt-out label istio.io/dataplane-mode=none
# 디폴트 네임스페이서 모든 파드들에 ambient mesh 통신 적용 설정
# You can enable all pods in a given namespace to be part of the ambient mesh by simply labeling the namespace:
kubectl label namespace default istio.io/dataplane-mode=ambient

# 파드 정보 확인 : 사이트카가 없다! , 파드 수명 주기에 영향도 없다! -> mTLS 암호 통신 제공, L4 텔레메트리(메트릭) 제공
docker exec -it myk8s-control-plane istioctl proxy-status
kubectl get pod

#
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
NAMESPACE          POD NAME                                    ADDRESS    NODE                WAYPOINT PROTOCOL
default            details-v1-766844796b-nfsh8                 10.10.2.16 myk8s-worker        None     HBONE
default            netshoot                                    10.10.0.7  myk8s-control-plane None     HBONE
default            productpage-v1-54bb874995-xkq54             10.10.2.20 myk8s-worker        None     HBONE
...

docker exec -it myk8s-control-plane istioctl ztunnel-config workload --address 10.10.2.20
NAMESPACE POD NAME                        ADDRESS    NODE         WAYPOINT PROTOCOL
default   productpage-v1-54bb874995-xkq54 10.10.2.20 myk8s-worker None     HBONE

docker exec -it myk8s-control-plane istioctl ztunnel-config workload --address 10.10.2.20 -o json
[
    {
        "uid": "Kubernetes//Pod/default/productpage-v1-54bb874995-xkq54",
        "workloadIps": [
            "10.10.2.20"
        ],
        "protocol": "HBONE",
        "name": "productpage-v1-54bb874995-xkq54",
        "namespace": "default",
        "serviceAccount": "bookinfo-productpage",
        "workloadName": "productpage-v1",
        "workloadType": "pod",
        "canonicalName": "productpage",
        "canonicalRevision": "v1",
        "clusterId": "Kubernetes",
        "trustDomain": "cluster.local",
        "locality": {},
        "node": "myk8s-worker",
        "status": "Healthy",
...


#
PPOD=$(kubectl get pod -l app=productpage -o jsonpath='{.items[0].metadata.name}')

kubectl pexec $PPOD -it -T -- bash
-------------------------------------------------------
iptables-save
iptables -t mangle -S
iptables -t nat -S
ss -tnlp
ss -tnp
ss -xnp
ls -l  /var/run/ztunnel

# 메트릭 정보 확인
curl -s http://localhost:15020/metrics | grep '^[^#]'
...

# Viewing Istiod state for ztunnel xDS resources
curl -s http://localhost:15000/config_dump

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


# 노드에서 ipset 확인 : 파드들의 ip를 멤버로 관리 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node ipset list; echo; done
...
Members:
10.10.2.15 comment "57009539-36bb-42e0-bdac-4c2356fabbd3"
10.10.2.19 comment "64f46320-b85d-4e10-ab97-718d4e282116"
10.10.2.17 comment "b5ff9b4c-722f-48ef-a789-a95485fe9fa8"
10.10.2.18 comment "c6b368f7-6b6f-4d1d-8dbe-4ee85d7c9c22"
10.10.2.16 comment "cd1b1016-5570-4492-ba3a-4299790029d9"
10.10.2.20 comment "2b97238b-3b37-4a13-87a2-70755cb225e6"

# istio-cni-node 로그 확인
kubectl -n istio-system logs -l k8s-app=istio-cni-node -f
...

# ztunnel 파드 로그 모니터링 : IN/OUT 트래픽 정보
kubectl -n istio-system logs -l app=ztunnel -f | egrep "inbound|outbound"
2025-06-01T06:37:49.162266Z     info    access  connection complete     src.addr=10.10.2.20:48392 src.workload="productpage-v1-54bb874995-xkq54" src.namespace="default" src.identity="spiffe://cluster.local/ns/default/sa/bookinfo-productpage" dst.addr=10.10.2.18:15008 dst.hbone_addr=10.10.2.18:9080 dst.service="reviews.default.svc.cluster.local" dst.workload="reviews-v2-556d6457d-4r8n8" dst.namespace="default" dst.identity="spiffe://cluster.local/ns/default/sa/bookinfo-reviews" direction="inbound" bytes_sent=602 bytes_recv=192 duration="31ms"
2025-06-01T06:37:49.162347Z     info    access  connection complete     src.addr=10.10.2.20:53412 src.workload="productpage-v1-54bb874995-xkq54" src.namespace="default" src.identity="spiffe://cluster.local/ns/default/sa/bookinfo-productpage" dst.addr=10.10.2.18:15008 dst.hbone_addr=10.10.2.18:9080 dst.service="reviews.default.svc.cluster.local" dst.workload="reviews-v2-556d6457d-4r8n8" dst.namespace="default" dst.identity="spiffe://cluster.local/ns/default/sa/bookinfo-reviews" direction="outbound" bytes_sent=192 bytes_recv=602 duration="31ms"


# ztunnel 파드 확인 : 파드 이름 변수 지정
kubectl get pod -n istio-system -l app=ztunnel -owide
kubectl get pod -n istio-system -l app=ztunnel
ZPOD1NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[0].metadata.name}")
ZPOD2NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[1].metadata.name}")
ZPOD3NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[2].metadata.name}")
echo $ZPOD1NAME $ZPOD2NAME $ZPOD3NAME

#
kubectl pexec $ZPOD1NAME -it -T -n istio-system -- bash
-------------------------------------------------------
iptables -t mangle -S
iptables -t nat -S
ss -tnlp
ss -tnp
ss -xnp
ls -l  /var/run/ztunnel

# 메트릭 정보 확인
curl -s http://localhost:15020/metrics | grep '^[^#]'
...

# Viewing Istiod state for ztunnel xDS resources
curl -s http://localhost:15000/config_dump

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

# netshoot 파드만 ambient mode 에서 제외해보자
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
kubectl label pod netshoot istio.io/dataplane-mode=none
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
NAMESPACE          POD NAME                                    ADDRESS    NODE                WAYPOINT PROTOCOL
default            netshoot                                    10.10.0.7  myk8s-control-plane None     TCP

default NS는 HBONE으로 동작하는것을 알 수 있다.

 

PS.

기존 사이드카 구성에서 ambient mode로 변경하려면... 어떡해야할까..

https://ambientmesh.io/docs/setup/sidecar-migration/

 

Migrating to ambient mesh from Istio in sidecar mode

The Solo ambient mesh migration tool provides a prescriptive path for migrating from Istio’s sidecar mode to ambient mode. This migration is zero-downtime when used with the free Solo builds of Istio, but can also translate Kubernetes manifests for users

ambientmesh.io

 

 

ztunnel-config

# A group of commands used to update or retrieve Ztunnel configuration from a Ztunnel instance.
docker exec -it myk8s-control-plane istioctl ztunnel-config
  all         Retrieves all configuration for the specified Ztunnel pod.
  certificate Retrieves certificate for the specified Ztunnel pod.
  connections Retrieves connections for the specified Ztunnel pod.
  log         Retrieves logging levels of the Ztunnel instance in the specified pod.
  policy      Retrieves policies for the specified Ztunnel pod.
  service     Retrieves services for the specified Ztunnel pod.
  workload    Retrieves workload configuration for the specified Ztunnel pod.
...

docker exec -it myk8s-control-plane istioctl ztunnel-config service
NAMESPACE      SERVICE NAME            SERVICE VIP  WAYPOINT ENDPOINTS
default        bookinfo-gateway-istio  10.200.1.122 None     1/1
default        details                 10.200.1.202 None     1/1
default        kubernetes              10.200.1.1   None     1/1
default        productpage             10.200.1.207 None     1/1
default        ratings                 10.200.1.129 None     1/1
default        reviews                 10.200.1.251 None     3/3
...

docker exec -it myk8s-control-plane istioctl ztunnel-config service --service-namespace default --node myk8s-worker
docker exec -it myk8s-control-plane istioctl ztunnel-config service --service-namespace default --node myk8s-worker2
docker exec -it myk8s-control-plane istioctl ztunnel-config service --service-namespace default --node myk8s-worker2 -o json
...
    {
        "name": "productpage",
        "namespace": "default",
        "hostname": "productpage.default.svc.cluster.local",
        "vips": [
            "/10.200.1.207"
        ],
        "ports": {
            "9080": 9080
        },
        "endpoints": {
            "Kubernetes//Pod/default/productpage-v1-54bb874995-xkq54": {
                "workloadUid": "Kubernetes//Pod/default/productpage-v1-54bb874995-xkq54",
                "service": "",
                "port": {
                    "9080": 9080
                }
            }
        },
        "ipFamilies": "IPv4"
    },
...

docker exec -it myk8s-control-plane istioctl ztunnel-config workload
docker exec -it myk8s-control-plane istioctl ztunnel-config workload --workload-namespace default
docker exec -it myk8s-control-plane istioctl ztunnel-config workload --workload-namespace default --node myk8s-worker2
docker exec -it myk8s-control-plane istioctl ztunnel-config workload --workload-namespace default --node myk8s-worker -o json
...
    {
        "uid": "Kubernetes//Pod/default/productpage-v1-54bb874995-xkq54",
        "workloadIps": [
            "10.10.2.20"
        ],
        "protocol": "HBONE",
        "name": "productpage-v1-54bb874995-xkq54",
        "namespace": "default",
        "serviceAccount": "bookinfo-productpage",
        "workloadName": "productpage-v1",
        "workloadType": "pod",
        "canonicalName": "productpage",
        "canonicalRevision": "v1",
        "clusterId": "Kubernetes",
        "trustDomain": "cluster.local",
        "locality": {},
        "node": "myk8s-worker",
        "status": "Healthy",
        "hostname": "",
        "capacity": 1,
        "applicationTunnel": {
            "protocol": ""
        }
    },
...

docker exec -it myk8s-control-plane istioctl ztunnel-config certificate --node myk8s-worker
CERTIFICATE NAME                                              TYPE     STATUS        VALID CERT     SERIAL NUMBER                        NOT AFTER                NOT BEFORE
spiffe://cluster.local/ns/default/sa/bookinfo-details         Leaf     Available     true           6399f0ac1d1f508088c731791930a03a     2025-06-02T06:21:46Z     2025-06-01T06:19:46Z
spiffe://cluster.local/ns/default/sa/bookinfo-details         Root     Available     true           8a432683a288fb4b61d5775dcf47019d     2035-05-30T04:53:58Z     2025-06-01T04:53:58Z
spiffe://cluster.local/ns/default/sa/bookinfo-productpage     Leaf     Available     true           f727d33914e23029b3c889c67efe12e1     2025-06-02T06:21:46Z     2025-06-01T06:19:46Z
spiffe://cluster.local/ns/default/sa/bookinfo-productpage     Root     Available     true           8a432683a288fb4b61d5775dcf47019d     2035-05-30T04:53:58Z     2025-06-01T04:53:58Z
spiffe://cluster.local/ns/default/sa/bookinfo-ratings         Leaf     Available     true           d1af8176f5047f620d7795a4775869ad     2025-06-02T06:21:46Z     2025-06-01T06:19:46Z
spiffe://cluster.local/ns/default/sa/bookinfo-ratings         Root     Available     true           8a432683a288fb4b61d5775dcf47019d     2035-05-30T04:53:58Z     2025-06-01T04:53:58Z
spiffe://cluster.local/ns/default/sa/bookinfo-reviews         Leaf     Available     true           af013ce3f7dca5dc1bead36455155d65     2025-06-02T06:21:46Z     2025-06-01T06:19:46Z
spiffe://cluster.local/ns/default/sa/bookinfo-reviews         Root     Available     true           8a432683a288fb4b61d5775dcf47019d     2035-05-30T04:53:58Z     2025-06-01T04:53:58Z

docker exec -it myk8s-control-plane istioctl ztunnel-config certificate --node myk8s-worker -o json
...


docker exec -it myk8s-control-plane istioctl ztunnel-config connections --node myk8s-worker
WORKLOAD                                DIRECTION LOCAL                                        REMOTE                                                REMOTE TARGET                          PROTOCOL
productpage-v1-54bb874995-xkq54.default Inbound   productpage-v1-54bb874995-xkq54.default:9080 bookinfo-gateway-istio-6cbd9bcd49-fwqqp.default:33052                                        HBONE
ratings-v1-5dc79b6bcd-64kcm.default     Inbound   ratings-v1-5dc79b6bcd-64kcm.default:9080     reviews-v2-556d6457d-4r8n8.default:56440              ratings.default.svc.cluster.local      HBONE
reviews-v2-556d6457d-4r8n8.default      Outbound  reviews-v2-556d6457d-4r8n8.default:41722     ratings-v1-5dc79b6bcd-64kcm.default:15008             ratings.default.svc.cluster.local:9080 HBONE

docker exec -it myk8s-control-plane istioctl ztunnel-config connections --node myk8s-worker --raw 
WORKLOAD                                DIRECTION LOCAL            REMOTE           REMOTE TARGET                     PROTOCOL
productpage-v1-54bb874995-xkq54.default Inbound   10.10.2.20:9080  10.10.1.6:33064                                    HBONE
productpage-v1-54bb874995-xkq54.default Inbound   10.10.2.20:9080  10.10.1.6:33052                                    HBONE
ratings-v1-5dc79b6bcd-64kcm.default     Inbound   10.10.2.15:9080  10.10.2.18:56440 ratings.default.svc.cluster.local HBONE
ratings-v1-5dc79b6bcd-64kcm.default     Inbound   10.10.2.15:9080  10.10.2.19:56306 ratings.default.svc.cluster.local HBONE
reviews-v2-556d6457d-4r8n8.default      Outbound  10.10.2.18:45530 10.10.2.15:15008 10.200.1.129:9080                 HBONE
reviews-v3-564544b4d6-nmf92.default     Outbound  10.10.2.19:56168 10.10.2.15:15008 10.200.1.129:9080                 HBONE

docker exec -it myk8s-control-plane istioctl ztunnel-config connections --node myk8s-worker -o json
...
   {
        "state": "Up",
        "connections": {
            "inbound": [
                {
                    "src": "10.10.2.18:56440",
                    "originalDst": "ratings.default.svc.cluster.local",
                    "actualDst": "10.10.2.15:9080",
                    "protocol": "HBONE"
                },
                {
                    "src": "10.10.2.19:56306",
                    "originalDst": "ratings.default.svc.cluster.local",
                    "actualDst": "10.10.2.15:9080",
                    "protocol": "HBONE"
                }
            ],
            "outbound": []
        },
        "info": {
            "name": "ratings-v1-5dc79b6bcd-64kcm",
            "namespace": "default",
            "trustDomain": "",
            "serviceAccount": "bookinfo-ratings"
        }
    },
...

#
docker exec -it myk8s-control-plane istioctl ztunnel-config policy   
NAMESPACE POLICY NAME ACTION SCOPE

#
docker exec -it myk8s-control-plane istioctl ztunnel-config log   
ztunnel-25hpt.istio-system:
current log level is hickory_server::server::server_future=off,info
...

 

 

Secure Application Access : L4 Authorization Policy - Docs

Istio의 앰비언트 모드에서는 ztunnel을 통해 L4 보안 정책을 지원하며 호환 CNI 플러그인 기반 쿠버네티스 네트워크 정책과의 병행 사용이 가능하고, ztunnel과 웨이포인트 프록시의 계층화 구조를 통해 워크로드 단위로 L7 처리 여부를 선택적으로 적용할 수 있으며 정책 시행 지점이 다중화되는 특성이 있다.

# netshoot 파드만 ambient mode 다시 참여
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
kubectl label pod netshoot istio.io/dataplane-mode=ambient --overwrite
docker exec -it myk8s-control-plane istioctl ztunnel-config workload


# L4 Authorization Policy 신규 생성
# Explicitly allow the netshoot and gateway service accounts to call the productpage service:
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: productpage-viewer
  namespace: default
spec:
  selector:
    matchLabels:
      app: productpage
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        - cluster.local/ns/default/sa/netshoot
EOF

# L4 Authorization Policy 생성 확인
kubectl get authorizationpolicy
NAME                 AGE
productpage-viewer   8s

# ztunnel 파드 로그 모니터링
kubectl logs ds/ztunnel -n istio-system -f | grep -E RBAC

# L4 Authorization Policy 동작 확인
## 차단 확인!
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done

## 허용 확인!
kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title
while true; do kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done


# L4 Authorization Policy 업데이트
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: productpage-viewer
  namespace: default
spec:
  selector:
    matchLabels:
      app: productpage
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        - cluster.local/ns/default/sa/netshoot
        - cluster.local/ns/default/sa/bookinfo-gateway-istio
EOF

kubectl logs ds/ztunnel -n istio-system -f | grep -E RBAC


# 허용 확인!
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done

 

netshoot POD에서 실행된 요청은 정상 접근되는것을 알 수 있다.

Configure waypoint proxies - Docs

Istio 앰비언트 모드는 **ztunnel(L4)**과 **웨이포인트 프록시(L7)**의 계층적 구조로 운영되며, ztunnel은 노드 단위 mTLS 암호화 및 L4 트래픽 관리를 담당하고 웨이포인트는 선택적 L7 기능(HTTP 라우팅/정책/메트릭)을 처리한다. 웨이포인트 프록시는 네임스페이스 단위로 배포되며 여러 워크로드에서 공유 가능해 사이드카 대비 90% 이상의 리소스 절감 효과를 제공한다. L7 기능 필요 시에만 웨이포인트를 활성화할 수 있어 점진적 도입이 가능하며, HTTP 기반 트래픽 관리/보안 정책/관측성이 필요한 경우에 한해 배포딘다. 정책 시행 지점이 목적지 측 웨이포인트로 이동해 중앙 집중식 정책 관리가 가능하며, HBONE 프로토콜을 통해 ztunnel-웨이포인트 간 트래픽을 안전하게 전달한다. 앰비언트 모드 전환 시 기존 사이드카 구성과의 혼용 운영이 가능하며, CNI 플러그인과의 호환성을 통해 쿠버네티스 네트워크 정책과의 병행 사용이 지원된다.

# istioctl can generate a Kubernetes Gateway resource for a waypoint proxy. 
# For example, to generate a waypoint proxy named waypoint for the default namespace that can process traffic for services in the namespace:
kubectl describe pod bookinfo-gateway-istio-6cbd9bcd49-6cphf | grep 'Service Account'
Service Account:  bookinfo-gateway-istio

# Generate a waypoint configuration as YAML
docker exec -it myk8s-control-plane istioctl waypoint generate -h

# --for string        Specify the traffic type [all none service workload] for the waypoint
istioctl waypoint generate --for service -n default
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  labels:
    istio.io/waypoint-for: service
  name: waypoint
  namespace: default
spec:
  gatewayClassName: istio-waypoint
  listeners:
  - name: mesh
    port: 15008
    protocol: HBONE

#
docker exec -it myk8s-control-plane istioctl waypoint apply -n default
✅ waypoint default/waypoint applied

kubectl get gateway 
kubectl get gateway waypoint -o yaml
...

#
kubectl get pod -l service.istio.io/canonical-name=waypoint -owide
NAME                      READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
waypoint-66b59898-p7v5x   1/1     Running   0          2m15s   10.10.1.21   myk8s-worker   <none>           <none>

#
docker exec -it myk8s-control-plane istioctl waypoint list  
NAME         REVISION     PROGRAMMED
waypoint     default      True

docker exec -it myk8s-control-plane istioctl waypoint status
NAMESPACE     NAME         STATUS     TYPE           REASON         MESSAGE
default       waypoint     True       Programmed     Programmed     Resource programmed, assigned to service(s) waypoint.default.svc.cluster.local:15008

docker exec -it myk8s-control-plane istioctl proxy-status   
NAME                                                CLUSTER        CDS               LDS               EDS               RDS               ECDS        ISTIOD                     VERSION
bookinfo-gateway-istio-6cbd9bcd49-6cphf.default     Kubernetes     SYNCED (5m5s)     SYNCED (5m5s)     SYNCED (5m4s)     SYNCED (5m5s)     IGNORED     istiod-86b6b7ff7-gmtdw     1.26.0
waypoint-66b59898-p7v5x.default                     Kubernetes     SYNCED (5m4s)     SYNCED (5m4s)     IGNORED           IGNORED           IGNORED     istiod-86b6b7ff7-gmtdw     1.26.0
ztunnel-52d22.istio-system                          Kubernetes     IGNORED           IGNORED           IGNORED           IGNORED           IGNORED     istiod-86b6b7ff7-gmtdw     1.26.0
ztunnel-ltckp.istio-system                          Kubernetes     IGNORED           IGNORED           IGNORED           IGNORED           IGNORED     istiod-86b6b7ff7-gmtdw     1.26.0
ztunnel-mg4mn.istio-system                          Kubernetes     IGNORED           IGNORED           IGNORED           IGNORED           IGNORED     istiod-86b6b7ff7-gmtdw     1.26.0

docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/waypoint                              
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                        NOT AFTER                NOT BEFORE
default           Cert Chain     ACTIVE     true           4346296f183e559a876c0410cb154f50     2025-06-02T10:11:45Z     2025-06-01T10:09:45Z
ROOTCA            CA             ACTIVE     true           aa779aa04b241aedaa8579c0bc5c3b5f     2035-05-30T09:19:54Z     2025-06-01T09:19:54Z

#
kubectl pexec waypoint-66b59898-dlrj4 -it -T -- bash
----------------------------------------------
ip -c a

curl -s http://localhost:15020/stats/prometheus

ss -tnlp
State           Recv-Q          Send-Q                   Local Address:Port                    Peer Address:Port         Process                                 
LISTEN          0               4096                         127.0.0.1:15000                        0.0.0.0:*             users:(("envoy",pid=18,fd=18))         
LISTEN          0               4096                           0.0.0.0:15008                        0.0.0.0:*             users:(("envoy",pid=18,fd=35))         
LISTEN          0               4096                           0.0.0.0:15008                        0.0.0.0:*             users:(("envoy",pid=18,fd=34))         
LISTEN          0               4096                           0.0.0.0:15021                        0.0.0.0:*             users:(("envoy",pid=18,fd=23))         
LISTEN          0               4096                           0.0.0.0:15021                        0.0.0.0:*             users:(("envoy",pid=18,fd=22))         
LISTEN          0               4096                           0.0.0.0:15090                        0.0.0.0:*             users:(("envoy",pid=18,fd=21))         
LISTEN          0               4096                           0.0.0.0:15090                        0.0.0.0:*             users:(("envoy",pid=18,fd=20))         
LISTEN          0               4096                                 *:15020                              *:*             users:(("pilot-agent",pid=1,fd=11))  

ss -tnp

ss -xnlp
ss -xnp

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

 

 

 

 

이로써 9주간의 istio 스터디가 끝났다.

가시다 님 및 운영진님들 고생하셨고 좋은 스터디 제공해주셔서 감사합니다.

 

 

13.4 DNS 프록시 이해하기 Demystifying the DNS proxy

가상머신에서 클러스터 서비스 호스트네임을 해석하려면 DNS 프록시의 동작 방식을 반드시 이해해야 한다.

  1. 클라이언트 DNS 쿼리 생성
    • 애플리케이션(예: webapp.istioinaction)이 호스트네임 해석을 요청한다.
  2. 운영체제 hosts 파일 확인
    • /etc/hosts에 정적 매핑이 없는 경우, 기본 DNS 해석기(예: systemd-resolved)로 전달된다.
  3. Iptables 리다이렉트
    • istio-agent가 설정한 iptables 규칙이 DNS 쿼리를 127.0.0.53:53 대신 **DNS 프록시(15053 포트)**로 강제 전송한다.
  4. DNS 프록시 내부 검색
    • NDS API로 동기화된 메시 내 서비스(쿠버네티스 서비스, ServiceEntry) 정보를 기반으로 호스트네임을 해석한다.
    • 클러스터 서비스인 경우, 해당 VIP 또는 엔드포인트 IP를 반환한다.
  5. 외부 DNS 폴백
    • 메시 내 서비스가 아닌 경우, resolv.conf의 네임서버(예: 8.8.8.8)로 쿼리를 전달한다.

운영상 주의사항

  • resolv.conf 설정:
    DNS 프록시 주소(127.0.0.1:15053)를 첫 번째 네임서버로 반드시 지정해야 한다.
  • 성능 이점:
    클러스터 서비스 해석 시 CoreDNS 부하 감소레이턴시 개선 효과를 제공한다.
  • 보안 강화:
    외부 DNS 서버 노출을 최소화해 DNS 스푸핑 공격 위험을 줄인다.

iptables 규칙이 DNS 쿼리를 systemd-resolverd(127.0.0.53) 대신 로컬 DNS 프록시(127.0.0.1:15053)로 반드시 리다이렉트하도록 설정한다.

# [forum-vm]
# Iptables 규칙 확인 : proxyConfig.proxyMetadata ISTIO_META_DNS_CAPTURE="true" 설정 시 아래 규칙 추가됨
iptables-save | grep 'to-ports 15053'
-A OUTPUT -d 127.0.0.53/32 -p udp -m udp --dport 53 -j REDIRECT --to-ports 15053
-A ISTIO_OUTPUT -d 127.0.0.53/32 -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 15053

 

# tcp, udp 를 127.0.0.1 에 port 15053 에서 이스티오 에이전트(pilot-agent)가 DNS 프록시 처리 확인
netstat -ltunp | egrep 'PID|15053'
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:15053         0.0.0.0:*               LISTEN      3195/pilot-agent
udp        0      0 127.0.0.1:15053         0.0.0.0:*                           3195/pilot-agent

# 해당 주소로 DNS 쿼리해보자
dig +short @localhost -p 15053 webapp.istioinaction
10.10.200.48

dig +short @localhost -p 15053 catalog.istioinaction
dig +short @localhost -p 15053 forum.forum-services


# (옵션)
dig +short @localhost -p 15053 www.daum.net
daum-4vdtymgd.kgslb.com.
121.53.105.193


# (옵션) coredns 쿼리 로그 확인
KUBE_EDITOR="nano" kubectl edit cm -n kube-system coredns
apiVersion: v1
data:
  Corefile: |
    .:53 {
        log
        errors
        health
...

# 설정 반영되는데 다소 시간 소요
kubectl logs -n kube-system -l k8s-app=kube-dns -f

DNS 쿼리는 iptables 규칙에 의해 15053 포트의 Istio DNS 프록시로 반드시 리다이렉트된다. DNS 프록시는 istiod로부터 동기화된 메시 내 서비스 정보를 활용해 **FQDN(예: webapp.istioinaction)**을 즉시 해석한다. 외부 호스트네임은 resolv.conf에 정의된 업스트림 DNS로 자동 전달된다.

 

 

13.4.2 DNS 프록시가 인식하는 호스트네임은 무엇인가? Which hostnames is the DNS proxy aware of?

DNS 프록시는 istiod의 NDS API를 통해 클러스터 서비스의 FQDN 변형을 자동 생성하여 해석을 제공한다.

#
istioctl proxy-status | awk '{print $1}'
catalog-77fdb4997c-f8qj4.istioinaction
istio-eastwestgateway-86f6cb4699-4xfsn.istio-system
istio-ingressgateway-7b7ccd6454-pv8zp.istio-system
forum-vm.forum-services
webapp-684c568c59-vrj97.istioinaction

# NDS 설정을 가져올 때 proxyID 파라미터에 이름을 사용한다.
kubectl -n istio-system exec deploy/istiod -- curl -Ls "localhost:8080/debug/ndsz?proxyID=forum-vm.forum-services" | jq
{
  "resource": {
    "@type": "type.googleapis.com/istio.networking.nds.v1.NameTable",
    "table": {
      "catalog.istioinaction.svc.cluster.local": {
        "ips": [
          "10.10.200.138"
        ],
        "registry": "Kubernetes",
        "shortname": "catalog",
        "namespace": "istioinaction"
      },
      "forum.forum-services.svc.cluster.local": {
        "ips": [
          "10.10.200.72"
        ],
        "registry": "Kubernetes",
        "shortname": "forum",
        "namespace": "forum-services"
      },
      "webapp.istioinaction.svc.cluster.local": {
        "ips": [
          "10.10.200.48"
        ],
        "registry": "Kubernetes",
        "shortname": "webapp",
        "namespace": "istioinaction"
      },
...

 

  • webapp.istioinaction.svc.cluster.local과 같은 풀 네임 외에 webapp.istioinaction, webapp.istioinaction.svc 등 짧은 형식도 반드시 해석 가능하도록 변형을 생성한다.
  • 이는 쿠버네티스의 DNS 네임 컨벤션을 기반으로 자동화된다.
    • 동작 원리
      • istio-agent가 NDS 설정을 수신할 때 모든 가능한 FQDN 변형을 매핑 테이블에 추가한다.
      • 애플리케이션이 짧은 호스트명(예: webapp.istioinaction)을 사용해도 DNS 프록시가 정확한 클러스터 IP로 해석한다.

이를 통해 Istio는 다양한 네이밍 형식을 지원하며, 애플리케이션 수정 없이 메시 내 서비스 검색을 단순화한다.

 

핵심은 다음과 같다.

  • DNS 프록시는 istiod가 알고 있는 서비스들로 설정된다.
  • istio-agent는 호스트네임의 더 짧은 변형들을 생성한다 (쿠버네티스 내의 경험과 일치시키기 위함이다)
  • 이런 DNS 프록시 내의 레코드는 클러스터 내 서비스 호스트네임을 해석하는 데 사용된다.
  • 클러스터가 아닌 호스트네임(퍼블릭 도메인 같은)쿼리는 머신에서 처음 설정한 네임서버로 넘어간다.

 

 

13.5 에이전트 동작 커스터마이징하기 Customizing the agent’s behavior

DNS 프록시의 디버그 로깅 및 인증서 수명 조정을 위해 /var/lib/istio/envoy/sidecar.env 파일을 수정해야 하며, 변경 후 /var/log/istio/istio.log 확인과 /etc/certs/cert-chain.pem 갱신 여부를 검증해야 한다.

#
cat /var/lib/istio/envoy/sidecar.env
grep "^[^#]" /var/lib/istio/envoy/sidecar.env # 주석 처리

#
echo 'ISTIO_AGENT_FLAGS="--log_output_level=dns:debug"' >> /var/lib/istio/envoy/sidecar.env
echo 'SECRET_TTL="12h0m0s"' >> /var/lib/istio/envoy/sidecar.env
grep "^[^#]" /var/lib/istio/envoy/sidecar.env # 주석 처리
ISTIO_AGENT_FLAGS="--log_output_level=dns:debug"
SECRET_TTL="12h0m0s"

# 변경 사항 적용
systemctl restart istio


# www.daum.net 도메인은 질의 처리를 하지 못해서, 로컬에 resolver DNS 서버를 통해 질의 로그 확인.
tail -f /var/log/istio/istio.log
2025-05-25T07:23:03.660035Z	debug	dns	response for hostname "www.daum.net." not found in dns proxy, querying upstream
2025-05-25T07:23:03.662845Z	debug	dns	upstream response for hostname "www.daum.net." : ;; opcode: QUERY, status: NOERROR, id: 30399
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version 0; flags: ; udp: 65494

;; QUESTION SECTION:
;www.daum.net.	IN	 A

;; ANSWER SECTION:
www.daum.net.	77	IN	CNAME	daum-4vdtymgd.kgslb.com.
daum-4vdtymgd.kgslb.com.	5	IN	A	121.53.105.193

#
dig +short @localhost -p 15053 www.daum.net

 

13.6 메시에서 WorkloadEntry 제거하기 Removing a WorkloadEntry from the mesh

aws에서 forum VM을 강제로 삭제(terminated)한다.

 

 

 

지금까지는 이스티오 서비스 메시를 컨테이너 및 쿠버네티스 관점에서 다뤘다. 그러나 실제 워크로드는 자주 가상머신이나 물리 머신에서 실행된다. 엔터프라이즈는 규제 준수나 전문 지식 부족 등 다양한 이유로 워크로드를 온프레미스에서 실행해야 할 수 있다. 이번 장에서는 사이드카 프록시를 설치하고 설정함으로써 어떤 워크로드든 메시의 일부로 삼을 수 있는 방법을 보여준다. 이 접근법은 레거시 워크로드를 복원력 있고 안전하며 고가용성적인 방식으로 메시로 통합하길 원하는 엔터프라이즈에게 흥미로운 기능을 제공한다.

 

 

실습환경 구성

구성 : VPC 1개, EC2 인스턴스 1대 (Ubuntu 22.04 LTS, t3.xlarge - vCPU 4 , Mem 16) , forum-vm 1대는 t3.small

AWSTemplateFormatVersion: '2010-09-09'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "<<<<< Deploy EC2 >>>>>"
        Parameters:
          - KeyName
          - SgIngressSshCidr
          - MyInstanceType
          - LatestAmiId

      - Label:
          default: "<<<<< Region AZ >>>>>"
        Parameters:
          - TargetRegion
          - AvailabilityZone1
          - AvailabilityZone2

      - Label:
          default: "<<<<< VPC Subnet >>>>>"
        Parameters:
          - VpcBlock
          - PublicSubnet1Block
          - PublicSubnet2Block

Parameters:
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instances.
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  SgIngressSshCidr:
    Description: The IP address range that can be used to communicate to the EC2 instances.
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  MyInstanceType:
    Description: Enter EC2 Type(Spec) Ex) t2.micro.
    Type: String
    Default: t3.xlarge
  LatestAmiId:
    Description: (DO NOT CHANGE)
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: '/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id'
    AllowedValues:
      - /aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id

  TargetRegion:
    Type: String
    Default: ap-northeast-2
  AvailabilityZone1:
    Type: String
    Default: ap-northeast-2a
  AvailabilityZone2:
    Type: String
    Default: ap-northeast-2c

  VpcBlock:
    Type: String
    Default: 192.168.0.0/16
  PublicSubnet1Block:
    Type: String
    Default: 192.168.10.0/24
  PublicSubnet2Block:
    Type: String
    Default: 192.168.20.0/24

Resources:
# VPC
  IstioVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcBlock
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: Istio-VPC

# PublicSubnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PublicSubnet1Block
      VpcId: !Ref IstioVPC
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Istio-PublicSubnet1
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PublicSubnet2Block
      VpcId: !Ref IstioVPC
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Istio-PublicSubnet2

  InternetGateway:
    Type: AWS::EC2::InternetGateway
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref IstioVPC

  PublicSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref IstioVPC
      Tags:
        - Key: Name
          Value: Istio-PublicSubnetRouteTable
  PublicSubnetRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnetRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicSubnetRouteTable
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicSubnetRouteTable

# EC2 Hosts
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Kans EC2 Security Group
      VpcId: !Ref IstioVPC
      Tags:
        - Key: Name
          Value: Istio-SG
      SecurityGroupIngress:
      - IpProtocol: '-1'
        CidrIp: !Ref SgIngressSshCidr
      - IpProtocol: '-1'
        CidrIp: !Ref VpcBlock
      - IpProtocol: '-1'
        CidrIp: 10.10.200.0/24
      - IpProtocol: '-1'
        CidrIp: 172.16.0.0/16
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 8080
        ToPort: 8080
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 30000
        ToPort: 30000
        CidrIp: 0.0.0.0/0

  EC21:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref MyInstanceType
      ImageId: !Ref LatestAmiId
      KeyName: !Ref KeyName
      Tags:
        - Key: Name
          Value: k3s-s
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref PublicSubnet1
          GroupSet:
          - !Ref EC2SG
          AssociatePublicIpAddress: true
          PrivateIpAddress: 192.168.10.10
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp3
            VolumeSize: 30
            DeleteOnTermination: true
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            hostnamectl --static set-hostname k3s-s

            # Config convenience
            echo 'alias vi=vim' >> /etc/profile
            echo "sudo su -" >> /home/ubuntu/.bashrc
            ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

            # Disable ufw & apparmor 
            systemctl stop ufw && systemctl disable ufw
            systemctl stop apparmor && systemctl disable apparmor

            # Install packages
            apt update && apt-get install bridge-utils net-tools conntrack ngrep jq tree unzip kubecolor -y

            # local dns - hosts file
            echo "192.168.10.10 k3s-s" >> /etc/hosts

            # Install k3s-server
            curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.28.15+k3s1 INSTALL_K3S_EXEC=" --disable=traefik"  sh -s - server --token istiotoken --cluster-cidr "172.16.0.0/16" --service-cidr "10.10.200.0/24" --write-kubeconfig-mode 644 

            # Change kubeconfig
            echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> /etc/profile

            # Install Helm
            curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

            # Alias kubectl to k
            echo 'alias kc=kubecolor' >> /etc/profile
            echo 'alias k=kubectl' >> /etc/profile
            echo 'complete -o default -F __start_kubectl k' >> /etc/profile

            # kubectl Source the completion
            source <(kubectl completion bash)
            echo 'source <(kubectl completion bash)' >> /etc/profile
            
            # Install Kubectx & Kubens
            git clone https://github.com/ahmetb/kubectx /opt/kubectx
            ln -s /opt/kubectx/kubens /usr/local/bin/kubens
            ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx

            # Install Kubeps & Setting PS1
            git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
            cat <<"EOT" >> ~/.bash_profile
            source /root/kube-ps1/kube-ps1.sh
            KUBE_PS1_SYMBOL_ENABLE=true
            function get_cluster_short() {
              echo "$1" | cut -d . -f1
            }
            KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
            KUBE_PS1_SUFFIX=') '
            PS1='$(kube_ps1)'$PS1
            EOT


  EC24:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.small
      ImageId: !Ref LatestAmiId
      KeyName: !Ref KeyName
      Tags:
        - Key: Name
          Value: forum-vm
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref PublicSubnet1
          GroupSet:
          - !Ref EC2SG
          AssociatePublicIpAddress: true
          PrivateIpAddress: 192.168.10.200
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp3
            VolumeSize: 30
            DeleteOnTermination: true
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            hostnamectl --static set-hostname forum-vm

            # Config convenience
            echo 'alias vi=vim' >> /etc/profile
            echo "sudo su -" >> /home/ubuntu/.bashrc
            ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

            # Disable ufw & apparmor 
            systemctl stop ufw && systemctl disable ufw
            systemctl stop apparmor && systemctl disable apparmor

            # Install packages
            apt update && apt-get install net-tools ngrep jq tree unzip apache2 -y

            # local dns - hosts file
            echo "192.168.10.200  forum-vm" >> /etc/hosts


Outputs:
  Serverhost:
    Value: !GetAtt EC21.PublicIp

 

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/istio-8w.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file kans-7w.yaml --stack-name mylab --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region ap-northeast-2
예시) aws cloudformation deploy --template-file istio-8w.yaml --stack-name mylab --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

## Tip. 인스턴스 타입 변경 : MyInstanceType=t3.xlarge (vCPU 4, Mem 16)
예시) aws cloudformation deploy --template-file istio-8w.yaml --stack-name mylab --parameter-overrides MyInstanceType=t3.xlarge KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2

# [모니터링] CloudFormation 스택 상태 : 생성 완료 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

# 배포된 aws ec2 유동 공인 IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text
k3s-s   43.202.60.44    running
testpc  15.165.15.104   running

# EC2 SSH 접속 : 바로 접속하지 말고, 3~5분 정도 후에 접속 할 것
ssh -i ~/.ssh/kp-gasida.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
...
(⎈|default:N/A) root@k3s-s:~# <- kubeps 가 나오지 않을 경우 ssh logout 후 다시 ssh 접속 할 것!

 

k3s-s , forum-vm 각각 접속 후 확인

aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text
k3s-s   3.201.48.21    running
testpc  15.63.11.32   running

#k3s-s 접속 후 확인 : ssh -i <> ubuntu@3.201.48.21
kc get node -owide
NAME     STATUS   ROLES                  AGE     VERSION         INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
k3s-s    Ready    control-plane,master   2m12s   v1.28.15+k3s1   192.168.10.10    <none>        Ubuntu 22.04.5 LTS   6.8.0-1029-aws   containerd://1.7.22-k3s1.28

hostnamectl 
...
 Hardware Vendor: Amazon EC2
  Hardware Model: t3.xlarge


# (옵션) krew 설치
wget -O /root/krew-linux_amd64.tar.gz https://github.com/kubernetes-sigs/krew/releases/download/v0.4.4/krew-linux_amd64.tar.gz
tar zxvf /root/krew-linux_amd64.tar.gz
/root/krew-linux_amd64 install krew
export PATH="$PATH:/root/.krew/bin"
echo 'export PATH="$PATH:/root/.krew/bin:/root/go/bin"' >> /etc/profile
kubectl krew install get-all neat view-secret rolesum pexec


# (옵션) termshark 설치
apt install termshark -y # 대화형창에서 yes 입력 => DEBIAN_FRONTEND=noninteractive 
tshark -D
termshark -v




#forum-vm 접속 후 확인 : ssh -i <> ubuntu@15.63.11.32
ip -br -c addr
ip -c a

hostnamectl 
...
 Hardware Vendor: Amazon EC2
  Hardware Model: t3.small
  
# (옵션) termshark 설치
apt install termshark -y # 대화형창에서 yes 입력
tshark -D
termshark -v

 

 

13.1 이스티오의 가상머신 지원

이스티오의 가상머신 지원은 초기부터 가능했으나 제어 평면 외부의 복잡한 설정이 필요했고, 1.9.0 버전에서 WorkloadGroup, WorkloadEntry 리소스 도입 및 DNS 해석 기능 강화를 통해 베타 단계로 진화했다. 가상머신 통합을 위해 istioctl로 간소화된 사이드카 프록시 설치, 고가용성 보장, 메시 내 서비스 DNS 해석 등 3가지 핵심 기능이 구현됐다. 

 

가상머신을 이스티오 메시에 통합하려면 사이드카 프록시를 설치해야 하며, 프록시가 istiod와 연결되도록 설정해야 한다. 또한 ID 토큰을 부여해 메시 인증을 반드시 완료해야 한다. 쿠버네티스에서는 웹훅/istioctl로 자동화되지만, 가상머신은 수동 구성이 필수적이다.

단일 네트워크 환경에서는 VM과 쿠버네티스 클러스터가 동일 L3 네트워크를 공유하므로 직접 통신이 가능하며, WorkloadGroup 템플릿을 통해 자동 등록(WorkloadEntry 생성)이 가능하다. 멀티 네트워크 환경에서는 이스트-웨스트 게이트웨이를 통해 네트워크를 연결해야 하며, 모든 트래픽(제어 플레인/데이터 플레인)이 게이트웨이를 경유하도록 설정이 필요하다.

이스티오 1.9 버전부터 WorkloadGroup, WorkloadEntry 리소스와 DNS 프록시 기능이 도입되어 가상머신 통합 프로세스가 단순화됐다.

 

가상머신에 ID를 프로비저닝하려면 쿠버네티스 서비스 어카운트 토큰을 수동으로 생성 및 전달해야 한다. 쿠버네티스 파드는 자동 토큰 주입이 가능하지만, 가상머신은 istio-agent가 토큰을 사용해 istiod에 인증하는 과정을 반드시 수동으로 구성해야 한다.

단점으로는 멀티 클라우드 환경에서 토큰 생성/전달 프로세스의 반복적 작업 부담이 발생하며, 플랫폼 할당 ID (예: AWS IAM 역할)를 활용한 자동화 솔루션이 현재 개발 중이다. 이스티오 커뮤니티는 클라우드 프로바이더의 신뢰 체계를 활용해 VM 인증을 단순화하는 방안을 제시하고 있다.

현재 예제에서는 실습 편의를 위해 쿠버네티스를 신뢰 원천으로 사용하며, 토큰 전달을 수동으로 진행한다. 향후 플랫폼 기반 ID 통합이 완료되면 운영 효율성이 크게 개선될 전망이다.

 

 

가상머신의 고가용성 달성을 위해 WorkloadGroupWorkloadEntry 리소스를 반드시 활용해야 한다.

  1. WorkloadGroup은 쿠버네티스의 디플로이먼트와 유사하게 포트 설정, 레이블, 서비스 어카운트, 상태 프로브 방법 등 공통 속성을 정의한다.
  2. WorkloadEntry는 개별 가상머신 인스턴스를 나타내며, 인스턴스 상태주소 같은 고유 속성을 관리한다. 쿠버네티스의 파드와 동일한 역할을 수행한다.
  3. 수동으로 WorkloadEntry를 생성할 수 있지만, **워크로드 자동 등록(auto-registration)**을 통해 신규 VM이 메시에 자동 통합되도록 구성하는 것이 권장된다. 이를 통해 인스턴스 확장/축소 시 가용성을 유지할 수 있다.

이스티오는 쿠버네티스의 고가용성 메커니즘을 차용해 VM 워크로드의 운영 효율성을 높인다.

워크로드 자동 등록을 구현하려면 WorkloadGroup을 사전에 정의해야 하며, 가상머신이 ID 토큰으로 인증해 WorkloadEntry를 자동 생성하는 과정이 필수적이다.

  1. 트래픽 라우팅
    쿠버네티스 서비스나 ServiceEntry가 레이블 셀렉터를 통해 WorkloadEntry를 백엔드로 선택해야 한다. 이를 통해 VM과 파드를 동일한 서비스 엔드포인트로 통합 관리할 수 있다.
  2. 마이그레이션 전략
    레거시 VM과 현대화 파드를 병렬 운영하면서 트래픽 전환 기능(예: 카나리아 배포)을 적용해야 한다. 오류 발생 시 VM으로 트래픽을 즉시 되돌리는 롤백 메커니즘이 반드시 필요하다.
  3. 운영 효율성
    워크로드 상태에 따라 자동으로 인스턴스를 확장/축소할 수 있어 고가용성을 보장한다. 클라이언트 측 변경 없이 인프라 변경이 가능하다는 장점이 있다.

 

서비스 메시에서 readiness 프로브는 반드시 이스티오가 관리하여 워크로드의 트래픽 처리 준비 상태를 확인해야 한다. liveness 프로브는 플랫폼(예: 쿠버네티스, AWS/Azure/GCP)의 책임이며, 애플리케이션 건강 상태를 모니터링하고 장애 시 인스턴스 자동 복구를 수행해야 한다.

  • Readiness 프로브: 이스티오가 트래픽 수신 가능 여부를 검사해 메시 내 통합을 제어한다.
  • Liveness 프로브: 클라우드 플랫폼별 자동 복구 기능(예: AWS Auto Scaling, Azure VM 복구)을 반드시 활용해야 한다.

따라서 가상머신 운영 시 플랫폼의 헬스 체크 및 복구 메커니즘을 필수적으로 구성해야 한다.

가장 인기 있는 세 클라우드 프로바이더의 liveness 검사 및 자동 복구

이스티오의 가상머신 Readiness 프로브 구현 방식

가상머신의 readiness 프로브WorkloadGroup에 정의된 설정에 따라 istio-agent가 주기적으로 애플리케이션 상태를 검사해야 한다. 검사 결과는 istiod에 보고되며, 정상 상태일 때만 트래픽을 수신하도록 데이터 플레인을 구성한다.

핵심 메커니즘

  1. 상태 보고 및 라우팅 제어
  • istio-agent는 애플리케이션 헬스 체크 결과를 istiod에 전달해야 한다.
  • 비정상 상태 감지 시 해당 VM 엔드포인트를 데이터 플레인에서 즉시 제거해야 한다.
  1. 프로브 구성 권장 사항
  • Readiness 프로브:
    • 공격적 설정 적용이 필수적이다(예: 5초 주기 체크, 2회 연속 실패 시 제외).
    • 오류 발생 시 트래픽 유입을 즉시 차단해야 한다.
  • Liveness 프로브:
    • 클라우드 플랫폼(AWS/Azure/GCP)의 자동 복구 기능을 반드시 활용해야 한다.
    • 보수적 설정 적용(예: 30초 주기 체크, 3회 연속 실패 후 인스턴스 재생성).

운영 주의사항

  • 유예 기간 고려:
    인스턴스 종료 전 진행 중인 요청 완료를 위해 grace period를 설정해야 한다.
  • 실패 순서 관리:
    Readiness 프로브가 Liveness 프로브보다 먼저 실패하도록 설계해 불필요한 인스턴스 재생성을 방지해야 한다.

이스티오는 쿠버네티스의 헬스 체크 메커니즘을 확장해 VM 환경에 적용하며, 운영자 반드시 인프라 계층과의 연동을 검증해야 한다.

 

가상머신이 메시 내 서비스의 DNS를 해석하려면 로컬 DNS 프록시를 반드시 활용해야 한다. 쿠버네티스 외부 VM은 클러스터 내부 DNS에 접근할 수 없어 istio-agent에 내장된 DNS 프록시로 트래픽을 리다이렉트해야 한다.

  1. DNS 쿼리 처리 메커니즘
  • 애플리케이션의 DNS 요청은 iptables 규칙을 통해 DNS 프록시로 전달해야 한다.
  • DNS 프록시는 istiod가 제공하는 메시 서비스 정보(쿠버네티스 서비스, ServiceEntry)로 동적으로 업데이트되어야 한다.
  1. NDS(Name Discovery Service) API
  • 서비스 추가/변경 시 NDS API를 통해 DNS 프록시에 실시간으로 변경 사항을 반영해야 한다.
  • 이를 통해 외부 DNS 서버(external-dns) 의존성을 제거하고 통합 관리가 가능하다.
  1. 장점
  • 멀티 네트워크 환경에서도 DNS 해석이 자동화되어 VM-파드 간 원활한 통신을 보장해야 한다.
  • DNS 프록시는 TCP/UDP 트래픽 캡처 기능과 결합되어 애플리케이션 수정 없이 트래픽 제어가 가능하다.

이스티오 1.8 이상에서는 DNS 프록시가 기본 구성 요소로 포함되며, 운영자는 VM의 /etc/resolv.conf 설정을 DNS 프록시(127.0.0.1:15053)로 변경해야 한다.

 

Istio DNS Proxying은 DNS 라운드트립을 반드시 제거해야 응답 속도 개선과 CoreDNS 부하 감소를 동시에 달성할 수 있다.

  1. 트래픽 처리 방식
  • 메시 내 Kubernetes 서비스, Pod, ServiceEntry 정보를 미리 동기화해 애플리케이션 변경 없이 DNS 해석을 수행해야 한다.
  • 외부 서비스(예: AWS RDS)는 ServiceEntry 등록을 통해 VIP(Class E 대역)를 자동 할당해야 하며, 이를 통해 포트 충돌 없이 리스너를 구성해야 한다.
  1. 운영 효율성
  • DNS 서버 변경 없이 새 도메인 추가가 가능하도록 ServiceEntry에 도메인/IP 정보를 반드시 등록해야 한다.
  • 외부 TCP 서비스 통합 시 VIP 자동 할당 기능을 활용해 복수 엔드포인트 관리 효율성을 높여야 한다.

이 방식은 애플리케이션 레벨 수정 없이 DNS 해석과 트래픽 라우팅을 통합 관리할 수 있는 이스티오의 핵심 장점이다.

 

 

13.2 인프라 준비하기 Setting up the infrastructure

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
echo -e "http://$(curl -s ipinfo.io/ip):30007/#scale=1.5"
echo -e "http://$(curl -s ipinfo.io/ip):30007/#scale=1.3"

# 소스코드 clone
git clone https://github.com/AcornPublishing/istio-in-action
tree istio-in-action/book-source-code-master -L 1

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false

# 클러스터와 가상머신이 다른 네트워크에 있으므로, 이스티오를 설치한 네임스페이스에 네트워크 정보 레이블을 지정해야 한다.
kubectl create namespace istio-system
kubectl label namespace istio-system topology.istio.io/network=west-network

# demo 프로파일 컨트롤 플레인 배포
cat istio-in-action/book-source-code-master/ch13/controlplane/cluster-in-west-network.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: west-cluster
      network: west-network

istioctl install -f istio-in-action/book-source-code-master/ch13/controlplane/cluster-in-west-network.yaml --set values.global.proxy.privileged=true -y


# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons


# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

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

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kc describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
echo -e "http://$(curl -s ipinfo.io/ip):30001"

# Grafana 접속
echo -e "http://$(curl -s ipinfo.io/ip):30002"

# Kiali 접속 : NodePort
echo -e "http://$(curl -s ipinfo.io/ip):30003"

# tracing 접속 : 예거 트레이싱 대시보드
echo -e "http://$(curl -s ipinfo.io/ip):30004"

 

 

cool-store 서비스/gw/vs 배포 및 http 요청 확인

#cool-store 서비스/gw/vs 배포 및 http 요청 확인
# cool-store 서비스/gw/vs 배포
kubectl -n istioinaction apply -f istio-in-action/book-source-code-master/ch12/webapp-deployment-svc.yaml
kubectl -n istioinaction apply -f istio-in-action/book-source-code-master/ch12/webapp-gw-vs.yaml
kubectl -n istioinaction apply -f istio-in-action/book-source-code-master/ch12/catalog.yaml

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



#http 요청 확인
# k3s-s
curl -s -H "Host: webapp.istioinaction.io" http://192.168.10.10:30000/api/catalog/ | jq
curl -s -H "Host: webapp.istioinaction.io" http://192.168.10.10:30000/api/catalog/items/1 | jq

# [forum-vm] k8s cool-store 요청 시도 확인
APP_IP=43.202.64.8 # k8s-s 의 공인 IP
curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/catalog/items/1 | jq
while true; do curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/catalog/ ; echo; date; sleep 1; done

# [자신의 PC] k8s cool-store 요청 시도 확인
바로 위 [testpc]와 동일 요청

# [k8s-s] forum-vm web 요청 시도 확인
curl 192.168.10.200
curl 192.168.10.200 -I

VM_IP=15.165.15.104 # testpc 의 공인 IP
curl $VM_IP
curl $VM_IP -I

 

13.3 가상머신까지 메시 확인

가상머신 메시 통합 기능은 현재 베타 단계이며, 기본 비활성화 상태이므로 IstioOperator CRD를 통해 반드시 명시적으로 활성화해야 한다.

필수 활성화 항목

  • 워크로드 자동 등록: WorkloadGroup/WorkloadEntry 자동 생성
  • 헬스 체크: readiness/liveness 프로브 관리
  • DNS 프록시: 클러스터 내 서비스 DNS 해석 자동화

베타 단계 특성

  • 기능 안정성은 검증됐으나 향후 API 변경 가능성이 존재한다.
  • 운영 환경 적용 시 Istio 1.9+ 버전 필수이며, 주기적인 컨트롤 플레인 업데이트가 권장된다.

이 설정은 메시 확장성을 위해 반드시 구현해야 하는 핵심 요소로, 온프레미스/멀티클라우드 환경에서 레거시 워크로드 통합에 적합하다.

# 컨트롤 플레인 업데이트 Mesh expansion to VMs
cat istio-in-action/book-source-code-master/ch13/controlplane/cluster-in-west-network-with-vm-features.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  meshConfig:
    defaultConfig:
      proxyMetadata:
        ISTIO_META_DNS_CAPTURE: "true" # DNS 쿼리가 캡처돼 DNS 프록시로 리다이렉트된다
  values:
    pilot:
      env:
        PILOT_ENABLE_WORKLOAD_ENTRY_AUTOREGISTRATION: true # 워크로드를 컨트롤 플레인에 자동 등록할 수 있다
        PILOT_ENABLE_WORKLOAD_ENTRY_HEALTHCHECKS: true # 가상머신 워크로드의 상태를 검사한다
    global:
      meshID: usmesh
      multiCluster:
        clusterName: west-cluster
      network: west-network

istioctl install -f istio-in-action/book-source-code-master/ch13/controlplane/cluster-in-west-network-with-vm-features.yaml --set values.global.proxy.privileged=true -y
kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec": {"type": "NodePort"}}'

업데이트된 컨트롤 플레인은 DNS 쿼리 리다이렉션, 워크로드 자동 등록, 헬스 체크 기능을 제공하며, 가상머신이 istiod와 연결되어 설정 및 ID를 반드시 수신해야 한다.

 

13.3.1 istiod와 클러스터 서비스들은 가상머신에 노출하기* Exposing istiod and cluster services to the VM

가상머신이 멀티 네트워크 환경에서 메시에 통합되려면 이스트-웨스트 게이트웨이 설치가 반드시 필요하다. 이 게이트웨이는 **mTLS 포트(15443)**를 노출해 VM과 istiod/클러스터 서비스 간 통신을 프록시해야 한다.

#
cat istio-in-action/book-source-code-master/ch13/gateways/cluster-east-west-gw.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-eastwestgateway
  namespace: istio-system
spec:
  profile: empty
  components:
    ingressGateways:
    - name: istio-eastwestgateway
      label:
        istio: eastwestgateway
        app: istio-eastwestgateway
        topology.istio.io/network: west-network
      enabled: true
      k8s:
        env:
        - name: ISTIO_META_ROUTER_MODE
          value: "sni-dnat"
        # The network to which traffic is routed
        - name: ISTIO_META_REQUESTED_NETWORK_VIEW
          value: west-network
        service:
          ports:
          - name: status-port
            port: 15021
            targetPort: 15021
          - name: mtls
            port: 15443
            targetPort: 15443
          - name: tcp-istiod
            port: 15012
            targetPort: 15012
          - name: tcp-webhook
            port: 15017
            targetPort: 15017  
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: west-cluster
      network: west-network

#
istioctl install -f istio-in-action/book-source-code-master/ch13/gateways/cluster-east-west-gw.yaml -y

#
kubectl get pod -n istio-system -l chart=gateways
NAME                                     READY   STATUS    RESTARTS   AGE
istio-eastwestgateway-86f6cb4699-nbhnp   1/1     Running   0          9m47s
istio-ingressgateway-7b7ccd6454-xmqgw    1/1     Running   0          22m

kubectl get svc -n istio-system -l istio.io/rev=default
NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                                                                      AGE
istio-eastwestgateway   LoadBalancer   10.10.200.252   192.168.10.10   15021:32278/TCP,15443:31099/TCP,15012:31437/TCP,15017:32573/TCP              178m
istio-ingressgateway    NodePort       10.10.200.125   <none>          15021:31538/TCP,80:30000/TCP,443:30005/TCP,31400:31525/TCP,15443:30143/TCP   4h1m
istiod                  ClusterIP      10.10.200.111   <none>          15010/TCP,15012/TCP,443/TCP,15014/TCP                                        4h1m

kubectl get svc -n istio-system istio-eastwestgateway -o json | jq
...
      {
        "name": "status-port",
        "nodePort": 32278,
        "port": 15021,
        "protocol": "TCP",
        "targetPort": 15021
      },
      {
        "name": "mtls",
        "nodePort": 31099,
        "port": 15443,
        "protocol": "TCP",
        "targetPort": 15443
      },
      {
        "name": "tcp-istiod",
        "nodePort": 31437,
        "port": 15012,
        "protocol": "TCP",
        "targetPort": 15012
      },
      {
        "name": "tcp-webhook",
        "nodePort": 32573,
        "port": 15017,
        "protocol": "TCP",
        "targetPort": 15017
      }
...

kubectl get svc -n istio-system istiod -o json | jq
...
      {
        "name": "https-dns",
        "port": 15012,
        "protocol": "TCP",
        "targetPort": 15012
      },
      {
        "name": "https-webhook",
        "port": 443,
        "protocol": "TCP",
        "targetPort": 15017
      },
...

 

  1. 네트워크 분리 시:
    • VM ↔ istiod/서비스 직접 통신 불가 → 게이트웨이를 통한 트래픽 프록시 필수
  2. 포트 노출:
    • 15443 포트: 메시 내 서비스로의 역방향 프록시 담당
    • 15012/TCP(istiod XDS), 15017/TCP(인증 웹훅) 추가 노출 권장
     
#
cat istio-in-action/book-source-code-master/ch13/expose-services.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: cross-network-gateway
  namespace: istio-system
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        number: 15443
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH
      hosts:
        - "*.local"

kubectl apply -f istio-in-action/book-source-code-master/ch13/expose-services.yaml 
kubectl get gw,vs -A
NAMESPACE       NAME                                                AGE
istio-system    gateway.networking.istio.io/cross-network-gateway   2m23s
istioinaction   gateway.networking.istio.io/coolstore-gateway       82m

NAMESPACE       NAME                                                       GATEWAYS                HOSTS                         AGE
istioinaction   virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   82m
#
cat istio-in-action/book-source-code-master/ch13/expose-istiod.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istiod-gateway
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        name: tls-istiod
        number: 15012
        protocol: tls
      tls:
        mode: PASSTHROUGH        
      hosts:
        - "*"
    - port:
        name: tls-istiodwebhook
        number: 15017
        protocol: tls
      tls:
        mode: PASSTHROUGH          
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istiod-vs
spec:
  hosts:
  - "*"
  gateways:
  - istiod-gateway
  tls:
  - match:
    - port: 15012
      sniHosts:
      - "*"
    route:
    - destination:
        host: istiod.istio-system.svc.cluster.local
        port:
          number: 15012
  - match:
    - port: 15017
      sniHosts:
      - "*"
    route:
    - destination:
        host: istiod.istio-system.svc.cluster.local
        port:
          number: 443

# 
kubectl apply -f istio-in-action/book-source-code-master/ch13/expose-istiod.yaml -n istio-system


# 13.3.2 실습 전 아래와 같이 gw,vs 리소스가 생성되어있는지 확인하자!
kc get gw,vs -A
NAMESPACE       NAME                                                AGE
istio-system    gateway.networking.istio.io/istiod-gateway          9s
istio-system    gateway.networking.istio.io/cross-network-gateway   4m40s
istioinaction   gateway.networking.istio.io/coolstore-gateway       84m

NAMESPACE       NAME                                                       GATEWAYS                HOSTS                         AGE
istio-system    virtualservice.networking.istio.io/istiod-vs               ["istiod-gateway"]      ["*"]                         9s
istioinaction   virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   84m

 

  • WorkloadGroup 생성: VM 그룹의 공통 속성(포트, 레이블, 헬스 체크) 정의 필요
  • 자동 등록 구성: VM 시작 시 WorkloadEntry 자동 생성되도록 설정

이 과정을 통해 멀티 네트워크 환경에서도 VM을 메시에 안정적으로 통합할 수 있다.

 

13.3.2 WorkloadGroup 으로 워크로드 그룹 나타내기* Representing a group of workloads with a WorkloadGroup

워크로드 그룹(WorkloadGroup)은 가상머신 집합의 공통 속성을 반드시 정의해야 한다.

  1. 레이블(labels): 쿠버네티스 서비스가 워크로드 엔트리를 선택하기 위한 기준으로 활용해야 한다.
  2. 네트워크(network): 동일 네트워크 시 IP 직접 접근, 다른 네트워크 시 이스트-웨스트 게이트웨이를 통해 트래픽을 라우팅해야 한다.
  3. 서비스 어카운트(serviceAccount): 워크로드 식별을 위해 반드시 지정해야 하며, VM은 해당 토큰으로 인증을 완료해야 한다.
  4. 헬스 체크(probe): HTTP GET 방식을 사용해 애플리케이션 준비 상태를 주기적으로 검증해야 한다(예: 5초 주기, /api/healthz 경로).
#
cat istio-in-action/book-source-code-master/ch13/workloadgroup.yaml
apiVersion: networking.istio.io/v1alpha3
kind: WorkloadGroup
metadata:
  name: forum
  namespace: forum-services
spec:
  metadata:
    annotations: {}
    labels:
      app: forum # 서비스는 레이블을 사용해 이 그룹의 워크로드를 대상으로 삼을 수 있다
  template:
    serviceAccount: forum-sa # 워크로드가 이 워크로드 그룹에 등록하려면 forum-sa 인증 토큰을 보유하고 있어야 한다
    network: vm-network # 이스티오가 동일한 네트워크에 있는 워크로드 사이의 직접 접근을 설정할 수 있도록 한다
  probe: # 이 워크로드 그룹의 인스턴스에서 실행되는 istio-agent는 HTTP GET 요청을 8080 포트의 /api/healthz 경로로 보내 앱의 준비 상태를 확인한다
    periodSeconds: 5
    initialDelaySeconds: 1
    httpGet:
      port: 8080
      path: /api/healthz

이 설정을 통해 VM 그룹의 자동 등록 및 트래픽 관리가 가능해진다.

#네임스페이스와 서비스 어카운트를 만들고 WorkloadGroup 설정을 클러스터에 적용해보자.
cat istio-in-action/book-source-code-master/ch13/workloadgroup.yaml
kubectl create namespace forum-services
kubectl create serviceaccount forum-sa -n forum-services
kubectl apply -f istio-in-action/book-source-code-master/ch13/workloadgroup.yaml

#
kubectl get-all -n forum-services
kubectl get workloadgroup -n forum-services
NAME    AGE
forum   2m2s

이제 클러스터는 WorkloadGroup에 명세된 forum-sa 서비스 어카운트의 유효한 토큰을 제시할 수 있는 워크로드를 자동으로 등록하도록 설정된다.

 

가상머신의 사이드카 설정을 생성하려면 istioctl 명령어와 WorkloadGroup을 반드시 활용해야 한다.

  1. istioctl은 WorkloadGroup의 정보와 클러스터 쿼리 결과를 기반으로 호스트 파일, 루트 인증서, 서비스 어카운트 토큰, 메시 설정 파일을 자동 생성해야 한다.
  2. 생성된 hosts 파일은 이스트-웨스트 게이트웨이 IP를 통해 istiod 서비스 DNS를 반드시 매핑해야 한다.
  3. **루트 인증서(root-cert.pem)**는 istiod와의 mTLS 통신 시 인증서 검증에 필수적으로 사용해야 한다.
  4. **서비스 어카운트 토큰(istio-token)**은 WorkloadGroup 소속 인증을 위해 반드시 istio-agent에 제공해야 한다.
  5. mesh.yaml 파일은 네트워크 및 공통 속성 설정을 포함하며, WorkloadGroup 정의에 따라 자동 적용해야 한다.
# Generates all the required configuration files for workload instance on a VM or non-Kubernetes environment from a WorkloadGroup artifact.
istioctl x workload entry configure -h
istioctl x workload entry configure \
--name forum \ # forum-services 네임스페이스에 있는 forum WorkloadGroup 을 읽고 워크로드 설정을 생성한다
--namespace forum-services \ # 상동
--clusterID "west-cluster" \ # 반드시 이스티오 설치 시 지정한 클러스터 이름으로 설정해야 한다
--externalIP $VM_IP \ # 워크로드가 클러스터와 동일한 네트워크에 있지 않은 경우 workloadIP 파라미터가 필요하다. 디폴트 설정에 의하면, 정의하지 않은 경우 네트워크에서 할당한 사설 IP를 사용한다.
--autoregister \ # 워크로드를 자동으로 등록하도록 설정한다.
-o /tmp/my-workload-files/ # 설정 파일을 저장할 디렉터리 위치를 명령 실행 위치에 대한 상대 경로로 지정한다.

#
istioctl x workload entry configure -f istio-in-action/book-source-code-master/ch13/workloadgroup.yaml -o /tmp/my-workload-files/ --clusterID "west-cluster" --autoregister
cat /tmp/my-workload-files/hosts
192.168.10.10 istiod.istio-system.svc

chown ubuntu:ubuntu -R /tmp/my-workload-files/
tree /tmp/my-workload-files/
├── cluster.env
├── hosts
├── istio-token
├── mesh.yaml
└── root-cert.pem

이 설정 파일들을 VM에 배포하면 서비스 프록시가 istiod와 보안 연결을 수립하고, xDS 구성을 수신해 메시에 통합된다.

 

 

가상머신으로 생성된 설정 파일 전송 시 SSH를 통한 안전한 복사를 반드시 수행해야 하며, 운영 환경에서는 자동화된 프로비저닝 도구를 활용해 수동 작업을 제거해야 한다.

# 임시로, 자신의 로컬 PC에 위 파일들 복사 : 바로 vm_ip 에 복사해도 되지만, 현재 실습 환경 편리성으로 경유 복사.
APP_IP=43.202.64.8
mkdir my-workload-files
scp -i ~/.ssh/kp-gasida.pem ubuntu@$APP_IP:/tmp/my-workload-files/\* ./my-workload-files # macOS
scp -i ~/.ssh/kp-gasida.pem ubuntu@$APP_IP:/tmp/my-workload-files/*  ./my-workload-files # linux

ls -al
openssl x509 -in ./my-workload-files/root-cert.pem -noout -text # istio CA 인증서
jwt decode $(cat ./my-workload-files/istio-token) # 토큰
...
Token claims
------------
{
  "aud": [
    "istio-ca"
  ],
  "exp": 1748083668,
  "iat": 1748080068,
  "iss": "https://kubernetes.default.svc.cluster.local",
  "kubernetes.io": {
    "namespace": "forum-services",
    "serviceaccount": {
      "name": "forum-sa",
      "uid": "64ab40b3-3bad-49f1-a1c9-0464d72c18c1"
    }
  },
  "nbf": 1748080068,
  "sub": "system:serviceaccount:forum-services:forum-sa"
}

cat ./my-workload-files/mesh.yaml
defaultConfig:
  discoveryAddress: istiod.istio-system.svc:15012
  meshId: usmesh
  proxyMetadata:
    CANONICAL_REVISION: latest
    CANONICAL_SERVICE: forum
    ISTIO_META_AUTO_REGISTER_GROUP: forum
    ISTIO_META_CLUSTER_ID: west-cluster
    ISTIO_META_DNS_CAPTURE: "true"
    ISTIO_META_MESH_ID: usmesh
    ISTIO_META_NETWORK: vm-network
    ISTIO_META_WORKLOAD_NAME: forum
    ISTIO_METAJSON_LABELS: '{"app":"forum","service.istio.io/canonical-name":"forum","service.istio.io/canonical-revision":"latest"}'
    POD_NAMESPACE: forum-services
    SERVICE_ACCOUNT: forum-sa
    TRUST_DOMAIN: cluster.local
  readinessProbe:
    httpGet:
      path: /api/healthz
      port: 8080
    initialDelaySeconds: 1
    periodSeconds: 5
  tracing:
    zipkin:
      address: zipkin.istio-system:9411


# 로컬 PC의 파일들을 forum-vm로 복사
FORUM=54.180.240.239
scp -i ~/.ssh/kp-gasida.pem ./my-workload-files/cluster.env ubuntu@$FORUM:/tmp/
scp -i ~/.ssh/kp-gasida.pem ./my-workload-files/istio-token ubuntu@$FORUM:/tmp/
scp -i ~/.ssh/kp-gasida.pem ./my-workload-files/mesh.yaml ubuntu@$FORUM:/tmp/
scp -i ~/.ssh/kp-gasida.pem ./my-workload-files/root-cert.pem ubuntu@$FORUM:/tmp/


# forum-vm 에서 파일 확인
ls -l /tmp
-rwxr--r-- 1 ubuntu ubuntu  714 May 24 19:08 cluster.env
-rwxr--r-- 1 ubuntu ubuntu  844 May 24 19:08 istio-token
-rwxr--r-- 1 ubuntu ubuntu  792 May 24 19:08 mesh.yaml
-rwxr--r-- 1 ubuntu ubuntu 1094 May 24 19:08 root-cert.pem
...

 

13.3.3 가상머신에 istio-agent 설치 및 설정하기* Installing and configuring the istio-agent in the VM

#forum-vm 가상머신에 istio-agent 를 다운로드하고 설치
#
cat /etc/resolv.conf
nameserver 127.0.0.53
...

ss -tnlp
State      Recv-Q     Send-Q         Local Address:Port         Peer Address:Port    Process
LISTEN     0          4096           127.0.0.53%lo:53                0.0.0.0:*        users:(("systemd-resolve",pid=357,fd=14))

ss -unlp
State     Recv-Q    Send-Q           Local Address:Port         Peer Address:Port    Process
UNCONN    0         0                127.0.0.53%lo:53                0.0.0.0:*        users:(("systemd-resolve",pid=357,fd=13))

resolvectl status
...
resolv.conf mode: stub # stub 모드로 설정, 127.0.0.53(local stub resolver)을 가리키며, systemd-resolved가 이 요청을 처리함

Link 2 (ens5)
    Current Scopes: DNS
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.168.0.2
...

#
iptables -t nat -L -n -v
iptables -t filter -L -n -v
iptables -t mangle -L -n -v
iptables -t raw -L -n -v


# 데비안 패키지 형식으로 다운로드 후 설치
curl -LO https://storage.googleapis.com/istio-release/releases/1.17.8/deb/istio-sidecar.deb
file istio-sidecar.deb
dpkg -i istio-sidecar.deb
which pilot-agent
pilot-agent version
which envoy
envoy --version

tree /etc/istio
/etc/istio
├── config
│   └── mesh
├── envoy
│   ├── cluster.env
│   ├── envoy_bootstrap_tmpl.json
│   └── sidecar.env
├── extensions
│   ├── metadata-exchange-filter.compiled.wasm
│   ├── metadata-exchange-filter.wasm
│   ├── stats-filter.compiled.wasm
│   └── stats-filter.wasm
└── proxy

tree /var/lib/istio/
/var/lib/istio/
├── config
│   └── mesh
├── envoy
│   ├── envoy_bootstrap_tmpl.json
│   └── sidecar.env
├── extensions
│   ├── metadata-exchange-filter.compiled.wasm
│   ├── metadata-exchange-filter.wasm
│   ├── stats-filter.compiled.wasm
│   └── stats-filter.wasm
└── proxy

# istio-agent 는 특정 위치에서 설정 파일을 읽으므로, 복사해둔 파일을 옮겨보자
mkdir -p /etc/certs
mkdir -p /var/run/secrets/tokens

cp /tmp/root-cert.pem /etc/certs/root-cert.pem
cp /tmp/istio-token /var/run/secrets/tokens/istio-token
cp /tmp/cluster.env /var/lib/istio/envoy/cluster.env
cp /tmp/mesh.yaml /etc/istio/config/mesh


# istiod.istio-system.svc 요청을 istiod 인스턴스로 프록시하는 east-weat 게이트웨이 IP 주소로 정적으로 해석하도록 한다.
cat /etc/hosts
echo "192.168.10.10 istiod.istio-system.svc" | sudo sh -c 'cat >> /etc/hosts'
cat /etc/hosts

# istio-agent가 호스트네임 해석을 방해하지 않도록 hosts 파일에 forum-vm 머신의 호스트네임을 하드코딩하자 : 설정되어 있음
echo "$(hostname --all-ip-addresses | cut -d ' ' -f 1) $(hostname)" | sudo sh -c 'cat >> /etc/hosts'
cat /etc/hosts

 

클러스터 내 호스트네임 해석을 위해 DNS 프록시 설정이 반드시 필요하며, 이스트-웨스트 게이트웨이를 가리키는 네트워크 로드 밸런서를 활용해 동적 DNS 관리를 구현해야 한다.

 

#에이전트가 읽고 쓸 디렉터리에 소유자 권한을 부여
cat /etc/passwd | tail -n 3
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
istio-proxy:x:998:999::/var/lib/istio:/bin/sh

tree /etc/istio
chown -R istio-proxy /var/lib/istio /etc/certs /etc/istio/proxy /etc/istio/config /var/run/secrets /etc/certs/root-cert.pem

#
systemctl status istio
systemctl start istio
systemctl enable istio
systemctl status istio
...
     CGroup: /system.slice/istio.service
             ├─32047 sudo -E -u istio-proxy -s /bin/bash -c "ulimit -n 1024; INSTANCE_IP=15.165.15.104 POD_NAME=testpc POD_NAMESPACE=forum-services >
             ├─32140 /usr/local/bin/pilot-agent proxy
             └─32148 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-strategy immediate --local-address-ip-version >

May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_OUTPUT -o lo -p tcp -m tcp ! --dport 53 -m owner ! --gid-owner 998 -j RETURN
May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_OUTPUT -m owner --gid-owner 998 -j RETURN
May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_OUTPUT -d 127.0.0.53/3cat 2 -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 15053
May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_OUTPUT -j ISTIO_REDIRECT
May 24 20:10:05 testpc istio-start.sh[32128]: -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
May 24 20:10:05 testpc istio-start.sh[32128]: COMMIT
May 24 20:10:05 testpc istio-start.sh[32128]: # Completed on Sat May 24 20:10:05 2025
May 24 20:10:05 testpc sudo[32047]:     root : PWD=/ ; USER=istio-proxy ; COMMAND=/bin/bash -c '\\/bin\\/bash -c ulimit\\ -n\\ 1024\\;\\ INSTANCE_IP>
May 24 20:10:05 testpc sudo[32047]: pam_unix(sudo:session): session opened for user istio-proxy(uid=998) by (uid=0)
...
## 혹은
journalctl -u istio -f
...

which istio-start.sh
cat /usr/local/bin/istio-start.sh


#
tree /etc/certs/
├── cert-chain.pem
├── key.pem
└── root-cert.pem

#
ps aux |grep istio

#
iptables -t nat -L -n -v
iptables -t filter -L -n -v
iptables -t mangle -L -n -v
iptables -t raw -L -n -v

 

에이전트 로그 확인하기 CHECKING THE AGENT LOGS

이스티오 에이전트 로그는 /var/log/istio/istio.log(표준 출력)와 /var/log/istio/istio.err(표준 오류)에 반드시 기록되며, 표준 출력 로그를 확인해 istiod 연결 성공 여부를 검증해야 한다.

#
cat /var/log/istio/istio.log | grep xdsproxy
2025-05-25T01:09:42.094214Z	info	xdsproxy	Initializing with upstream address "istiod.istio-system.svc:15012" and cluster "west-cluster"
2025-05-25T01:09:42.227661Z	info	xdsproxy	connected to upstream XDS server: istiod.istio-system.svc:15012

# 만약 로그 파일이 생성되지 않았다면? 서비스 시작이 실패한 경우에 그럴 수 있다. 아래 처럼 systemd 로그를 확인하자
journalctl -u istio -f

 

워크로드가 메시에 등록됐는지 확인하기 VERIFYING THAT THE WORKLOAD REGISTERED TO THE MESH

#
kubectl get workloadentries -n forum-services
NAME                              AGE    ADDRESS
forum-192.168.10.200-vm-network   110m   192.168.10.200

kc get workloadentries -n forum-services -o yaml
...
  status:
    conditions:
    - lastProbeTime: "2025-05-25T02:35:47.436392452Z"
      lastTransitionTime: "2025-05-25T02:35:47.436392780Z"
      message: 'Get "http://192.168.10.200:8080/api/healthz": dial tcp 127.0.0.6:0->192.168.10.200:8080:
        connect: connection refused'
      status: "False"
      type: Healthy
...

istioctl proxy-status
NAME                                                    CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
forum-vm.forum-services                                 west-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-d6549b9fc-rpg7l     1.17.0
webapp-684c568c59-9wtbt.istioinaction                   west-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-d6549b9fc-rpg7l     1.17.8
...

가상머신의 WorkloadEntry가 정상 등록되어 고유 주소가 표시되는 것을 반드시 확인해야 하며, 이후 트래픽 라우팅 동작을 점검해야 한다.

 

13.3.4 클러스터 서비스로 트래픽 라우팅하기 Routing traffic to cluster services

트래픽이 클러스터 서비스로 라우팅되는지 확인하기 위해 가상머신에서 webapp 워크로드로 curl 요청을 보내보자

# 신규 터미널 : 패킷 모니터링 https://github.com/gcla/termshark/blob/master/docs/UserGuide.md
tcpdump -i any -w - udp port 53 | termshark
## CTRL+C 로 취소 후 수집된 패킷이 termshark 에서 확인 가능

# testpc : 도메인 해석은 잘 됨
dig +short webapp.istioinaction
10.10.200.48

curl -s webapp.istioinaction/api/catalog/items/1 | jq
watch curl -s webapp.istioinaction/api/catalog/items/1

# 신규 터미널 : 15443 연결하는 envoy 프로세스(데이터 플래인) 확인!
watch -d ss -tnp 
watch -d 'ss -tnp | grep envoy'
State     Recv-Q  Send-Q   Local Address:Port     Peer Address:Port  Process                                                   
ESTAB 0      0      192.168.10.200:41238 192.168.10.10:15443 users:(("envoy",pid=3203,fd=40))
ESTAB 0      0      192.168.10.200:41242 192.168.10.10:15443 users:(("envoy",pid=3203,fd=41))
...

# 신규 터미널 : 
watch -d iptables -t nat -L -n -v
watch -d iptables -t raw -L -n -v


# k3s-s
kubectl get svc,ep -n istioinaction webapp
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/webapp   ClusterIP   10.10.200.48   <none>        80/TCP    148m

NAME               ENDPOINTS         AGE
endpoints/webapp   172.16.0.8:8080   148m

 

실제 forum은 다이렉트로 연결하는게 아니라, 경유하여 접근한다.

가상머신에서 클러스터 서비스로의 트래픽 라우팅 과정은 다음 4단계로 수행된다.

  1. DNS 쿼리 리다이렉트
    • 애플리케이션의 DNS 요청은 iptables 규칙에 의해 Istio DNS 프록시로 전달되어야 한다.
    • DNS 프록시는 메시 내 서비스(쿠버네티스 서비스/ServiceEntry) 정보를 기반으로 IP 해석을 반드시 완료해야 한다.
  2. 아웃바운드 트래픽 캡처
    • 해석된 IP로의 요청은 iptablesEnvoy 사이드카로 리다이렉트해야 한다.
    • 이 단계에서 mTLS 암호화, 트래픽 정책 적용이 이루어진다.
  3. 이스트-웨스트 게이트웨이 경유
    • 멀티 네트워크 환경에서는 Envoy가 트래픽을 east-west 게이트웨이로 라우팅해야 한다.
    • 게이트웨이는 클러스터 네트워크와 VM 네트워크 간 보안 터널링을 반드시 수행한다.
  4. 최종 서비스 연결
    • 게이트웨이는 트래픽을 webapp 서비스로 전달하며, webapp은 내부 catalog 서비스와 연동된다.

 

 

13.3.5 트래픽을 WorkloadEntry로 라우팅하기 Routing traffic to the WorkloadEntry

클러스터 내부에서 가상머신 워크로드로 트래픽을 라우팅할 때는 WorkloadEntry의 IP 주소를 직접 사용하지 않는다. 쿠버네티스 파드 IP를 직접 사용하지 않는 것과 동일한 원리로, 플랫폼 유연성과 인스턴스 교체 용이성을 위해 쿠버네티스 서비스를 반드시 활용해야 한다.

  1. 레이블 기반 선택:
    • 쿠버네티스 서비스레이블 셀렉터를 통해 WorkloadEntry를 대상으로 지정해야 한다.
    • 예를 들어 app: forum 레이블이 부여된 WorkloadEntry를 선택하는 서비스를 생성한다.
  2. 동적 라우팅:
    • 이스티오는 서비스가 선택한 WorkloadEntry의 IP를 동적으로 설정해 트래픽을 전달해야 한다.
    • 이를 통해 VM 인스턴스의 추가/제거 시 클라이언트 측 변경 없이 자동 조정이 가능하다.
  3. 운영 장점:
    • 인스턴스 교체 용이: VM 장애 시 새 WorkloadEntry가 자동 등록되어 서비스 연속성을 보장해야 한다.
    • 멀티 클라우드 지원: 레이블 기반 라우팅을 통해 다양한 환경(온프레미스, 클라우드)의 워크로드를 통합 관리할 수 있다.

예시로 forum 서비스를 생성해 WorkloadEntry를 백엔드로 지정하면, 클러스터 내 애플리케이션은 VM의 IP를 알 필요 없이 서비스 DNS로 트래픽을 전송한다. 이스티오가 내부적으로 WorkloadEntry의 실제 엔드포인트로 라우팅을 처리한다.

#
cat istio-in-action/book-source-code-master/services/forum/kubernetes/forum-svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: forum
  name: forum
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: forum

kubectl apply -f istio-in-action/book-source-code-master/services/forum/kubernetes/forum-svc.yaml -n forum-services

kubectl get svc,ep -n forum-services
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/forum   ClusterIP   10.10.200.72   <none>        80/TCP    20s

NAME              ENDPOINTS   AGE
endpoints/forum   <none>      20s

#
istioctl proxy-config route deploy/webapp.istioinaction
istioctl proxy-config route deploy/webapp.istioinaction --name 80 -o json
...
      "name": "forum.forum-services.svc.cluster.local:80",
      "domains": [
          "forum.forum-services.svc.cluster.local",
          "forum.forum-services",
          "forum.forum-services.svc",
          "10.10.200.72"
      ],
      "routes": [
          {
              "name": "default",
              "match": {
                  "prefix": "/"
              },
              "route": {
                  "cluster": "outbound|80||forum.forum-services.svc.cluster.local",
...

#
istioctl proxy-config cluster deploy/webapp.istioinaction
istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn forum.forum-services.svc.cluster.local -o json
istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn forum.forum-services.svc.cluster.local
SERVICE FQDN                               PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
forum.forum-services.svc.cluster.local     80       -          outbound      EDS   

# 아직 없다!
istioctl proxy-config endpoint deploy/webapp.istioinaction
istioctl proxy-config endpoint deploy/webapp.istioinaction | grep forum

#
istioctl proxy-config listener deploy/istio-eastwestgateway.istio-system
istioctl proxy-config route deploy/istio-eastwestgateway.istio-system
istioctl proxy-config cluster deploy/istio-eastwestgateway.istio-system
istioctl proxy-config endpoint deploy/istio-eastwestgateway.istio-system
istioctl proxy-config endpoint deploy/istio-eastwestgateway.istio-system | grep forum


#forum-vm 의 envoy config 확인
# [testpc] istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

#
curl -s localhost:15000/config_dump | istioctl proxy-config listener --file -
curl -s localhost:15000/config_dump | istioctl proxy-config route --file -
curl -s localhost:15000/config_dump | istioctl proxy-config clusters --file -
curl -s localhost:15000/config_dump | istioctl proxy-config endpoint --file -
curl -s localhost:15000/config_dump | istioctl proxy-config secret --file -
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
default           Cert Chain     ACTIVE     true           310309461583688467984066399721764000962     2025-05-26T01:09:42Z     2025-05-25T01:07:42Z
ROOTCA            CA             ACTIVE     true           46141372426695670978289547947687101983      2035-05-23T01:04:09Z     2025-05-25T01:04:09Z

서비스가 만들어지면 WorkloadEntry 엔드포인트가 선택되고, 이를 사용해 istiod가 데이터 플레인을 설정한다.

 

#
istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn forum.forum-services.svc.cluster.local
istioctl proxy-config endpoint deploy/webapp.istioinaction | grep forum

# 로그 모니터링
kubectl logs -n istioinaction deploy/webapp -c istio-proxy -f
[2025-05-25T04:53:18.841Z] "GET /api/users HTTP/1.1" 503 UH no_healthy_upstream - "-" 0 19 0 - "" "beegoServer" "63377970-9d0f-4591-a4d4-039b4321863d" "forum.forum-services:80" "-" outbound|80||forum.forum-services.svc.cluster.local - 10.10.200.72:80 :0 - default
[2025-05-25T04:53:18.839Z] "GET /api/users HTTP/1.1" 500 - via_upstream - "-" 0 27 2 2 "" "curl/8.7.1" "63377970-9d0f-4591-a4d4-039b4321863d" "webapp.istioinaction.io" "172.16.0.8:8080" inbound|8080|| 127.0.0.6:36439 172.16.0.8:8080 :0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default

# 자신의 PC
curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/catalog/
while true; do curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/catalog/ ; echo; date; sleep 1; done

curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/users
curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/users -I
HTTP/1.1 500 Internal Server Error

UH 응답 플래그는 정상 엔드포인트가 없어 트래픽을 라우팅할 수 없음을 의미하며, webapp에는 forum 서비스의 엔드포인트가 존재하지 않음을 나타낸다.

 

가상머신에서 forum 애플리케이션 시작하기 STARTING THE FORUM APPLICATION IN THE VM

# testpc
wget -O forum https://git.io/J3QrT
file forum
chmod +x forum
./forum

# testpc
curl http://localhost:8080/api/healthz -v

ss -tnlp | grep 8080
LISTEN 0      4096               *:8080             *:*    users:(("forum",pid=15081,fd=3))

# k8s-s
istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn forum.forum-services.svc.cluster.local
istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||forum.forum-services.svc.cluster.local' -o json
istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||forum.forum-services.svc.cluster.local'
ENDPOINT                STATUS      OUTLIER CHECK     CLUSTER
192.168.10.200:8080     HEALTHY     OK                outbound|80||forum.forum-services.svc.cluster.local

#
kc get workloadentries -n forum-services -o yaml
...
  status:
    conditions:
    - lastProbeTime: "2025-05-25T05:02:23.116408430Z"
      lastTransitionTime: "2025-05-25T05:02:23.116409282Z"
      status: "True"
      type: Healthy
...

 

# 자신의 PC
curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/users
while true; do curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/users ; echo; date; sleep 1; done

# 로그 모니터링
kubectl logs -n istioinaction deploy/webapp -c istio-proxy -f
[2025-05-25T05:05:51.328Z] "GET /api/users HTTP/1.1" 200 - via_upstream - "-" 0 5645 28 27 "218.153.65.54" "beegoServer" "888f982d-f7f3-4232-ac0b-826cf65ef294" "forum.forum-services:80" "192.168.10.200:8080" outbound|80||forum.forum-services.svc.cluster.local 172.16.0.8:38170 10.10.200.72:80 218.153.65.54:0 - default
[2025-05-25T05:05:51.326Z] "GET /api/users HTTP/1.1" 200 - via_upstream - "-" 0 3679 30 30 "218.153.65.54" "curl/8.7.1" "888f982d-f7f3-4232-ac0b-826cf65ef294" "webapp.istioinaction.io" "172.16.0.8:8080" inbound|8080|| 127.0.0.6:36439 172.16.0.8:8080 218.153.65.54:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default

이스티오는 비정상 워크로드를 데이터 플레인에서 제외시켜 클라이언트 트래픽이 오류 발생 인스턴스로 전달되지 않도록 방지하며, 운영 환경에서 안정적인 라우팅을 제공한다.

 

13.3.6 컨트롤 플레인이 가상머신 설정: 상호 인증 강제 VMs are configured by the control plane: Enforcing mutual authentication

가상머신의 8080 포트가 외부에 노출되어 권한 없는 접근이 가능한 상황에서 PeerAuthentication을 통해 STRICT mTLS를 반드시 적용해야 메시 내 트래픽의 상호 인증을 강제할 수 있다.

#
cat istio-in-action/book-source-code-master/ch13/strict-peer-auth.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

kubectl apply -f istio-in-action/book-source-code-master/ch13/strict-peer-auth.yaml
kubectl get peerauthentication -A
# 자신의 PC에서 요청
curl -is $FORUM:8080/api/users -v

# istio-ingressgateway 경유 요청 
while true; do curl -s -H "Host: webapp.istioinaction.io" http://$APP_IP:30000/api/users ; echo; date; sleep 1; done

가상머신이 컨트롤 플레인의 설정을 준수함을 보여주며, PeerAuthentication뿐만 아니라 모든 이스티오 API로 프록시 정책을 자유롭게 적용할 수 있음을 의미한다.

 

이스티오 확장을 통한 맞춤형 네트워킹 구현
기본 기능으로 부족한 조직별 요구사항 충족을 위해 Envoy 프록시의 설정을 직접 수정하거나 확장한다. EnvoyFilter 리소스를 활용해 엔보이의 동작을 세부 제어하며, Lua 스크립트WebAssembly(Wasm) 모듈을 적용해 요청/응답 경로를 커스터마이징할 수 있다.

주요 확장 사례

  • 외부 인가 서비스 연동, HMAC 서명 검증 등 비표준 프로토콜 처리
  • 헤더 조작, 페이로드 보강을 위한 추가 서비스 호출
  • 조직별 보안 토큰 처리 로직 구현

이를 통해 Istio의 코어 변경 없이 특정 사용 사례에 최적화된 네트워킹 레이어를 구축할 수 있다.

 

 

실습환경 구성

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # kube-ops-view
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.0.0/22
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server



# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 172.18.0.100
/myk8s-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 myk8s-control-plane
docker exec -it myk8s-control-plane ping -c 1 mypc



# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml

# 확인
kubectl get crd  
kubectl get pod -n metallb-system

# IPAddressPool, L2Advertisement 설정 : MetalLB 파드(speaker) 배포 정상 완료 후 아래 설정 실행하자.
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A





#
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF


# 확인
kubectl get deploy,pod,svc,ep 
kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
LBIP=$(kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 외부?에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
docker exec -it mypc curl -s $LBIP
docker exec -it mypc curl -s $LBIP -v -I


# 확인 후 삭제
kubectl delete deploy,svc --all




# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# demo 프로파일 컨트롤 플레인 배포
istioctl install --set profile=demo --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

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

# istio-ingressgateway 서비스 : nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

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

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

 

이스티오 고급 설정을 위한 EnvoyFilter 활용

기존 API로 구현 불가한 시나리오에서 EnvoyFilter를 사용해 엔보이 프록시의 리스너·라우트·클러스터·필터를 직접 설정한다. 이 리소스는 이스티오의 고수준 추상화를 벗어나 저수준 엔보이 기능을 제어할 수 있지만, 버전 간 호환성 보장 없어 신중한 검증이 필수다. 잘못된 구성은 데이터 플레인 전체 장애로 이어질 수 있으므로, 반드시 테스트 환경에서 검증 후 배포해야 한다. 비상 시에만 사용하는 것이 권장되며, 일반적인 경우에는 VirtualService·DestinationRule 등 표준 API를 우선 적용한다.

 


Tap 필터를 적용해 webapp 서비스의 HTTP 트래픽을 샘플링·스트리밍하며, workloadSelector로 특정 워크로드에만 적용한다.
VirtualService/DestinationRule 이후 적용되며, 엔보이 저수준 설정 변경으로 인한 메시 장애 리스크 존재해 신중한 구성이 필수다.

 

# cat ch14/tap-envoy-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: tap-filter
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: webapp # 워크로드 셀렉터
  configPatches:
  - applyTo: HTTP_FILTER # 설정할 위치
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 8080
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch: # 엔보이 설정 패치
      operation: INSERT_BEFORE
      value:
       name: envoy.filters.http.tap
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap"
          commonConfig:
            adminConfig:
              configId: tap_config
              
              
              
              
#
cat ch14/tap-envoy-filter.yaml
kubectl apply -f ch14/tap-envoy-filter.yaml
kubectl get envoyfilter -n istioinaction
NAME         AGE
tap-filter   10s

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.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
...
                                {
                                    "name": "envoy.filters.http.tap",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap",
                                        "commonConfig": {
                                            "adminConfig": {
                                                "configId": "tap_config"
                                            }
                                        }
                                    }
                                },
                                {
                                    "name": "envoy.filters.http.router",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
                                    }
                                }
...

EnvoyFilter 설정 요약

  1. workloadSelector로 istioinaction 네임스페이스의 특정 워크로드에 적용 대상을 지정한다.
  2. SIDECAR_INBOUND 리스너의 8080 포트 HTTP 필터 체인에서 envoy.filters.http.router 필터 앞에 tap 필터를 추가한다.
  3. tap 필터는 envoy.filters.http.tap 타입으로 설정되며, streaming_admin 출력을 통해 실시간 트래픽 디버깅이 가능하다.

 

 

 

# 터미널 1 : 포트 포워딩 설정 후 tap 시작
kubectl port-forward -n istioinaction deploy/webapp 15000 &
curl -X POST -d @./ch14/tap-config.json localhost:15000/tap

# 터미널 2 : 기존 반복 접속하는 것 활용
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
while true; do docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; echo ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 터미널 3 : 로그 확인
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level http:debug
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level tap:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-18T05:59:47.704918Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:329	[C2100] new stream	thread=31
2025-05-18T05:59:47.705028Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1049	[C2100][S11845543740779751481] request headers complete (end_stream=true):
':authority', 'webapp.istioinaction.io'
':path', '/api/catalog'
':method', 'GET'
'user-agent', 'curl/8.7.1'
'accept', '*/*'
'x-app-tap', 'true'
...
2025-05-18T05:59:47.713852Z	debug	envoy tap external/envoy/source/extensions/filters/http/tap/tap_config_impl.cc:172	submitting buffered trace sink	thread=31
2025-05-18T05:59:47.713860Z	debug	envoy tap external/envoy/source/extensions/common/tap/admin.cc:145	admin submitting buffered trace to main thread	thread=31
...

 

 

 

14.3 외부 호출로 요청 속도 제한하기

엔보이 글로벌 속도 제한은 모든 서비스 인스턴스가 동일한 외부 속도 제한 서버(예: Redis 백엔드)를 호출해 요청 허용 여부를 결정하며, 서비스 복제본 수와 무관하게 일관된 트래픽 제한을 적용할 수 있다.

 

속도 제한 서버 설정
x-loyalty 헤더 값에 따라 골드(10/분), 실버(5/분), 브론즈(3/분) 등급별 요청 제한을 적용하고, 미확인 등급은 1/분으로 제한한다.
Redis 백엔드를 사용해 글로벌 카운터를 관리하며, Envoy의 RateLimitService 설정을 통해 외부 서버와 통합한다.

 

# cat ch14/rate-limit/rlsconfig.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-ratelimit-config
  namespace: istioinaction
data:
  config.yaml: |
    domain: catalog-ratelimit
    descriptors:
      - key: header_match
        value: no_loyalty
        rate_limit:
          unit: MINUTE
          requests_per_unit: 1
      - key: header_match
        value: gold_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 10
      - key: header_match
        value: silver_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 5
      - key: header_match
        value: bronze_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 3

 

요청 경로에 속도 제한 걸기* CONFIGURING THE REQUEST PATH FOR RATE LIMITING

EnvoyFilter를 통해 /items 경로 요청의 x-loyalty 헤더를 포착하는 rate-limit action을 정의해, 특정 로열티 등급별로 속도 제한 서버에 전달할 descriptor 생성을 구성한다.
VirtualService 라우트에 rate_limits 섹션 추가 없이, EnvoyFilter의 header_value_match 액션으로 헤더 기반 속성 추출 및 Redis 기반 글로벌 제한 정책을 적용한다.

# cat ch14/rate-limit/catalog-ratelimit-actions.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: catalog-ratelimit-actions
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: catalog
  configPatches:
    - applyTo: VIRTUAL_HOST
      match:
        context: SIDECAR_INBOUND
        routeConfiguration:
          vhost:
            route:
              action: ANY
      patch:
        operation: MERGE
        # Applies the rate limit rules.
        value:
          rate_limits: # 속도 제한 조치
            - actions:
              - header_value_match:
                  descriptor_value: no_loyalty
                  expect_match: false
                  headers:
                  - name: "x-loyalty"
            - actions:
              - header_value_match:
                  descriptor_value: bronze_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: bronze
            - actions:
              - header_value_match:
                  descriptor_value: silver_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: silver
            - actions:
              - header_value_match:
                  descriptor_value: gold_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: gold
                    
                    
                    
#
tree ch14/rate-limit
ch14/rate-limit
├── catalog-ratelimit-actions.yaml
├── catalog-ratelimit.yaml
├── rls.yaml
└── rlsconfig.yaml

cat ch14/rate-limit/rlsconfig.yaml
cat ch14/rate-limit/rls.yaml

#
kubectl apply -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/rls.yaml -n istioinaction

# 확인
kubectl get cm -n istioinaction catalog-ratelimit-config
kubectl get pod -n istioinaction                                
NAME                       READY   STATUS    RESTARTS      AGE
ratelimit-99d5d9c5-q4zcm   1/1     Running   2 (25s ago)   34s
redis-6cf4ff9768-x97m8     1/1     Running   0             34s
...





# 기존에 반복 호출은 취소해두자

# 
cat ch14/rate-limit/catalog-ratelimit.yaml
cat ch14/rate-limit/catalog-ratelimit-actions.yaml
kubectl apply -f ch14/rate-limit/catalog-ratelimit.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/catalog-ratelimit-actions.yaml -n istioinaction
kubectl get envoyfilter -A
NAMESPACE       NAME                        AGE
istioinaction   catalog-ratelimit-actions   27s
istioinaction   catalog-ratelimit-filter    28s
...

 

 

속도 제한 기능을 시험해보기 위해 sleep 앱을 배포하고 catalog 서비스를 호출하는 클라이언트를 시뮬레이션해보자.

# sleep 앱에서 catalog 서비스를 호출 시도 : 대략 1분에 한 번 정도 호출 성공! >> x-loyalty 헤더가 없을 때 속도 제한 값!
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
...

# silver 헤더는? 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
...


#
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4' -o json | grep actions

docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json | grep actions
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json
...
                "rateLimits": [
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "no_loyalty",
                                    "expectMatch": false,
                                    "headers": [
                                        {
                                            "name": "x-loyalty"
                                        }
                                    ]
                                }
                            }
                        ]
                    },
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "bronze_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "bronze"
...

 

 

14.4 루아로 이스티오의 데이터 플레인 확장하기

Lua를 활용한 이스티오 데이터 플레인 확장
Lua 스크립트를 엔보이 필터 체인에 주입해 요청/응답 경로의 헤더 조작·바디 검사 등 커스텀 로직 구현이 가능하다.
예를 들어 A/B 테스트 그룹 분류를 위해 외부 서비스 호출 후 결과를 헤더에 추가하는 동적 라우팅을 구성할 수 있다.
단, 바디 전체 메모리 적재 등 무거운 연산은 성능 저하를 유발할 수 있으니 주의가 필요하다.

| 일전에 실 예로 내가 운영하던 서비스중 프론트/백 사이에서 데이터가 안들어온다는 개발자 확인 요청으로 Lua 활용해서 바디 전체를 로깅하는 루아 스크립트 작성했다가 뻗었던 기억이 있다.

#
cat ch14/bucket-tester-service.yaml
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bucket-tester
    version: v1
  name: bucket-tester
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bucket-tester
      version: v1
  template:
    metadata:
      labels:
        app: bucket-tester
        version: v1
    spec:
      containers:
      - image: hashicorp/http-echo:1.0 # 수정 https://hub.docker.com/r/hashicorp/http-echo/tags
        imagePullPolicy: IfNotPresent
        name: bucket-tester
        args:
        - "-text=dark-launch-7"        
        ports:
        - containerPort: 5678
          name: http
          protocol: TCP
        securityContext:
          privileged: false

#
kubectl apply -f ch14/httpbin.yaml -n istioinaction
kubectl apply -f ch14/bucket-tester-service.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction
NAME                             READY   STATUS    RESTARTS      AGE
bucket-tester-688c598b47-86fbr   2/2     Running   0             25s
httpbin-85d76b4bb6-dz6b5         2/2     Running   0             2m56s
...




# cat ch14/lua-filter.yaml
...
            function envoy_on_request(request_handle)
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle)
              response_handle:headers():add("istioinaction", "it works!")
            end
...

 

Lua 스크립트는 envoy_on_request/envoy_on_response 함수로 요청·응답 헤더를 조작하고, httpCall()을 통해 외부 서비스와 논블로킹 통신해 동적 헤더 주입을 구현한다.

# cat ch14/lua-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: httpbin-lua-extension
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value: 
       name: envoy.lua
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            function envoy_on_request(request_handle) # 아래 줄에 코드 입력
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle) # 아래 줄에 코드 입력
              response_handle:headers():add("istioinaction", "it works!")
            end
  - applyTo: CLUSTER
    match:
      context: SIDECAR_OUTBOUND
    patch:
      operation: ADD
      value: # cluster specification
        name: bucket_tester
        type: STRICT_DNS
        connect_timeout: 0.5s
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: bucket_tester
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    protocol: TCP
                    address: bucket-tester.istioinaction.svc.cluster.local
                    port_value: 80

 

#
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction

# istio-proxy config 확인 내용 추가해두자


# httpbin 서비스 호출 확인! 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/ -v
...
< HTTP/1.1 503 Service Unavailable
< content-length: 39
< content-type: text/plain
< istioinaction: it works!
< date: Sun, 18 May 2025 07:59:34 GMT
< server: envoy
< x-envoy-upstream-service-time: 51
...
invalid header value for: x-test-cohort

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/headers
...
invalid header value for: x-test-cohort


# 정상 실습 시..
{
    "headers": {
        "Accept": "*/*",
        "Content-Length": "0",
        "Host": "httpbin.istioinaction:8000",
        "User-Agent": "curl/7.69.1",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "1d066f4b17ee147b",
        "X-B3-Traceid": "1ec27110e4141e131d066f4b17ee147b",
        "X-Test-Cohort": "dark-launch-7" # A/B 테스트 서비스를 호출할 때 덧붙이는 새 헤더 x-test-cohort 가 보임
    }
}

 

 

 

14.5 웹어셈블리로 이스티오의 데이터 플레인 확장하기

**웹어셈블리(Wasm)**를 통해 Envoy 필터를 동적 로드해 Istio 데이터 플레인을 확장하며, WasmPlugin CRD로 OCI 레지스트리에서 모듈을 배포해 커스텀 인증·트래픽 제어 기능을 구현할 수 있다. **웹어셈블리(Wasm)**는 이식성·고성능·안전성을 갖춘 바이너리 포맷으로, 다양한 언어로 작성된 커스텀 로직을 엔보이 필터에 동적 로드해 이스티오 데이터 플레인을 확장한다.

 

웹어셈블리(Wasm) 사용 이유
C++ 종속성·정적 빌드 문제 해결: 런타임 동적 로드로 커스텀 필터를 즉시 적용하며, 다양한 언어 지원으로 유연한 확장 가능. 안전한 샌드박스 환경에서 실행되어 메모리/CPU 제어·프록시 장애 격리되며, OCI 레지스트리 통합으로 배포 관리가 간소화된다.

**WebAssembly(Wasm)**로 Envoy 필터를 생성하려면 wasme 도구를 사용해 어셈블리스크립트·러스트 등 지원 언어로 코드 작성 후 빌드하며, 실험적 기능이므로 운영 전 철저한 테스트가 필요하다.

 

Distributing WebAssembly Modules 실습

Istio의 WasmPlugin을 사용해 OCI 레지스트리에서 원격 Wasm 모듈을 동적으로 로드하며, HTTP 기본 인증 확장 등을 구성할 수 있다.
WasmPlugin 리소스로 필터 단계(phase)와 설정을 정의하면 Istio 에이전트가 자동으로 모듈을 다운로드해 Envoy에 주입한다.

# 설정 전 호출 확인
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


#
kubectl explain wasmplugins.extensions.istio.io
...

# 
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: basic-auth
  namespace: istio-system # 모든 네임스페이스에 영향
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/istio-ecosystem/wasm-extensions/basic_auth:1.12.0
  phase: AUTHN
  pluginConfig:
    basic_auth_rules:
      - prefix: "/api"
        request_methods:
          - "GET"
          - "POST"
        credentials:
          - "ok:test"
          - "YWRtaW4zOmFkbWluMw=="
EOF

#
kubectl get WasmPlugin -A
NAMESPACE      NAME         AGE
istio-system   basic-auth   21s



#구성된 Wasm 모듈을 확인
# 자격 증명 없이 테스트
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


# 자격 증명으로 테스트
docker exec -it mypc curl -s -H "Authorization: Basic YWRtaW4zOmFkbWluMw==" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v

자격증명 유무(WASM 플러그인 동작)에 따른 성공

 

 

 

Istio 에 Coraza (코라자) WAF 적용 - KrBlog

CorazaGo 언어로 개발된 **오픈소스 웹 애플리케이션 방화벽(WAF)**으로, **OWASP CRS(핵심 규칙 세트)**와 완벽 호환되며 ModSecurity SecLang 규칙을 지원한다.

 

 

 

#
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: coraza-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/corazawaf/coraza-proxy-wasm
  phase: AUTHN
  pluginConfig:
    default_directives: default
    directives_map:
      default:
      - Include @demo-conf
      - SecDebugLogLevel 9
      - SecRuleEngine On
      - Include @crs-setup-conf
      - Include @owasp_crs/*.conf
EOF

#
kubectl logs -n istio-system -l app=istio-ingressgateway | grep -i wasm 
2025-05-18T09:22:39.344842Z     info    wasm    fetching image corazawaf/coraza-proxy-wasm from registry ghcr.io with tag latest

#
kubectl get WasmPlugin -A
NAMESPACE      NAME                    AGE
istio-system   coraza-ingressgateway   9s
#동작 확인

#
curl -s http://webapp.istioinaction.io:30000/api/catalog

# XSS (Cross-Site Scripting) 공격 시도 : REQUEST-941-APPLICATION-ATTACK-XSS
curl -s 'http://webapp.istioinaction.io:30000/api/catalog?arg=<script>alert(0)</script>' -IL
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:34:21.049522Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 1). Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11347"] [id "949111"] [rev ""] [msg "Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog?arg=<script>alert(0)</script>"] [unique_id "ztBHScSiiDnOiwJfmOy"]      thread=30
[2025-05-18T09:34:21.014Z] "HEAD /api/catalog?arg=<script>alert(0)</script> HTTP/1.1" 403 -  - "-" 0 0 35 - "172.18.0.1" "curl/8.7.1" "27f43c69-359d-9d95-99cd-553081bb9346" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.10:8080 172.18.0.1:65166 - -


# SQLI phase 2 (reading the body request)
curl -i -X POST 'http://webapp.istioinaction.io:30000/api/catalog' --data "1%27%20ORDER%20BY%203--%2B"
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:42:40.613124Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 10) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11358"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 10)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog"] [unique_id "GYSnZlurGTXFtujeNgl"]  thread=34
[2025-05-18T09:42:40.575Z] "POST /api/catalog HTTP/1.1" 403 -  - "-" 26 0 39 - "172.18.0.1" "curl/8.7.1" "c30d47e1-5631-99ae-91f2-5dfd8174b421" "webapp.istioinaction.io:30000" "10.10.0.16:8080" outbound|80||webapp.istioinaction.svc.cluster.local 10.10.0.10:44518 10.10.0.10:8080 172.18.0.1:63266 - -
...

WAF(WASM)으로 인해 악의적인 접근이 차단된것을 알 수 있다.

 

 

다중 클러스터 서비스 메시는 장애 격리 및 고가용성을 통해 클러스터 장애 시 트래픽을 다른 클러스터로 자동 전환하며, 다중 클라우드 지원 및 지연 시간 최소화를 위해 다양한 환경에서 워크로드를 실행하고 지리적 근접성을 기반으로 라우팅할 수 있다.

 

다중 클러스터 서비스 메시는 클러스터 간 워크로드 디스커버리·연결성·공통 신뢰를 통해 트래픽 제어·보안·관측 기능을 유지하며, Istio는 쿠버네티스 API 연동으로 서비스 프록시를 자동 구성한다. 보안 리스크 시 **메시 연합(Mesh Federation)**을 적용해 클러스터 API 접근 권한 분리와 독립적인 메시 운영이 가능하다.

 

이스티오 컨트롤 플레인은 쿠버네티스 API 서버에 접근해 서비스와 엔드포인트 정보를 수집한다.
API 접근은 서비스 어카운트와 토큰, RBAC 권한으로 제어한다.
istiod에 원격 클러스터의 서비스 어카운트 토큰

을 제공해 인증 후 워크로드를 디스커버리한다.


클러스터가 플랫 네트워크라면 워크로드가 IP로 바로 연결된다.
서로 다른 네트워크라면 이스티오 인그레스 게이트웨이(동서 게이트웨이)가 클러스터 간 트래픽을 프록시한다.
이 구조는 중복 IP, 네트워크 경계, 장애 허용, 네트워크 분리 등 다양한 요구를 충족한다.

 


다중 클러스터 메시에서는 클러스터 간 공통 신뢰가 필요하다.
공통 루트 CA에서 발급한 플러그인 중간 CA 인증서나 외부 CA 통합 방식이 있다.
플러그인 CA 방식은 간단하지만 노출 위험이 있고, 외부 CA 통합은 더 안전하지만 복잡하다.

 

 

 

12.3 다중 클러스터, 다중 네트워크, 다중 컨트롤 플레인 서비스 메시 개요

인프라 구성 환경

실세계 엔터프라이즈 서비스를 모방하는 인프라를 준비해보자. 이 서비스는 여러 클러스터에서 실행되고, 여러 리전에 걸쳐 배포되며, 서로 다른 네트워크에 위치한다.

  • west-cluster : 사설 네트워크가 us-west 리전에 있는 쿠버네티스 클러스터. 여기서 webapp 서비스를 실행.
  • east-cluster : 사설 네트워크가 us-east 리전에 있는 쿠버네티스 클러스터. 여기서 catalog 서비스를 실행할 것이다.

 

#실습환경 - west 배포

# 
kind create cluster --name west --image kindest/node:v1.23.17 --kubeconfig ./west-kubeconfig --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # istio-ingrssgateway HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # kube-ops-view
    hostPort: 30005
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.100.0.0/24
EOF

# 설치 확인
docker ps
cat west-kubeconfig
kubectl get node --kubeconfig=./west-kubeconfig
kubectl get pod -A --kubeconfig=./west-kubeconfig
    
# 노드에 기본 툴 설치
docker exec -it west-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30005 --set env.TZ="Asia/Seoul" --namespace kube-system --kubeconfig=./west-kubeconfig
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view --kubeconfig=./west-kubeconfig

## kube-ops-view 접속 URL 확인
open "http://localhost:30005/#scale=1.5"
open "http://localhost:30005/#scale=1.3"

 

 

# 실습환경 - east 배포

# 
kind create cluster --name east --image kindest/node:v1.23.17 --kubeconfig ./east-kubeconfig --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 31000 # istio-ingrssgateway HTTP
    hostPort: 31000
  - containerPort: 31001 # Prometheus
    hostPort: 31001
  - containerPort: 31002 # Grafana
    hostPort: 31002
  - containerPort: 31003 # Kiali
    hostPort: 31003
  - containerPort: 31004 # Tracing
    hostPort: 31004
  - containerPort: 31005 # kube-ops-view
    hostPort: 31005
networking:
  podSubnet: 10.20.0.0/16
  serviceSubnet: 10.200.0.0/24
EOF

# 설치 확인
docker ps
cat east-kubeconfig
kubectl get node --kubeconfig=./east-kubeconfig
kubectl get pod -A --kubeconfig=./east-kubeconfig

# 노드에 기본 툴 설치
docker exec -it east-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=31005 --set env.TZ="Asia/Seoul" --namespace kube-system --kubeconfig=./east-kubeconfig
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view --kubeconfig=./east-kubeconfig

## kube-ops-view 접속 URL 확인
open "http://localhost:31005/#scale=1.5"
open "http://localhost:31005/#scale=1.3"

 

# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 172.18.0.100
/east-control-plane 172.18.0.3
/west-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 172.18.0.3
docker exec -it mypc ping -c 1 west-control-plane
docker exec -it mypc ping -c 1 east-control-plane

#
docker exec -it west-control-plane ping -c 1 east-control-plane
docker exec -it east-control-plane ping -c 1 west-control-plane

docker exec -it west-control-plane ping -c 1 mypc
docker exec -it east-control-plane ping -c 1 mypc
#metal LB 배포

# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml \
  --kubeconfig=./west-kubeconfig

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml \
  --kubeconfig=./east-kubeconfig

# 확인
kubectl get crd --kubeconfig=./west-kubeconfig                  
kubectl get crd --kubeconfig=./east-kubeconfig           
       
kubectl get pod -n metallb-system --kubeconfig=./west-kubeconfig
kubectl get pod -n metallb-system --kubeconfig=./east-kubeconfig


# IPAddressPool, L2Advertisement 설정
cat << EOF | kubectl apply --kubeconfig=./west-kubeconfig -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

cat << EOF | kubectl apply --kubeconfig=./east-kubeconfig -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.201-172.18.255.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A --kubeconfig=./west-kubeconfig
kubectl get IPAddressPool,L2Advertisement -A --kubeconfig=./east-kubeconfig
#샘플 애플리케이션 배포 후 확인
#
cat << EOF | kubectl apply --kubeconfig=./west-kubeconfig -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF

#
cat << EOF | kubectl apply --kubeconfig=./east-kubeconfig -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF

# 확인
kubectl get deploy,pod,svc,ep --kubeconfig=./west-kubeconfig
kubectl get deploy,pod,svc,ep --kubeconfig=./east-kubeconfig

kubectl get svc nginx-service --kubeconfig=./west-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
kubectl get svc nginx-service --kubeconfig=./east-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

WNIP=$(kubectl get svc nginx-service --kubeconfig=./west-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
ENIP=$(kubectl get svc nginx-service --kubeconfig=./east-kubeconfig -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 외부(mypc)에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
docker exec -it mypc curl -s $WNIP
docker exec -it mypc curl -s $WNIP -v -I

docker exec -it mypc curl -s $ENIP
docker exec -it mypc curl -s $ENIP -v -I


# 확인 후 삭제
kubectl delete deploy,svc --all --kubeconfig=./west-kubeconfig
kubectl delete deploy,svc --all --kubeconfig=./east-kubeconfig


#
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
alias keast='kubectl --kubeconfig=./east-kubeconfig'

# 확인
kwest get node
keast get node

 

 

12.3.3 플러그인 CA 인증서 설정
기본 CA 대신 사용자 정의 CA를 적용해 클러스터 간 공통 신뢰를 구축한다. cacerts 시크릿에 루트/중간 CA 인증서와 키를 포함시켜 Istio가 워크로드 인증서에 서명하도록 한다. 제공된 스크립트로 동일 루트 CA에서 파생된 중간 CA를 생성해 다중 클러스터 메시 보안을 통합한다.

플러그인 CA 인증서 적용하기

# cat ./ch12/scripts/generate-certificates.sh
#!/bin/bash

set -ex

cert_dir=`dirname "$BASH_SOURCE"`/../certs

echo "Clean up contents of dir './chapter12/certs'"
rm -rf ${cert_dir}

echo "Generating new certificates"
mkdir -p ${cert_dir}/west-cluster
mkdir -p ${cert_dir}/east-cluster

# step CLI 설치 확인 : step CLI가 설치되어 있지 않으면 에러 출력 후 종료.
## macOS : brew install step
if ! [ -x "$(command -v step)" ]; then 
  echo 'Error: Install the smallstep cli (https://github.com/smallstep/cli)'
  exit 1
fi

step certificate create root.istio.in.action ${cert_dir}/root-cert.pem ${cert_dir}/root-ca.key \
  --profile root-ca --no-password --insecure --san root.istio.in.action \
  --not-after 87600h --kty RSA 

step certificate create west.intermediate.istio.in.action ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/west-cluster/ca-key.pem --ca ${cert_dir}/root-cert.pem --ca-key ${cert_dir}/root-ca.key --profile intermediate-ca --not-after 87600h --no-password --insecure --san west.intermediate.istio.in.action --kty RSA 
step certificate create east.intermediate.istio.in.action ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/east-cluster/ca-key.pem --ca ${cert_dir}/root-cert.pem --ca-key ${cert_dir}/root-ca.key --profile intermediate-ca --not-after 87600h --no-password --insecure --san east.intermediate.istio.in.action --kty RSA 

cat ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/west-cluster/cert-chain.pem
cat ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/east-cluster/cert-chain.pem

 

#앞서 구성한 실습 환경 확인
tree ch12/certs 
ch12/certs
├── east-cluster
│   ├── ca-cert.pem    # 중간 CA 인증서
│   ├── ca-key.pem     # 중간 CA 개인zl
│   └── cert-chain.pem # 인증서 체인
├── root-ca.key        # 루트 인증서
├── root-cert.pem      # 루트 개인키
└── west-cluster
    ├── ca-cert.pem
    ├── ca-key.pem
    └── cert-chain.pem

# (참고) 인증서 체인 생성
## 중간 CA 인증서(ca-cert.pem)와 루트 CA 인증서(root-cert.pem)를 결합 -> 결과는 {west/east}-cluster/cert-chain.pem 에 저장
cat ${cert_dir}/west-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/west-cluster/cert-chain.pem
cat ${cert_dir}/east-cluster/ca-cert.pem ${cert_dir}/root-cert.pem > ${cert_dir}/east-cluster/cert-chain.pem


# 인증서 계층 구조
root.istio.in.action (Root CA)
   │
   └── east.intermediate.istio.in.action (Intermediate CA)


# 루트 CA 인증서 확인
openssl x509 -in ch12/certs/root-cert.pem -noout -text
...
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=root.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1
...

#
openssl x509 -in ch12/certs/east-cluster/ca-cert.pem -noout -text
...
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=east.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0 # 이 중간 CA는 추가적인 하위 CA를 만들 수 없음
...

openssl x509 -in ch12/certs/east-cluster/cert-chain.pem -noout -text
        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=east.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

#
openssl x509 -in ch12/certs/west-cluster/ca-cert.pem -noout -text
openssl x509 -in ch12/certs/west-cluster/cert-chain.pem -noout -text
# istio-system 네임스페이스를 만든 후 인증서를 cacerts 라는 시크릿으로 배포하여 각 클러스터에 중간 CA를 만들어두기
# west-cluster 용 인증서 설정하기
kwest create namespace istio-system
kwest create secret generic cacerts -n istio-system \
--from-file=ch12/certs/west-cluster/ca-cert.pem \
--from-file=ch12/certs/west-cluster/ca-key.pem \
--from-file=ch12/certs/root-cert.pem \
--from-file=ch12/certs/west-cluster/cert-chain.pem

# east-cluster 용 인증서 설정하기
keast create namespace istio-system
keast create secret generic cacerts -n istio-system \
--from-file=ch12/certs/east-cluster/ca-cert.pem \
--from-file=ch12/certs/east-cluster/ca-key.pem \
--from-file=ch12/certs/root-cert.pem \
--from-file=ch12/certs/east-cluster/cert-chain.pem

# 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get ns istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret cacerts  -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl view-secret cacerts -n istio-system --all --kubeconfig=./$i-kubeconfig; echo; done

 

12.3.4 각 클러스터에 (Istiod) 컨트롤 플레인 설치하기* Installing the control planes in each cluster

클러스터에 네트워크 메타데이터 추가로 이스티오가 토폴로지 인지 → 근접 워크로드 우선 라우팅 및 동서 게이트웨이 자동 설정 가능하다.

 

#클러스터 간 연결을 위해 네트워크에 레이블 붙이기
kwest label namespace istio-system topology.istio.io/network=west-network
keast label namespace istio-system topology.istio.io/network=east-network

# 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get ns istio-system --show-labels --kubeconfig=./$i-kubeconfig; echo; done

 

 

# cat ./ch12/controlplanes/cluster-west.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways: # 이그레스 게이트웨이 비활성화
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh # 메시 이름
      multiCluster:
        clusterName: west-cluster # 멀티 클러스터 메시 내부의 클러스터 식별자
      network: west-network # 이 설치가 이뤄지는 네트워크
      
      
# cat ./ch12/controlplanes/cluster-east.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways: # 이그레스 게이트웨이 비활성화
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh # 메시 이름
      multiCluster:
        clusterName: east-cluster
      network: east-network

 

 

# west-control-plane 진입 후 설치 진행
docker exec -it west-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# IstioOperator 파일 작성
cat << EOF > west-istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: west-cluster
      network: west-network
EOF

# 컨트롤 플레인 배포
istioctl install -f west-istio.yaml --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kwest get istiooperators -n istio-system -o yaml
...
        meshID: usmesh
        meshNetworks: {}
        mountMtlsCerts: false
        multiCluster:
          clusterName: west-cluster
          enabled: false
        network: west-network
...

kwest get all,svc,ep,sa,cm,secret,pdb -n istio-system
kwest get secret -n istio-system cacerts -o json # 미리 만들어둔 인증서/키 확인

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kwest patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kwest patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kwest describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kwest patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kwest patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kwest patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kwest patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

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

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

 

 

이번엔 east 클러스터에도 비슷하게 설정해준다.

# west-control-plane 진입 후 설치 진행
docker exec -it east-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# IstioOperator 파일 작성
cat << EOF > east-istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-controlplane
  namespace: istio-system
spec:
  profile: demo
  components:
    egressGateways:
    - name: istio-egressgateway
      enabled: false
  values:
    global:
      meshID: usmesh
      multiCluster:
        clusterName: east-cluster
      network: east-network
EOF

# 컨트롤 플레인 배포
istioctl install -f east-istio.yaml --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
keast get istiooperators -n istio-system -o yaml
...
        meshID: usmesh
        meshNetworks: {}
        mountMtlsCerts: false
        multiCluster:
          clusterName: east-cluster
          enabled: false
        network: east-network
...

keast get all,svc,ep,sa,cm,secret,pdb -n istio-system
keast get secret -n istio-system cacerts -o json # 미리 만들어둔 인증서/키 확인


# NodePort 변경 및 nodeport 31001~31003으로 변경 : prometheus(31001), grafana(31002), kiali(31003), tracing(31004)
keast patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 31001}]}}'
keast patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 31002}]}}'
keast patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 31003}]}}'
keast patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 31004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:31001

# Grafana 접속
open http://127.0.0.1:31002

# Kiali 접속
open http://127.0.0.1:31003

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

 

# istioctl alias 설정

#
docker exec -it west-control-plane istioctl -h
docker exec -it east-control-plane istioctl -h

#
alias iwest='docker exec -it west-control-plane istioctl'
alias ieast='docker exec -it east-control-plane istioctl'

#
iwest proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                      VERSION
istio-ingressgateway-5db74c978c-9qmt9.istio-system     west-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-5585445f4c-zf8g6     1.17.8

ieast proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
istio-ingressgateway-7f6f8f8d99-4mlp5.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8

#
iwest proxy-config secret deploy/istio-ingressgateway.istio-system
RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
default           Cert Chain     ACTIVE     true           80349990876331570640723939723244297816      2025-05-17T08:47:28Z     2025-05-16T08:45:28Z
ROOTCA            CA             ACTIVE     true           100900981840825465297757884708490534092     2032-06-25T14:11:35Z     2022-06-28T14:11:35Z

iwest proxy-config secret deploy/istio-ingressgateway.istio-system -o json
...

# 아래는 default 에 inlineBytes 값을 decode64 -d 시 3개의 인증서 정보 출력 후 각 개별 인증서를 openssl x509 -in Y.pem -noout -text 로 출력 확인 
## (1) 사용자 인증서
-----BEGIN CERTIFICATE-----
MIIDdjCCAl6gAwIBAgIQPHLYaJhiIjAwJkg6cAVeWDANBgkqhkiG9w0BAQsFADAs
...
5xYNi7u0APTzE1swNbx2TF5eeTsFvYcbFh56ahLp0izGkahOv97bEgnZdeTsLRyH
K+5+1ZdJ2n8CuxoSY+FXUlMDwGjdvCXAKBM=
-----END CERTIFICATE-----

        Issuer: CN=west.intermediate.istio.in.action
        Validity
            Not Before: May 16 08:45:28 2025 GMT
            Not After : May 17 08:47:28 2025 GMT
        Subject: 
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                D3:83:9A:3A:51:D9:03:62:35:8F:6A:A4:DA:99:88:BB:74:70:4F:33
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account


## (2) 중간 CA 루트 인증서
-----BEGIN CERTIFICATE-----
MIIDPDCCAiSgAwIBAgIRAMkJ23sotpqiiWps+38Df/YwDQYJKoZIhvcNAQELBQAw
...
usSjiM6KR77xogslodbQw4QQG+w5HQOwMa1k8WTCNrplxdsnaQJjdqUwCdixicq2
DeHuSkz4cykAI/NWc2cZIw==
-----END CERTIFICATE-----

        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=west.intermediate.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

## (3) 최상위 루트 인증서
-----BEGIN CERTIFICATE-----
MIIDDTCCAfWgAwIBAgIQS+jSffZX7itohjyrautczDANBgkqhkiG9w0BAQsFADAf
...
3fRtDApNHbbmi7WXrM+pG4D+Buk2FUEHJVpu16Ch2K2vpRzpkliqes+T/5E92uY9
ob7MBgt61g4VZ/p8+RMJWYw=
-----END CERTIFICATE-----

        Issuer: CN=root.istio.in.action
        Validity
            Not Before: Jun 28 14:11:35 2022 GMT
            Not After : Jun 25 14:11:35 2032 GMT
        Subject: CN=root.istio.in.action
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1

#
iwest proxy-config listener deploy/istio-ingressgateway.istio-system
iwest proxy-config route deploy/istio-ingressgateway.istio-system
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system
iwest proxy-config secret deploy/istio-ingressgateway.istio-system
iwest proxy-config bootstrap deploy/istio-ingressgateway.istio-system
iwest proxy-config ecds deploy/istio-ingressgateway.istio-system

#
ieast proxy-config listener deploy/istio-ingressgateway.istio-system
ieast proxy-config route deploy/istio-ingressgateway.istio-system
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system
ieast proxy-config secret deploy/istio-ingressgateway.istio-system
ieast proxy-config bootstrap deploy/istio-ingressgateway.istio-system
ieast proxy-config ecds deploy/istio-ingressgateway.istio-system

 

두 클러스터 모두에 워크로드 실행하기 Running workloads on both clusters

#
kwest create ns istioinaction
kwest label namespace istioinaction istio-injection=enabled
kwest -n istioinaction apply -f ch12/webapp-deployment-svc.yaml
kwest -n istioinaction apply -f ch12/webapp-gw-vs.yaml
kwest -n istioinaction apply -f ch12/catalog-svc.yaml # Stub catalog service to which webapp makes request
cat ch12/catalog-svc.yaml
piVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: catalog
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: catalog


# 확인
kwest get deploy,pod,svc,ep -n istioinaction
kwest get svc,ep catalog -n istioinaction
NAME              TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.2.43   <none>        80/TCP    2m

NAME                ENDPOINTS   AGE
endpoints/catalog   <none>      2m

kwest get gw,vs,dr -A
NAMESPACE       NAME                                            AGE
istioinaction   gateway.networking.istio.io/coolstore-gateway   16s

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

#
iwest proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
istio-ingressgateway-5db74c978c-9j96n.istio-system     west-cluster     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-5585445f4c-zcvp4     1.17.8
webapp-5c8b4fff64-tfs8m.istioinaction                  west-cluster     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-5585445f4c-zcvp4     1.17.8

# endpoint 에 IP 는 10.10.0.0/16 대역들 다수 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS 

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog

 

 

stub 서비스 필요성 은 FQDN의 DNS 해결 실패를 방지해 애플리케이션 트래픽이 프록시로 리다이렉션될 수 있게 한다.

 

#east cluast에 catalog 배포

#
keast create ns istioinaction
keast label namespace istioinaction istio-injection=enabled
keast -n istioinaction apply -f ch12/catalog.yaml
cat ch12/catalog.yaml


# 확인
keast get deploy,pod,svc,ep -n istioinaction
keast get svc,ep catalog -n istioinaction
keast get gw,vs,dr -A # 없음

#
ieast proxy-status
NAME                                                   CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
catalog-6cf4b97d-ff7lq.istioinaction                   east-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-85976468f-9q8ck     1.17.8
istio-ingressgateway-7f6f8f8d99-fjn92.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-9q8ck     1.17.8

# endpoint 에 IP 는 10.20.0.0/16 대역들 다수 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS

ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

 

지금까지 실습환경 구성이 끝났다.

 

12.3.5 클러스터 간 워크로드 디스커버리 활성화하기 Enabling cross-cluster workload discovery

istio-reader-service-account 생성 → 원격 클러스터 인증 및 워크로드 정보 조회 가능, 토큰/인증서 공유로 보안 연결 설정.

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get sa -n istio-system --kubeconfig=./$i-kubeconfig; echo; done

# east
keast describe sa -n istio-system istio-reader-service-account      
...
Mountable secrets:   istio-reader-service-account-token-566n2
Tokens:              istio-reader-service-account-token-566n2

keast get sa -n istio-system istio-reader-service-account -o yaml
...
  name: istio-reader-service-account
  namespace: istio-system
  resourceVersion: "16805"
  uid: 40fa07c3-2e49-4003-a09b-afccdbcec7a2
secrets:
- name: istio-reader-service-account-token-566n2

keast get sa -n istio-system istio-reader-service-account -o jsonpath='{.secrets[0].name}'
eirsa=$(keast get sa -n istio-system istio-reader-service-account -o jsonpath='{.secrets[0].name}')
keast get secret -n istio-system $eirsa
keast get secret -n istio-system $eirsa -o json
{
    "apiVersion": "v1",
    "data": {
        "ca.crt": "LS0t,,,==",
        "namespace": "aXN0aW8tc3lzdGVt", # istio-system
        "token": "ZXl...VN2Zw=="
    },
...

kubectl rolesum istio-reader-service-account -n istio-system --kubeconfig=./east-kubeconfig
...
Policies:

• [CRB] */istio-reader-clusterrole-istio-system ⟶  [CR] */istio-reader-clusterrole-istio-system
  Resource                                                                                         Name  Exclude  Verbs  G L W C U P D DC  
  *.[config.istio.io,security.istio.io,networking.istio.io,authentication.istio.io,rbac.istio.io]  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  customresourcedefinitions.apiextensions.k8s.io                                                   [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  endpoints                                                                                        [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  endpointslices.discovery.k8s.io                                                                  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  namespaces                                                                                       [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  nodes                                                                                            [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  pods                                                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  replicasets.apps                                                                                 [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  replicationcontrollers                                                                           [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  secrets                                                                                          [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  serviceexports.multicluster.x-k8s.io                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✖ ✖ ✔ ✖   
  serviceimports.multicluster.x-k8s.io                                                             [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  services                                                                                         [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  subjectaccessreviews.authorization.k8s.io                                                        [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  tokenreviews.authentication.k8s.io                                                               [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖   
  workloadentries.networking.istio.io                                                              [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   


• [CRB] */istio-reader-istio-system ⟶  [CR] */istio-reader-istio-system
  Resource                                                                                         Name  Exclude  Verbs  G L W C U P D DC  
  *.[config.istio.io,security.istio.io,networking.istio.io,authentication.istio.io,rbac.istio.io]  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
...

keast auth can-i --list
keast auth can-i --as=system:serviceaccount:istio-system:istio-reader-service-account --list
Resources                                        Non-Resource URLs                     Resource Names   Verbs
tokenreviews.authentication.k8s.io               []                                    []               [create]
selfsubjectaccessreviews.authorization.k8s.io    []                                    []               [create]
selfsubjectrulesreviews.authorization.k8s.io     []                                    []               [create]
subjectaccessreviews.authorization.k8s.io        []                                    []               [create]
serviceexports.multicluster.x-k8s.io             []                                    []               [get list watch create delete]
endpoints                                        []                                    []               [get list watch]
namespaces                                       []                                    []               [get list watch]
nodes                                            []                                    []               [get list watch]
pods                                             []                                    []               [get list watch]
replicationcontrollers                           []                                    []               [get list watch]
secrets                                          []                                    []               [get list watch]
services                                         []                                    []               [get list watch]
...

#
ieast x create-remote-secret --help
Create a secret with credentials to allow Istio to access remote Kubernetes apiservers

ieast x create-remote-secret --name="east-cluster"
# This file is autogenerated, do not edit.
apiVersion: v1
kind: Secret
metadata:
  annotations:
    networking.istio.io/cluster: east-cluster
  creationTimestamp: null
  labels:
    istio/multiCluster: "true" # 이 레이블이 true로 설정된 시크릿은 이스티오의 컨트롤 플레인이 새 클러스터를 등록하기 위해 감시한다
  name: istio-remote-secret-east-cluster
  namespace: istio-system
stringData:
  east-cluster: |
    apiVersion: v1
    clusters:
    - cluster: # 아래 'certificate-authority-data'는 이 클러스터에 보안 커넥션을 시작하는 데 사용하는 CA
        certificate-authority-data: LS0tLS1CR....
        server: https://east-control-plane:6443
      name: east-cluster
    contexts:
    - context:
        cluster: east-cluster
        user: east-cluster
      name: east-cluster
    current-context: east-cluster
    kind: Config
    preferences: {}
    users:
    - name: east-cluster
      user: # 아래 'token'은 서비스 어카운트의 ID를 나타내는 토큰
        token: eyJhb...
---

## certificate-authority-data 정보 : k8s 루트 인증서
openssl x509 -in YYY -noout -text
...
        Issuer: CN=kubernetes
        Validity
            Not Before: May 16 05:13:20 2025 GMT
            Not After : May 14 05:13:20 2035 GMT
        Subject: CN=kubernetes
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE

## user.token 정보 : 
jwt decode YYY
Token header
------------
{
  "alg": "RS256",
  "kid": "oyrLHJhI-1aEvcPmbnFZEI6avASPC3fJttjoEaXt-iU"
}

Token claims
------------
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "istio-system",
  "kubernetes.io/serviceaccount/secret.name": "istio-reader-service-account-token-566n2",
  "kubernetes.io/serviceaccount/service-account.name": "istio-reader-service-account",
  "kubernetes.io/serviceaccount/service-account.uid": "40fa07c3-2e49-4003-a09b-afccdbcec7a2",
  "sub": "system:serviceaccount:istio-system:istio-reader-service-account"
}

 

# 시크릿 내용을 출력하는 대신, kubectl 명령어에 파이프해 west-cluster 에 적용하자
# west 에 시크릿 생성
ieast x create-remote-secret --name="east-cluster" | kwest apply -f -
secret/istio-remote-secret-east-cluster created

# istiod 로그 확인 : 시크릿이 생성되면, 바로 istiod가 이 시크릿을 가지고 새로 추가된 원격 클러스터(east)에 워크로드를 쿼리한다.
kwest logs deploy/istiod -n istio-system | grep 'Adding cluster'
2025-05-16T22:45:00.679666Z     info    Adding cluster  cluster=east-cluster secret=istio-system/istio-remote-secret-east-cluster

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
kwest get secret -n istio-system istio-remote-secret-east-cluster
kwest get secret -n istio-system istio-remote-secret-east-cluster -o yaml
...

# west 확인 : east 의 모든 CDS/EDS 정보를 west 에서도 확인 가능!
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# west 에서 10.20.0.15(10.20.0.0/16)로 라우팅이 가능한 상태인가?
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...
                "address": {
                    "socketAddress": {
                        "address": "10.20.0.15",
                        "portValue": 3000
...

# east 확인 : west 의 CDS/EDS 정보를 아직 모름!
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep catalog
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

 

 서로다른 대역(10.10.x 와 10.20.x)이 동시에 불러와지는데, west에서 east 정보가 가져와지는것을 알 수 있다. east 가 west 쿼리 할 수 있게 반대로도 설정하자.

# east 에 시크릿 생성
iwest x create-remote-secret --name="west-cluster" | keast apply -f -
secret/istio-remote-secret-west-cluster created

# istiod 로그 확인 : 시크릿이 생성되면, 바로 istiod가 이 시크릿을 가지고 새로 추가된 원격 클러스터(east)에 워크로드를 쿼리한다.
keast logs deploy/istiod -n istio-system | grep 'Adding cluster'
2025-05-17T00:09:02.438756Z     info    Adding cluster  cluster=west-cluster secret=istio-system/istio-remote-secret-west-cluster

#
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get secret -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
keast get secret -n istio-system istio-remote-secret-west-cluster
keast get secret -n istio-system istio-remote-secret-west-cluster -o yaml
...

# east 확인 : west 의 모든 CDS/EDS 정보를 east 에서도 확인 가능!
for i in listener route cluster endpoint; do echo ">> k8s cluster : east - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
ieast proxy-config cluster deploy/istio-ingressgateway.istio-system | grep webapp

# east 에서 10.10.0.15(10.10.0.0/16)로 라우팅이 가능한 상태인가?
ieast proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep webapp
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

 

이제 컨트롤 플레인이 상대 클러스터의 워크로드를 퀴리할 수 있다.

 

12.3.6 클러스터 간 연결 설정하기 Setting up cross-cluster connectivity

north-south 트래픽: 외부→내부 네트워크 진입 트래픽으로, 이스티오 인그레스 게이트웨이를 통해 처리된다.

east-west 트래픽: 내부 네트워크 간 통신으로, VPC 피어링 불가 시 이스티오 east-west 게이트웨이를 사용해 크로스 클라우드/온프레미스 연결을 구성한다.

클러스터 간 트래픽을 암호화된 상호 인증으로 투명하게 라우팅하며, 운영자 추가 설정 없이 정밀한 트래픽 제어가 가능하다.

 

SNI 클러스터SNI 자동 통과 기능이 게이트웨이 동작을 구현해 클러스터 경계 넘어 자동화된 라우팅을 지원한다.

클라이언트는 SNI 헤더에 목적지 클러스터 정보(서비스명·포트·버전)를 인코딩해 전송하며, 게이트웨이는 이를 파싱해 암호화된 트래픽을 정확한 워크로드로 라우팅한다.mTLS 연결 유지 상태에서 프록시가 트래픽을 복호화 없이 전달하므로 보안성을 확보하면서도 클러스터 간 세부 라우팅 제어가 가능하다. 이 구조는 운영자의 추가 설정 없이 자동화된 크로스 클러스터 통신과 상호 인증을 동시에 구현한다.

 

SNI 클러스터가 있는 east-west 게이트웨이 설치하기 Installing the east-west gateway with SNI clusters

east-west 게이트웨이에 SNI 클러스터를 활성화하려면 IstioOperator에서 ISTIO_META_ROUTER_MODE를 sni-dnat으로 설정하면 된다. 이 설정으로 게이트웨이가 SNI 기반 트래픽 라우팅을 자동 지원하며, 추가 리소스 없이 다중 클러스터 mTLS 통신이 가능해진다.

# cat ch12/gateways/cluster-east-eastwest-gateway.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-eastwestgateway # IstioOperator 이름은 앞 선 이스티오 설정 이름과 겹치지 않아야 한다
  namespace: istio-system
spec:
  profile: empty # empty 프로필은 추가 이스티오 구성 요소를 설치하지 않는다
  components:
    ingressGateways:
    - name: istio-eastwestgateway # 게이트웨이 이름
      label:
        istio: eastwestgateway
        app: istio-eastwestgateway
        topology.istio.io/network: east-network
      enabled: true
      k8s:
        env:
        - name: ISTIO_META_ROUTER_MODE # sni-dnat 모드는 트래픽을 프록시하는 데 필요한 SNI 클러스터를 추가한다
          value: "sni-dnat"
        # The network to which traffic is routed
        - name: ISTIO_META_REQUESTED_NETWORK_VIEW # 게이트웨이가 트래픽을 라우팅하는 네트워크
          value: east-network
        service:
          ports:
          ... (생략) ...
  values:
    global:
      meshID: usmesh # 메시, 클러스터, 네트워크 식별 정보
      multiCluster:
        clusterName: east-cluster
      network: east-network
# 설치 전 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
kwest get IstioOperator -n istio-system installed-state-istio-controlplane -o yaml
keast get IstioOperator -n istio-system installed-state-istio-controlplane -o yaml

# 설치 전 확인 : west 에서 catalog endpoint 에 IP 확인
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep catalog
10.20.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# IstioOperator 로 east 클러스터에 east-west 게이트웨이를 설치
cat ch12/gateways/cluster-east-eastwest-gateway.yaml
docker cp ./ch12/gateways/cluster-east-eastwest-gateway.yaml east-control-plane:/cluster-east-eastwest-gateway.yaml
ieast install -f /cluster-east-eastwest-gateway.yaml --set values.global.proxy.privileged=true -y

# east 클러스터에 east-west 게이트웨이를 설치 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
...
NAME                                     READY   STATUS    RESTARTS        AGE
istio-eastwestgateway-866794c798-k9xfl   1/1     Running   0               14s
istio-ingressgateway-7f6f8f8d99-4mlp5    1/1     Running   1 (3h38m ago)   17h
istiod-85976468f-vp4zg                   1/1     Running   1 (3h38m ago)   17h

keast get IstioOperator -n istio-system installed-state-istio-eastwestgateway -o yaml
...
    ingressGateways:
    - enabled: true
      k8s:
        env:
        - name: ISTIO_META_ROUTER_MODE
          value: sni-dnat
        - name: ISTIO_META_REQUESTED_NETWORK_VIEW
          value: east-network
        service:
          ports:
          - name: status-port
            port: 15021
            targetPort: 15021
          - name: mtls
            port: 15443
            targetPort: 15443
          - name: tcp-istiod
            port: 15012
            targetPort: 15012
          - name: tcp-webhook
            port: 15017
            targetPort: 15017
      label:
        app: istio-eastwestgateway
        istio: eastwestgateway
        topology.istio.io/network: east-network
      name: istio-eastwestgateway
...

# east 정보 확인
ieast proxy-status
NAME                                                    CLUSTER          CDS        LDS        EDS        RDS          ECDS         ISTIOD                     VERSION
catalog-6cf4b97d-9c995.istioinaction                    east-cluster     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-85976468f-vp4zg     1.17.8
istio-eastwestgateway-866794c798-k9xfl.istio-system     east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8
istio-ingressgateway-7f6f8f8d99-4mlp5.istio-system      east-cluster     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-85976468f-vp4zg     1.17.8

# east 에 istio-ingressgateway  에 istio-config 정보 확인 : west 의 CDS/EDS 모두 알고 있음!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# east 에 istio-eastwestgateway 에 istio-config 정보 확인 : webapp(CDS) OK, west 에 EDS 아직 모름!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : eastwestgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-eastwestgateway.istio-system; echo; done

ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system | grep istioinaction
catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS            
webapp.istioinaction.svc.cluster.local                       80        -          outbound      EDS

ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
ieast proxy-config cluster deploy/istio-eastwestgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json | grep sni
                        "sni": "outbound_.80_._.webapp.istioinaction.svc.cluster.local"

ieast proxy-config endpoint deploy/istio-eastwestgateway.istio-system | grep istioinaction
ieast proxy-config endpoint deploy/istio-eastwestgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json


# west 정보 확인
iwest proxy-status

# west 에 istio-ingressgateway 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system | grep istioinaction
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json | grep sni
                        "sni": "outbound_.80_._.catalog.istioinaction.svc.cluster.local"

# west 에 istio-ingressgateway : east EDS 모든 정보에서 east의 eastwestgateway에 mtls 정보로 변경!
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep istioinaction
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# 출력되는 172.18.X.Y에 IP 확인 : east 에 eastwestgateway 의 Service(LoadBalancer)의 External-IP.
keast get svc,ep -n istio-system istio-eastwestgateway
NAME                            TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)                                                           AGE
service/istio-eastwestgateway   LoadBalancer   10.200.0.90   172.18.255.202   15021:32281/TCP,15443:30196/TCP,15012:32628/TCP,15017:32346/TCP   27m

NAME                              ENDPOINTS                                                        AGE
endpoints/istio-eastwestgateway   10.20.0.16:15021,10.20.0.16:15017,10.20.0.16:15012 + 1 more...   27m


# west 에 webapp 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : webapp - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/webapp.istioinaction; echo; done
iwest proxy-config endpoint deploy/webapp.istioinaction | grep istioinaction
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local


# west 에서 호출 시도
kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    63m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    63m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            63m
endpoints/webapp    10.10.0.15:8080   63m

kwest exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl catalog.istioinaction.svc.cluster.local -v
*   Trying 10.100.0.170:80...
* connect to 10.100.0.170 port 80 failed: Connection refused
...

배포전
배포후

 

east-west 게이트웨이 설치 후 라우터 모드를 sni-dnat으로 설정하면, SNI 자동 통과 모드를 통해 다중 클러스터 mTLS 포트를 노출시켜 암호화된 트래픽을 복호화 없이 목적지 클러스터로 라우팅할 수 있다.

# cat ch12/gateways/expose-services.yaml              
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: cross-network-gateway
  namespace: istio-system
spec:
  selector:
    istio: eastwestgateway # 셀렉터와 일치하는 게이트웨이에만 설정이 적용된다.
  servers:
    - port:
        number: 15443 # 이스티오에서 15443 포트는 멀티 클러스터 상호 TLS 트래픽 용도로 지정된 특수 포트다
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH # SNI 헤더를 사용해 목적지를 해석하고 SNI 클러스터를 사용한다.
      hosts:
        - "*.local" # 정규식 *.local 과 일치하는 SNI에 대해서만 트래픽을 허용한다.

 

# east 클러스터에 적용하자. east-cluster의 워크로드를 west-cluster 에 노출한다.
cat ch12/gateways/expose-services.yaml
keast apply -n istio-system -f ch12/gateways/expose-services.yaml

# 확인
keast get gw,vs,dr -A
NAMESPACE      NAME                                                AGE
istio-system   gateway.networking.istio.io/cross-network-gateway   85s

# west 에서 호출 시도
kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    63m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    63m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            63m
endpoints/webapp    10.10.0.15:8080   63m

kwest exec -it deploy/webapp -c istio-proxy -n istioinaction -- curl catalog.istioinaction.svc.cluster.local -v
*   Trying 10.100.0.170:80...
* connect to 10.100.0.170 port 80 failed: Connection refused
...


# east 에 istio-ingressgateway  에 istio-config 정보 확인 : 이전 내용과 동일하게 west 의 CDS/EDS 모두 알고 있음!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# east 에 istio-eastwestgateway 에 istio-config 정보 확인 : SNI 자동 통과를 위한 listener 추가 확인!
for i in listener route cluster endpoint; do echo ">> east k8s cluster : eastwestgateway - istio-config $i <<"; docker exec -it east-control-plane istioctl proxy-config $i deploy/istio-eastwestgateway.istio-system; echo; done

ieast proxy-config listener deploy/istio-eastwestgateway.istio-system
ieast proxy-config listener deploy/istio-eastwestgateway.istio-system | grep istioinaction
0.0.0.0 15443 SNI: outbound_.80_._.webapp.istioinaction.svc.cluster.local; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2                    Cluster: outbound_.80_._.webapp.istioinaction.svc.cluster.local
0.0.0.0 15443 SNI: outbound_.80_._.catalog.istioinaction.svc.cluster.local; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2                   Cluster: outbound_.80_._.catalog.istioinaction.svc.cluster.local

ieast proxy-config listener deploy/istio-eastwestgateway.istio-system --port 15443  -o json
...
                "filterChainMatch": {
                    "serverNames": [
                        "outbound_.80_._.catalog.istioinaction.svc.cluster.local"
                 ...
                },
                "filters": [
                    ...
                    {
                        "name": "envoy.filters.network.tcp_proxy",
                        "typedConfig": {
                            "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
                            "statPrefix": "outbound_.80_._.catalog.istioinaction.svc.cluster.local",
                            "cluster": "outbound_.80_._.catalog.istioinaction.svc.cluster.local",
...



# west 정보 확인
iwest proxy-status

# west 에 istio-ingressgateway 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : ingressgateway - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done

# west 에 webapp 에 istio-config 정보 확인
for i in listener route cluster endpoint; do echo ">> west k8s cluster : webapp - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/webapp.istioinaction; echo; done

 

#반대편 클러스터에도 작업 수행. west-cluster 에 east-west 게이트웨이를 만들고, 그 서비스를 east-cluster 워크로드에 노출.

# IstioOperator 로 west 클러스터에 east-west 게이트웨이를 설치
cat ch12/gateways/cluster-west-eastwest-gateway.yaml
docker cp ./ch12/gateways/cluster-west-eastwest-gateway.yaml west-control-plane:/cluster-west-eastwest-gateway.yaml
iwest install -f /cluster-west-eastwest-gateway.yaml --set values.global.proxy.privileged=true -y

# west 클러스터에 east-west 게이트웨이를 설치 확인
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get IstioOperator -n istio-system --kubeconfig=./$i-kubeconfig; echo; done
for i in west east; do echo ">> k8s cluster : $i <<"; kubectl get pod -n istio-system -l istio.io/rev=default --kubeconfig=./$i-kubeconfig; echo; done
kwest get IstioOperator -n istio-system installed-state-istio-eastwestgateway -o yaml
iwest proxy-status


# west 클러스터에 적용하자. east-cluster의 워크로드를 west-cluster 에 노출한다.
cat ch12/gateways/expose-services.yaml
kwest apply -n istio-system -f ch12/gateways/expose-services.yaml

# 확인
kwest get gw,vs,dr -A
NAMESPACE       NAME                                                AGE
istio-system    gateway.networking.istio.io/cross-network-gateway   5s
istioinaction   gateway.networking.istio.io/coolstore-gateway       89m

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

kwest get svc,ep -n istioinaction
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.100.0.170   <none>        80/TCP    93m
service/webapp    ClusterIP   10.100.0.141   <none>        80/TCP    93m

NAME                ENDPOINTS         AGE
endpoints/catalog   <none>            93m
endpoints/webapp    10.10.0.15:8080   93m

ieast pc clusters deploy/istio-eastwestgateway.istio-system | grep catalog
catalog.istioinaction.svc.cluster.local                                       80        -          outbound      EDS            
outbound_.80_._.catalog.istioinaction.svc.cluster.local                       -         -          -             EDS 

# sni 클러스터 확인
ieast pc clusters deploy/istio-eastwestgateway.istio-system | grep catalog | awk '{printf "CLUSTER: %s\n", $1}'
CLUSTER: catalog.istioinaction.svc.cluster.local
CLUSTER: outbound_.80_._.catalog.istioinaction.svc.cluster.local # catalog 서비스용 SNI 클러스터

지금까지의 설정으로 SNI 자동 통과로 클러스터 간 라우팅 활성화 → 제어 플레인이 원격 엔드포인트 감지 후 워크로드 설정 업데이트, mTLS 기반 보안 통신 유지한다.

 

클러스터 간 워크로드 디스커버리 검증하기* VALIDATING CROSS-CLUSTER WORKLOAD DISCOVERY

east-west 게이트웨이 IP 확인으로 webapp 엔보이의 catalog 엔드포인트 노출 검증 → 클러스터 간 트래픽 라우팅 가능 확인한다.

#이를 확인하기 위해 east-cluster 의 east-west 게이트웨이 주소(Service)를 알아보자.
keast -n istio-system get svc istio-eastwestgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
172.18.255.202



# 이제 이 값을 west-cluster 의 워크로드가 클러스터 간 트래픽을 라우팅할 때 사용하는 주소와 비교해보자.
iwest pc endpoints deploy/webapp.istioinaction | grep catalog
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
# west 에 istio-ingressgateway 인입을 위한 접속 정보 확인
kwest get svc -n istio-system istio-ingressgateway
NAME                           TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)                                                                      AGE
service/istio-ingressgateway   LoadBalancer   10.100.0.82   172.18.255.101   15021:30627/TCP,80:30000/TCP,443:31615/TCP,31400:32694/TCP,15443:32016/TCP   119m

EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $EXT_IP
172.18.255.101

#
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog | jq
[
  {
    "id": 1,
    "color": "amber",
...

# 신규 터미널 : 반복 접속
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

 

 

다른 클러스터를 통해서 트래픽이 통신되는것을 알 수 있다.

 

 

12.3.7 클러스터 간 로드 밸런싱 Load-balancing across clusters

#
tree ch12/locality-aware/west
ch12/locality-aware/west
├── simple-backend-deployment.yaml
├── simple-backend-dr.yaml
├── simple-backend-gw.yaml
├── simple-backend-svc.yaml
└── simple-backend-vs.yaml

# west-cluster 에 간단한 백엔드 디플로이먼트/서비스를 배포
cat ch12/locality-aware/west/simple-backend-deployment.yaml
...
        - name: "MESSAGE"
          value: "Hello from WEST"    
...

cat ch12/locality-aware/west/simple-backend-svc.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-deployment.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-svc.yaml
kwest get deploy -n istioinaction simple-backend-west
kwest get svc,ep -n istioinaction simple-backend

# 트래픽을 허용하기 위해 Gateway, 게이트웨이에서 백엔드 워크로드로 트래픽을 라우팅하기 위해 VirtualService 적용
cat ch12/locality-aware/west/simple-backend-gw.yaml
cat ch12/locality-aware/west/simple-backend-vs.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-gw.yaml
kwest apply -f ch12/locality-aware/west/simple-backend-vs.yaml
kwest get gw,vs,dr -n istioinaction
NAME                                                 AGE
gateway.networking.istio.io/coolstore-gateway        7h15m
gateway.networking.istio.io/simple-backend-gateway   3m10s

NAME                                                               GATEWAYS                     HOSTS                                 AGE
virtualservice.networking.istio.io/simple-backend-vs-for-gateway   ["simple-backend-gateway"]   ["simple-backend.istioinaction.io"]   3m10s
virtualservice.networking.istio.io/webapp-virtualservice           ["coolstore-gateway"]        ["webapp.istioinaction.io"]           7h15m

 

 

# west-cluster의 서비스로 요청하고 클러스터 이름을 반환하는지 확인
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body"
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP
{
  "name": "simple-backend-west",
  "uri": "/",
  "type": "HTTP",
  "ip_addresses": [
    "10.10.0.17"
  ],
  "start_time": "2025-05-17T14:48:43.973591",
  "end_time": "2025-05-17T14:48:44.124935",
  "duration": "151.346ms",
  "body": "Hello from WEST",
  "code": 200
}

# 신규 터미널 : 반복 접속
alias kwest='kubectl --kubeconfig=./west-kubeconfig'
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done



#east에도 배포
tree ch12/locality-aware/east
ch12/locality-aware/east
├── simple-backend-deployment.yaml
└── simple-backend-svc.yaml

# east-cluster 에 서비스를 배포
cat ch12/locality-aware/east/simple-backend-deployment.yaml
...
        - name: "MESSAGE"
          value: "Hello from EAST"
...

cat ch12/locality-aware/east/simple-backend-svc.yaml
keast apply -f ch12/locality-aware/east/simple-backend-deployment.yaml
keast apply -f ch12/locality-aware/east/simple-backend-svc.yaml
keast get deploy -n istioinaction simple-backend-east
keast get svc,ep -n istioinaction simple-backend

 

 

# 10회 요청 후 확인
for i in {1..10}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done
for i in {1..10}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done | sort | uniq -c
   4 "Hello from EAST"
   6 "Hello from WEST"


# 정보 확인
kwest get svc,ep -n istioinaction simple-backend
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/simple-backend   ClusterIP   10.100.0.156   <none>        80/TCP    37m

NAME                       ENDPOINTS         AGE
endpoints/simple-backend   10.10.0.17:8080   37m # k8s service 에 endpoint 에는 west 에 파드 ip만 출력

#
for i in listener route cluster endpoint; do echo ">> k8s cluster : west - istio-config $i <<"; docker exec -it west-control-plane istioctl proxy-config $i deploy/istio-ingressgateway.istio-system; echo; done
>> k8s cluster : west - istio-config listener <<
ADDRESS PORT  MATCH DESTINATION
0.0.0.0 8080  ALL   Route: http.8080
...

>> k8s cluster : west - istio-config route <<
NAME          DOMAINS                             MATCH                  VIRTUAL SERVICE
http.8080     simple-backend.istioinaction.io     /*                     simple-backend-vs-for-gateway.istioinaction
...

>> k8s cluster : west - istio-config cluster <<
SERVICE FQDN                                                 PORT      SUBSET     DIRECTION     TYPE           DESTINATION RULE      
simple-backend.istioinaction.svc.cluster.local               80        -          outbound      EDS     
...

>> k8s cluster : west - istio-config endpoint <<
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.17:8080                                         HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
...

#
iwest proxy-config listener deploy/istio-ingressgateway.istio-system
iwest proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json

iwest proxy-config route deploy/istio-ingressgateway.istio-system
iwest proxy-config route deploy/istio-ingressgateway.istio-system --name http.8080
iwest proxy-config route deploy/istio-ingressgateway.istio-system --name http.8080 -o json

iwest proxy-config cluster deploy/istio-ingressgateway.istio-system
iwest proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn simple-backend.istioinaction.svc.cluster.local -o json

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep simple
10.10.0.17:8080                                         HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443                                    HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
[
    {
        "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
        "addedViaApi": true,
        "hostStatuses": [
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.17",
                        "portValue": 8080
                    }
                "weight": 1,
                "locality": {}
            ...
            {
                "address": {
                    "socketAddress": {
                        "address": "172.18.255.202",
                        "portValue": 15443
                    }
                "weight": 1,
                "locality": {}
...

 

 

클러스터 간 지역 인식 라우팅 검증하기 VERIFYING LOCALITY-AWARE ROUTING ACROSS CLUSTERS

#
kwest label node west-control-plane 'topology.kubernetes.io/region=westus'
kwest label node west-control-plane 'topology.kubernetes.io/zone=0'
kwest get node -o yaml
...
      topology.kubernetes.io/region: westus
      topology.kubernetes.io/zone: "0"
...

keast label node east-control-plane 'topology.kubernetes.io/region=eastus'
keast label node east-control-plane 'topology.kubernetes.io/zone=0'
keast get node -o yaml
...
      topology.kubernetes.io/region: eastus
      topology.kubernetes.io/zone: "0"
...

# istio eds 에 정보 반영을 위해 파드 재기동하자 : isiotd 가 노드의 지역성 정보 레이블을 엔드포인트 설정할 때 워크로드로 전파.
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": {}
...

kwest rollout restart -n istio-system deploy/istio-ingressgateway
kwest rollout restart -n istio-system deploy/istio-eastwestgateway
kwest rollout restart -n istioinaction deploy/simple-backend-west
keast rollout restart -n istio-system deploy/istio-ingressgateway
keast rollout restart -n istio-system deploy/istio-eastwestgateway
keast rollout restart -n istioinaction deploy/simple-backend-east

iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": {
                    "region": "eastus", # east-cluster 에 있는 워크로드의 위치 정보
                    "zone": "0"
                }
   ...
                "weight": 1,
                "locality": {
                    "region": "westus", # west-cluster 에 있는 워크로드의 위치 정보
                    "zone": "0"
                }
...

 

 

#엔드포인트 상태를 수동적으로 확인하도록, 이상값 감지를 사용하는 DestinationRole 을 적용해보자.
cat ch12/locality-aware/west/simple-backend-dr.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
  namespace: istioinaction
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 10
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 1
      interval: 20s
      baseEjectionTime: 30s

kwest apply -f ch12/locality-aware/west/simple-backend-dr.yaml
kwest get gw,vs,dr -n istioinaction

# 확인
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT                 STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:8080          HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local



# 
EXT_IP=$(kwest -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP
docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body"

# 동일 클러스터 안에서 라우팅 되는 것을 확인
for i in {1..20}; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; echo ; done | sort | uniq -c
 20 "Hello from WEST"



#
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
...
                "weight": 1,
                "locality": { 
                    "region": "westus", # priority 가 없으면(생략 시), 0으로 우선 순위가 가장 높음
                    "zone": "0"
                }
            ...
                "weight": 1,
                "priority": 1, # priority 0 다음으로, 두 번쨰 우선순위
                "locality": {
                    "region": "eastus",
                    "zone": "0"
                }
...

지역별 라우팅으로인해, 한쪽으로만 트래픽이 흐르는것을 알 수 있다.

 

 

 

클러스터 간 장애 극복 확인하기 VERIFYING CROSS-CLUSTER FAILOVER

# 신규 터미널 : 반복 접속 해두기
while true; do docker exec -it mypc curl -s -H "Host: simple-backend.istioinaction.io" http://$EXT_IP | jq ".body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
...
"Hello from WEST"
                 2025-05-18 09:31:21

"Hello from EAST" # failover 시점 
                 2025-05-18 09:31:23
...


#
kwest -n istioinaction set env deploy simple-backend-west ERROR_RATE='1'
kwest exec -it -n istioinaction deploy/simple-backend-west -- env | grep ERROR
ERROR_RATE=1

#
iwest proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT                 STATUS      OUTLIER CHECK     CLUSTER
10.10.0.21:8080          HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local
172.18.255.202:15443     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

 

 

인가 정책을 사용해 클러스터 간 접근 제어 확인하기 VERIFYING CROSS-CLUSTER ACCESS CONTROL USING AUTHORIZATION POLICIES

AuthorizationPolicy 적용으로 east-cluster의 서비스 트래픽을 인그레스 게이트웨이 출처만 허용하고 외부 접근을 차단한다.

 

# 적용 전에 west-cluster 서비스를 제거해서 east 에서만 트래픽을 처리하게 하자 >> 이미 위에서 장애 상황이라 안해도 되긴함
kwest delete deploy simple-backend-west -n istioinaction

#
cat ch12/security/allow-only-ingress-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-only-ingress"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: simple-backend
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]

keast apply -f ch12/security/allow-only-ingress-policy.yaml
keast get authorizationpolicy -A



#업데이트가 전파되고 나면, west-cluster 의 워크로드에서 요청을 만들어 정책을 시험해보자. 이를 위해 임시 파드를 실행한다.
kwest run netshoot -n istioinaction --rm -it --image=nicolaka/netshoot -- zsh
-----------------------------------
#
curl -s webapp.istioinaction/api/catalog

# 직접 요청하면 실패!
curl -s simple-backend.istioinaction.svc.cluster.local
RBAC: access denied

# istio-ingressgateway 로 요청하면 성공!
curl -s -H "Host: simple-backend.istioinaction.io" http://istio-ingressgateway.istio-system
...

# kiali 등 확인을 위해 반복 접속 실행
watch curl -s simple-backend.istioinaction.svc.cluster.local
watch 'curl -s -H "Host: simple-backend.istioinaction.io" http://istio-ingressgateway.istio-system'

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

접속하는 환경에따라 성공/실패

AuthorizationPolicy로 인그레스 게이트웨이 출처 트래픽만 허용해 외부 접근 차단 가능함을 확인된다. 이는 상호 인증된 mTLS 기반 메타데이터를 활용해 클러스터 간 세밀한 접근 제어 구현 가능하다는것을 알 수 있다.
이를 통해 다중 클러스터 환경에서도 별도 설정 없이 일관된 보안 정책 적용 및 기능 통합 가능하다는것.

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

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

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

 

 

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

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

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


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

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

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

 

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

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

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

 

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

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

 

 

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

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

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

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

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

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

메트픽 설명

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

 

 

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

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

 

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

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

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

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


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

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


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

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

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

 

 

 

쪼끔 올라가긴 한다.

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

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

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

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


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

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

kubectl get gw,vs -n istioinaction
...

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

202개씩 생성..

 

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

 

#!/bin/bash

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

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

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

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

  echo "Pre Pushes: $PRE_PUSHES"

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

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

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

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

  sleep 10

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

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

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

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

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

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

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

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

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

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

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

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


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

 

부하가 발생중이다.

 

 

#딜레이없이 실행

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

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

 

 

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

 

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

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

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

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

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

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

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

 

 

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

 

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

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

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

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


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

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

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

 

사이드카 배포 후...

안나온다.

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

 

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

 

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

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

 

 

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

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

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

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

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

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

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

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

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

설정이 빠졌다.

 

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

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

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

 

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

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

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

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

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

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

 

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

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

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

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


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

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

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

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

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

 

 

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

 

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

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

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

 

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

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

 

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

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

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

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

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

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

 

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

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

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

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

 

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

 

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

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

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

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

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


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


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

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

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

에러 확인

 

원인 해결 후 정상 확인

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

 

 

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

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

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

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

 

 

 

 

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

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

 

 

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

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

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

 

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

 

 

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

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

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

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

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

 

 

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

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

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


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

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




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

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

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

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

 

결과가 없음.

 

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

 

 

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

 

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

 

 

 

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

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

 

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

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


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

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


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



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

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

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

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

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

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

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

 

 

 

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

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

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

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

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

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

 

 

 

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

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

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

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

 

#로그 내용

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

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

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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

 

 

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

 

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

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

 

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

 

 

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

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

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

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

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

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

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

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

 

 

## PromQL 쿼리별 분석

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

---

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

---

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

---

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

---

### **요약 표**

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

---

 

 

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

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

 

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

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


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

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

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


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

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

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

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

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

 

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

 

 

 

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

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

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

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

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

 

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

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

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

 

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

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

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

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

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


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

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

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

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

 

 

 

 

 

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

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

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

 

#예시 정책

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

 

실습환경 구성

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

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


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

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

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

 

 

 

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

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

 

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

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

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

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

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

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

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

 

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

 

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

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

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


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


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

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

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

 

access denied가 출력됨.

 

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

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

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

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

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

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

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

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

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

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

 

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

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

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

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

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


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


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

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

 

 

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

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

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

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

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

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

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

 

 

 

 

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

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

 

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

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

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

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

 

 

정책의 조건부 적용

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

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

 

인가 정책 평가 순서 

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

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

 

 

9.4 최종 사용자 인증 및 인가

JWT란

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

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

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

 

#
cat ./ch9/enduser/user.jwt

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

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

 

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

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

1. 인증서버의 역할

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

2. JWT 토큰 발급

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

3. 공개 키(JWKS) 제공

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

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

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

5. 서명 검증 방식

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

 

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

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

 

실습환경 전 기존 실습 삭제

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

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

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


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

 

 

RequestAuthentication으로 JWT 검증하기

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

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

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

 

RequestAuthentication 리소스 만들기

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

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

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

 

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

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

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

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

 

 

 

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

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


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

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

 

 

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

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

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

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

 

 

JWT가 없는 요청 거부하기

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

 

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

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

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

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


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

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

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

 

 

 

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

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

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

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

 

 

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

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

 

 

#실습

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

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

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

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


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

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

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

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


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

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

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

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

일반 사용자
관리자

 

 

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

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

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

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

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

 

 

외부 인가 실습

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

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

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

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

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

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

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

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

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

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

 

 

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

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

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

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

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

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

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

 

커스텀 AuthorizationPolicy 리소스 사용하기

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

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

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


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

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

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


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

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

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

 

 

 

 

정리

PeerAuthentication (피어 인증 정책)

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

AuthorizationPolicy (인가 정책)

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

RequestAuthentication (요청 인증 정책)

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

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

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

요약 


 

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

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

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

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

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

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

 

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

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

 

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

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

 

MSA에서의 인증/인가 

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

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

 

Istio에서의 인증/인가 

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

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

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

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

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

 

 

9.2 자동 상호 TLS (Auto mTLS)

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

 

실습환경

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

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

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

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

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

 

 

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

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

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

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

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

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

 

 

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

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

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


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

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

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

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

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

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

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

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

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

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

 

 

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

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

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

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

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

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

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

 

 

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

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

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

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

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

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

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

 

 

이스티오 보안: SPIFFE

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

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

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

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

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

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

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

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

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

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

 

 

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

 

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

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

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

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

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

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

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

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


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

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


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


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

 

 

 

요청 ID 이해하기

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

 

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

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


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

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


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


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

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

 

 

 

 

정리

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

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

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

 

 

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

 

 

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

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

#
cd ch8

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

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

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

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

 

 

8.2 Distributed tracing 분산 트레이싱

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

 

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

 

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

 

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

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

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

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

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

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

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

 

 

 

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

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

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

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

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

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

 

 

 

 

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

cat ch8/tracing/thin-httpbin-virtualservice.yaml
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
    - "httpbin.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: thin-httbin-virtualservice
spec:
  hosts:
  - "httpbin.istioinaction.io"
  gateways:
  - coolstore-gateway
  http:
  - route:
    - destination:
        host: httpbin.org
---        
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-httpbin-org
spec:
  hosts:
  - httpbin.org 
  ports:
  - number: 80
    name: http
    protocol: HTTP
  location: MESH_EXTERNAL
  resolution: DNS

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

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

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




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

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

 

 

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

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

 
 

 

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

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

이점

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

주의점

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

 

 

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

 

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

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

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

 

 

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

#기본설정

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

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



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

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

# 확인
kubectl get cm -n istioinaction

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

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


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

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

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

 

 

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

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

 

 

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

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

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

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



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

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


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

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

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

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

 

Kiali의 주요 메뉴

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

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

 
 
 

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

 

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

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

 
이스티오는 엔보이 프록시를 통해 HTTP/TCP 요청 수, 지연 시간, 오류율 등의 데이터 플레인 메트릭을 자동 수집하며, 프로메테우스와 그라파나 연동으로 실시간 모니터링이 가능하다.
 
 
 
실습 시작
#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # kube-ops-view
    hostPort: 30007
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server






# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false

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

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

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

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway


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





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

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

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

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

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

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

 

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

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

 

 

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

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

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


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

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

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

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

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

 

 

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

 

 

 

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

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

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

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



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

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

 

 

Scraping Istio metrics with Prometheus (실습)

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

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

 

 

 

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

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

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

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

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

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

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

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

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


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

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



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

 

 

 

 

 

 

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

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

 

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

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

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

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

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

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

 

 

 

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

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

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

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

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

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

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


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

 

 

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

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

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


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

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

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

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

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

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

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

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

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

 

 

 

 

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

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

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

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

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

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





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

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




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

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

 

 

 

 

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

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

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

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

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

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

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

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

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

 

 

 

 

 

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

 

 

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

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

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # kube-ops-view
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server



# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false

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

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
kubectl get pod -n istio-system

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

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

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

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

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

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

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


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

 

 

 

6.1 Building resilience into the application

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

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

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

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

 

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

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

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

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

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

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

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

# 확인
kubectl get gw,vs -n istioinaction

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


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

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

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


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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

 

 

 

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

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

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

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

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

 

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

 

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

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

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

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

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


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

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

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

 

 

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

 

 

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

⇒ Start 클릭

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

 

 

 

 

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

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

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

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

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

 

사용자경험이 좋아졌다.

 

 

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

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

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

 

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

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

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

 

 

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

 

 

호출테스트 1

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

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

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

 

 

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

 

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

 

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

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

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


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


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

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

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

 

 

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

 

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

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

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


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

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

 

 

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

 

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

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

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

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

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


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


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

 

 

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

 

 

6.4 Transparent timeouts and retries (실습)

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

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

 

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


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

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

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

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

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


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

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


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

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

 

 

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

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

 

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

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

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

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

 

 

 

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

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

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

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

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

# 호출테스트 : simple-backend-1 호출 시 failures (500) 발생
# simple-backend-1 --(503)--> simple-web --(500)--> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 6% cpu 0.200 total
jq .code  0.00s user 0.00s system 3% cpu 0.199 total
500
...

# app, istio-proxy log 에서 500, 503 로그 확인해보자.

 

 

 

이제 이스티오의 재시도 정책을 다시 바꾼다.

#
cat ch6/simple-backend-enable-retry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2

kubectl apply -f ch6/simple-backend-enable-retry.yaml -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80 -o json
...
               "name": "simple-backend.istioinaction.svc.cluster.local:80",
                "domains": [
                    "simple-backend.istioinaction.svc.cluster.local",
                    "simple-backend",
                    "simple-backend.istioinaction.svc",
                    "simple-backend.istioinaction",
                    "10.200.1.161"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
...

# 호출테스트 : 모두 성공!
# simple-backend-1 --(503, retry 후 정상 응답)--> simple-web --> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

# app, istio-proxy log 에서 503 로그 확인해보자.

 

 

500에러가 없어졌다.

 

사용자 경험이 좋아지고 있다.

 

503에러가 아닌 500 에러에 대해서는 ?

 

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2 # 최대 재시도 횟수
      retryOn: gateway-error,connect-failure,retriable-4xx # 다시 시도해야 할 오류
      perTryTimeout: 300ms # 타임 아웃
      retryRemoteLocalities: true # 재시도 시 다른 지역의 엔드포인트에 시도할지 여부
      

# 500 에러 코드 리턴
cat ch6/simple-backend-periodic-failure-500.yaml
...
        - name: "ERROR_TYPE"
          value: "http_error"           
        - name: "ERROR_RATE"
          value: "0.75"                              
        - name: "ERROR_CODE"
          value: "500"
...

kubectl apply -f ch6/simple-backend-periodic-failure-500.yaml -n istioinaction

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=500
exit
---------------------------------------------------------------

# envoy 설정 확인 : 재시도 동작(retryOn) 에 retriableStatusCodes 는 503만 있음.
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
...
                       "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    503
                                ]
                            },
                            "maxGrpcTimeout": "0s"
                        },
...


# 호출테스트 : Retry 동작 안함.
# simple-backend-1 --(500, retry 안함)--> simple-web --(500)> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...
curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 30% cpu 0.036 total
jq .code  0.00s user 0.00s system 14% cpu 0.035 total
200
curl -s http://simple-web.istioinaction.io:30000  0.00s user 0.01s system 5% cpu 0.184 total
jq .code  0.00s user 0.00s system 2% cpu 0.183 total
500
...

 

위와같이 500 에러가 발생하도록 하고...

 

#
cat ch6/simple-backend-vs-retry-500.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5xx # HTTP 5xx 모두에 재시도

kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction

# 호출테스트 : 모두 성공!
# simple-backend-1 --(500, retry)--> simple-web --(200)> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

 

 

RetryOn을 5xx로 했떠니 전부 200 응답이 나는것을 확인할 수 있다.

 

 

그럼 타임아웃에 따른 재시도는 ?

재시도 정책 설정 시
(시도별 타임아웃 * 재시도 횟수) + (백오프 지연 * (재시도 횟수-1)) < 전체 타임아웃 조건을 반드시 충족시켜야 의도한 재시도 동작을 보장합니다.

이스티오의 재시도 동작은 다음과 같이 작동합니다:

  1. 기본 재시도 정책: HTTP 요청 실패 시 최대 2회 재시도 (총 3회 시도), 재시도 간 백오프는 25ms부터 시작해 지수적으로 증가 (예: 25ms → 50ms → 100ms).
  2. Thundering Herd 위험: 다중 계층 서비스 체인에서 재시도 시 요청 수가 기하급수적으로 증가 (예: 5계층 시 최대 32회 요청 발생.
  3. 안전하지 않은 기본 설정: 503 오류 자동 재시도로 인해 중복 처리 위험 (결제 중복 등), retryOn 설정을 connect-failure 등으로 제한 권장.
  4. 지역 제한 재시도: 기본적으로 동일 지역 엔드포인트만 재시도하지만, retryRemoteLocalities: true 설정 시 다른 리전으로 재시도 확장 가능.
  5. 세부 제어 필요: VirtualService에서 attempts, perTryTimeout, retryOn 조건을 서비스별로 명시적 설정해야 안전성 보장.

※ 재시도 정책은 VirtualService 리소스에서 retries 필드로 제어되며, 백오프 알고리즘은 현재 커스터마이징 불가.

 

6.4.3 Advanced retries : Istio Extension API (EnvoyFilter)

이스티오는 기본적으로 HTTP 503 오류에 대해 25ms 백오프 시간으로 재시도를 수행하지만, 상세 설정(재시도 조건, 백오프 시간)은 EnvoyFilter API를 통해 엔보이 프록시 설정을 직접 수정해야 커스터마이징이 가능합니다.

※ 주의: EnvoyFilter 사용은 호환성 문제를 유발할 수 있으므로 Istio 버전과 Envoy 문서를 반드시 확인해야 합니다.

# cat ch6/simple-backend-ef-retry-status-codes.yaml                                            
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: simple-backend-retry-status-codes
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: simple-web
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: SIDECAR_OUTBOUND
      routeConfiguration:
        vhost:
          name: "simple-backend.istioinaction.svc.cluster.local:80"          
    patch:
      operation: MERGE
      value:
        route:
          retry_policy: # 엔보이 설정에서 직접 나온다?
            retry_back_off: 
              base_interval: 50ms # 기본 간격을 늘린다
            retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
            - 408
            - 400
            

# 408 에러코드를 발생
kubectl apply -f ch6/simple-backend-periodic-failure-408.yaml -n istioinaction

# 파드 정상 기동 후 수정
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=408
exit
---------------------------------------------------------------

# 호출테스트 : 408 에러는 retryOn: 5xx 에 포함되지 않으므로 에러를 리턴.
# simple-backend-1 --(408)--> simple-web --(500)--> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...

 

 

이제 408 에러도 재시도 하도록 적용한다.

#
cat ch6/simple-backend-ef-retry-status-codes.yaml
...
    patch:
      operation: MERGE
      value:
        route:
          retry_policy: # 엔보이 설정에서 직접 나온다?
            retry_back_off: 
              base_interval: 50ms # 기본 간격을 늘린다
            retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
            - 408
            - 400

kubectl apply -f ch6/simple-backend-ef-retry-status-codes.yaml -n istioinaction

# 확인
kubectl get envoyfilter -n istioinaction -o json
kubectl get envoyfilter -n istioinaction
NAME                                AGE
simple-backend-retry-status-codes   45s

# VirtualService 에도 재시도 할 대상 코드 추가
cat ch6/simple-backend-vs-retry-on.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5xx,retriable-status-codes # retryOn 항목에 retriable-status-codes 를 추가

kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction

# envoy 설정 확인 : 재시도 동작(retryOn) 에 5xx 과 retriableStatusCodes 는 408,400 있음.
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
...
                        "route": {
                            "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "5xx,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    408,
                                    400
                                ],
                                "retryBackOff": {
                                    "baseInterval": "0.050s"
                                }
                            },
                            "maxGrpcTimeout": "0s"
                        }
...

# 호출테스트 : 성공
# simple-backend-1 --(408, retry 성공)--> simple-web --> curl(외부)
for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
...

 

 

 

408에러도 200에러로 응답되는것을 알 수 있다.

 

 

6.5 Circuit breaking with Istio

  1. 서킷 브레이커 구현 방식: 이스티오는 DestinationRule의 connectionPool (최대 연결/요청 수 제한)과 outlierDetection (이상 엔드포인트 감지) 설정을 통해 서킷 브레이커 패턴을 구현합니다.
  2. 과부하 방지:
    • connectionPool은 동시 연결 수(http1MaxPendingRequests)와 대기 요청 수(maxRequestsPerConnection)를 제한해 과부하 시 503 오류로 즉시 실패 처리하여 연쇄 장애를 차단합니다.
  3. 장애 엔드포인트 격리:
    • outlierDetection은 연속 오류(consecutive5xxErrors) 발생 시 해당 엔드포인트를 일시적으로 풀에서 제거(ejection)해 시스템 회복 시간을 확보합니다.

서킷 브레이커를 2가지 방식으로 제공을 하는데..

 

6.5.1 Guarding against slow services wih connection-pool control* : 커넥션 풀 제어로 느린 서비스에 대응하기

# tracing.sampling=100
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.accessLogFile=/dev/stdout --set meshConfig.defaultConfig.tracing.sampling=100 --set meshConfig.defaultHttpRetryPolicy.attempts=0
y
exit
----------------------------------------

# 확인
kubectl describe cm -n istio-system istio
...
defaultConfig:
  discoveryAddress: istiod.istio-system.svc:15012
  proxyMetadata: {}
  tracing:
    sampling: 100.0
    zipkin:
      address: zipkin.istio-system:9411
...

# 적용 : rollout 
kubectl rollout restart deploy -n istio-system istiod
kubectl rollout restart deploy -n istio-system istio-ingressgateway
kubectl rollout restart deploy -n istioinaction simple-web
kubectl rollout restart deploy -n istioinaction simple-backend-1



# 현재 적용되어 있는 상태
kubectl apply -f ch6/simple-web.yaml -n istioinaction
kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction
kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction

# destinationrule 삭제
kubectl delete destinationrule --all -n istioinaction

# simple-backend-2 제거
kubectl scale deploy simple-backend-2 --replicas=0 -n istioinaction

# 응답지연(1초)을 발생하는 simple-backend-1 배포
kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction

# 동작 중 파드에 env 직접 수정..
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
-----------------------------------
export TIMING_50_PERCENTILE=1000ms
exit
-----------------------------------

# 테스트
curl -s http://simple-web.istioinaction.io:30000 | grep duration              
  "duration": "1.058699s",
      "duration": "1.000934s",

위와같이 실습환경을 구성하고..

fortio로 테스트를 해본다.

 

# cat ch6/simple-backend-dr-conn-limit.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1 # 커넥션 총 개수 Total number of connections
      http:
        http1MaxPendingRequests: 1 # 대기 중인 요청 Queued requests
        maxRequestsPerConnection: 1 # 커넥션당 요청 개수 Requests per connection
        maxRetries: 1 # Maximum number of retries that can be outstanding to all hosts in a cluster at a given time.
        http2MaxRequests: 1 # 모든 호스트에 대한 최대 동시 요청 개수 Maximum concurrent requests to all hosts

# DestinationRule 적용 (connection-limiting) 
kubectl apply -f ch6/simple-backend-dr-conn-limit.yaml -n istioinaction
kubectl get dr -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction | egrep 'RULE|backend'
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
                                                        8080      -          inbound       ORIGINAL_DST     simple-backend-dr.istioinaction
simple-backend.istioinaction.svc.cluster.local          80        -          outbound      EDS              simple-backend-dr.istioinaction

# 설정 적용 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 1, # tcp.maxConnections, 커넥션 총 개수 Total number of connections
                    "maxPendingRequests": 1, # http.http1MaxPendingRequests, 대기 중인 요청 Queued requests
                    "maxRequests": 1, # http.http2MaxRequests, 모든 호스트에 대한 최대 동시 요청 개수 
                    "maxRetries": 1, # http.maxRetries
                    "trackRemaining": true
                }
            ]
        },
        "typedExtensionProtocolOptions": {
            "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
                "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
                "commonHttpProtocolOptions": { 
                    "maxRequestsPerConnection": 1 # http.maxRequestsPerConnection, 커넥션당 요청 개수
...

# (참고) 기본값?
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn simple-web.istioinaction.svc.cluster.local -o json
...
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295,
                    "trackRemaining": true
...

이스티오의 DestinationRule에서 maxConnections는 대상 호스트당 HTTP1/TCP 커넥션 최대 수를 제한하고, http1MaxPendingRequests는 사용 가능한 커넥션이 없을 때 대기 중인 요청 수를 제한하며, http2MaxRequests(이름과 달리 HTTP1.1/HTTP2 모두 적용)는 호스트별 동시 요청 수를 제어합니다.

 

이스티오는 기본적으로 프록시 메트릭 카디널리티를 줄이기 위해 통계를 제한하지만, 특정 서비스(예: simple-web)에 대해 상세 통계 수집을 활성화해 서킷 브레이커 동작과 업스트림 서비스(simple-backend) 장애를 명확히 구분할 수 있습니다.

 

좀더 명확한 테스트를 위해 다음과 같이 설정한다.

# simple-web 디플로이먼트에 sidecar.istio.io/statsInclusionPrefixes="cluster.<이름>" 애너테이션 추가하자
## sidecar.istio.io/statsInclusionPrefixes: cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local
cat ch6/simple-web-stats-incl.yaml | grep statsInclusionPrefixes 
        sidecar.istio.io/statsInclusionPrefixes: "cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local"        
kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction

# 정확한 확인을 위해 istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# simple-web 에 istio-proxy 의 stats 조회
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

위와같이 초기화를 진행하고

  • 커넥션 개수와 초당 요청 수를 2로 늘리면 어떨까? 2개의 커넥션에서 요청을 초당 하나씩 보내기 시작해보자.
    • 커넥션과 요청이 지정한 임계값(병렬 요청이 너무 많거나 요청이 너무 많이 쌓임)을 충분히 넘겨 서킷 브레이커를 동작시켰음을 확인.
# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 요청이 17개 실패한 것으로 반환됐다(HTTP 5xx)
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 19 (for perfect keepalive, would be 2)
Code 200 : 30 (63.8 %)
Code 500 : 17 (36.2 %)
All done 47 calls (plus 2 warmup) 925.635 ms avg, 1.5 qps
...

# 로그 확인 : simple-web
kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
...
# 오류 요청 (503 Service Unavailable, UO 플래그
## HTTP 503: 서비스가 일시적으로 사용 불가능. Envoy가 업스트림 서버(simple-backend:80)에 요청을 전달하지 못함.
## UO 플래그: "Upstream Overflow"로, Envoy의 서킷 브레이커가 트리거되었거나 최대 연결/요청 제한에 도달했음을 의미.
## upstream_reset_before_response_started{overflow}: 업스트림 서버가 응답을 시작하기 전에 연결이 리셋되었으며, 이는 오버플로우(리소스 제한)로 인함.
[2025-04-22T03:17:24.830Z] "GET // HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 4 - ...

# 오류 요청 (500 Internal Server Error) : 최종 사용자에게 500 에러 리턴
[2025-04-22T03:17:24.825Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 687 11 11 ...
## simple-web 서비스에서 backend 정보를 가져오지 못하여 애플리케이션 레벨 오류 발생
## HTTP 500: 서버 내부 오류. 업스트림 서버(simple-web:30000)가 요청을 처리하는 중 예기치 않은 오류 발생.
## via_upstream: 오류가 Envoy가 아니라 업스트림 서버에서 발생했음을 나타냄.
...

# 통계 확인 : 18개로 +/- 1개 정도는 무시하고 보자. 성능 테스트 실패 갯수(17개)와 아래 통계값이 일치 한다(18-1).
# 큐 대기열이 늘어나 결국 서킷 브레이커를 발동함. 
# fail-fast 동작은 이렇게 보류 중 혹은 병행 요청 갯수가 서킷 브레이커 임계값을 넘어 수행된다.
# The fail-fast behavior comes from those pending or parallel requests exceeding the circuit-breaking thresholds. 
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 18
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

 

 

 

병렬로 발생하는 요청(현재 로드테스트 동시 요청 2)을 더 처리하고자 http2MaxRequests(parallel requests)를 늘려보자

# 설정 전 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                    "maxRequests": 1,
                    "maxRequestsPerConnection": 1

# http2MaxRequests 조정: 1 → 2, '동시요청 처리개수'를 늘림
kubectl patch destinationrule simple-backend-dr -n istioinaction \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 2}}}}}'

# 설정 후 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                    "maxRequests": 2,
                    "maxRequestsPerConnection": 1

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 로그 확인 : simple-web >> 아래 500(503) 발생 로그 확인
kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
... 
## jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13
[2025-04-22T03:55:22.424Z] "GET / HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 0 - "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-backend:80" "-" outbound|80||simple-backend.istioinaction.svc.cluster.local - 10.200.1.137:80 172.18.0.1:0 - -
[2025-04-22T03:55:22.410Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 688 15 15 "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-web.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:43259 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.simple-web.istioinaction.svc.cluster.local default
...

# 로그 확인 : simple-backend >> 503 에러가 발생하지 않았다??? 
kubectl logs -n istioinaction -l app=simple-backend -c istio-proxy -f


# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 동시요청 처리개수가 기존 1 에서 2로 증가되어서 거의 대부분 처리했다. >> 참고로 모두 성공 되기도함.
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 3 (for perfect keepalive, would be 2)
Code 200 : 33 (97.1 %)
Code 500 : 1 (2.9 %)
All done 34 calls (plus 2 warmup) 1789.433 ms avg, 1.1 qps
...

# 'cx_overflow: 40' 대비 'rq_pending_overflow: 1' 가 현저히 낮아짐을 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 40
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 1
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

맥스 리퀘스트를 1개에서 2개로 늘려주니 503 에러가 확연히 줄어들었다.

 

이번엔 보류 대기열 댑스를 2로 늘리고 실행해본다.

 

# http1MaxPendingRequests : 1 → 2, 'queuing' 개수를 늘립니다
kubectl patch destinationrule simple-backend-dr \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 2}}}}}'

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests           
                    "maxPendingRequests": 2,
                    
# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters


# 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 모두 성공!
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 2 (for perfect keepalive, would be 2) # 큐 길이 증가 덕분에, 소켓을 2개만 사용했다.
Code 200 : 33 (100.0 %)
All done 33 calls (plus 2 warmup) 1846.745 ms avg, 1.1 qps
...

# 'cx_overflow가 45이 발생했지만, upstream_rq_pending_overflow 는 이다!
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep overflow
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0

kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

이제 500에러는 발생하지 않는다.

 

Istio 서킷 브레이커가 발동되면 x-envoy-overloaded 헤더를 통해 요청 실패 원인이 서킷 브레이커 임계값 초과임을 식별할 수 있으며, 이를 통해 애플리케이션/네트워크 장애와 구분합니다.

 
# 
kubectl patch destinationrule simple-backend-dr \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 1}}}}}'

kubectl patch destinationrule simple-backend-dr -n istioinaction \
-n istioinaction --type merge --patch \
'{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 1}}}}}'

# 설정 적용 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 로드 테스트
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000

# 로드 테스트 하는 상태에서 아래 curl 접속 
curl -v http://simple-web.istioinaction.io:30000
{
  "name": "simple-web",
  "uri": "/",
  "type": "HTTP",
  "ip_addresses": [
    "10.10.0.18"
  ],
  "start_time": "2025-04-22T04:23:50.468693",
  "end_time": "2025-04-22T04:23:50.474941",
  "duration": "6.247ms",
  "body": "Hello from simple-web!!!",
  "upstream_calls": [
    {
      "uri": "http://simple-backend:80/",
      "headers": {
        "Content-Length": "81",
        "Content-Type": "text/plain",
        "Date": "Tue, 22 Apr 2025 04:23:50 GMT",
        "Server": "envoy",
        "X-Envoy-Overloaded": "true" # Header indication
      },
      "code": 503,
      "error": "Error processing upstream request: http://simple-backend:80//, expected code 200, got 503"
    }
  ],
  "code": 500
}
 
 
 
 
 

 

 

 

 

6.5.2 Guarding against unhealthy services with outlier detection* : 이상값 감지로 비정상 서비스에 대응하기

Istio는 엔보이의 이상값 감지(Outlier Detection) 기능을 활용해 오동작하는 호스트를 서비스에서 자동 제거하여 시스템 안정성을 유지합니다.

 

- 실습 환경 초기화
    - 동작을 살펴보기 위해 이스티오의 **기본 재시도 메커니즘도 비활성화** 한다.
    - 재시도와 이상값 감지는 잘 어울리지만, 이 예제에서는 **이상값 감지 기능을 고립**시키려고 한다.
    - 재시도는 마지막에 추가해서 이상값 감지와 재시도가 서로 어떻게 보완하는지 확인해본다.

#
kubectl delete destinationrule --all -n istioinaction
kubectl delete vs simple-backend-vs -n istioinaction

# disable retries (default) : 이미 적용 되어 있음
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
y
exit
----------------------------------------

#
kubectl apply -f ch6/simple-backend.yaml -n istioinaction
kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction # 통계 활성화

# istio-proxy stats 카운터 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 호출 테스트 : 모두 성공
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000

# 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
 
 
 
 
 
#
kubectl apply -n istioinaction -f ch6/simple-backend-periodic-failure-500.yaml
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep ERROR

#
kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
---------------------------------------------------------------
export ERROR_TYPE=http_error
export ERROR_RATE=0.75
export ERROR_CODE=500
exit
---------------------------------------------------------------

# 정보 확인
kubectl get deploy,pod -n istioinaction -o wide
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS       IMAGES                                 SELECTOR
deployment.apps/simple-backend-1   1/1     1            1           20h   simple-backend   nicholasjackson/fake-service:v0.14.1   app=simple-backend
deployment.apps/simple-backend-2   2/2     2            2           20h   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
deployment.apps/simple-web         1/1     1            1           21h   simple-web       nicholasjackson/fake-service:v0.17.0   app=simple-web

NAME                                   READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
pod/simple-backend-1-bdb6c7ff8-rqqlr   2/2     Running   0          2m25s   10.10.0.30   myk8s-control-plane   <none>           <none>
pod/simple-backend-2-6799f8bf-d4b6t    2/2     Running   0          11m     10.10.0.27   myk8s-control-plane   <none>           <none>
pod/simple-backend-2-6799f8bf-dk78j    2/2     Running   0          11m     10.10.0.29   myk8s-control-plane   <none>           <none>
pod/simple-web-865f4949ff-56kbq        2/2     Running   0          3h32m   10.10.0.18   myk8s-control-plane   <none>           <none>


# 로드 테스트 실행 : 재시도를 끄고, backend-1 엔드포인트에 주기적인 실패를 설정했으니, 테스트 일부는 실패
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 19 (for perfect keepalive, would be 2)
Code 200 : 43 (71.7 %)
Code 500 : 17 (28.3 %)
All done 60 calls (plus 2 warmup) 134.138 ms avg, 2.0 qps
...

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

 

설정에서처럼 약 75프로 가량 실패하는것을 확인할 수 있다.

 

 

 

 

  • 정기적으로 실패하는 서비스에 요청을 보내고 있는데 서비스의 다른 엔드포인트들은 실패하지 않고 있다면, 해당 엔드포인트가 과부하됐거나 어떤 이유로든 성능이 저하된 상태일 수 있으므로 당분간 그 엔드포인트로 트래픽을 전송하는 것을 멈춰야 한다.
  • 이상값 감지를 설정해보자 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다. - Docs
    • consecutive5xxErrors: 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5, 연속적인 에러 횟수 threshold
    • interval: 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
    • baseEjectionTime: 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime.
      • 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초.
    • maxEjectionPercent: 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%).
      • 100% 설정 시모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%
#
cat ch6/simple-backend-dr-outlier-5s.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: simple-backend-dr
spec:
  host: simple-backend.istioinaction.svc.cluster.local
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 1 # 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5
      interval: 5s # 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
      baseEjectionTime: 5s # 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime. 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초. 
      maxEjectionPercent: 100 # 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%). 모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%

kubectl apply -f ch6/simple-backend-dr-outlier-5s.yaml -n istioinaction
kubectl get dr -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
...
        "outlierDetection": {
            "consecutive5xx": 1,
            "interval": "5s",
            "baseEjectionTime": "5s",
            "maxEjectionPercent": 100,
            "enforcingConsecutive5xx": 100,
            "enforcingSuccessRate": 0
        },
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local



# 통계 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
	while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done
	ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local


# 로드 테스트 실행 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다.
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 5 (for perfect keepalive, would be 2)
Code 200 : 58 (96.7 %)
Code 500 : 2 (3.3 %)
All done 60 calls (plus 2 warmup) 166.592 ms avg, 2.0 qps
...

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

# 엔드포인트 이상 감지 전에 3번 실패했고, 이상 상태가 되고 나면 로드 밸런서 풀에서 제거되어서 이후 부터는 정상 엔드포인트로 호출 응답됨.
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep outlier
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_active: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_gateway_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_local_origin_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_5xx: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_gateway_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_local_origin_failure: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_failure_percentage: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_total: 3
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_success_rate: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_total: 3


# 5초 후(baseEjectionTime: 5s) 다시 엔드포인트 모니터링
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

 

위와같이 오류율이 확 떨어진것을 알 수 있다.

오류율이 2개가 뜨는 이유는 ? 처음 2개 실패 후 이후부터 성공한것이기 때문이다.

 

 

오류율을 더 떨어트리려면 ?

 

#
cat ch6/simple-backend-vs-retry-500.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-backend-vs
spec:
  hosts:
  - simple-backend
  http:
  - route:
    - destination:
        host: simple-backend
    retries:
      attempts: 2
      retryOn: 5x

kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction

# 통계 초기화
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
-- curl -X POST localhost:15000/reset_counters

# 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done

# 로드 테스트 실행 : 모두 성공!
fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
...
Sockets used: 2 (for perfect keepalive, would be 2)
Code 200 : 60 (100.0 %)
All done 60 calls (plus 2 warmup) 173.837 ms avg, 2.0 qps
...

# 엔드포인트 이상 감지 전에 3번 실패했지만, 재시도 retry 덕분에 결과적으로 모두 성공!
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend | grep outlier

# 통계 확인
kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
 -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream | grep retry
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry: 4
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_exponential: 4
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_ratelimited: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_limit_exceeded: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_success: 4

 

 

 

6장 요약

  1. 로드 밸런싱 알고리즘: DestinationRule을 통해 ROUND_ROBIN, RANDOM, LEAST_CONN 등의 알고리즘 선택 가능하며, 기본값은 LEAST_CONN(최소 연결 우선)입니다.
  2. 지역 기반 트래픽 제어:
    • Locality-aware LB: 동일 리전/영역 내 엔드포인트 우선 라우팅 (outlierDetection 활성화 필수).
    • 가중치 분배: DestinationRule의 distribute 설정으로 특정 지역에 트래픽 비율 할당 가능(예: 70% 로컬, 30% 타 영역).
  3. 복원력 설정:
    • 재시도/타임아웃: VirtualService에서 retries.attempts, timeout 지정.
    • 서킷 브레이커: DestinationRule의 connectionPool+outlierDetection으로 연결 수/에러 임계값 관리.
    • EnvoyFilter: Istio 미지원 기능(예: 백오프 시간 커스텀)을 Envoy 프록시 레벨에서 확장 적용.

istio3주차 스터디를 시작한다.

5.1 Reducing the risk of deploying new code

블루/그린 배포 전략은 현재에도 굉장히 많이 쓰이지만 해당 배포 전략은 모든 코드 변경사항이 한번에 해제된다. 이 리스크를 줄이는 방안을 살펴볼껀데, 그전에 배포와 릴리스가 뭔지 알아본다.

  • 배포(Deploy)는 새 버전의 코드를 운영 환경 리소스에 설치하는 과정이지만, 이때는 실제 사용자 트래픽이 전달되지 않습니다.
  • 배포된 코드는 운영 환경에서 테스트와 검증이 가능하며, 사용자에게 영향을 주지 않습니다.
  • 릴리스(Release)는 운영 환경에 배포된 새 코드를 실제 사용자 트래픽에 노출하는 행위입니다.
  • 배포와 릴리스를 분리하면, 카나리 릴리스처럼 일부 사용자만 신버전을 사용하게 하여 위험을 점진적으로 관리할 수 있습니다.
  • 이렇게 하면 문제가 있을 때 빠르게 롤백할 수 있고, 안정적으로 전체 사용자에게 릴리스를 확장할 수 있습니다

5.2 Routing requests with Istio (실습)

VirtualService

VirtualService는 Istio에서 트래픽을 어떤 서비스로, 어떤 조건에 따라, 어떻게 라우팅할지 세밀하게 제어하는 역할을 하는 핵심 리소스

# Let’s deploy v1 of our catalog service. From the root of the book’s source code, run the following command
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
catalog-6cf4b97d-ftl77   2/2     Running   0          42s   10.10.0.14   myk8s-control-plane   <none>           <none>

# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3

# netshoot로 내부에서 catalog 접속 확인
kubectl exec -it netshoot -- curl -s http://catalog.istioinaction/items | jq

# 외부 노출을 위해 Gateway 설정
cat ch5/catalog-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: catalog-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "catalog.istioinaction.io"

kubectl apply -f ch5/catalog-gateway.yaml -n istioinaction

# 트래픽을 catalog 서비스로 라우팅하는 VirtualService 리소스 설정
cat ch5/catalog-vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - route:
    - destination:
        host: catalog

kubectl apply -f ch5/catalog-vs.yaml -n istioinaction

# 확인
kubectl get gw,vs -n istioinaction

NAME                                          AGE
gateway.networking.istio.io/catalog-gateway   95s

NAME                                                    GATEWAYS              HOSTS                          AGE
virtualservice.networking.istio.io/catalog-vs-from-gw   ["catalog-gateway"]   ["catalog.istioinaction.io"]   3s


# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3


# istio-ingressgateway Service(NodePort)에 포트 정보 확인
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
[
  {
    "name": "status-port",
    "nodePort": 31674,
    "port": 15021,
    "protocol": "TCP",
    "targetPort": 15021
  },
  {
    "name": "http2",
    "nodePort": 30000, # 순서1
    "port": 80,        
    "protocol": "TCP",
    "targetPort": 8080 # 순서2
  },
  {
    "name": "https",
    "nodePort": 30005,
    "port": 443,
    "protocol": "TCP",
    "targetPort": 8443
  }
]

# 호스트에서 NodePort(Service)로 접속 확인
curl -v -H "Host: catalog.istioinaction.io" http://localhost:30000
kubectl stern -l app=catalog -n istioinaction

open http://localhost:30000
open http://catalog.istioinaction.io:30000
open http://catalog.istioinaction.io:30000/items


# 신규 터미널 : 반복 접속 실행 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items/ ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 0.5; echo; done

 

위와 같이 카탈로그 서비스를 배포한다.

 

그리고 v2도 배포한다.

# catalog 서비스 v2 를 배포 : v2에서는 imageUrl 필드가 추가
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction

#
kubectl get deploy -n istioinaction --show-labels
NAME         READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
catalog      1/1     1            1           30m   app=catalog,version=v1
catalog-v2   1/1     1            1           34s   app=catalog,version=v2

kubectl get pod -n istioinaction -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
catalog-6cf4b97d-ftl77        2/2     Running   0          43m   10.10.0.14   myk8s-control-plane   <none>           <none>
catalog-v2-6df885b555-6hmcl   2/2     Running   0          13m   10.10.0.15   myk8s-control-plane   <none>           <none>

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
catalog-6cf4b97d-ftl77.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
catalog-v2-6df885b555-6hmcl.istioinaction             Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
istio-ingressgateway-996bc6bb6-zvtdc.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8


# 호출 테스트 : v1 , v2 호출 확인
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done


# istio-ingressgateway proxy-config 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295,
                    "trackRemaining": true
                }
            ]
        },
        "commonLbConfig": {
            "localityWeightedLbConfig": {}
        },
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...
   {
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "addedViaApi": true,
        "hostStatuses": [
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.14",
                        "portValue": 3000
                    }
                },
                "stats": [
                    {
                        "name": "cx_connect_fail"
                    },
                    {
                        "value": "8",
                        "name": "cx_total"
                    },
                    {
                        "name": "rq_error"
                    },
                    {
                        "value": "315",
                        "name": "rq_success"
                    },
                    {
                        "name": "rq_timeout"
                    },
                    {
                        "value": "315",
                        "name": "rq_total"
                    },
                    {
                        "type": "GAUGE",
                        "value": "8",
                        "name": "cx_active"
                    },
                    {
                        "type": "GAUGE",
                        "name": "rq_active"
                    }
                ],
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },
                "weight": 1,
                "locality": {}
            },
            {
                "address": {
                    "socketAddress": {
                        "address": "10.10.0.15",
                        "portValue": 3000
                    }
                },
                "stats": [
                    {
                        "name": "cx_connect_fail"
                    },
                    {
                        "value": "8",
                        "name": "cx_total"
                    },
                    {
                        "name": "rq_error"
                    },
                    {
                        "value": "308",
                        "name": "rq_success"
                    },
                    {
                        "name": "rq_timeout"
                    },
                    {
                        "value": "308",
                        "name": "rq_total"
                    },
                    {
                        "type": "GAUGE",
                        "value": "8",
                        "name": "cx_active"
                    },
                    {
                        "type": "GAUGE",
                        "name": "rq_active"
                    }
                ],
                "healthStatus": {
                    "edsHealthStatus": "HEALTHY"
                },
                "weight": 1,
                "locality": {}
            }
        ],
        "circuitBreakers": {
            "thresholds": [
                {
                    "maxConnections": 4294967295,
                    "maxPendingRequests": 4294967295,
                    "maxRequests": 4294967295,
                    "maxRetries": 4294967295
                },
                {
                    "priority": "HIGH",
                    "maxConnections": 1024,
                    "maxPendingRequests": 1024,
                    "maxRequests": 1024,
                    "maxRetries": 3
                }
            ]
        },
        "observabilityName": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "edsServiceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
    },
...

# catalog proxy-config 도 직접 확인해보자

 

 

 

5.2.4 Routing all traffic to v1 of the catalog service

  • 모든 트래픽을 catalog v1 으로 라우팅. 이것이 다크런치를 시작하기 전의 일반적인 트래픽 패턴이다.
  • 어느 워크로드가 v1, v2 인지 이스티오에게 힌트를 줘야한다.
  • catalog v1은 deployment 리소스에서 레이블 app:catalog, version:v1 을 사용한다.
  • catalog v2은 deployment 리소스에서 레이블 app:catalog, version:v2 을 사용한다.
  • 이스티오에게는 이렇게 다른 버전들의 부분집합 subset 으로 지정하는 DestinationRule 을 만들어준다.
#
kubectl get pod -l app=catalog -n istioinaction --show-labels
NAME                          READY   STATUS    RESTARTS   AGE   LABELS
catalog-6cf4b97d-ftl77        2/2     Running   0          56m   app=catalog,...,version=v1
catalog-v2-6df885b555-6hmcl   2/2     Running   0          26m   app=catalog,...,version=v2

#
cat ch5/catalog-dest-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: catalog
spec:
  host: catalog.istioinaction.svc.cluster.local
  subsets:
  - name: version-v1
    labels:
      version: v1
  - name: version-v2
    labels:
      version: v2

kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction

# 확인
kubectl get destinationrule -n istioinaction
NAME      HOST                                      AGE
catalog   catalog.istioinaction.svc.cluster.local   8s


# catalog proxy-config 확인 : SUBSET(v1, v2, -) 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
...

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v2 -o json
...
        "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local"
        },
        "connectTimeout": "10s",
        "lbPolicy": "LEAST_REQUEST",
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
        },
...

# 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
10.10.0.16:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
10.10.0.17:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local



# VirtualService 수정 (subset 추가)
cat ch5/catalog-vs-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v1.yaml -n istioinaction

# 호출 테스트 : v1
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done


# 세부 정보 확인
# routes 에 virtualHosts 항목에 routes.route 에 cluster 부분이 ...version-v1... 설정 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.io:80",
                "domains": [
                    "catalog.istioinaction.io"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
...

# cluster 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1
SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
        "type": "EDS",
        "edsClusterConfig": {
            "edsConfig": {
                "ads": {},
                "initialFetchTimeout": "0s",
                "resourceApiVersion": "V3"
            },
            "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
        },
        ...
        "metadata": {
            "filterMetadata": {
                "istio": {
                    "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                    "default_original_port": 80,
                    "services": [
                        {
                            "host": "catalog.istioinaction.svc.cluster.local",
                            "name": "catalog",
                            "namespace": "istioinaction"
                        }
                    ],
                    "subset": "version-v1"
...

# endpoint 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.16:3000     HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
...

# istio-proxy (catalog)
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --subset version-v1 -o json
...
        "metadata": {
            "filterMetadata": {
                "istio": {
                    "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                    "default_original_port": 80,
                    "services": [
                        {
                            "host": "catalog.istioinaction.svc.cluster.local",
                            "name": "catalog",
                            "namespace": "istioinaction"
                        }
                    ],
                    "subset": "version-v1"
                }
            }
        },
...

 

 

destination rule에 의해 subset을 기반으로 catalog 로만 트래픽이 간다.

 

5.2.5 Routing specific requests to v2

http 요청 헤더를 기반으로 v2로 보내는 방법을 알아본다.

#
cat ch5/catalog-vs-v2-request.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog-vs-from-gw
spec:
  hosts:
  - "catalog.istioinaction.io"
  gateways:
  - catalog-gateway
  http:
  - match:
    - headers:
        x-istio-cohort:
          exact: "internal"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v2-request.yaml -n istioinaction

# 호출 테스트 : 여전히 v1
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done

# 요청 헤더 포함 호출 테스트 : v2!
curl http://catalog.istioinaction.io:30000/items -H "x-istio-cohort: internal"

# (옵션) 신규 터미널 : v2 반복 접속
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# 상세 확인
# route 추가 : routes 에 2개의 route 확인 - 위에서 부터 적용되는 순서 중요!
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080
NAME          DOMAINS                      MATCH     VIRTUAL SERVICE
http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction
http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.io:80",
                "domains": [
                    "catalog.istioinaction.io"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/",
                            "caseSensitive": true,
                            "headers": [
                                {
                                    "name": "x-istio-cohort",
                                    "stringMatch": {
                                        "exact": "internal"
                                    }
                                }
                            ]
                        },
                        "route": {
                            "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                ...
                   {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'

# istio-proxy (catalog)에는 routes 정보가 아래 cluster 로 보내는 1개만 있다. 즉 istio-proxy(istio-ingressgateway)가 routes 분기 처리하는 것을 알 수 있다.
## "cluster": "outbound|80||catalog.istioinaction.svc.cluster.local"
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog
80                                                            catalog, catalog.istioinaction + 1 more...          /*

위와같이 헤더를 포함하여 접속하게 되면

 

위와같이 일반 트래픽(0.1초 sleep 접속)외에 2초에 한번씩 접속해둔 트래픽이 흐르는것을 볼 수 있다.

 

 

 

5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)

 

이 스크린샷은 Istio를 사용해 마이크로서비스 아키텍처에서 트래픽을 세밀하게 제어하는 방법을 설명하고 있습니다.

  • 왼쪽의 Istio ingress gateway는 외부에서 들어오는 요청을 받아 webapp service로 전달합니다.
  • webapp service는 내부적으로 catalog 서비스의 두 버전(v1, v2) 중 하나를 호출합니다.
  • 여기서 중요한 점은, 트래픽 라우팅(즉, 어떤 요청이 catalog v1으로 갈지, v2로 갈지 결정하는 것)이 반드시 ingress gateway에서만 일어나는 것이 아니라, 서비스 간 호출(예: webapp → catalog)처럼 호출 그래프 내의 더 깊은 위치에서도 적용될 수 있다는 점입니다.
  • 즉, Istio의 VirtualService 같은 리소스를 활용하면, webapp에서 catalog로 가는 요청 중에서도 요청의 내용(예: 헤더, 경로, 파라미터 등)에 따라 세밀하게 트래픽을 분기할 수 있습니다.
  • 이런 방식으로, 예를 들어 특정 조건을 만족하는 요청만 catalog v2로 보내고, 나머지는 v1으로 보내는 식의 "미세 조정된 트래픽 제어"가 가능합니다.

즉, 이 그림과 설명은 Istio가 단순히 외부 트래픽만 제어하는 게 아니라, 서비스 간 내부 호출까지도 세밀하게 트래픽을 라우팅할 수 있음을 보여줍니다.

# 초기화
kubectl delete gateway,virtualservice,destinationrule --all -n istioinaction

# webapp 기동
kubectl apply -n istioinaction -f services/webapp/kubernetes/webapp.yaml
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태

# 확인
kubectl get deploy,pod,svc,ep -n istioinaction
NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/catalog      1/1     1            1           55m
deployment.apps/catalog-v2   1/1     1            1           48m
deployment.apps/webapp       1/1     1            1           42s

NAME                              READY   STATUS    RESTARTS   AGE
pod/catalog-6cf4b97d-jxpb8        2/2     Running   0          55m
pod/catalog-v2-6df885b555-rg9f5   2/2     Running   0          48m
pod/webapp-7685bcb84-2q7rg        2/2     Running   0          42s

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/catalog   ClusterIP   10.200.1.254   <none>        80/TCP    55m
service/webapp    ClusterIP   10.200.1.61    <none>        80/TCP    42s

NAME                ENDPOINTS                         AGE
endpoints/catalog   10.10.0.16:3000,10.10.0.17:3000   55m
endpoints/webapp    10.10.0.18:8080                   42s


# Now, set up the Istio ingress gateway to route to the webapp service
cat services/webapp/istio/webapp-catalog-gw-vs.yaml
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webapp-virtualservice
spec:
  hosts:
  - "webapp.istioinaction.io"
  gateways:
  - coolstore-gateway
  http:
  - route:
    - destination:
        host: webapp
        port:
          number: 80

kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction

# 확인
kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   3s

NAME                                                       GATEWAYS                HOSTS                         AGE
virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   3s


# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3


# 호출테스트 : 외부(web, curl) → ingressgw → webapp → catalog (v1, v2)
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq

# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 해두기
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# proxy-config : istio-ingressgateway
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 
NAME          DOMAINS                     MATCH     VIRTUAL SERVICE
http.8080     webapp.istioinaction.io     /*        webapp-virtualservice.istioinaction
=> route."cluster": "outbound|80||webapp.istioinaction.svc.cluster.local"

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system | egrep 'webapp|catalog'
catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS            
webapp.istioinaction.svc.cluster.local                  80        -          outbound      EDS

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
...
        "name": "outbound|80||webapp.istioinaction.svc.cluster.local",
        "type": "EDS",
...

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||webapp.istioinaction.svc.cluster.local'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:8080     HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local


# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction

# proxy-config : catalog
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction


# webapp istio-proxy 로그 활성화
# 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# webapp istio-proxy 로그 활성화 적용
cat << EOF | kubectl apply -f -
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: webapp
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  accessLogging:
  - providers:
    - name: envoy #2 액세스 로그를 위한 프로바이더 설정
    disabled: false #3 disable 를 false 로 설정해 활성화한다
EOF

# webapp → catalog 는 k8s service(clusterIP) 라우팅 사용 확인!
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-18T13:27:57.178Z] "HEAD /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 0 8 8 "172.18.0.1" "curl/8.7.1" "8d425652-17a9-4b41-a21c-874acab3b1f4" "webapp.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:51809 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
=> 이 로그는 webapp 서비스의 사이드카 프록시가 클라이언트로부터 직접 HTTP 요청을 받은 장면이고, 이 요청을 10.10.0.18:8080 (즉, webapp 서비스의 실제 컨테이너)으로 보냄을 의미
[2025-04-18T13:27:58.237Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "172.18.0.1" "beegoServer" "49b55b86-2505-4a5c-aadf-950d03705b87" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.18:45152 10.200.1.254:80 172.18.0.1:0 - default
=>  이 로그는 webapp 서비스가 catalog 서비스로 HTTP 요청을 보낸 상황이에요. Envoy는 catalog.istioinaction이라는 Kubernetes catalog 서비스(clusterIp 10.200.1.254:80)로 라우팅하고, 실제 Pod IP는 10.10.0.16:3000으로 연결되었어요.
...

 

위와같이 로그 활성화까지 진행한다.

 

위와 같이 catalog VS에서 트래픽을 분산시킬 수 있다.

 

#
cat ch5/catalog-vs-v2-request-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
    - mesh
  http:
  - match:
    - headers:
        x-istio-cohort:
          exact: "internal"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v2-request-mesh.yaml -n istioinaction


# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 >> v1, v2 각기 라우팅 확인
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 추가된 2개 항목 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json | jq
...
        "virtualHosts": [
            {
                "name": "catalog.istioinaction.svc.cluster.local:80",
                "domains": [
                    "catalog.istioinaction.svc.cluster.local",
                    "catalog",
                    "catalog.istioinaction.svc",
                    "catalog.istioinaction",
                    "10.200.1.254"
                ],
                "routes": [
                    {
                        "match": {
                            "prefix": "/",
                            "caseSensitive": true,
                            "headers": [
                                {
                                    "name": "x-istio-cohort",
                                    "stringMatch": {
                                        "exact": "internal"
                                    }
                                }
                            ]
                        },
                        "route": {
                            "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                     ....
                    {
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
...


docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'

# proxy-config (catalog) : mesh 이므로 VS가 아래 routes(catalog)에도 적용됨
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction

 

 

catalog VS에서 catalog-v2 파드로 트래픽이 흐르게끔 할 수 있다. 

 

 

 

5.3 Traffic shifting (실습)

Istio의 가중치 기반 라우팅을 활용해 신규 버전(catalog v2)에 트래픽을 10%씩 점진적으로 분배함으로써 카나리 릴리스를 수행하고, 문제 발생 시 가중치 조정으로 빠르게 롤백할 수 있어 배포 위험을 최소화합니다.

 

# 이전 절부터 다음 서비스가 실행 중이다 : 파드에 labels 에 버전 정보 없을 경우 latest 설정되며, kiali 에 Workloads Details 에 'Missing Version' 표기됨
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태
kubectl get deploy,rs,pod -n istioinaction --show-labels
NAME                          READY   STATUS    RESTARTS   AGE     LABELS
catalog-6cf4b97d-q4xv5        2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v1,version=v1
catalog-v2-6df885b555-df7g4   2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v2,version=v2
webapp-7685bcb84-skzgg        2/2     Running   0          2m48s   app=webapp,...,service.istio.io/canonical-revision=latest

# 반복 호출테스트 : 신규터미널
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 모든 트래픽을 catalog service v1 으로 재설정하자
cat ch5/catalog-vs-v1-mesh.yaml
...
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1

kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction

# 호출테스트
curl -s http://webapp.istioinaction.io:30000/api/catalog | jq


#
cat ch5/catalog-vs-v2-10-90-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1
      weight: 90
    - destination:
        host: catalog
        subset: version-v2
      weight: 10 
kubectl apply -f ch5/catalog-vs-v2-10-90-mesh.yaml -n istioinaction

#
kubectl get vs -n istioinaction catalog
NAME      GATEWAYS   HOSTS         AGE
catalog   ["mesh"]   ["catalog"]   112s

# 호출 테스트 : v2 호출 비중 확인
for i in {1..10};  do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l

# proxy-config(webapp) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
...
                        "route": {
                            "weightedClusters": {
                                "clusters": [
                                    {
                                        "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                        "weight": 90
                                    },
                                    {
                                        "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                        "weight": 10
                                    }
                                ],
                                "totalWeight": 100
...

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog


# proxy-config(catalog) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
...

 

 

 

9:1 로 트래픽이 맞춰지는걸 볼 수 있다.

 

  • 각 서비스 버전의 트래픽 가중치를 1에서 100 사이로 바꿀 수 있지만, 가중치 총합은 반드시 100이어야 한다.
  • 그렇지 않으면 예기치 못한 트래픽 라우팅이 발생할 수 있다.
  • v1, v2 외 다른 버전이 있을 경우, DestinationRule 에서 subnet 으로 선언해야 한다는 것도 유념하자.

5.3.1 (Automating) Canary releasing with Flagger (Progressive Delivery Operator for Kubernetes) - Link

Flagger는 Istio와 연동해 카나리 배포를 자동화하는 도구로, 릴리스 진행, 트래픽 분배, 모니터링, 롤백까지 모든 과정을 메트릭 기반으로 자동 처리하여 운영자의 수작업과 실수 가능성을 줄여줍니다

 

실습을 진행하기 위해 다음과 같이 기존 셋팅은 삭제한다.

# catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 제거
kubectl delete virtualservice catalog -n istioinaction
kubectl delete deploy catalog-v2 -n istioinaction
kubectl delete service catalog -n istioinaction
kubectl delete destinationrule catalog -n istioinaction

# 남은 리소스 확인
kubectl get deploy,svc,ep -n istioinaction
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/catalog   1/1     1            1           77m
deployment.apps/webapp    1/1     1            1           78m

NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/webapp   ClusterIP   10.200.1.73   <none>        80/TCP    78m

NAME               ENDPOINTS         AGE
endpoints/webapp   10.10.0.19:8080   78m

kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   73m

NAME                                                       GATEWAYS                HOSTS                         AGE
virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   73m

 

  • Flagger는 서비스 상태를 판단할 때 메트릭에 의존하며, 카나리 릴리스를 사용할 때 특히 그렇다.
  • Flagger가 성공 메트릭을 사용하려면 프로메테우스를 설치해 이스티오 데이터 플레인수집해야 한다.

Flagger를 다음과 같이 설치한다.

# CRD 설치 
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
kubectl get crd | grep flagger
alertproviders.flagger.app                 2025-04-19T03:11:50Z
canaries.flagger.app                       2025-04-19T03:11:50Z
metrictemplates.flagger.app                2025-04-19T03:11:50Z

# Helm 설치
helm repo add flagger https://flagger.app
helm install flagger flagger/flagger \
  --namespace=istio-system \
  --set crd.create=false \
  --set meshProvider=istio \
  --set metricServer=http://prometheus:9090

# 디플로이먼트 flagger 에 의해 배포된 파드 확인
kubectl get pod -n istio-system -l app.kubernetes.io/name=flagger
NAME                       READY   STATUS    RESTARTS   AGE
flagger-6d4ffc5576-q78ls   1/1     Running   0          2m54s

# 시크릿
kubectl get secret -n istio-system | grep flagger-token
flagger-token-v2f5z                                kubernetes.io/service-account-token   3      4m11s

# 시크릿 확인 : ca.crt 는 k8s 루프 인증서
kubectl view-secret -n istio-system flagger-token-v2f5z --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTI1MDQxOTAxNDEyMVoXDTM1MDQxNzAxNDEyMVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzN
onEbSSXBHfHhJICwREU4EX4D0K2Bwg7SXNwZNl3QwwPOpjFoaRbr6Hdog88jmo8A
Mo/RDKDj+Lvr0FE3hBvm5igLndWgnjYqpIHfDq31AYvWCoJvbBQ/omDIal4u1HHI
8XNqEpxl3JhsV9M9pMEx2+Gvlp1og8qjbB3B5amutriNQom6VOG0HBzJQuvNG8op
2GhWD4IOQf3vfKltGE9Y/KzbBLajtPueMkHZr/kH4Qy/Xu9kSGc8lhdsxrRSqoTX
ytyr2rOe83vliKhGKYtkiWESIm35BcVF1rp+jl0nLGs8IMhmR5Ll9A9pZ5xsqFzN
ndg7DjpdcKKwCxzw9BsCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFPm+n2+NblRv8ZWaoVW4fMvmFzuNMBUGA1UdEQQO
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAFkDbnnWS+9u9AKnlih0
Cltedk01oId5I31SWzvfI1owgudBH6pGxs3dGeij5iNDlV2StDzG5mqUIW6Y5iXR
hVMUUl/GvscaoSqFb5cIEgfmzdDSsNm1eBON8HpoN4VuEyuRZn1O39JAYIzVQcwD
LgO/dTCZwqM6hs6LC2Qx/PlxlQLt3agT3sZWXkztbOjLpLCuqVrz0NIRzFS3M2hA
b1+ACPllYGIRiEpSNxzbybgjui4W8bt8W2AjTPuqXIB/TuEcQrgAzrVcQsVf2XID
nC+WEpmUURKxQ51dLpLLQgFfojz+LXrdrlGJ4hpcfj0aN5j221+rjHTnI6PrsQdT
qME=
-----END CERTIFICATE-----
'
namespace='istio-system'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InFIUnV5blBfSUVDaDQ0MUxyOXR2MFRqV1g5ekVjaU1wdWZvSDFXZXl6Z3cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJpc3Rpby1zeXN0ZW0iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZmxhZ2dlci10b2tlbi12MmY1eiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJmbGFnZ2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNWIxZWM4MjUtODU4My00OGViLWI4MGMtNmYyNzEzZTBlMzA3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmlzdGlvLXN5c3RlbTpmbGFnZ2VyIn0.Eb14h5EKU9FfYZa3XkydrFFYUSk4gPYUM0j76Cbsb4XTtAL0U54-RfMiNcX5rfyK6WFOUhU5W6yuAChRhsl7TEzZCpgj3aVRNNe5TRsy-mYpG89FfBSpU0H6wmZyJnvHDcweo1eh-BLIThH6-_1GuUeJDc18WsapllkcHNIXiR_7gudgY7tfn29KoKxlv72K_HPYIerIsTGZe9tHr7K__lvl0Yz779yKNXKUlSerqho0-z2cPsmhFRR1KvPwrhi6UQck70s_snMlaecVJvkrYXCnEvsMkUwpaa6JmDmamKC3NNm9zWJYKtEt0fHHomZoJQFHHQCiYDTVkjYi8ErE2A'

# token 을 jtw.io 에서 Decoded 확인
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "istio-system",
  "kubernetes.io/serviceaccount/secret.name": "flagger-token-v2f5z",
  "kubernetes.io/serviceaccount/service-account.name": "flagger",
  "kubernetes.io/serviceaccount/service-account.uid": "5b1ec825-8583-48eb-b80c-6f2713e0e307",
  "sub": "system:serviceaccount:istio-system:flagger"
}

 

그리고 Flagger 카나리 리소스를 사용해서 카나리 릴리스에 대한 설정을 정의한다

  • 이 Canary 리소스에서는 어떤 쿠버네티스 Deployment가 카나리 대상인지, 어떤 쿠버네티스 Service와 이스티오 VirtualService가 자동으로 만들어져야 하는지, 카나리를 어떻게 진행해야 하는지 등을 지정한다.
  • Canary 리소스의 마지막 부분은 카나리를 얼마나 빨리 진행할지, 생존을 판단하기 위해 지켜볼 메트릭은 무엇인지, 성공을 판단할 임계값은 얼마인지를 기술하고 있다.
  • 45초마다 카나리의 각 단계를 평가하고, 단계별로 트래픽을 10%씩 늘린다. 트래픽이 **50%에 도달하면 100%**로 바꾼다.
  • 성공률 메트릭의 경우 1분 동안의 성공률이 99% 이상이어야 한다. 또한 P99(상위 99%) 요청 시간은 500ms까지 허용한다.
  • 이 메트릭들이 연속으로 5회를 초과해 지정한 범위와 다르면, 롤백한다.
#  cat ch5/flagger/catalog-release.yaml        
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: catalog-release
  namespace: istioinaction
spec:   
  targetRef: #1 카나리 대상 디플로이먼트 https://docs.flagger.app/usage/how-it-works#canary-target
    apiVersion: apps/v1
    kind: Deployment
    name: catalog  
  progressDeadlineSeconds: 60
  # Service / VirtualService Config
  service: #2 서비스용 설정 https://docs.flagger.app/usage/how-it-works#canary-service
    name: catalog
    port: 80
    targetPort: 3000
    gateways:
    - mesh    
    hosts:
    - catalog
  analysis: #3 카니리 진행 파라미터 https://docs.flagger.app/usage/how-it-works#canary-analysis
    interval: 45s
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    match: 
    - sourceLabels:
        app: webapp
    metrics: # https://docs.flagger.app/usage/metrics , https://docs.flagger.app/faq#metrics
    - name: request-success-rate # built-in metric 요청 성공률
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration # built-in metric 요청 시간
      thresholdRange:
        max: 500
      interval: 30s
      
      
      
# 반복 호출테스트 : 신규터미널
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# flagger (operator) 가 catalog를 위한 canary 배포환경을 구성
kubectl apply -f ch5/flagger/catalog-release.yaml -n istioinaction

# flagger 로그 확인 : Service, Deployment, VirtualService 등을 설치하는 것을 확인할 수 있습니다.
kubectl logs -f deploy/flagger -n istio-system
...

# 확인
kubectl get canary -n istioinaction -w
NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
catalog-release   Initializing  0        2025-04-19T05:10:00Z
catalog-release   Initialized   0        2025-04-19T05:15:54Z

kubectl get canary -n istioinaction -owide
NAME              STATUS        WEIGHT   SUSPENDED   FAILEDCHECKS   INTERVAL   MIRROR   STEPWEIGHT   STEPWEIGHTS   MAXWEIGHT   LASTTRANSITIONTIME
catalog-release   Initialized   0                    0              45s                 10                         50          2025-04-19T05:15:54Z

# flagger Initialized 동작 확인
## catalog-primary deployment/service 가 생성되어 있음, 기존 catalog deploy/service 는 파드가 0으로 됨
kubectl get deploy,svc,ep -n istioinaction -o wide

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                         SELECTOR
deployment.apps/catalog           0/0     0            0           3h34m   catalog      istioinaction/catalog:latest   app=catalog,version=v1
deployment.apps/catalog-primary   1/1     1            1           6m41s   catalog      istioinaction/catalog:latest   app=catalog-primary
deployment.apps/webapp            1/1     1            1           3h36m   webapp       istioinaction/webapp:latest    app=webapp

NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
service/catalog           ClusterIP   10.200.1.168   <none>        80/TCP    5m56s   app=catalog-primary
service/catalog-canary    ClusterIP   10.200.1.242   <none>        80/TCP    6m41s   app=catalog
service/catalog-primary   ClusterIP   10.200.1.119   <none>        80/TCP    6m41s   app=catalog-primary
service/webapp            ClusterIP   10.200.1.73    <none>        80/TCP    3h36m   app=webapp

NAME                        ENDPOINTS         AGE
endpoints/catalog           10.10.0.23:3000   5m56s
endpoints/catalog-canary    <none>            6m41s
endpoints/catalog-primary   10.10.0.23:3000   6m41s
endpoints/webapp            10.10.0.19:8080   3h36m

## VS catalog 생성되었음
kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   137m

NAME                                                       GATEWAYS                HOSTS                         AGE
virtualservice.networking.istio.io/catalog                 ["mesh"]                ["catalog"]                   8m17s
virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   137m

## VS catalog 확인
kubectl get vs -n istioinaction catalog -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  annotations:
    helm.toolkit.fluxcd.io/driftDetection: disabled
    kustomize.toolkit.fluxcd.io/reconcile: disabled
  name: catalog
  namespace: istioinaction
spec:
  gateways:
  - mesh
  hosts:
  - catalog
  http:
  - match:
    - sourceLabels:
        app: webapp
    route:
    - destination:
        host: catalog-primary
      weight: 100
    - destination:
        host: catalog-canary
      weight: 0
  - route:
    - destination:
        host: catalog-primary
      weight: 100

# destinationrule 확인
kubectl get destinationrule -n istioinaction
NAME              HOST              AGE
catalog-canary    catalog-canary    15m
catalog-primary   catalog-primary   15m

kubectl get destinationrule -n istioinaction catalog-primary -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: catalog-primary
  namespace: istioinaction
spec:
  host: catalog-primary

kubectl get destinationrule -n istioinaction catalog-canary -o yaml | kubectl neat
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: catalog-canary
  namespace: istioinaction
spec:
  host: catalog-canary

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 
NAME     DOMAINS                                                        MATCH     VIRTUAL SERVICE
80       catalog-canary, catalog-canary.istioinaction + 1 more...       /*        
80       catalog-primary, catalog-primary.istioinaction + 1 more...     /*        
80       catalog, catalog.istioinaction + 1 more...                     /*        catalog.istioinaction
...


docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | egrep 'RULE|catalog'
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
catalog-canary.istioinaction.svc.cluster.local          80        -          outbound      EDS              catalog-canary.istioinaction
catalog-primary.istioinaction.svc.cluster.local         80        -          outbound      EDS              catalog-primary.istioinaction
catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS 

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-primary.istioinaction.svc.cluster.local -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-canary.istioinaction.svc.cluster.local -o json

docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog-primary.istioinaction.svc.cluster.local
10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# 해당 EDS에 메트릭 통계 값 0.
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...

# 현재 EDS primary 에 메트릭 통계 값 출력 중. 해당 EDS 호출.
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog-primary.istioinaction.svc.cluster.local' -o json
...

 

 

 

  • 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않았다.
  • Flagger는 원본 디폴로이먼트 대상(여기서는 catalog 디플로이먼트 ?catalog-primary가 아닌가?)의 변경 사항을 지켜보고, 카나리 디폴로이먼트(catalog-canary) 및 서비스(catalog-canary)를 생성하고, VirtualService 의 가중치를 조정한다.

 

 

# 반복 호출테스트 : 신규터미널1 - 부하 만들기
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# flagger 로그 확인 : 신규터미널2
kubectl logs -f deploy/flagger -n istio-system
{"level":"info","ts":"2025-04-19T05:15:54.453Z","caller":"controller/events.go:33","msg":"Initialization done! catalog-release.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:09.442Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:09:09.444Z","caller":"controller/events.go:33","msg":"New revision detected! Scaling up catalog.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:54.441Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:09:54.446Z","caller":"controller/events.go:33","msg":"Starting canary analysis for catalog.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:09:54.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 10","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:10:39.443Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:10:39.469Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 20","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:11:24.437Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:11:24.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 30","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:12:09.445Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:12:09.472Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 40","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:12:54.429Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:12:54.445Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 50","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:13:39.444Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:13:39.453Z","caller":"controller/events.go:33","msg":"Copying catalog.istioinaction template spec to catalog-primary.istioinaction","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:14:24.438Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:14:24.440Z","caller":"controller/events.go:33","msg":"Routing all traffic to primary","canary":"catalog-release.istioinaction"}
{"level":"info","ts":"2025-04-19T06:15:09.436Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
{"level":"info","ts":"2025-04-19T06:15:09.642Z","caller":"controller/events.go:33","msg":"Promotion completed! Scaling down catalog.istioinaction","canary":"catalog-release.istioinaction"}

# flagger 상태 확인 : 신규터미널3
## 카나리는 Carary 오브젝트에 설정한 대로 45초마다 진행될 것이다.
## 트래픽의 50%가 카나리로 이동할 때까지는 단계별로 10%씩 증가한다.
## flagger가 메트릭에 문제가 없고 기준과 차이가 없다고 판단되면, 모든 트래픽이 카나리로 이동해 카나리가 기본 서비스로 승격 될 때까지 카나리가 진행된다.
## 만약 문제가 발생하면 flagger는 자동으로 카나리 릴리스를 롤백할 것이다.
kubectl get canary -n istioinaction -w
NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
catalog-release   Initialized   0        2025-04-19T05:15:54Z
catalog-release   Progressing   0        2025-04-19T06:09:09Z
catalog-release   Progressing   10       2025-04-19T06:09:54Z # 45초 간격
catalog-release   Progressing   20       2025-04-19T06:10:39Z
catalog-release   Progressing   30       2025-04-19T06:11:24Z
catalog-release   Progressing   40       2025-04-19T06:12:09Z
catalog-release   Progressing   50       2025-04-19T06:12:54Z
catalog-release   Promoting     0        2025-04-19T06:13:39Z
catalog-release   Finalising    0        2025-04-19T06:14:24Z
catalog-release   Succeeded     0        2025-04-19T06:15:09Z


# imageUrl 출력 (v2)을 포함하는 catalog deployment v2 배포
cat ch5/flagger/catalog-deployment-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: catalog
    version: v1
  name: catalog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: catalog
      version: v1
  template:
    metadata:
      labels:
        app: catalog
        version: v1
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: SHOW_IMAGE
          value: "true"
        image: istioinaction/catalog:latest
        imagePullPolicy: IfNotPresent
        name: catalog
        ports:
        - containerPort: 3000
          name: http
          protocol: TCP
        securityContext:
          privileged: false

kubectl apply -f ch5/flagger/catalog-deployment-v2.yaml -n istioinaction
	kubectl get vs -n istioinaction catalog -o yaml -w # catalog vs 에 가중치 변경 모니터링
	---
    route:
    - destination:
        host: catalog-primary
      weight: 90
    - destination:
        host: catalog-canary
      weight: 10
  - route:
    - destination:
        host: catalog-primary
      weight: 90
---
    route:
    - destination:
        host: catalog-primary
      weight: 80
    - destination:
        host: catalog-canary
      weight: 20
  - route:
    - destination:
        host: catalog-primary
      weight: 80
---
    route:
    - destination:
        host: catalog-primary
      weight: 70
    - destination:
        host: catalog-canary
      weight: 30
  - route:
    - destination:
        host: catalog-primary
      weight: 70
---
    route:
    - destination:
        host: catalog-primary
      weight: 60
    - destination:
        host: catalog-canary
      weight: 40
  - route:
    - destination:
        host: catalog-primary
      weight: 60
---
    route:
    - destination:
        host: catalog-primary
      weight: 50
    - destination:
        host: catalog-canary
      weight: 50
  - route:
    - destination:
        host: catalog-primary
      weight: 50
---
    route:
    - destination:
        host: catalog-primary
      weight: 100
    - destination:
        host: catalog-canary
      weight: 0
  - route:
    - destination:
        host: catalog-primary
      weight: 100
---
 
# canary CRD 이벤트 확인
kubectl describe canary -n istioinaction catalog-release | grep Events: -A20
Events:
  Type    Reason  Age    From     Message
  ----    ------  ----   ----     -------
  Normal  Synced  10m    flagger  New revision detected! Scaling up catalog.istioinaction
  Normal  Synced  9m54s  flagger  Starting canary analysis for catalog.istioinaction
  Normal  Synced  9m54s  flagger  Advance catalog-release.istioinaction canary weight 10
  Normal  Synced  9m9s   flagger  Advance catalog-release.istioinaction canary weight 20
  Normal  Synced  8m24s  flagger  Advance catalog-release.istioinaction canary weight 30
  Normal  Synced  7m39s  flagger  Advance catalog-release.istioinaction canary weight 40
  Normal  Synced  6m54s  flagger  Advance catalog-release.istioinaction canary weight 50
  Normal  Synced  6m9s   flagger  Copying catalog.istioinaction template spec to catalog-primary.istioinaction
  Normal  Synced  5m24s  flagger  Routing all traffic to primary
  Normal  Synced  4m39s  flagger  (combined from similar events): Promotion completed! Scaling down catalog.istioinaction

# 최종 v2 접속 확인
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
     100

 

위와같이 신규 버전을 배포하고 모니터링해본다.

 

 

 

 

 

 

자동으로 WEIGHT이 오르고 있다. 그리고 당연하게 kiali에서는 트래픽이 점점 많이 흐르는것기 확인된다.

 

 

 

5.4 Reducing risk even further: Traffic mirroring

트래픽 미러링은 실제 운영 환경 트래픽을 복사해 별도의 서비스(catalog-v2)로 전송하되, 사용자 응답에는 영향을 주지 않는 방식입니다. 이를 통해 신규 버전의 동작을 실제 트래픽으로 검증하면서도 서비스 중단 위험을 제로에 가깝게 관리할 수 있습니다.

 

 

# cat ch5/catalog-vs-v2-mirror.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  gateways:
    - mesh
  http:
  - route:
    - destination:
        host: catalog
        subset: version-v1
      weight: 100
    mirror:
      host: catalog
      subset: version-v2
      
      
# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; don

# catalog istio-proxy 로그 활성화
cat << EOF | kubectl apply -f -
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: catalog
  namespace: istioinaction
spec:
  accessLogging:
  - disabled: false
    providers:
    - name: envoy
  selector:
    matchLabels:
      app: catalog
EOF

kubectl get telemetries -n istioinaction
NAME      AGE
catalog   16s
webapp    5h49m

# istio-proxy 로그 확인 : 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl logs -n istioinaction -l version=v1 -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -l version=v2 -c istio-proxy -f
혹은
kubectl stern -n istioinaction -l app=catalog -c istio-proxy

# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | grep catalog
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog

# proxy-config : catalog
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep catalog 
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep catalog 


# 미러링 VS 설정
kubectl apply -f ch5/catalog-vs-v2-mirror.yaml -n istioinaction

# v1 으로만 호출 확인
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
       0

# v1 app 로그 확인
kubectl logs -n istioinaction -l app=catalog -l version=v1 -c catalog -f
request path: /items
blowups: {}
number of blowups: 0
GET catalog.istioinaction:80 /items 200 502 - 0.375 ms
GET /items 200 0.375 ms - 502
...

# v2 app 로그 확인 : 미러링된 트래픽이 catalog v2로 향할때, Host 헤더가 수정돼 미러링/섀도잉된 트래픽임을 나타낸다.
## 따라서 Host:catalog:8080 대신 Host:catalog-shadow:8080이 된다.
## -shadow 접미사가 붙은 요청을 받는 서비스는 그 요청이 미러링된 요청임을 식별할 수 있어, 요청을 처리할 때 고려할 수 있다
## 예를 들어, 응답이 버려질 테니 트랜잭션을 롤백하지 않거나 리소스를 많이 사용하는 호출을 하지 않는 것 등.
kubectl logs -n istioinaction -l app=catalog -l version=v2 -c catalog -f
request path: /items
blowups: {}
number of blowups: 0
GET catalog.istioinaction-shadow:80 /items 200 698 - 0.503 ms
GET /items 200 0.503 ms - 698


#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json
...
                        "route": {
                            "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                            "timeout": "0s",
                            "retryPolicy": {
                                "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                "numRetries": 2,
                                "retryHostPredicate": [
                                    {
                                        "name": "envoy.retry_host_predicates.previous_hosts",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                        }
                                    }
                                ],
                                "hostSelectionRetryMaxAttempts": "5",
                                "retriableStatusCodes": [
                                    503
                                ]
                            },
                            "requestMirrorPolicies": [
                                {
                                    "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                    "runtimeFraction": {
                                        "defaultValue": {
                                            "numerator": 100
                                        }
                                    },
                                    "traceSampled": false
...


# 위 webapp과 상동 : 그거슨 mesh(gateway)이니까...
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json > webapp-routes.json
cat catalog-routes.json
...

 

catalog에 미러링 표시가 추가된것을 볼 수 있다.

 

# Istio 메시 내부망에서 모든 mTLS 통신 기능 끄기 설정 : (참고) 특정 네임스페이스 등 세부 조절 설정 가능 
cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: DISABLE
EOF

kubectl get PeerAuthentication -n istio-system
NAME      MODE      AGE
default   DISABLE   6h13m


--------------------------------------------------------------------------------
# catalog v2 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
## catalog v2 파드 IP 확인
C2IP=$(kubectl get pod -n istioinaction -l app=catalog -l version=v2 -o jsonpath='{.items[*].status.podIP}')
echo $C2IP

## veth 이름 확인
docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }'
C2VETH=$(docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }')
echo $C2VETH
veth853288c7 <- 해당 이름을 메모해두기
--------------------------------------------------------------------------------

# ngrep 확인(메모 정보 직접 기입!) : catalog v2 파드 tcp 3000
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d veth4ede8164 '' 'tcp port 3000'"
## 요청
T 2025/04/19 08:13:56.766981 10.10.0.19:49446 -> 10.10.0.27:3000 [AP] #19
GET /items HTTP/1.1.
host: catalog.istioinaction-shadow:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1,10.10.0.19.
x-forwarded-proto: http.
x-request-id: 769c8e1f-2a2a-4498-b98d-0683c7c46281.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: 8a650108ee32974ad419ff2948d9f8f2.
x-b3-spanid: 12c05a3f651b36fa.
x-b3-parentspanid: 2c77705aee579141.
x-b3-sampled: 0.
.

## 응답
T 2025/04/19 08:13:56.770458 10.10.0.27:3000 -> 10.10.0.19:49446 [AP] #20
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 698.
etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
date: Sat, 19 Apr 2025 08:13:56 GMT.
x-envoy-upstream-service-time: 2.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
...

 

위와같이 패킷덤프를 하기 위한 설정을 추가한다.

 

 

 

미러링된 대상으로 트래픽이 흐르는게 의아했다. 역시나 실습에서 잘 설명해준다.

--------------------------------------------------------------------------------
# webapp 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
## webapp 파드 IP 확인
WEBIP=$(kubectl get pod -n istioinaction -l app=webapp -o jsonpath='{.items[*].status.podIP}')
echo $WEBIP

## veth 이름 확인
docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }'
WEBVETH=$(docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }')
echo $WEBVETH
vetha33715c3 <- 해당 이름을 메모해두기
--------------------------------------------------------------------------------

# ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 8080 
## => 아래 tcp 3000에서 미러링 응답이 있지만 8080에 없다는건, webapp istio-proxy 가 최초 외부 요청자에게는 전달하지 않고 무시(drop?).
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 8080'"
## 요청 
T 2025/04/19 08:16:05.698957 10.10.0.8:39476 -> 10.10.0.19:8080 [AP] #10
HEAD /api/catalog HTTP/1.1.
host: webapp.istioinaction.io:30000.
user-agent: curl/8.7.1.
accept: */*.
x-forwarded-for: 172.18.0.1.
x-forwarded-proto: http.
x-envoy-internal: true.
x-request-id: a6cee7e5-277a-420d-a41f-78f7ca4266a4.
x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuOAoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi4KBE5BTUUSJhokaXN0aW8taW5ncmVzc2dhdGV3YXktOTk2YmM2YmI2LThzY3BwChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ==.
x-envoy-peer-metadata-id: router~10.10.0.8~istio-ingressgateway-996bc6bb6-8scpp.istio-system~istio-system.svc.cluster.local.
x-envoy-attempt-count: 1.
x-b3-traceid: d07b1cb671a13906948c4e8cdb04fc5f.
x-b3-spanid: 948c4e8cdb04fc5f.
x-b3-sampled: 0.
.

## 응답
T 2025/04/19 08:16:05.708158 10.10.0.19:8080 -> 10.10.0.8:39476 [AP] #11
HTTP/1.1 200 OK.
content-length: 357.
content-type: application/json; charset=utf-8.
date: Sat, 19 Apr 2025 08:16:05 GMT.
x-envoy-upstream-service-time: 8.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.


# ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 3000
docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 3000'"
## webapp 파드가 catalog v1 요청
T 2025/04/19 08:19:17.965769 10.10.0.19:59682 -> 10.10.0.26:3000 [AP] #7
GET /items HTTP/1.1.
host: catalog.istioinaction:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1.
x-forwarded-proto: http.
x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: acea9966a116ec369261bf1007049ccf.
x-b3-spanid: a9db407fee14ea00.
x-b3-parentspanid: 88ea1c8edba97e58.
x-b3-sampled: 0.
.

## webapp 파드가 catalog v2 미러링 전달
T 2025/04/19 08:19:17.965805 10.10.0.19:49454 -> 10.10.0.27:3000 [AP] #8
GET /items HTTP/1.1.
host: catalog.istioinaction-shadow:80.
user-agent: beegoServer.
x-envoy-attempt-count: 1.
x-forwarded-for: 172.18.0.1,10.10.0.19.
x-forwarded-proto: http.
x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
accept-encoding: gzip.
x-envoy-internal: true.
x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
x-b3-traceid: acea9966a116ec369261bf1007049ccf.
x-b3-spanid: 06a47967e15bebdf.
x-b3-parentspanid: a9db407fee14ea00.
x-b3-sampled: 0.
.

## catalog v1 에서 응답 받음
T 2025/04/19 08:19:17.968372 10.10.0.26:3000 -> 10.10.0.19:59682 [AP] #11
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 502.
etag: W/"1f6-ih2h+hDQ0yLLcKIlBvwkWbyQGK4".
date: Sat, 19 Apr 2025 08:19:17 GMT.
x-envoy-upstream-service-time: 1.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI2ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjEKDwoHdmVyc2lvbhIEGgJ2MQoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhhjYXRhbG9nLTZkNWI5YmJiNjYtdnpnNGoKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUQoFT1dORVISSBpGa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZwoXChFQTEFURk9STV9NRVRBREFUQRICKgAKGgoNV09SS0xPQURfTkFNRRIJGgdjYXRhbG9n.
x-envoy-peer-metadata-id: sidecar~10.10.0.26~catalog-6d5b9bbb66-vzg4j.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00"
  },
...

## catalog v2 에서 응답 받음
T 2025/04/19 08:19:17.968259 10.10.0.27:3000 -> 10.10.0.19:49454 [AP] #9
HTTP/1.1 200 OK.
x-powered-by: Express.
vary: Origin, Accept-Encoding.
access-control-allow-credentials: true.
cache-control: no-cache.
pragma: no-cache.
expires: -1.
content-type: application/json; charset=utf-8.
content-length: 698.
etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
date: Sat, 19 Apr 2025 08:19:17 GMT.
x-envoy-upstream-service-time: 1.
x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
server: istio-envoy.
.
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00",
    "imageUrl": "http://lorempixel.com/640/480"
  },
...

위와같이 확인해보면, 미러링된 파드로는 트래픽이 흐르고 처리는 하지만 실제로 드랍하는것을 알 수 있다.

 

 

 

5.5 Routing to services outside your cluster by using Istio’s service discovery

이스티오는 기본적으로 서비스 메시 밖으로 나가는 트래픽을 허용하지만, 정책을 변경해 외부 트래픽을 차단할 수 있다.

외부 트래픽 차단은 내부 서비스가 손상됐을 때 악의적 행위자가 외부로 데이터를 유출하는 것을 방지하는 심층 방어 전략이다.

그러나 프록시 우회 등 추가 위협에 대비해 3, 4계층 네트워크 차단 등 복합적인 보안 대책이 필요하다.

 

# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
                "meshConfig": {
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "enablePrometheusMerge": true
                },
...

# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml

# webapp 로그 : 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -

# 다음 명령을 실행해 이스티오의 기본값을 ALLOW_ANY 에서 REGISTRY_ONLY 로 바꾸자.
# 이느 서비스 메시 저장소에 명시적으로 허용된 경우(화이트 리스트)에만 트래픽이 메시를 떠나도록 허용하겠다는 의미다.
# 아래 설정 방법 이외에도 IstioOperator 로 설정을 변경하거나, istio-system 의 istio configmap 을 변경해도 됨.
# outboundTrafficPolicy 3가지 모드 : ALLOW_ANY (default) , REGISTRY_ONLY , ALLOW_LIST
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
y 

exit
----------------------------------------

# 배포 확인
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
catalog-6d5b9bbb66-vzg4j.istioinaction                 Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
catalog-v2-6df885b555-n9nxw.istioinaction              Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
istio-ingressgateway-6bb8fb6549-s4pt8.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
webapp-7685bcb84-skzgg.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8


# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
Connecting to raw.githubusercontent.com (185.199.109.133:443)
wget: error getting response: Connection reset by peer
command terminated with exit code 1

# webapp 로그 : BlackHoleCluster 차단
# UH : NoHealthyUpstream - No healthy upstream hosts in upstream cluster in addition to 503 response code.
# https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -

# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn BlackHoleCluster -o json
[
    {
        "name": "BlackHoleCluster",
        "type": "STATIC",
        "connectTimeout": "10s"
    }
]

# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
                "meshConfig": {
                    "accessLogFile": "/dev/stdout",
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "enablePrometheusMerge": true,
                    "extensionProviders": [
                        {
                            "envoyOtelAls": {
                                "port": 4317,
                                "service": "opentelemetry-collector.istio-system.svc.cluster.local"
                            },
                            "name": "otel"
                        },
                        {
                            "name": "skywalking",
                            "skywalking": {
                                "port": 11800,
                                "service": "tracing.istio-system.svc.cluster.local"
                            }
                        }
                    ],
                    "outboundTrafficPolicy": {
                        "mode": "REGISTRY_ONLY"
                    }
                },

 

 

 

서비스 엔트리란

ServiceEntry는 이스티오 서비스 메시 내부 서비스들이 외부(클러스터 밖) 서비스나 메시 외부의 엔드포인트와 통신할 수 있도록, 해당 외부 서비스를 이스티오의 내부 서비스 레지스트리에 등록해주는 리소스입니다

# cat ch5/forum-serviceentry.yaml                                        
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: jsonplaceholder
spec:
  hosts:
  - jsonplaceholder.typicode.com
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS
  location: MESH_EXTERNAL
  
  
  
# forum 설치
cat services/forum/kubernetes/forum-all.yaml
kubectl apply -f services/forum/kubernetes/forum-all.yaml -n istioinaction

# 확인
kubectl get deploy,svc -n istioinaction -l app=webapp
docker exec -it myk8s-control-plane istioctl proxy-status


# webapp 웹 접속
open http://webapp.istioinaction.io:30000/

 

 

 

이때 curl -s http://webapp.istioinaction.io:30000/api/users 명령어로 포럼 서비스를 호출하면 포럼 서비스에 서비스 엔트리 설정 적용 안돼있으니 아래와 같이 에러가 난다.

 

 

서비스엔트리 설정을 추가해보자.

# istio proxy (forum)
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-1.json

#
cat ch5/forum-serviceentry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: jsonplaceholder
spec:
  hosts:
  - jsonplaceholder.typicode.com
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS
  location: MESH_EXTERNAL
  
kubectl apply -f ch5/forum-serviceentry.yaml -n istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-2.json
diff forum-1.json forum-2.json
25a26
> jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS       
96a98
> 80                                                            jsonplaceholder.typicode.com                       /*  

#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction | grep json
80                                                            jsonplaceholder.typicode.com                       /* 

docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction --fqdn jsonplaceholder.typicode.com -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction | grep json
jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS  

# 목적 hosts 의 도메인 질의 응답 IP로 확인된 엔드포인트들 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction --cluster 'outbound|80||jsonplaceholder.typicode.com'
ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
104.21.112.1:80     HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.16.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.32.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.48.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.64.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.80.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
104.21.96.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com


# 메시 안에서 새로운 포럼 서비스를 호출 : 사용자 목록 반환 OK
curl -s http://webapp.istioinaction.io:30000/api/users


# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/ ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done


# webapp 웹 접속
open http://webapp.istioinaction.io:30000/

 

 

외부에서 잘 가져와는지는것을 확인할 수 있따.

 

 

 

5장에 대한 요약

 

  • DestinationRule 로 워크로드를 v1, v2 버전과 같이 더 작은 부분집합들로 분리할 수 있다.
  • VirtualService는 이런 부분집합들을 사용해 트래픽을 세밀하게 라우팅한다.
  • VirtualService는 HTTP 헤더 같은 애플리케이션 계층 정보를 기반으로 라우팅 결정을 설정한다.
    • 이 덕분에 베타 테스터 같은 특정 사용자 집합을 서비스의 신 버전으로 보내 테스트하는 다크 런치 기법을 사용할 수 있다.
  • 가중치 라우팅(VirtualService 리소스로 설정)을 사용하는 서비스 프록시는 트래픽을 점진적으로 새 배포로 라우팅할 수 있는데, 덕분에 카나리 배포(트래픽 전환이라고도 함) 같은 방법을 사용할 수 있다.
  • 트래픽 전환은 Flagger를 사용해 자동화할 수 있다. Flagger는 수집한 메트릭을 사용해 새 배포로 라우팅되는 트래픽을 점진적으로 늘리는 오픈소스 솔루션이다.
  • outboundTrafficPolicyREGISTER_ONLY로 설정하면 어떤 트래픽도 클러스터를 떠나지 못하게 함으로써 악의적인 사용자가 외부로 정보를 전송하는 것을 방지할 수 있다.
  • outboundTrafficPolicyREGISTER_ONLY로 설정했을 때는 ServiceEntry로 외부로 향하는 트래픽을 허용할 수 있다.

istio 스터디 2주차 내용을 정리하였다.

Istio 프록시의 실제 동작 도구인 Envoy 프록시에 대한 내용과 secure istio에 대한 내용을 다루었다.

그럼시작한다.

Envoy란 ?

Envoy는 원래 Lyft에서 개발된 고성능 오픈소스 프록시(proxy)입니다. 주로 마이크로서비스 아키텍처에서 서비스 간 트래픽을 중계하고, 로드밸런싱, 서비스 디스커버리, TLS 암호화, 서킷 브레이커, 트래픽 라우팅, 모니터링 등 다양한 네트워크 기능을 제공한다. Envoy의 가장 큰 특징은 API 기반의 실시간 설정 업데이트와 뛰어난 가시성과 성능이다.

istio가 Envoy 프록시를 사이드카로 활용하여 서비스 메시를 구현하고, 트래픽 관리·보안·모니터링 등 다양한 기능을 제공하는 오픈소스 플랫폼이기 때문에 Envoy 프록시를 필히 알아야 한다.

Envoy의 가장 큰 장점은 동적 구성(Dynamic Configuration)과 API 기반 관리이다.
이 장점 덕분에 실시간으로 프록시 설정(트래픽 라우팅, 보안 정책, 로드밸런싱 등)을 변경할 수 있고, 서비스 중단 없이 네트워크 정책을 빠르게 반영할 수 있다.

예를 들어, 대규모 마이크로서비스 환경에서 특정 서비스에 장애가 발생했을 때, 운영자는 Envoy의 xDS API를 통해 실시간으로 트래픽을 우회하거나 라우팅 정책을 수정할 수 있다. 이 과정에서 애플리케이션이나 프록시를 재시작할 필요가 없으므로, 서비스 연속성과 가용성을 향상 시킬 수 있다. DevOps 엔지니어 입장에서는 배포 자동화 파이프라인이나 운영 도구와 연동해 네트워크 정책을 코드로 관리하고, 장애 대응이나 신규 서비스 추가 시에도 빠르고 유연하게 대응할 수 있다.

Envoy 실습

docker pull envoyproxy/envoy:v1.19.0
docker pull curlimages/curl
docker pull mccutchen/go-httpbin

# mccutchen/go-httpbin 는 기본 8080 포트여서, 책 실습에 맞게 8000으로 변경
# docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin -p 8000:8000
docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin 
docker ps

# curl 컨테이너로 httpbin 호출 확인
docker run -it --rm --link httpbin curlimages/curl curl -X GET http://httpbin:8000/headers

여기까지는 envoy를 타지않고 httpbin 컨테이너로 바로 접속한 모습이다.

admin:  # Envoy 관리 인터페이스 설정
  address:  # 관리 인터페이스 주소 구성
    socket_address: { address: 0.0.0.0, port_value: 15000 }  # 모든 IP에서 15000 포트로 관리 인터페이스 개방

static_resources:  # 정적 리소스 정의(리스너, 클러스터 등)
  listeners:  # 트래픽 수신을 위한 리스너 목록
  - name: httpbin-demo  # 리스너 식별 이름
    address:  # 리스너 바인딩 주소
      socket_address: { address: 0.0.0.0, port_value: 15001 }  # 모든 IP에서 15001 포트로 트래픽 수신
    filter_chains:  # 필터 체인 구성
    - filters:  # 네트워크 필터 목록
      - name:  envoy.filters.network.http_connection_manager  # HTTP 연결 관리 필터 사용
        typed_config:  # 타입 지정 구성
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager  # gRPC API 타입 지정
          stat_prefix: ingress_http  # 메트릭 접두사 설정
          http_filters:  # HTTP 필터 스택
          - name: envoy.filters.http.router  # 라우터 필터 사용(기본 요청 라우팅)
          route_config:  # 라우팅 구성
            name: httpbin_local_route  # 라우트 구성 이름
            virtual_hosts:  # 가상 호스트 목록
            - name: httpbin_local_service  # 가상 호스트 이름
              domains: ["*"]  # 모든 도메인 매칭
              routes:  # 라우팅 규칙 목록
              - match: { prefix: "/" }  # 모든 경로(/로 시작) 매칭
                route:  # 라우팅 대상 설정
                  auto_host_rewrite: true  # 업스트림 호스트 헤더 자동 재작성
                  cluster: httpbin_service  # 타겟 클러스터 지정
  clusters:  # 업스트림 서비스 클러스터 정의
    - name: httpbin_service  # 클러스터 이름
      connect_timeout: 5s  # 연결 타임아웃 설정(5초)
      type: LOGICAL_DNS  # DNS 기반 서비스 디스커버리 사용
      dns_lookup_family: V4_ONLY  # IPv4 전용 DNS 조회
      lb_policy: ROUND_ROBIN  # 라운드 로빈 로드 밸런싱 정책
      load_assignment:  # 엔드포인트 할당 정보
        cluster_name: httpbin  # 대상 클러스터 이름(실제 서비스 이름)
        endpoints:  # 엔드포인트 그룹
        - lb_endpoints:  # 로드밸런싱 대상 엔드포인트
          - endpoint:  # 단일 엔드포인트 설정
              address:  # 대상 주소
                socket_address:  # 소켓 주소 지정
                  address: httpbin  # 서비스 도메인 주소(httpbin)
                  port_value: 8000  # 대상 포트(8000)

위의 설정파일을 사용하여 엔보이를 실행시킨다.

그리고 다음 명령어로 envoy

docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple.yaml)"

그다음 다시 다음 명령어로 엔보이 컨테이너로 붙어본다.

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/headers

그럼 다음 화면처럼 아까완 다른 헤더값

  • X-Envoy-Expected-Rq-Timeout-Ms
  • X-Request-Id

들이 추가되면서 결국 httpbin 의 결과가 출력되는것을 알 수 있다. 이는 곧 엔보이 프록시를 통해 httpbin 컨테이너에 접속된것을 알 수 있다.

이것들은 envoy의 설정을 바꾸면 해당값도 바뀐다.

              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  timeout: 1s

보는것처럼 타임아웃 값이 1.5초에서 1초로 바뀐것을 알 수 있다.

# 추가 테스트 : Envoy Admin API(TCP 15000) 를 통해 delay 설정
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/0.5
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/1
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/2
upstream request timeout

위와같이 현재 엔보이 프록시의 로그 설정을 확인할 수 있고 또 debug 모드로도 변경할 수 있다.

그리고 아까 타임아웃을 1초로 해둔 상태인데, delay를 0.5와 1초 했을 때의 결과를 다음과같이 확인 할 수 있다.

Envoy's Admin API를 사용하면 프록시 동작에 대해 이해할 수 있고 메트릭과 설정에 접근 할 수 있다.

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats/prometheus # 엔보이 통계(프로메테우스 레코드 형식)

특히 위와같은 접근으로 프로메테우스 형식의 메트릭을 접근할 수 있다.

이를 활용하여 이스티오 관련 모니터링 대시보드를 만들 수 있을것이다. 예를들면 다음과 같다.

https://grafana.com/orgs/istio/dashboards

[istio Dashboards | Grafana Labs

uploaded on February 28, 2020 Downloads: 33513582 Reviews: 0

grafana.com](https://grafana.com/orgs/istio/dashboards)

docker rm -f proxy

#
cat ch3/simple_retry.yaml
docker run -p 15000:15000 --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_retry.yaml)"
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

# /stats/500 경로로 프록시를 호출 : 이 경로로 httphbin 호출하면 오류가 발생
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/status/500

# 호출이 끝났는데 아무런 응답도 보이지 않는다. 엔보이 Admin API에 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats | grep retry

위 스크립트는 1주차때 진행했던 500 에러 발생시 재시도 하는 테스트이다.

앞선 1주차때 했떤 이스티오 설정과 동일하게 엔보이에서도(당연히) 3회 시도에 대한 메트릭을 확인할 수 있다.

Summary

  • Envoy는 애플리케이션이 애플리케이션 수준의 동작에 사용할 수 있는 프록시입니다.
  • Envoy는 이스티오의 데이터 플레인입니다.
  • Envoy는 클라우드 신뢰성 문제(네트워크 장애, 토폴로지 변경, 탄력성)를 일관되고 정확하게 해결하는 데 도움을 줄 수 있습니다.
  • Envoy는 런타임 제어를 위해 동적 API를 사용합니다(Istio는 이를 사용합니다).
  • Envoy는 애플리케이션 사용 및 프록시 내부에 대한 강력한 지표와 정보를 많이 노출합니다.

istio 실습

실습 환경 구성

실제 실습을 진행하면서, 추가로 현재 운영중인 istio 환경도 함께 확인해볼 예정이다.

K8s 설치

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # New Gateway 
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server

istio 설치

# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false

# default 프로파일 컨트롤 플레인 배포
istioctl install --set profile=default -y

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
kubectl get pod -n istio-system

# 빠져나오기
exit
-----------------------------------

# 실습을 위한 네임스페이스 설정
kubectl create ns istioinaction
kubectl label namespace istioinaction istio-injection=enabled
kubectl get ns --show-labels

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

# Kiali 접속 1 : NodePort
open http://127.0.0.1:30003

# (옵션) Kiali 접속 2 : Port forward
kubectl port-forward deployment/kiali -n istio-system 20001:20001 &
open http://127.0.0.1:20001

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004


# 접속 테스트용 netshoot 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot
spec:
  containers:
  - name: netshoot
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

실습 진행

이스티오 인그레스 게이트웨이는 클러스터 외부에서 내부로 들어오는 트래픽의 진입점을 제어하는 역할을 한다. 인그레스 게이트웨이는 Envoy 프록시 기반의 로드밸런서로 동작하며, 외부 트래픽을 받아 필요한 포트와 프로토콜을 지정해 노출한다. 하지만 실제 트래픽 라우팅(어떤 서비스로 전달할지)은 VirtualService 리소스를 통해 L7(애플리케이션 레이어)에서 세밀하게 제어한다.

즉, Gateway는 L4/L5 계층에서 트래픽을 받아들이고, VirtualService는 L7 계층에서 트래픽의 목적지와 라우팅 정책을 결정한다. 이 구조를 통해 이스티오는 외부 트래픽의 보안, 로드밸런싱, 가상 호스트 라우팅 등 다양한 네트워크 기능을 유연하게 제공한다.

현재 운영중인 istio 환경 정보

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: live-ops-query-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "ops-query.test.co.kr"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ops-query-vs
  namespace: devops
spec:
  hosts:
  - "ops-query.test.co.kr"
  gateways:
  - istio-system/live-ops-query-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: ops-query-svc-active
      weight: 100
    - destination:
        host: ops-query-svc-preview
      weight: 0

 

 

아래는 실습환경이다.

404 에러가 발생한다. 이는 host 헤더가 알맞지 않아서 발생하는 문제이다.

 

헤더 수정후 다시 입력하면 정상적으로 응답되는것을 알 수 있다.

 

 

securing gateway traffic 실습

실무환경에서는 앞단의 ALB에 인증서만 적용해두고 내부의 이스티오간 인증서는 적용돼있지 않은상태다. 추후 보안심사때 문제가 발생하면 그때 적용해야겠다....

 

어쨋든.. 실습 환경에서 현재 적용 돼 있는 인증서는 다음과 같다.

kubectl create -n istio-system secret tls webapp-credential \
--key ch4/certs/3_application/private/webapp.istioinaction.io.key.pem \
--cert ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem

 kubectl apply -f ch4/coolstore-gw-tls.yaml -n istioinaction

 

이제 위 명령어로 시크릿을 만들고 게이트웨이를 재배포한다.

 

그러면 위와같이 새로운 인증서가 추가된것을 볼 수 있다.

그리고 curl 로 접속 테스트를 하면 에러가 나는것을 볼 수 있다.

 

이는 서버(istio-ingressgateway 파드)에서 제공하는 인증서는 기본 CA 인증서 체인을 사용해 확인할 수 없다는 의미다.

다시  cacert 옵션을 줘서 테스트를 한다. 또 에러가 난다. 이번엔 Local host로 접속해서 발생한 문제이다.

 

echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts

위 명령어를 입력해서 임시로 dns 를 설정해준다.

 

다시 호출 테스트를 진행하면 정상적으로 접속되는것을 알 수 있다.

 

 

이번에는 https만 사용하도록 하는 리다이렉트 설정이다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
    tls:
      httpsRedirect: true 
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: webapp-credential
    hosts:
    - "webapp.istioinaction.io"

 

 

이번엔  mTLS이다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io"
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: MUTUAL # mTLS 설정
      credentialName: webapp-credential-mtls # 신뢰할 수 있는 CA가 구성된 자격 증명
    hosts:
    - "webapp.istioinaction.io"

 

다음과 같이 클라이언트 인증서 없이 에러가 발생한다.

 

 

클라이언트 인증서를 명시하면 정상적으로 동작하는것을 알 수 있다.

 

 

Serving multiple virtual hosts with TLS

이스티오 인그레스 게이트웨이는 동일한 HTTPS 포트(443)에서 각기 다른 인증서와 비밀 키를 사용해 여러 도메인(가상 호스트)의 트래픽을 동시에 처리할 수 있다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https-webapp
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: webapp-credential
    hosts:
    - "webapp.istioinaction.io"
  - port:
      number: 443
      name: https-catalog
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: catalog-credential
    hosts:
    - "catalog.istioinaction.io"

두개의 버추어 서비스를 처리하는것을 확인 할 수 있다. 이를 처리하는 플로우는 클라이언트가 게이트웨이(엔보이)로 HTTPS 연결을 시작하면, TLS 핸드셰이크의 ClientHello 단계에서 SNI(서버 이름 표시) 확장에 접근하려는 서비스의 도메인을 명시해 전달하고, 엔보이는 이 SNI 정보를 기반으로 올바른 인증서를 선택해 제시하고 해당 서비스로 트래픽을 라우팅한다.

 

TCP 트래픽에 대한 실습이다.

이스티오 게이트웨이는 TCP 기반 서비스도 외부에 노출할 수 있지만, HTTP처럼 세밀한 트래픽 제어나 고급 기능은 사용할 수 없다.
이는 엔보이가 프로토콜을 이해하지 못해 단순 TCP 프록시만 가능하기 때문이다.
클러스터 외부에서 내부 TCP 서비스를 이용하려면 게이트웨이에서 해당 포트를 노출하면 된다.

#
cat ch4/echo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tcp-echo-deployment
  labels:
    app: tcp-echo
    system: example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tcp-echo
  template:
    metadata:
      labels:
        app: tcp-echo
        system: example
    spec:
      containers:
        - name: tcp-echo-container
          image: cjimti/go-echo:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: TCP_PORT
              value: "2701"
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: SERVICE_ACCOUNT
              valueFrom:
                fieldRef:
                  fieldPath: spec.serviceAccountName
          ports:
            - name: tcp-echo-port
              containerPort: 2701
---
apiVersion: v1
kind: Service
metadata:
  name: "tcp-echo-service"
  labels:
    app: tcp-echo
    system: example
spec:
  selector:
    app: "tcp-echo"
  ports:
    - protocol: "TCP"
      port: 2701
      targetPort: 2701

kubectl apply -f ch4/echo.yaml -n istioinaction

#
kubectl get pod -n istioinaction
NAME                                   READY   STATUS    RESTARTS   AGE
tcp-echo-deployment-584f6d6d6b-xcpd8   2/2     Running   0          27s
...

 

# tcp 서빙 포트 추가 : 편집기는 vi 대신 nano 선택 <- 편한 툴 사용
KUBE_EDITOR="nano"  kubectl edit svc istio-ingressgateway -n istio-system
...
  - name: tcp
    nodePort: 30006
    port: 31400
    protocol: TCP
    targetPort: 31400
...

# 확인
kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.name=="tcp")]}'
{"name":"tcp","nodePort":30006,"port":31400,"protocol":"TCP","targetPort":31400}


# 게이트웨이 생성
cat ch4/gateway-tcp.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: echo-tcp-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 31400
      name: tcp-echo
      protocol: TCP
    hosts:
    - "*"
    
kubectl apply -f ch4/gateway-tcp.yaml -n istioinaction
kubectl get gw -n istioinaction

# 에코 서비스로 라우팅하기 위해 VirtualService 리소스 생성
cat ch4/echo-vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: tcp-echo-vs-from-gw
spec:
  hosts:
  - "*"
  gateways:
  - echo-tcp-gateway
  tcp:
  - match:
    - port: 31400
    route:
    - destination:
        host: tcp-echo-service
        port:
          number: 2701

#
kubectl apply -f ch4/echo-vs.yaml -n istioinaction
kubectl get vs -n istioinaction
NAME                  GATEWAYS                HOSTS                          AGE
catalog-vs-from-gw    ["coolstore-gateway"]   ["catalog.istioinaction.io"]   44m
tcp-echo-vs-from-gw   ["echo-tcp-gateway"]    ["*"]                          6s
webapp-vs-from-gw     ["coolstore-gateway"]   ["webapp.istioinaction.io"]    3h59m


# mac 에 telnet 설치
brew install telnet

#
telnet localhost 30006
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Welcome, you are connected to node myk8s-control-plane.
Running on Pod tcp-echo-deployment-584f6d6d6b-xcpd8.
In namespace istioinaction.
With IP address 10.10.0.20.
Service default.
hello istio! # <-- type here
hello istio! # <-- echo here

# telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit

 

 

 

SNI passthrough 실습

이스티오 인그레스 게이트웨이는 TLS 연결을 종료하지 않고 SNI 호스트네임을 기반으로 TCP 트래픽을 라우팅하며, 백엔드 서비스에서 TLS 처리를 담당함으로써 다양한 레거시 애플리케이션 및 TCP 서비스를 서비스 메시에 통합할 수 있다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: sni-passthrough-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 31400 #1 HTTP 포트가 아닌 특정 포트 열기
      name: tcp-sni
      protocol: TLS
    hosts:
    - "simple-sni-1.istioinaction.io" #2 이 호스트를 포트와 연결
    tls:
      mode: PASSTHROUGH #3 통과 트래픽으로 처리

 

# TLS 인증을 직접 처리하는 앱 배포. (gw는 route 만 처리, pass through )
cat ch4/sni/simple-tls-service-1.yaml
kubectl apply -f ch4/sni/simple-tls-service-1.yaml -n istioinaction
kubectl get pod -n istioinaction

# 기존 Gateway 명세(echo-tcp-gateway) 제거 : istio-ingressgateway의 동일한 port (31400, TCP)를 사용하므로 제거함
kubectl delete gateway echo-tcp-gateway -n istioinaction

# 신규 Gateway 설정
kubectl apply -f ch4/sni/passthrough-sni-gateway.yaml -n istioinaction
kubectl get gw -n istioinaction

 

# 두 번째 서비스 배포
cat ch4/sni/simple-tls-service-2.yaml
kubectl apply -f ch4/sni/simple-tls-service-2.yaml -n istioinaction

# gateway 설정 업데이트
cat ch4/sni/passthrough-sni-gateway-both.yaml
kubectl apply -f ch4/sni/passthrough-sni-gateway-both.yaml -n istioinaction

# VirtualService 설정
cat ch4/sni/passthrough-sni-vs-2.yaml
kubectl apply -f ch4/sni/passthrough-sni-vs-2.yaml -n istioinaction

# 호출테스트2
echo "127.0.0.1       simple-sni-2.istioinaction.io" | sudo tee -a /etc/hosts

curl https://simple-sni-2.istioinaction.io:30006 \
--cacert ch4/sni/simple-sni-2/2_intermediate/certs/ca-chain.cert.pem

 

 

Split gateway responsibilities 실습

여러 인그레스 게이트웨이를 배포하면 서비스별 요구사항이나 팀별로 트래픽 경로와 설정을 독립적으로 관리·격리할 수 있다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: my-user-gateway-install
  namespace: istioinaction
spec:
  profile: empty
  values:
    gateways:
      istio-ingressgateway:
        autoscaleEnabled: false
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: false    
    - name: my-user-gateway
      namespace: istioinaction
      enabled: true
      label:
        istio: my-user-gateway
      k8s:
        service:
          ports:
            - name: tcp  # my-user-gateway 에서 사용할 포트 설정
              port: 30007
              targetPort: 31400
#
docker exec -it myk8s-control-plane bash
------------------------------------------
# istioinaction 네임스페이스에 Ingress gateway 설치
cat <<EOF > my-user-gateway-edited.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: my-user-gateway-install
  namespace: istioinaction
spec:
  profile: empty
  values:
    gateways:
      istio-ingressgateway:
        autoscaleEnabled: false
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: false    
    - name: my-user-gateway
      namespace: istioinaction
      enabled: true
      label:
        istio: my-user-gateway
      k8s:
        service:
          ports:
            - name: tcp  # my-user-gateway 에서 사용할 포트 설정
              port: 31400
              targetPort: 31400
              nodePort: 30007 # 외부 접속을 위해 NodePort Number 직접 설정
EOF

# istioctl manifest generate -n istioinaction -f my-user-gateway-edited.yaml
istioctl install -y -n istioinaction -f my-user-gateway-edited.yaml

exit
------------------------------------------

# IstioOperator 확인
kubectl get IstioOperator -A
NAMESPACE       NAME                      REVISION   STATUS   AGE
istio-system    installed-state                               5h48m
istioinaction   my-user-gateway-install                       17s

#
kubectl get deploy my-user-gateway -n istioinaction
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
my-user-gateway   1/1     1            1           65s

# 포트 확인
kubectl get svc my-user-gateway -n istioinaction -o yaml
...
  - name: tcp
    nodePort: 30007
    port: 31400
    protocol: TCP
    targetPort: 31400
...

 

 

 

Gateway injection

게이트웨이 주입(gateway injection)을 사용하면, 사용자가 IstioOperator 리소스 전체 권한 없이도 미완성(서브된) 게이트웨이 Deployment 리소스를 배포할 수 있고, Istio가 나머지 설정을 자동으로 채워준다. 이 방식은 사이드카 주입과 유사하게, 각 팀이 필요한 최소한의 리소스만 정의하면 Istio가 필요한 프록시와 설정을 자동으로 붙여준다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-user-gateway-injected
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      ingress: my-user-gateway-injected
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "true" #1 주입 활성화
        inject.istio.io/templates: gateway #2 gateweay 템플릿  
      labels:
        ingress: my-user-gateway-injected
    spec:
      containers:
      - name: istio-proxy #3 반드시 이 이름이어야 한다
        image: auto #4 미완성 이미지
...

 

Ingress gateway access logs 실습

이스티오 데모 프로필에서는 엔보이 프록시의 액세스 로그가 표준 출력으로 기록되어 컨테이너 로그를 통해 쉽게 확인할 수 있다.

#
kubectl get cm -n istio-system istio -o yaml
data:
  mesh: |-
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:15012
      proxyMetadata: {}
      tracing:
        zipkin:
          address: zipkin.istio-system:9411
    enablePrometheusMerge: true
    rootNamespace: istio-system
    trustDomain: cluster.local
  meshNetworks: 'networks: {}'
..

#
docker exec -it myk8s-control-plane bash
------------------------------------------
# 표준 출력 스트림으로 출력하도록 accessLogFile 속성을 변경
istioctl install --set meshConfig.accessLogFile=/dev/stdout
y 입력

exit
------------------------------------------

# configmap 에 mesh 바로 아래에 accessLogFile 부분 추가됨
kubectl get cm -n istio-system istio -o yaml
...
  mesh: |-
    accessLogFile: /dev/stdout
...

# 애플리케이션 호출에 대한 로그도 출력됨!
kubectl stern -n istioinaction -l app=webapp -c istio-proxy
webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:47.764Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 1 1 "172.18.0.1" "beegoServer" "4c10dba4-f49a-4b58-9805-ac30515dd417" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60052 10.200.1.251:80 172.18.0.1:0 - default
webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:48.805Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 6 6 "172.18.0.1" "beegoServer" "7733f4d0-6e3a-4138-a126-fe25c30e51f4" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60898 10.200.1.251:80 172.18.0.1:0 - default
...

운영 환경에서는 트래픽과 로그량이 많기 때문에, 텔레메트리 API를 이용해 필요한 워크로드에만 액세스 로그를 selectively 활성화하는 것이 효율적이다.

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: ingress-gateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway #1 레이블과 일치하는 파드는 텔레메트리 설정을 가져온다
  accessLogging:
  - providers:
    - name: envoy #2 액세스 로그를 위한 프로바이더 설정
    disabled: false #3 disable 를 false 로 설정해 활성화한다

 

Reducing gateway configuration

이스티오는 기본적으로 모든 프록시가 메시 내 모든 서비스를 알도록 설정되어 있어 서비스가 많아지면 프록시 설정이 비대해지고 성능 및 확장성 문제가 발생할 수 있다. 이를 해결하기 위해 게이트웨이 프록시에는 필요한 설정만 포함하도록 "게이트웨이 설정 잘라내기" 기능을 명시적으로 활성화할 수 있다.

개인적으로 오늘 스터디에서 배운것중 가장 필요했던것이다. 일전에 istio 설정 잘못해서 서비스 연결이 제대로 안됐었는데, 디버깅 하기가 너무 힘들었다. 해당 설정때문에 앞으로 좀 더 수월해질듯 하다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: control-plane
spec:
  profile: minimal
  components:
    pilot:
      k8s:
        env:
        - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG
          value: "true"
  meshConfig:
    defaultConfig:
      proxyMetadata:
        ISTIO_META_DNS_CAPTURE: "true"
    enablePrometheusMerge: true

'job > devops' 카테고리의 다른 글

istio-1  (0) 2025.04.08
taskfile  (0) 2024.03.10
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

사내에서 istio 프록시로 구성하여 사용중인데, 뭐랄까. 제대로 못쓰고 있는듯하다. 굳이 istio를 붙인것같은.. 제대로 못쓸꺼면 아에 빼버리던가(네트워크 문제시 파악 구간이 더 많아져서 싫다.) 아니면 제대로 쓸 수 있는 실력이 필요했다. 예를들어, 서비스 장애가 발생할 경우 istio가 없었다면 단순히 네트워크 구간만 살펴보면 됐다. 그런데 현재는 istio 구간까지 추가로 살펴봐야 한다. 문제는 해당 구간에 대한 모니터링이 안되고 있는 상황. 그래서 명확히 이 구간은 문제가 없다라고 판단하기가 어렵다. 이를 해결하려면 istio에 빠삭히 알아야 할 것 같았다. 그래서 작년에 istio 시험을 사두기만 했다. 그렇게 시간을 보내다 때마침 가시다님께서 istio 스터디를 진행하신다기에 재빨리 신청했다. (과거 여러 스터디를 운영하셨던 분들을 포함한 가시다님, 김원일님, 김석필님 감사합니다.)

스터디를 끝낼 때 istio 운영을 유지하며 시스템을 더욱 고도화(모니터링 추가등)할 지, 아니면 istio를 걷어낼지를 판단 할 수 있는 실력이 쌓였으면 좋겠다.

현재 사내에 istio를 사용중이다. 따라서 실습하면서 실제 운영환경과 비교하고 다른점이 있다면 어떤 설정에 의해 왜 다른지도 다뤄보고자 한다.

그럼 1주차 시작

Istio에 대한 간략한 설명

Istio를 왜 사용해야 하는가 ? Istio를 사용하지 않을 경우 발생할 수 있는 문제.

서비스 간 통신의 복잡성 증가:
네트워크 장애, 과부하, 버그 등으로 인해 서비스 간 요청이 실패하거나 성능 저하가 발생할 수 있음.
예를 들어, 다운스트림 서비스가 느리거나 장애가 발생하면 연쇄적인 서비스 장애로 이어질 가능성이 큼.

복원력 패턴 구현의 어려움:
타임아웃, 재시도, 서킷 브레이커 등의 복원력 패턴을 각 애플리케이션에서 직접 구현해야 함.
여러 언어와 프레임워크를 사용하는 환경에서는 이러한 패턴을 일관되게 구현하기 어려움.

운영 및 유지보수 부담 증가:
라이브러리 의존성을 관리하고 각 애플리케이션에 맞게 코드를 수정해야 하며, 이는 시간이 많이 소요되고 오류 가능성을 높임.
새로운 언어나 프레임워크 도입 시 추가적인 구현 작업이 필요함.

관찰 가능성 부족:
서비스 간 트래픽, 요청 실패율, 성능 병목 등을 실시간으로 파악하기 어려움.
장애 원인을 추적하거나 시스템 상태를 모니터링하는 데 한계가 있음.

Istio를 사용하면 해결되는 점

서비스 간 통신의 표준화:
Istio는 Envoy 프록시를 통해 서비스 간 통신을 관리하며, 재시도, 타임아웃, 서킷 브레이커 등의 기능을 애플리케이션 외부에서 제공.
이를 통해 서비스 간 통신의 안정성과 복원력을 높일 수 있음.

언어 및 프레임워크 독립성:
애플리케이션 코드 수정 없이 네트워크 관련 기능을 제공하므로 언어나 프레임워크에 구애받지 않음.
다양한 기술 스택에서도 일관된 네트워킹 정책 적용 가능.

운영 부담 감소:
Istio가 네트워킹 및 보안 정책을 중앙에서 관리하므로 각 애플리케이션에서 이를 구현할 필요가 없음.
새로운 서비스 추가나 변경 시에도 운영 부담이 줄어듦.

강화된 관찰 가능성:
Istio는 메트릭, 로그, 분산 트레이싱을 통해 실시간으로 시스템 상태를 모니터링 가능.
장애 원인 분석 및 성능 최적화 작업이 용이해짐.

결론
Istio는 네트워킹 관련 문제를 애플리케이션에서 인프라로 전가(내가 생각하는 devops 엔지니어는, 개발자는 회사의 이익에 필요한 개발만 할 수 있도록 환경을 만들어주는거라 생각하는데.. 이 모토랑 일치하는듯)하여 운영 효율성을 높이고, 복잡한 클라우드 환경에서도 안정적으로 서비스를 운영할 수 있도록 돕습니다.

Istio란? 서비스메시란 ? 엔보이프록시? 사이드카?

서비스 메시 (Service Mesh)

서비스 메시란, 여러 서비스가 서로 대화할 때 그 대화를 도와주는 네트워크의 "통신 감독관" 같은 역할을 합니다.
예를 들어, 학교에서 선생님이 학생들끼리 조용히 대화하도록 도와주는 것과 비슷합니다.

엔보이 프록시 (Envoy Proxy)

엔보이 프록시는 서비스들 사이에서 주고받는 메시지를 대신 전달하는 "우체부" 역할을 합니다.
예를 들어, 친구에게 편지를 보낼 때 우체부가 대신 전달해 주는 것처럼, 엔보이는 서비스 간 데이터를 안전하고 빠르게 전달합니다.

사이드카 (Sidecar)

사이드카는 주 컨테이너(주요 프로그램)를 도와주는 "조수" 컨테이너입니다.
예를 들어, 오토바이에 붙어 있는 작은 캐빈처럼, 사이드카는 옆에서 필요한 일을 돕습니다. Istio에서는 이 사이드카가 엔보이 프록시로 동작합니다.

Istio 프록시

Istio 프록시는 엔보이 프록시를 사용하여 각 서비스 옆에 사이드카로 배치됩니다.
예를 들어, 학교에서 각 반마다 선생님(프록시)이 배치되어 학생들(서비스)이 서로 잘 소통하도록 돕는 것과 같습니다. 이 프록시는 메시지를 가로채고, 어디로 보내야 할지 알려주며, 보안도 책임집니다.

연관 관계

  • 서비스 메시 안에는 여러 서비스가 있고, 이들이 서로 대화할 때 엔보이 프록시가 중간에서 도와줍니다.
  • 엔보이 프록시는 각 서비스 옆에 사이드카 형태로 배치되어 Istio라는 시스템의 일부로 작동합니다.
  • Istio는 전체 네트워크를 관리하며 트래픽을 안전하고 효율적으로 제어합니다.

즉 정리하자면, Istio를 사용하지 않는다면 애플리케이션 레벨에서의 리소스가 많이 사용되는데, Istio를 사용하면 효율적으로 인프라 레벨로 옮길 수 있다. 어떻게? 사이드카 프록시로.

Istio의 단점은 ?

디버깅 복잡성 증가

Envoy 프록시가 추가되면서 네트워크 요청 경로가 복잡해지고, 프록시에 익숙하지 않은 경우 디버깅이 어려워질 수 있음.

테넌시 관리의 어려움
서비스 메시 구성 시 적절한 정책과 자동화가 없으면 잘못된 설정으로 인해 다수의 서비스에 영향을 미칠 가능성이 있음.

운영 복잡성 증가
서비스 메시 도입으로 새로운 레이어가 추가되어 시스템 아키텍처와 운영 절차가 복잡해질 수 있음.

조직의 기존 거버넌스 및 팀 간 협업과의 통합이 어려울 수 있음.

실습 진행

istio 설치

$ istioctl version --remote=false
$ kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
NAME                                       READY   STATUS    RESTARTS   AGE
pod/grafana-b854c6c8-rhsvm                 1/1     Running   0          19h
pod/istio-ingressgateway-996bc6bb6-lwqq5   1/1     Running   0          19h
pod/istiod-7df6ffc78d-r4jvv                1/1     Running   0          19h
pod/jaeger-5556cd8fcf-8z2zz                1/1     Running   0          19h
pod/kiali-648847c8c4-wcw8l                 1/1     Running   0          19h
pod/prometheus-7b8b9dd44c-zzsr8            2/2     Running   0          19h

NAME                           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                      AGE
service/grafana                ClusterIP      10.200.1.30    <none>        3000/TCP                                     19h
service/istio-ingressgateway   LoadBalancer   10.200.1.103   <pending>     15021:31155/TCP,80:30491/TCP,443:30079/TCP   19h
service/istiod                 ClusterIP      10.200.1.116   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP        19h
service/jaeger-collector       ClusterIP      10.200.1.31    <none>        14268/TCP,14250/TCP,9411/TCP                 19h
service/kiali                  ClusterIP      10.200.1.99    <none>        20001/TCP,9090/TCP                           19h
service/prometheus             ClusterIP      10.200.1.130   <none>        9090/TCP                                     19h
service/tracing                ClusterIP      10.200.1.38    <none>        80/TCP,16685/TCP                             19h
service/zipkin                 ClusterIP      10.200.1.200   <none>        9411/TCP                                     19h

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/grafana                1/1     1            1           19h
deployment.apps/istio-ingressgateway   1/1     1            1           19h
deployment.apps/istiod                 1/1     1            1           19h
deployment.apps/jaeger                 1/1     1            1           19h
deployment.apps/kiali                  1/1     1            1           19h
deployment.apps/prometheus             1/1     1            1           19h

NAME                                             DESIRED   CURRENT   READY   AGE
replicaset.apps/grafana-b854c6c8                 1         1         1       19h
replicaset.apps/istio-ingressgateway-996bc6bb6   1         1         1       19h
replicaset.apps/istiod-7df6ffc78d                1         1         1       19h
replicaset.apps/jaeger-5556cd8fcf                1         1         1       19h
replicaset.apps/kiali-648847c8c4                 1         1         1       19h
replicaset.apps/prometheus-7b8b9dd44c            1         1         1       19h

NAME                                                       REFERENCE                         TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/istio-ingressgateway   Deployment/istio-ingressgateway   8%/80%    1         5         1          19h
horizontalpodautoscaler.autoscaling/istiod                 Deployment/istiod                 0%/80%    1         5         1          19h

NAME                             ENDPOINTS                                                     AGE
endpoints/grafana                10.10.0.10:3000                                               19h
endpoints/istio-ingressgateway   10.10.0.8:15021,10.10.0.8:8080,10.10.0.8:8443                 19h
endpoints/istiod                 10.10.0.7:15012,10.10.0.7:15010,10.10.0.7:15017 + 1 more...   19h
endpoints/jaeger-collector       10.10.0.9:9411,10.10.0.9:14250,10.10.0.9:14268                19h
endpoints/kiali                  10.10.0.11:9090,10.10.0.11:20001                              19h
endpoints/prometheus             10.10.0.12:9090                                               19h
endpoints/tracing                10.10.0.9:16685,10.10.0.9:16686                               19h
endpoints/zipkin                 10.10.0.9:9411                                                19h

NAME                                                  SECRETS   AGE
serviceaccount/default                                1         19h
serviceaccount/grafana                                1         19h
serviceaccount/istio-ingressgateway-service-account   1         19h
serviceaccount/istio-reader-service-account           1         19h
serviceaccount/istiod                                 1         19h
serviceaccount/istiod-service-account                 1         19h
serviceaccount/kiali                                  1         19h
serviceaccount/prometheus                             1         19h

NAME                                            DATA   AGE
configmap/grafana                               4      19h
configmap/istio                                 2      19h
configmap/istio-ca-root-cert                    1      19h
configmap/istio-gateway-deployment-leader       0      19h
configmap/istio-gateway-status-leader           0      19h
configmap/istio-grafana-dashboards              2      19h
configmap/istio-leader                          0      19h
configmap/istio-namespace-controller-election   0      19h
configmap/istio-services-grafana-dashboards     4      19h
configmap/istio-sidecar-injector                2      19h
configmap/kiali                                 1      19h
configmap/kube-root-ca.crt                      1      19h
configmap/prometheus                            5      19h

NAME                                                      TYPE                                  DATA   AGE
secret/default-token-lmqrr                                kubernetes.io/service-account-token   3      19h
secret/grafana-token-74qt2                                kubernetes.io/service-account-token   3      19h
secret/istio-ca-secret                                    istio.io/ca-root                      5      19h
secret/istio-ingressgateway-service-account-token-tlq54   kubernetes.io/service-account-token   3      19h
secret/istio-reader-service-account-token-8zz99           kubernetes.io/service-account-token   3      19h
secret/istiod-service-account-token-zrr8b                 kubernetes.io/service-account-token   3      19h
secret/istiod-token-x64kr                                 kubernetes.io/service-account-token   3      19h
secret/kiali-token-84msl                                  kubernetes.io/service-account-token   3      19h
secret/prometheus-token-vqmbw                             kubernetes.io/service-account-token   3      19h

NAME                                              MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/istio-ingressgateway   1               N/A               0                     19h
poddisruptionbudget.policy/istiod                 1               N/A               0                     19h

이중 istio-ingressgateway 서비스 리소스의 경우 실제 운영환경은 다음과 같이 EXTERNAL-IP에 CSP의 LB 도메인이 들어가있다. 실습 환경에서는 PENDING이다.

$ k get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   198.19.240.44    istio-syste-istio-ingres-xxx.kr-fin.lb.naverncp.com   15021:31396/TCP,80:30616/TCP,443:31295/TCP   527d
istiod                 ClusterIP      198.19.157.147   <none>                                                                        15010/TCP,15012/TCP,443/TCP,15014/TCP        527d

이는 CSP(NCP)에서 설치된 쿠버네티스 클러스터(NKS)에는 기본적으로 cloud controller manager의 service controller에 의해 자동으로 NKS의 LB를 생성한다. 관련 설정은 kube-system 네임스페이스에 ncloud-config라는 ConfigMap이 적용 돼 있다.

$ k get cm -n kube-system -o yaml ncloud-config
apiVersion: v1
data:
  acgNo: "11111"
  apiUrl: https://nks.apigw.fin-ntruss.com
  basePath: /ncloud-api/v1
  clusterId: "1111"
  lbPublicSubnetNo: "11111"
  lbSubnetNo: "11111"
  regionCode: FKR
  regionNo: "1"
  vpcNo: "1111"
  zoneNo: "111"
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
생략
  creationTimestamp: "2023-10-30T02:09:49Z"
  name: ncloud-config
  namespace: kube-system
  resourceVersion: "1111111"
  uid: 11111-1a71-457e-813c-4b2d13f00446

해당 설정에 의해 서비스타입이 로드밸런서인경우 NCP API와 상호작용하여, 자동으로 NCP의 LB(기본값은 프록시 로드밸런서)를 생성하게 된다.
참고로 로드밸런서 이름(익스터널IP에 들어가는 도메인명)의 생성 규칙은 다음과 같다.
<네임스페이스-서비스이름-포트번호-랜덤문자열>

파드배포(사이드카로 Istio 도 같이 배포)

이제 배포되는 모든 파드들에 사이드카로 istio가 같이 배포되도록 해야 한다. 이는 2가지 방법이 있는데,
docker exec -it myk8s-control-plane istioctl kube-inject -f /istiobook/services/catalog/kubernetes/catalog.yaml
으로 매니페스트에 사이드카 설정을 추가하는 방법과

kubectl label namespace istioinaction istio-injection=enabled
으로, 네임스페이스에 istio-injection=enabled Label에 설정 돼 있는 경우 해당 네임스페이스의 파드 스펙에 자동으로 사이드카 설정을 한다.

이제 파드를 배포하면 다음과 같이 앱이 배포된다.

하나의 파드에 두개의 컨테이너가 있음이 확인된다. 이를 describe로 확인해보면 보는것처럼 catalog라는 메인 컨테이너외에 istio-proxy라는 사이드카 컨테이너가 올라온것을 알 수 있다.

istio proxy 상태 확인

# istioctl proxy-status : 단축어 ps
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
catalog-6cf4b97d-nccfj.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8
istio-ingressgateway-996bc6bb6-mz544.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8
webapp-7685bcb84-c55ck.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-bj7h7     1.17.8

ISTIOIGW=istio-ingressgateway-996bc6bb6-647tx.istio-system
WEBAPP=webapp-7685bcb84-nfntj.istioinaction

# istioctl proxy-config : 단축어 pc
docker exec -it myk8s-control-plane istioctl proxy-config all $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config all $WEBAPP

docker exec -it myk8s-control-plane istioctl proxy-config listener $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config route $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config cluster $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config endpoint $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config log $ISTIOIGW

docker exec -it myk8s-control-plane istioctl proxy-config listener $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config route $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config cluster $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config endpoint $WEBAPP
docker exec -it myk8s-control-plane istioctl proxy-config log $WEBAPP

# envoy 가 사용하고 있는 인증서 정보 확인
docker exec -it myk8s-control-plane istioctl proxy-config secret $ISTIOIGW
docker exec -it myk8s-control-plane istioctl proxy-config secret $WEBAPP

위 명령어들은 istio 서비스 매쉬 내 프록시의 상태를 확인(istioctl proxy-status)하고, 특정 프록시의 상세 구성 정보를 조회(istioctl proxy-config)하는 명령어다.

Istio 프록시(Envoy)의 네트워크 흐름에서 Listener, Route, Cluster, Endpoint는 트래픽 처리 단계에 따라 아래와 같은 순서로 작동합니다:

1. Listener
역할: Envoy가 수신하는 트래픽을 처리할 준비를 합니다. Listener는 특정 IP와 포트에 바인딩되어 들어오는 요청을 수신하고, 트래픽을 처리하기 위한 첫 번째 진입점입니다.

작동 위치: 네트워크에서 Envoy 프록시가 요청을 가로채고, 트래픽의 방향을 결정하기 위해 필터 체인을 적용합니다.

예시: HTTP 요청이 들어오면 Listener는 이를 처리할 라우팅 규칙을 찾습니다.

2. Route
역할: Listener에서 수신된 요청을 분석하여 어떤 서비스로 전달할지 결정합니다. Route는 요청 경로와 매칭되는 규칙을 기반으로 클러스터를 선택합니다.

작동 위치: Listener에서 필터 체인을 통해 전달된 요청은 Route에서 적절한 클러스터로 연결됩니다.

예시: 특정 URL 경로(/api/v1)에 대한 요청을 특정 클러스터로 라우팅.

3. Cluster
역할: 라우팅된 요청을 처리할 논리적인 서비스 그룹입니다. Cluster는 여러 Endpoint(IP와 포트)로 구성되며, Envoy가 외부 서비스와 연결하는 단위입니다.

작동 위치: Route가 선택한 클러스터는 실제 엔드포인트로 트래픽을 포워드합니다.

예시: service-cluster라는 클러스터가 외부 API 서버를 대표하며, 해당 클러스터의 엔드포인트로 트래픽을 전달.

4. Endpoint
역할: 클러스터 내에서 실제 요청이 전달되는 대상입니다. Endpoint는 IP 주소와 포트를 포함하며, 클라이언트의 요청이 최종적으로 도달하는 곳입니다.

작동 위치: Cluster에서 선택된 엔드포인트로 트래픽이 전달됩니다.

예시: 특정 API 서버의 IP 주소(예: 192.168.1.100)와 포트(예: 8080)이 Endpoint로 설정됩니다.

네트워크 흐름 순서 요약
단계    구성 요소    역할 및 작업
1    Listener    요청 수신 및 필터 체인을 통해 초기 처리
2    Route    요청 경로 분석 및 적절한 클러스터 선택
3    Cluster    논리적 서비스 그룹으로 요청 전달
4    Endpoint    실제 대상(IP/포트)으로 최종 트래픽 전달
이 순서는 Istio 프록시의 기본적인 트래픽 처리 흐름이며, 각 단계는 Envoy 내부의 설정과 xDS API를 통해 동적으로 구성됩니다.

의도적으로 ingress, gateway, VirtualService 설정중 한곳에 문제를 주고 해당 서비스가 어떤 문제가 있는지 한번 위 명령어들로 확인해보고자 한다.

먼저 문제있는 서비스의 Ingress와 gateway, Virtual Service 설정은 다음과 같이 진행했다.

$ k get ingress -n istio-system | grep query
query-ingress   alb     query.test.com                                                        query-server-ingress-fin.lb.naverncp.com    80      107d

위와같이 query-ingress는 현재 NCP ALB로 연결 된 상태다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
중략
  labels:
    app: query-server-ingress
  name: query-server-ingress
  namespace: istio-system
spec:
  ingressClassName: alb
  defaultBackend:
    service:
      name: istio-ingressgateway
      port:
        number: 80
  rules:
  - host: query-test.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ssl-redirect
            port: 
              name: use-annotation            

GW와 VS 매니페스트는 다음과 같다.

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
중략
  creationTimestamp: "2024-12-13T01:46:08Z"
  generation: 1
  labels:
    app.kubernetes.io/instance: opsquery
  name: query-gateway
  namespace: istio-system
  resourceVersion: "441696723"
  uid: 19fc1bd0-5c30-4dc1-b624-b956da859c15
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - query.test.co.kr
    port:
      name: http
      number: 80
      protocol: HTTP


---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ops-query-vs
  namespace: devops
spec:
  hosts:
  - "ops-query.test.co.kr"
  gateways:
  - istio-system/devops-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: query-svc-active
      weight: 100
    - destination:
        host: query-svc-preview
      weight: 0

이제 proxy-status와 proxy-config 명령어로 한번 원인을 찾아보고자 한다.

$ istioctl proxy-status | grep ops-query
ops-query-7cc7fc6f69-b6sml.devops                                    Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-784bcfdd5d-2fgbs     1.16.2

$ istioctl proxy-config all ops-query-7cc7fc6f69-b6sml.devops | grep ops 
##결과 없음

결과가 없다는건 Gateway나 VirtualService에 문제가 있을 가능성이 크다(고한다.)

Gateway 설정 문제:
Gateway가 Istio IngressGateway Pod와 제대로 연결되지 않았을 수 있습니다.
Gateway의 selector가 IngressGateway Pod의 라벨과 일치하지 않으면 트래픽을 수신할 Listener가 생성되지 않습니다.

VirtualService 설정 누락 또는 연결 문제:
VirtualService가 Gateway와 연계되어 있지 않거나, VirtualService에서 트래픽 라우팅 규칙이 제대로 정의되지 않았을 경우 Listener가 생성되지 않습니다.
VirtualService가 없으면 Gateway는 트래픽 처리 규칙을 알 수 없어 Listener를 생성하지 않습니다.

Istiod 구성 전달 문제:
Istiod(Control Plane)에서 Envoy 프록시(데이터 플레인)로 Gateway 및 VirtualService 설정이 전달되지 않았을 가능성이 있습니다.
이는 Istiod와 IngressGateway 간의 통신 문제, 혹은 설정 동기화 실패(STALE 상태)가 원인일 수 있습니다.

그럼 Ingress, GW, VS설정에 문제가 없는지 찾아보면 될것이다. 사실 VS 설정중 게이트웨이 설정하는부분을 잘못 넣어놨었다.

gateways:
-   istio-system/devops-gateway

정상적으로 하려면 devops-gateway가 아니라 query-gateway 으로 설정해줘야 한다.

$ istioctl proxy-config all ops-query-7cc7fc6f69-b6sml.devops  | grep ops-query
ops-query-svc-active.devops.svc.cluster.local                                  80        -          outbound      EDS              
ops-query-svc-preview.devops.svc.cluster.local                                 80        -          outbound      EDS              
80                                                                                   ops-query-svc-active, ops-query-svc-active.devops + 1 more...                                                            /*                     
80                                                                                   ops-query-svc-preview, ops-query-svc-preview.devops + 1 more...                                                          /*     

서비스도 정상적으로 동작하고, proxy-config 의 결과에도 정상적으로 출력되는것이 확인된다.
proxy-config에 대한(리스너, 라우드, 클러스터, 엔드포인트등 xDS)자세한 설명은 다음 주차때 envoy프록시 설명에서 자세히 다룰듯 하다.

다시 실습으로 넘어가서...

# istio-ingressgateway 서비스 NodePort 변경 및 nodeport 30000로 지정 변경
kubectl get svc,ep -n istio-system istio-ingressgateway
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl get svc -n istio-system istio-ingressgateway

# istio-ingressgateway 서비스 externalTrafficPolicy 설정 : ClientIP 수집 확인
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

curl -s http://127.0.0.1:30000/api/catalog | jq
curl -s http://127.0.0.1:30000/api/catalog/items/1 | jq
curl -s http://127.0.0.1:30000/api/catalog -I | head -n 1

위 명령어들로 실습환경의 30000포트로 접속가능하도록 설정해주고 curl로 접속을 확인하면 다음과 같이 정상적으로 접속됨을 확인 할 수 있다.

옵저버빌리티

이제 옵저버빌리티를 하기 위해 추가 설치한 프로메테우스, 그라파나, 키알리, 트레이싱에 대한 노드포트를 설정해준다.

kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

이후 localhost:서비스포트 로 접속하면 정상적으로 접속됨을 확인할 수 있다. 이중 Kiali를 살펴보면..

 

이미지와 같이 서비스매쉬의 트래픽 흐름을 확인 할 수 있다. 아까 장애 상황에서 라면 istio-ingressgateway에서 webapp으로 흐르는 트래픽이 안나왔을것이다.

 

이제 해당 서비스에 강제로 500에러를 50프로 빈도로 발생하도록 하면 kiali에서 다음 화면 처럼 실패가 나고 있는것을 볼 수 있다.

 

실무환경에서도 여러가지 이유로 간헐적으로 5xx대 에러가 발생할 수 있는데, 이때 다음 설정으로 5xx에러 발생시 재시도를 하도록 조치 할 수 있다.

cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  http:
  - route:
    - destination:
        host: catalog
    retries:
      attempts: 3
      retryOn: 5xx
      perTryTimeout: 2s
EOF

 

위 설정은 만약 5xx대 에러 발생시 2초 뒤 3번 재시도 하도록 할 수 있다. 사이드카 방식 istio의 큰 장점이라고 본다.

 

이밖에, istio 만으로 트래픽을 컨트롤하여 여러가지 배포방법을 이용할 수 있다.

 

우리는 argocd를 사용중인데, VS에서 route 설정으로 weight으로 active, preview를 설정하여 블루/그린 배포방법을 이용중이다.

 

추가로 다음과같이 VS에 헤더를 기반으로 매칭을 확인하여 트래픽을 보낼 수 있다.

cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  http:
  - match:
    - headers:
        x-dark-launch:
          exact: "v2"
    route:
    - destination:
        host: catalog
        subset: version-v2
  - route:
    - destination:
        host: catalog
        subset: version-v1
EOF

 

현재 운영환경에서 아르고 롤아웃으로 프리뷰가 배포 됐을 때, promote 하기 전에 해당 배포버전으로 미리 접속하여 QA를 진행하고자 할 수 있다. 이때 굉장히 유용할듯 하다.

 

마지막으로...

istio를 사용하면서 istio 컨테이너의 로그를 자세히 봐야 할 필요가 있었다.

실사례로, 프론트에서는 아무런 값이 안들어왔고, 백앤드에서는 데이터를 정상적으로 전송했다고 한다. 이때 istio 컨테이너 로그를 확인 할 필요가 있었다. 왜냐하면 결국 데이터가 전송됐다면 istio 로그에 찍혔을테니까 말이다. 

 

그래서 다음과같은 설정을 적용해서 로그를 찍었다.

 

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: request-size-limit
  namespace: test
spec:
  workloadSelector:
    labels:
      app: test-api
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: ANY
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.buffer
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer
          max_request_bytes: 50485760


  - applyTo: HTTP_FILTER
    match:
      context: ANY
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
          inlineCode: |
            function envoy_on_response(response_handle)
              local headers = response_handle:headers()
              for key, value in pairs(headers) do
                response_handle:logInfo("response Header:" .. key .. ":"  .. value)
              end
              local response_body = response_handle:body():getBytes(0,response_handle:body():length())
              response_handle:logInfo("response body:" .. response_body)
            end

  - applyTo: LISTENER
    match:
      context: ANY
    patch:
      operation: MERGE
      value:
        per_connection_buffer_limit_bytes: 20480000

 

해당 설정은 요청에 대한 response body 용량과 실제 body 내용을 로그로 남기는 설정이다.

 

마무리..

1주차부터 좋은 지식을 많이 얻었다. 스터디 운영진님들 감사합니다.

'job > devops' 카테고리의 다른 글

istio-2  (1) 2025.04.17
taskfile  (0) 2024.03.10
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

1. 패키지 관리 도구 및 저장소별로 nexus 프록시 타입 레포 생성

2. maven, npm등 포맷별로 만들어진 proxy 레포를 하나의 그룹으로 묶기

3. 깃헙에서 소스 패키지를 태그나 릴리즈등으로 받는 경우는 raw host 만들어서 직접 파일 업로드하여 패키지 관리. 물론 raw host들도 그룹으로 묶기

4. 빌드 환경에 따라 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경하기

5. 패키지 설치 후 빌드하는 과정에서 디펜던시 패키지가 또다시 패키지를 설치하는경우가 있음. 따라서 패키지 설치후 디펜던시 패키지내에 추가적으로 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경해야 할 수 있음

큰 틀로 보면 위 순서대로 하면 된다. 

 

구축된 환경들을 나열해보면

React Native, Flutter, Yarn, Maven, NPM, Docker image, composer, python, cocoapod, rubygem

그리고 apk, yum, homebrew, nuget까지.. 많이 사용되는 대부분의 환경은 다 한 것 같다.

 

해당 업무를 하면서 조언을 얻기가 쉽지 않았다.  우리나라에 망분리 환경에서 개발하는 개발자들이 그렇게 많겠지만 개발자는 인프라영역을 모르고, 인프라담당자는 개발영역을 잘 몰르니 말이다. 그래서 난관이 좀 많았다. 망분리 개발환경 구축만큼은 나름 목소리좀 낼 수 있을듯 하다.

어쨌든 뭐. 이정도면 나름 만족스러운 결과(개발자도 최대한 불편하지 않고, 보안에도 위법되지 않고)인듯 하다.

 

마지막으로 각 단계에서 발생했던 난관들을 적어보자면...

1. 패키지 관리 도구 및 저장소별로 nexus 프록시 타입 레포 생성

넥서스 캐시등의 설정에 유의해야하고.

넥서스에서 기본적으로 제공하는 레시피가 제한적이다. 그럴땐 넥서스 레포 '패키지관리도구이름' 검색해보면 직접 레시피 만들 수 있도록 깃헙에 등록돼있는 경우가 있음.(ex apk)

 

2. maven, npm등 포맷별로 만들어진 proxy 레포를 하나의 그룹으로 묶기

이건 뭐 어려울껀 없다.. 가장 많이 사용하는 레포를 순서상 가장 위로 하도록

 

3. 깃헙에서 소스 패키지를 태그나 릴리즈등으로 받는 경우는 raw host 만들어서 직접 파일 업로드하여 패키지 관리. 물론 raw host들도 그룹으로 묶기

Nexus는 기본적으로 바이너리 컴포넌트 관리 솔루션으로 설계되었으며, 소스 코드 관리 시스템이 아니다. 따라서 GitHub과 같은 소스 코드 저장소를 직접적으로 프록시하거나 통합하는 데 적합하지 않다.

그래서 패키지를 깃헙에서 받는 경우에는 어떻게 해야할까 고민이 많았다. 기 구축된 내부 깃랩 서버가 있어서 처음엔 깃헙>깃랩을 동일하게 구성(미러링 되도록)해서 해결해볼까 했다.

그러나 이경우 깃헙에서 기본적으로 사용하는 태그/아카이브/릴리즈 경로와 깃랩의 경로가 서로 달랐다. 미러링하다보니까 모든 패키지를 다 다운받아야하니 불필요하게 깃랩의 용량이 커지는것도 싫었다.

그래서 결국 필요한 패키지를 깃헙에서 다운받아 raw host 넥서스 레포로 업로드 하도록 했다. 현재 내가 다니는 회사의 규모가 크진 않아서 필요한 패키지를 직접 수동으로 업로드하긴 했는데, 만약 자동화가 필요하다면 구글 시트에 개발자가 필요한 패키지이름/깃헙 경로 업로드하면 스케쥴링 걸어서 자동으로 넥서스 레포에 업로드 하도록 하면 될듯 싶다.

 

4. 빌드 환경에 따라 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경하기

CI/CD 자동화 환경에서는 dockerfile에서 직접 설정을 해야 하고, 개발자 개인 개발 PC에서의 설정은 개발자가 해야 할 일이다.

패키지 저장소별 설정 방법 가이드는 https://help.sonatype.com/en/formats.html 를 참고하면 된다.

 

5. 패키지 설치 후 빌드하는 과정에서 디펜던시 패키지가 또다시 패키지를 설치하는경우가 있음. 따라서 패키지 설치후 디펜던시 패키지내에

추가적으로 기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경해야 할 수 있음

앞서 깃헙과 함께 가장 골치 아픈 상황이였다. 특히 프론트개발환경에 React Native에서 많았다. 이건 뭐.. 어쩔 수 없다. 이것도 찾아서 다 바꿔야 한다. 빌드 도구별로 패치 기능이 또 있어서 잘 활용하면 된다.

패키지 설치 -> 패치 -> 빌드 가 가능한 환경이면 devops가 replace(기존 저장소를 내부 넥서스로)를 할 수 있다. 근데 애매한게... 빌드 하는 과정에서 패치가 필요한경우가 있다. 이때는 개발자가 소스코드에서 직접 수정해줬다. 예를들어 Gradle 같은 경우는 init.gradle에서 설정이 가능하다.

 

 

넥서스에서 제공하는 기본 repository recipe중에 apk는 없다.

 

ps.

나는 apt랑 헷갈려서 apt로 만들었다가 다음과 같은 에러를 만났다.

package mentioned in index not found (try 'apk update')

 

어쨋든 apk용 레포를 만들기 위해 넥서스에 apk recipe를 추가해보자.

 

1. git clone https://github.com/sonatype-nexus-community/nexus-repository-apk.git

2. cd nexus-repo tab

3. mvn clean package -PbuildKar

3-1. mvn 없으면 maven 설치

3-2. 빌드가 완료되면 target 폴더에 .kar 파일이 생성됩니다

4. 생성된 .kar 파일을 Nexus Repository의 deploy 폴더로 복사

5. 넥서스 재시작

1

 

'job > linux' 카테고리의 다른 글

기본 엔지니어링 체크리스트  (0) 2024.03.20
docker Exited (137)  (0) 2024.01.09
ci/cd 파이프라인  (0) 2022.11.16
간헐적 500 Server Internal Error 원인 파악하기  (0) 2022.11.10
du -sch --exclude  (0) 2022.07.11

집합 자료형은 중복되지 않는 고유한 요소들의 모음을 저장하는 자료형으로, 여러 가지 실무 상황에서 유용하게 사용할 수 있다. 집합은 중괄호 {}를 사용하여 생성하며, 다양한 집합 연산을 지원한다. 아래에서는 집합 자료형을 실무에서 사용할 수 있는 몇 가지 상황을 설명해보겠다.

1. 중복 제거

가장 일반적인 집합의 사용 사례는 중복된 데이터를 제거하는 것이다. 예를 들어, 고객 이메일 목록에서 중복된 이메일 주소를 제거할 때 유용하다.

예시

emails = ["alice@example.com", "bob@example.com", "alice@example.com", "charlie@example.com"]
unique_emails = set(emails)
print(unique_emails)  # 출력: {'alice@example.com', 'bob@example.com', 'charlie@example.com'}

2. 교집합, 합집합, 차집합 연산

집합은 교집합, 합집합, 차집합 등의 집합 연산을 효율적으로 수행할 수 있다. 이는 데이터 분석, 필터링 등에 매우 유용하다.

예시

# 두 집합 생성
set_a = {"apple", "banana", "cherry"}
set_b = {"banana", "cherry", "date", "fig"}

# 교집합
intersection = set_a & set_b
print(intersection)  # 출력: {'banana', 'cherry'}

# 합집합
union = set_a | set_b
print(union)  # 출력: {'apple', 'banana', 'cherry', 'date', 'fig'}

# 차집합
difference = set_a - set_b
print(difference)  # 출력: {'apple'}

3. 데이터 무결성 유지

집합은 중복을 허용하지 않기 때문에, 데이터 무결성을 유지하는 데 유용하다. 예를 들어, 사용자 ID나 제품 코드와 같이 고유해야 하는 데이터를 저장할 때 사용한다.

예시

user_ids = {"user1", "user2", "user3"}

# 새로운 사용자 ID 추가
user_ids.add("user4")
print(user_ids)  # 출력: {'user1', 'user2', 'user3', 'user4'}

# 중복된 사용자 ID 추가 시도
user_ids.add("user2")
print(user_ids)  # 출력: {'user1', 'user2', 'user3', 'user4'} (중복 추가되지 않음)

4. 빠른 멤버십 테스트

집합은 특정 요소가 집합에 존재하는지 빠르게 확인할 수 있다. 이는 대규모 데이터에서 특정 요소를 검색할 때 유용하다.

예시

# 대규모 데이터 집합 생성
large_set = set(range(1000000))

# 특정 요소 존재 여부 확인
print(999999 in large_set)  # 출력: True
print(1000000 in large_set)  # 출력: False

5. 태그 시스템

집합은 태그 시스템을 구현할 때 유용하다. 예를 들어, 블로그 게시물에 여러 태그를 추가하고, 특정 태그를 가진 게시물을 검색할 때 사용한다.

예시

# 게시물에 태그 추가
post_tags = {"python", "programming", "tutorial"}

# 새로운 태그 추가
post_tags.add("coding")
print(post_tags)  # 출력: {'python', 'programming', 'tutorial', 'coding'}

# 특정 태그 존재 여부 확인
print("python" in post_tags)  # 출력: True
print("java" in post_tags)    # 출력: False

결론

집합 자료형은 중복 제거, 집합 연산, 데이터 무결성 유지, 빠른 멤버십 테스트, 태그 시스템 등 다양한 실무 상황에서 유용하게 사용할 수 있다. 집합의 특성과 장점을 이해하고 적절히 활용하면 데이터 처리와 분석을 더욱 효율적으로 수행할 수 있다.

프로그래밍을 할 때 리스트와 튜플을 사용하여 데이터를 저장할 수 있다. 이 두 자료형은 많은 면에서 비슷하지만, 메모리 사용량에서는 차이가 있다. 이 글에서는 리스트와 튜플의 메모리 사용량 차이를 설명해보겠다.

리스트와 튜플의 메모리 사용량 비교

리스트와 튜플은 각각 데이터를 저장하는 방식이 다르기 때문에 메모리 사용량에서도 차이가 난다. 일반적으로 튜플이 리스트보다 메모리를 덜 사용한다.

예시

# 리스트와 튜플 생성
a_list = [1, 2, 3]
a_tuple = (1, 2, 3)

# 메모리 사용량 확인
print(a_list.__sizeof__())  # 출력: 64
print(a_tuple.__sizeof__())  # 출력: 48

위 예시에서 볼 수 있듯이, 동일한 데이터를 저장할 때 리스트는 64바이트를 사용하고, 튜플은 48바이트를 사용한다.

메모리 사용량 차이의 이유

  1. 가변성:
    • 리스트는 가변적이어서 요소를 추가하거나 삭제할 수 있다. 이를 위해 리스트는 추가적인 메모리를 할당하여 데이터를 저장하고 관리해야 한다. 반면, 튜플은 불변적이어서 한 번 생성되면 변경할 수 없다. 따라서 튜플은 고정된 메모리만 할당하면 된다.
  2. 오버 할당:
    • 리스트는 요소를 추가할 때마다 메모리를 재할당하는 비용을 줄이기 위해 오버 할당(over-allocation) 기법을 사용한다. 이는 리스트가 더 많은 메모리를 사용할 수 있게 한다. 반면, 튜플은 이러한 오버 할당이 필요 없으므로 더 적은 메모리를 사용한다[1][2].
  3. 구조적 차이:
    • 리스트는 각 요소에 대한 포인터를 저장하는 데 추가 메모리를 사용한다. 반면, 튜플은 이러한 포인터를 저장하지 않아 더 적은 메모리를 사용한다[1].

[1] https://stackoverflow.com/questions/46664007/why-do-tuples-take-less-space-in-memory-than-lists

[2] https://www.reddit.com/r/learnpython/comments/1b9rdxq/list_vs_tuple_mutable_vs_immutable_performance/

Citations: [1] https://stackoverflow.com/questions/46664007/why-do-tuples-take-less-space-in-memory-than-lists [2] https://www.reddit.com/r/learnpython/comments/1b9rdxq/list_vs_tuple_mutable_vs_immutable_performance/ [3] https://stackoverflow.com/questions/20771470/list-memory-usage [4] https://www.geeksforgeeks.org/memory-management-in-lists-and-tuples-using-python/ [5] https://www.geeksforgeeks.org/python-memory-consumption-dictionary-vs-list-of-tuples/ [6] https://www.upgrad.com/blog/list-vs-tuple/ [7] https://github.com/BecomeWeasel/daily_algo_challenge/issues/2

프로그래밍을 할 때 리스트와 튜플을 언제 사용해야 할지 고민될 수 있다. 리스트와 튜플은 모두 데이터를 순서대로 저장할 수 있는 자료형이지만, 몇 가지 중요한 차이점이 있다.

리스트 (List)

리스트는 데이터를 수정, 추가, 삭제할 수 있는 변경 가능한 자료형이다. 대괄호 []로 감싸서 만들며, 다양한 데이터를 저장할 수 있다. 리스트는 다음과 같은 상황에서 사용하기 좋다:

  1. 데이터가 자주 변경될 때:
    • 리스트는 데이터를 자유롭게 수정할 수 있다. 예를 들어, 쇼핑 목록이나 할 일 목록처럼 자주 업데이트해야 하는 데이터를 저장할 때 유용하다.
  2. 동적 크기 조정이 필요할 때:
    • 리스트는 크기를 동적으로 조정할 수 있다. 데이터를 추가하거나 삭제할 수 있어, 크기가 변동하는 데이터에 적합하다.

튜플 (Tuple)

튜플은 한 번 생성되면 수정할 수 없는 변경 불가능한 자료형이다. 소괄호 ()로 감싸서 만들며, 다양한 데이터를 저장할 수 있다. 튜플은 다음과 같은 상황에서 사용하기 좋다:

  1. 데이터가 변경되지 않을 때:
    • 튜플은 데이터가 변경되지 않아야 할 때 사용하기 좋다. 예를 들어, 좌표나 RGB 색상 값처럼 고정된 데이터를 저장할 때 유용하다.
  2. 데이터의 무결성을 유지해야 할 때:
    • 튜플은 변경할 수 없기 때문에 데이터의 무결성을 유지할 수 있다. 중요한 데이터를 보호할 때 적합하다.
  3. 메모리 사용을 최적화할 때:
    • 튜플은 리스트보다 메모리를 적게 사용하고, 처리 속도가 빠르다. 따라서 메모리 사용을 최적화해야 하는 상황에서 유용하다.

리스트와 튜플의 차이점 정리

특징 리스트 (List) 튜플 (Tuple)

생성 방법 대괄호 [] 사용 소괄호 () 사용
변경 가능 여부 변경 가능 (요소 수정, 추가, 삭제 가능) 변경 불가능 (요소 수정, 추가, 삭제 불가)
사용 예시 동적으로 변하는 데이터 관리에 유용하다 고정된 데이터 관리에 유용하다

결론

리스트와 튜플은 각각의 특성과 장점을 가지고 있다. 리스트는 데이터가 자주 변경되거나 크기가 변동할 때 사용하기 좋다. 반면, 튜플은 데이터가 변경되지 않아야 하거나 데이터의 무결성을 유지해야 할 때, 그리고 메모리 사용을 최적화해야 할 때 사용하기 좋다.

리스트 (List)

리스트는 여러 데이터를 순서대로 저장할 수 있는 자료형이다. 리스트는 대괄호 []로 감싸서 만들고, 각 요소는 쉼표 ,로 구분한다. 리스트의 가장 큰 특징은 변경 가능하다는 점이다. 즉, 리스트에 있는 데이터를 수정, 추가, 삭제할 수 있다.

예시

# 리스트 생성
fruits = ["사과", "바나나", "딸기"]

# 리스트 요소 변경
fruits[1] = "오렌지"  # 바나나를 오렌지로 변경
print(fruits)  # 출력: ['사과', '오렌지', '딸기']

# 리스트에 요소 추가
fruits.append("포도")
print(fruits)  # 출력: ['사과', '오렌지', '딸기', '포도']

# 리스트에서 요소 삭제
fruits.remove("딸기")
print(fruits)  # 출력: ['사과', '오렌지', '포도']

튜플 (Tuple)

튜플은 리스트와 비슷하게 여러 데이터를 순서대로 저장할 수 있는 자료형이다. 하지만 튜플은 소괄호 ()로 감싸서 만들고, 리스트와 달리 변경 불가능하다. 즉, 한 번 생성된 튜플의 요소는 수정, 추가, 삭제할 수 없다.

예시

# 튜플 생성
colors = ("빨강", "초록", "파랑")

# 튜플 요소 접근
print(colors[1])  # 출력: 초록

# 튜플 요소 변경 시도 (오류 발생)
# colors[1] = "노랑"  # 오류: 튜플은 변경할 수 없음

# 튜플에 요소 추가 시도 (오류 발생)
# colors.append("노랑")  # 오류: 튜플은 변경할 수 없음

리스트와 튜플의 차이점 정리

특징 리스트 (List) 튜플 (Tuple)

생성 방법 대괄호 [] 사용 소괄호 () 사용
변경 가능 여부 변경 가능 (요소 수정, 추가, 삭제 가능) 변경 불가능 (요소 수정, 추가, 삭제 불가)
사용 예시 동적으로 변하는 데이터 관리에 유용하다 고정된 데이터 관리에 유용하다

결론

리스트와 튜플은 여러 데이터를 관리할 때 매우 유용한 자료형이다. 리스트는 데이터를 자유롭게 수정, 추가, 삭제할 수 있어 유연성이 높다. 반면, 튜플은 한 번 생성되면 변경할 수 없기 때문에 데이터의 무결성을 유지하는 데 유리하다. 이 두 자료형의 차이를 이해하고 상황에 맞게 사용하는 것이 중요하다.

2차원 배열이란

2차원 배열은 숫자나 문자를 정리해서 저장할 수 있는 표와 같은 것이다. 엑셀이나 구글 스프레드시트를 생각해보면 쉽게 이해할 수 있다. 표의 각 칸에는 하나의 값이 들어가고, 이 값들은 행과 열로 구분된다.

2차원 배열의 예시

예를 들어, 다음과 같은 표가 있다고 하자:

0열 1열 2열

0행 1 2 3
1행 4 5 6
2행 7 8 9

이 표는 3개의 행과 3개의 열로 이루어져 있다. 각 칸에는 숫자가 들어있다. 이 표를 2차원 배열이라고 부른다.

파이썬에서 2차원 배열 만들기

파이썬에서는 리스트라는 것을 사용해서 2차원 배열을 만들 수 있다. 리스트는 여러 개의 값을 한 곳에 모아놓는 방법이다. 2차원 배열을 만들기 위해서는 리스트 안에 리스트를 넣으면 된다.

array = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

위의 코드는 3x3 크기의 2차원 배열을 만든다. 첫 번째 리스트 [1, 2, 3]는 첫 번째 행을 나타내고, 두 번째 리스트 [4, 5, 6]는 두 번째 행을 나타낸다.

2차원 배열에서 값 꺼내기

2차원 배열에서 특정 값을 꺼내려면 행과 열의 위치를 알려줘야 한다. 예를 들어, 첫 번째 행과 두 번째 열에 있는 값을 꺼내려면 다음과 같이 하면 된다.

value = array[0][1]  # 결과는 2

여기서 array은 첫 번째 행(0행)과 두 번째 열(1열)에 있는 값을 의미한다. 파이썬에서는 숫자를 셀 때 0부터 시작한다는 점을 기억하자.

2차원 배열에서 부분 배열 꺼내기

2차원 배열에서 여러 개의 값을 한꺼번에 꺼내는 것도 가능하다. 이를 슬라이싱이라고 한다. 예를 들어, 첫 번째와 두 번째 행을 꺼내려면 다음과 같이 한다.

rows = array[0:2]  # 결과는 [[1, 2, 3], [4, 5, 6]]

여기서 array[0:2]는 첫 번째 행(0행)과 두 번째 행(1행)을 의미한다.

2차원 배열에서 인덱싱과 슬라이싱을 활용하면 특정 행, 열 또는 부분 배열을 쉽게 추출할 수 있다. 이를 통해 데이터 분석, 이미지 처리 등 다양한 분야에서 효율적으로 데이터를 다룰 수 있다. 아래 예제는 NumPy 라이브러리를 사용하여 2차원 배열을 처리하는 방법을 보여준다.

예시: 2차원 배열 생성 및 인덱싱, 슬라이싱

먼저, NumPy를 사용하여 2차원 배열을 생성하고, 인덱싱과 슬라이싱을 통해 특정 부분을 추출하는 예제를 살펴보자.

import numpy as np

# 2차원 배열 생성
array = np.array([
    [1, 2, 3, 4, 5],
    [6, 7, 8, 9, 10],
    [11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20]
])

# 배열 출력
print("원본 배열:")
print(array)

인덱싱 예제

특정 행과 열의 요소를 추출하는 방법이다.

# 두 번째 행의 세 번째 요소 (8) 추출
element = array[1, 2]
print("\\\\n두 번째 행의 세 번째 요소:", element)

# 마지막 행의 마지막 요소 (20) 추출
element = array[-1, -1]
print("마지막 행의 마지막 요소:", element)

슬라이싱 예제

배열의 특정 부분을 추출하는 방법이다.

# 첫 두 행과 첫 세 열 추출
sub_array = array[:2, :3]
print("\\\\n첫 두 행과 첫 세 열:")
print(sub_array)

# 두 번째 행부터 끝까지, 세 번째 열부터 네 번째 열까지 추출
sub_array = array[1:, 2:4]
print("\\\\n두 번째 행부터 끝까지, 세 번째 열부터 네 번째 열까지:")
print(sub_array)

# 모든 행에서 두 번째 열만 추출
column = array[:, 1]
print("\\\\n모든 행에서 두 번째 열:")
print(column)

# 모든 열에서 세 번째 행만 추출
row = array[2, :]
print("\\\\n모든 열에서 세 번째 행:")
print(row)

문자열 인덱싱

문자열 인덱싱은 문자열의 특정 위치에 있는 문자를 가져오는 방법이다. 인덱스는 0부터 시작하며, 음수 인덱스를 사용하면 문자열의 끝에서부터 역순으로 접근할 수 있다.

# 문자열 설정
text = "Hello, World!"

# 인덱싱 예제
print(text[0])  # 출력: H (첫 번째 문자)
print(text[7])  # 출력: W (여덟 번째 문자)
print(text[-1])  # 출력: ! (마지막 문자)
print(text[-5])  # 출력: o (뒤에서 다섯 번째 문자)

문자열 슬라이싱

문자열 슬라이싱은 문자열의 일부분을 추출하는 방법이다. 슬라이싱은 [start:end:step] 형식을 사용하며, start는 시작 인덱스, end는 끝 인덱스(포함되지 않음), step은 간격을 의미한다.

# 문자열 설정
text = "Hello, World!"

# 슬라이싱 예제
print(text[0:5])  # 출력: Hello (0번 인덱스부터 4번 인덱스까지)
print(text[7:12])  # 출력: World (7번 인덱스부터 11번 인덱스까지)
print(text[:5])  # 출력: Hello (처음부터 4번 인덱스까지)
print(text[7:])  # 출력: World! (7번 인덱스부터 끝까지)
print(text[:])  # 출력: Hello, World! (전체 문자열)
print(text[::2])  # 출력: Hlo ol! (2칸씩 건너뛰며 추출)
print(text[::-1])  # 출력: !dlroW ,olleH (역순으로 추출)

인덱싱과 슬라이싱을 동시에 활용한 실무 예제 - 로그 파일에서 날짜와 오류 메시지 추출하기

문자열 인덱싱과 슬라이싱을 동시에 활용하면 문자열의 특정 부분을 효율적으로 추출하고 조작할 수 있다. 예를 들어, 로그 파일에서 특정 정보를 추출하거나, 텍스트 데이터에서 특정 패턴을 찾는 작업에 유용하다.

로그 파일의 각 줄에서 날짜와 오류 메시지를 추출하는 예제를 살펴보자. 로그 파일의 형식은 다음과 같다:

2024-08-07 12:34:56 ERROR: Something went wrong
2024-08-07 12:35:56 INFO: All systems operational
2024-08-07 12:36:56 ERROR: Another error occurred

이 로그 파일에서 날짜와 오류 메시지만 추출하는 코드를 작성해보자.

# 로그 파일의 각 줄을 리스트로 저장
log_lines = [
    "2024-08-07 12:34:56 ERROR: Something went wrong",
    "2024-08-07 12:35:56 INFO: All systems operational",
    "2024-08-07 12:36:56 ERROR: Another error occurred"
]

# 날짜와 오류 메시지를 추출하는 함수
def extract_error_info(log_lines):
    for line in log_lines:
        if "ERROR" in line:
            # 날짜 추출 (인덱싱과 슬라이싱을 동시에 활용)
            date = line[:10]  # 처음 10글자: 2024-08-07
            # 오류 메시지 추출
            error_message = line[line.index("ERROR:") + 7:]  # "ERROR:" 다음부터 끝까지
            print(f"Date: {date}, Error: {error_message}")

# 함수 호출
extract_error_info(log_lines)

결과

Date: 2024-08-07, Error: Something went wrong
Date: 2024-08-07, Error: Another error occurred

설명

  1. 날짜 추출: line[:10]을 사용하여 문자열의 처음 10글자를 추출한다. 이는 날짜를 의미한다.
  2. 오류 메시지 추출: line.index("ERROR:") + 7을 사용하여 "ERROR:" 문자열의 위치를 찾고, 그 이후의 문자열을 추출한다.

이 예시는 인덱싱과 슬라이싱을 동시에 활용하여 문자열에서 필요한 정보를 효율적으로 추출하는 방법을 보여준다. 이러한 기법은 로그 파일 분석, 데이터 전처리 등 다양한 실무 상황에서 유용하게 사용될 수 있다.

복합 연산자는 변수의 값을 업데이트할 때 사용하는 연산자로, 기본 연산자와 할당 연산자를 결합한 형태이다. 예를 들어, +=, -=, *=, /=, %=, **=, //= 등이 있다. 이러한 연산자는 코드의 가독성을 높이고 간결하게 만들어준다.

예시 1: += 연산자

# 초기 값 설정
x = 5

# 복합 연산자 사용
x += 3  # x = x + 3과 동일

print(x)  # 출력: 8

예시 2: = 연산자

# 초기 값 설정
y = 10

# 복합 연산자 사용
y -= 4  # y = y - 4와 동일

print(y)  # 출력: 6

예시 3: = 연산자

# 초기 값 설정
z = 7

# 복합 연산자 사용
z *= 2  # z = z * 2와 동일

print(z)  # 출력: 14

예시 4: /= 연산자

# 초기 값 설정
a = 20

# 복합 연산자 사용
a /= 5  # a = a / 5와 동일

print(a)  # 출력: 4.0

예시 5: %= 연산자

# 초기 값 설정
b = 13

# 복합 연산자 사용
b %= 4  # b = b % 4와 동일

print(b)  # 출력: 1

예시 6: *= 연산자

# 초기 값 설정
c = 2

# 복합 연산자 사용
c **= 3  # c = c ** 3과 동일

print(c)  # 출력: 8

예시 7: //= 연산자

# 초기 값 설정
d = 15

# 복합 연산자 사용
d //= 2  # d = d // 2와 동일

print(d)  # 출력: 7

자료형이란 프로그래밍을 할 때 쓰이는 숫자, 문자열 등과 같이 자료 형태로 사용하는 모든 것을 뜻한다.

예시: Python에서 자료형 이해의 중요성

상황 설명:

Alice는 Python을 배우기 시작한 초보 프로그래머다. 그녀는 계산기를 만드는 프로젝트를 진행 중이다. 그러나 자료형에 대한 이해가 부족하여 여러 가지 문제에 직면하게 된다.

잘못된 접근: Alice는 자료형에 대한 이해 없이 바로 코딩을 시작했다. 그녀는 사용자로부터 입력을 받아 두 숫자를 더하는 간단한 계산기를 만들려고 했다.

# Alice의 코드
num1 = input("첫 번째 숫자를 입력하세요: ")
num2 = input("두 번째 숫자를 입력하세요: ")

result = num1 + num2
print("결과: ", result)

Alice는 두 숫자를 더한 결과가 기대와 다르다는 것을 발견했다. 예를 들어, '3'과 '5'를 입력했을 때 결과는 '35'가 나왔다.

문제 분석: Alice는 input 함수가 문자열을 반환한다는 사실을 몰랐다. 따라서 num1과 num2는 문자열로 저장되었고, 문자열끼리의 덧셈은 문자열을 이어붙이는 결과를 초래했다.

올바른 접근: 자료형을 이해한 후, Alice는 사용자로부터 입력받은 값을 정수형으로 변환해야 한다는 것을 알게 되었다.

# 수정된 코드
num1 = int(input("첫 번째 숫자를 입력하세요: "))
num2 = int(input("두 번째 숫자를 입력하세요: "))

result = num1 + num2
print("결과: ", result)

이제 Alice의 계산기는 올바르게 작동한다. '3'과 '5'를 입력했을 때 결과는 '8'이 된다.

이 예시는 자료형을 이해하지 않고 프로그래밍을 시작하면 발생할 수 있는 문제를 보여준다. 자료형을 충분히 이해하는 것은 프로그래밍의 기본이자 핵심이다. 자료형을 이해함으로써 Alice는 올바른 계산기를 만들 수 있었고, 이는 다른 복잡한 프로그램을 작성할 때도 중요한 기초가 된다.

소스 관리

기본 대상 브랜치는 잠겨 있습니다.
병합은 PR을 통해 이루어집니다.
PR은 관련 작업 항목을 참조합니다.
커밋 기록은 일관되고 커밋 메시지는 정보(내용, 이유)를 제공한다.
일관된 브랜치 이름 지정 규칙.
리포지토리 구조에 대한 명확한 문서화.
secret은 커밋 기록에 포함되지 않거나 공개되지 않습니다. (자격 증명 스캔 참조)
공개 리포지토리는 OSS 가이드라인을 따르며, "공개 리포지토리의 기본 브랜치에 필요한 파일을 참조하세요." 를 적는다.

 

 

 

 

'job > linux' 카테고리의 다른 글

Nexus Apk repo 생성하기  (0) 2024.10.25
docker Exited (137)  (0) 2024.01.09
ci/cd 파이프라인  (0) 2022.11.16
간헐적 500 Server Internal Error 원인 파악하기  (0) 2022.11.10
du -sch --exclude  (0) 2022.07.11

git clone시 프로젝트 단위로만 git clone이 가능하다.

 

여간 불편한게 아니다.

그룹 혹은 서브그룹단위로 한번에 다운로드하면 참 좋을텐데.

 

이떄 사용하는 도구는 gitlab CLI 또는 glab이다.

https://gitlab.com/gitlab-org/cli/-/releases

 

Releases · GitLab.org / cli · GitLab

A GitLab CLI tool bringing GitLab to your command line

gitlab.com

 

 

설치방법 : https://gitlab.com/gitlab-org/cli#installation

 

GitLab.org / cli · GitLab

A GitLab CLI tool bringing GitLab to your command line

gitlab.com

 

나는 brew install glab

 

 

토큰 인증 등록 : glab auth login —hostname domain.com —token glpat-12312382183

 

그룹 Clone 방법 : glab repo clone -g 최상위그룹/서브그룹1/서브그룹2 --preserve-namespace --paginate

 

 

 

  1. 로키 스트림의 생성에 사용되는 과정:
    • 로그 데이터는 라벨로 분류됩니다. 라벨은 로그 데이터를 구분하고 쿼리하기 위한 메타데이터로, 키/값의 쌍으로 이루어져 있습니다. 예를 들어, {component="printer", location="f2c16", level="error"}와 같은 라벨 세트가 로그 메시지에 할당됩니다.
    • 라벨 세트는 해시되어 고유한 '스트림 ID'를 생성합니다. 이 ID는 특정 로그 스트림을 식별하는 데 사용됩니다. 이미지에는 해시된 결과의 예로 3b2cea09797978fc가 있습니다.
  2. 청크의 생성과 저장:
    • 동일한 라벨 세트를 가진 추가적인 로그 메시지들은 같은 '청크'에 추가됩니다. 예를 들어, "Printing is not supported by this printer", "Out of paper", "Too much paper"와 같은 다양한 로그 메시지가 모두 같은 라벨을 공유하므로 같은 청크에 저장됩니다.
    • 이러한 청크는 채워진 후에 압축되고 저장됩니다.
  3. 청크 조회를 위한 인덱스:
    • 청크를 빠르게 찾기 위해, 별도의 작고 분리된 인덱스가 유지됩니다. 이 인덱스를 통해 청크를 빠르게 조회할 수 있습니다.
  4. 라벨 값의 변화와 새로운 청크 생성:
    • 만약 라벨의 키 또는 값이 달라지면, 다른 해시 값을 가지는 새로운 스트림과 새로운 청크가 생성됩니다. 예를 들어, {component="printer", location="f2c16", level="info"} 라벨 세트는 "Consider the environment before printing this log message"라는 로그 메시지와 함께 새로운 청크를 형성합니다.

 

 

자 그러면 로키의 Chunk 데이터 형식을 더 알아보자.

이 형식은 로키가 로그 데이터를 저장할 때 사용하는 내부 구조를 나타냅니다.

 

 

 

프롬쿼리를 공부중인데

카운터, 게이지, 히스토그램등 메트릭타입은 이해를 했다. 쿼리를 레이블로 필터링하거나 혹은 정규표현식으로 필터링하는것까지도 이해를했다.

 

다만 

instant vector 

range vector

rate함수

서브쿼리가 잘 이해가지 않아, 이해하기 위해 끄적인다.

 

사용 메트릭

promhttp_metric_handler_requests_total{code="200"}

메트릭 설명 : 

프로메테우스 메트릭 수집관련 http 응답코드중 200(정상)반환한 요청의 수

쿼리1. instant Vector

promhttp_metric_handler_requests_total{code="200"}

결과값

 

결과값 설명 :promhttp_metric_handler_requests_total 결과값

그래프 결과 :

 

 

쿼리2. Range Vector

promhttp_metric_handler_requests_total{code="200"}[5m]

결과값: 

결과값 설명:  지난 5분간의 반환값. 갯수는 총 20개다. 현재 프로메테우스에서 메트릭 수집하는게 15초간격으로 하니까 5분이면 20개가 맞다.

 

그래프결과: 결과값 자체가 타임스탬프별 결과값을 테이블 형식으로 가지고 있기 때문에 그래프 출력 못함. 

 

쿼리3. rate(promhttp_metric_handler_requests_total{code="200"}[5m])

결과값:

 

결과값 설명: rate함수(초당 평균 증가율을 계산하는 함수)를 사용하여 5분(300초)동안 초당 평균 요청 증가율을 출력한것.

앞서 5m일때의 값이 약 20개이다. 즉 rate함수가 초당 평균증가율을 계산하는거니까 300초([5m])동안 약 20개 가량이 증가했으니까  20 / 300 하면 대략 0.066666667이 나온다. 

 

그래프결과: 

 

06시 9분에 처음 메트릭 수집이 시작됐고 5분뒤인 14분에 약 20개 가량의 결과값이 쌓였다.

그래서 위와같이 약 14분가량에 밸류값이 0.06666667에 가까운것을 알수있다.

 

레인지 백터로 5m을 줬을 때 최근 5분동안의 결과값을 타임스탬프 형식으로 가져오는것은 맞다. 여기서 오해하면 안되는게 rate 함수를 사용했을 때 최근 5분동안의 증가율만 결과값으로 반환하지만 이를 그래프로 표현했을때는 수집 시점부터의 수집값을 가지고 그래프를 그린다는것

 

쿼리4-1.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[10m:1m]

 

결과값:

 

쿼리4-2.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[20m:1m]

결과값:

 

쿼리4-3.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[20m:10m]

결과값:

 

쿼리4-4.

rate(promhttp_metric_handler_requests_total{code="200"}[5m])[180m:10m]

결과값:

 

 

마지막으로 서브 쿼리에 대한 설명이다. [180m:10m]180분동안 10분간격으로 데이터를 가져온다는 뜻이다. 

 

설치

brew install go-task

아주 간단한 사용법

 cat taskfile.yaml
version: '3'

tasks:
  hello:
    cmds:
      - echo 'Hello World from Task!'
    silent: true
 task hello
Hello World from Task!

말그대로 task들을 코드로 관리하는 도구

  • 챗지피티 설명Taskfile의 주요 장점은 다음과 같습니다:
    1. 간단하고 명확한 문법: Taskfile은 가독성이 높고 쓰기 쉬운 문법을 제공합니다. 이는 개발자가 작업을 빠르게 정의하고 이해할 수 있게 해줍니다.
    2. 로컬 및 원격 실행 지원: Taskfile은 로컬 개발 환경과 원격 CI/CD 파이프라인에서 동일한 작업을 실행할 수 있게 해줍니다. 이는 개발 및 배포 과정의 일관성을 보장합니다.
    3. 작업 의존성 관리: Taskfile을 사용하면 작업 간의 의존성을 쉽게 정의하고 관리할 수 있습니다. 이는 작업 실행 순서를 자동화하고 복잡한 작업 흐름을 구성하는 데 도움이 됩니다.
    4. 재사용성 및 모듈화: Taskfile을 통해 정의된 작업은 재사용 가능하고 모듈화되어 있어, 다른 프로젝트나 파이프라인에서 쉽게 재사용할 수 있습니다.
    5. 확장성: Taskfile은 단순한 작업 실행부터 복잡한 파이프라인 구성까지 다양한 요구 사항을 수용할 수 있도록 설계되었습니다. 개발자는 필요에 따라 작업을 확장하고 사용자 정의할 수 있습니다.
    6. 통합 용이성: Taskfile은 GitHub Actions, Jenkins, GitLab CI 등 다양한 CI/CD 도구와 쉽게 통합될 수 있습니다. 이를 통해 개발 팀은 파이프라인을 효율적으로 관리하고 자동화할 수 있습니다.
    Taskfile은 Makefile과 비교할 때 더 현대적이고 사용자 친화적인 대안으로, 개발자들이 작업을 더 효율적으로 관리하고 실행할 수 있게 해줍니다. 추가적인 질문이 있거나 더 자세한 정보가 필요하시면 언제든지 문의해 주세요.
  • Taskfile은 개발 과정에서 다양한 작업을 관리하기 위한 도구입니다. 이 도구를 사용하면 테스트 실행, 빌딩, 패키징, 배포 등의 작업을 선언적 방식으로 정의하고 자동화할 수 있습니다. Taskfile의 주요 목적은 작업 실행을 단순화하고, 개발자가 로컬 및 CI/CD 파이프라인 환경에서 동일한 작업을 쉽게 실행할 수 있도록 하는 것입니다.

msbuild라던가 gradle의 여러 버전별로 사용해야 할 때 파이프라인에서 각 gradle의 환경변수를 직접 잡아줄 필요 없이 task를 사용하면 손쉽게 버전별 도구를 사용할수있지 않을가 싶다.

version: '3'

tasks:
  gradle6.1:
    cmds:
      - gradle6.1 wrapper --gradle-version=6.1
      - ./gradlew build
    desc: "Build the project with Gradle 6.1"

  gradle7.2:
    cmds:
      - gradle7.2 wrapper --gradle-version=7.2
      - ./gradlew build
    desc: "Build the project with Gradle 7.2"

이외에도 docker compose up , down 조차도 어려워하는 고객들에게 task를 사용하여 명령어를 만들어주고 사용하라고 해도 좋을듯.

가장 베스트는 개발자들이 직접 이 taskfile이라는 도구를 사용하면서 개발(로컬피씨)환경과 빌드서버환경을 통일하면 가장 좋을듯하다.

사용법 : https://www.youtube.com/watch?v=_-bpnCY3-FE

'job > devops' 카테고리의 다른 글

istio-2  (1) 2025.04.17
istio-1  (0) 2025.04.08
kubewarden  (0) 2024.01.20
openfunction  (0) 2023.12.01
git common flow(또는 gitlab flow) 브랜치 전략에 대한 설명  (0) 2023.06.13

+ Recent posts