upload failed: s1 to s3://s1 An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
권한설정을 잘 줘도 위와 같이 에러가 나는 경우
AWS CLI 및 SDK 특정 버전(예: AWS CLI v2.23.0, boto3 1.36) 이후 버전에서는 Object Storage에서 지원하지 않는 새로운 체크섬 알고리즘이 기본적으로 활성화되기 때문에 요청에 실패한다. 따라서 이전 버전을 사용하거나, 최신 버전에서 해당 체크섬 설정을 비활성화가 필요하다.
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 구현 애플리케이션에서는 트래픽이 차단되는 문제가 발생한다. 이러한 제약으로 인해 덜 침입적이고 사용하기 쉬운 서비스 메시 옵션의 필요성이 대두된다.
# 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
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
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
----------------------------------------------
가상머신에서 클러스터 서비스 호스트네임을 해석하려면 DNS 프록시의 동작 방식을 반드시 이해해야 한다.
클라이언트 DNS 쿼리 생성
애플리케이션(예: webapp.istioinaction)이 호스트네임 해석을 요청한다.
운영체제 hosts 파일 확인
/etc/hosts에 정적 매핑이 없는 경우, 기본 DNS 해석기(예: systemd-resolved)로 전달된다.
Iptables 리다이렉트
istio-agent가 설정한 iptables 규칙이 DNS 쿼리를 127.0.0.53:53 대신 **DNS 프록시(15053 포트)**로 강제 전송한다.
DNS 프록시 내부 검색
NDS API로 동기화된 메시 내 서비스(쿠버네티스 서비스, ServiceEntry) 정보를 기반으로 호스트네임을 해석한다.
클러스터 서비스인 경우, 해당 VIP 또는 엔드포인트 IP를 반환한다.
외부 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 변형을 자동 생성하여 해석을 제공한다.
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
지금까지는 이스티오 서비스 메시를 컨테이너 및 쿠버네티스 관점에서 다뤘다. 그러나 실제 워크로드는 자주 가상머신이나 물리 머신에서 실행된다. 엔터프라이즈는 규제 준수나 전문 지식 부족 등 다양한 이유로 워크로드를 온프레미스에서 실행해야 할 수 있다. 이번 장에서는 사이드카 프록시를 설치하고 설정함으로써 어떤 워크로드든 메시의 일부로 삼을 수 있는 방법을 보여준다. 이 접근법은 레거시 워크로드를 복원력 있고 안전하며 고가용성적인 방식으로 메시로 통합하길 원하는 엔터프라이즈에게 흥미로운 기능을 제공한다.
실습환경 구성
구성 : VPC 1개, EC2 인스턴스 1대 (Ubuntu 22.04 LTS, t3.xlarge - vCPU 4 , Mem 16) , forum-vm 1대는 t3.small
# 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 통합이 완료되면 운영 효율성이 크게 개선될 전망이다.
가상머신의 고가용성 달성을 위해 WorkloadGroup과 WorkloadEntry 리소스를 반드시 활용해야 한다.
WorkloadGroup은 쿠버네티스의 디플로이먼트와 유사하게 포트 설정, 레이블, 서비스 어카운트, 상태 프로브 방법 등 공통 속성을 정의한다.
WorkloadEntry는 개별 가상머신 인스턴스를 나타내며, 인스턴스 상태와 주소 같은 고유 속성을 관리한다. 쿠버네티스의 파드와 동일한 역할을 수행한다.
수동으로 WorkloadEntry를 생성할 수 있지만, **워크로드 자동 등록(auto-registration)**을 통해 신규 VM이 메시에 자동 통합되도록 구성하는 것이 권장된다. 이를 통해 인스턴스 확장/축소 시 가용성을 유지할 수 있다.
이스티오는 쿠버네티스의 고가용성 메커니즘을 차용해 VM 워크로드의 운영 효율성을 높인다.
워크로드 자동 등록을 구현하려면 WorkloadGroup을 사전에 정의해야 하며, 가상머신이 ID 토큰으로 인증해 WorkloadEntry를 자동 생성하는 과정이 필수적이다.
트래픽 라우팅 쿠버네티스 서비스나 ServiceEntry가 레이블 셀렉터를 통해 WorkloadEntry를 백엔드로 선택해야 한다. 이를 통해 VM과 파드를 동일한 서비스 엔드포인트로 통합 관리할 수 있다.
마이그레이션 전략 레거시 VM과 현대화 파드를 병렬 운영하면서 트래픽 전환 기능(예: 카나리아 배포)을 적용해야 한다. 오류 발생 시 VM으로 트래픽을 즉시 되돌리는 롤백 메커니즘이 반드시 필요하다.
운영 효율성 워크로드 상태에 따라 자동으로 인스턴스를 확장/축소할 수 있어 고가용성을 보장한다. 클라이언트 측 변경 없이 인프라 변경이 가능하다는 장점이 있다.
서비스 메시에서 readiness 프로브는 반드시 이스티오가 관리하여 워크로드의 트래픽 처리 준비 상태를 확인해야 한다. liveness 프로브는 플랫폼(예: 쿠버네티스, AWS/Azure/GCP)의 책임이며, 애플리케이션 건강 상태를 모니터링하고 장애 시 인스턴스 자동 복구를 수행해야 한다.
Readiness 프로브: 이스티오가 트래픽 수신 가능 여부를 검사해 메시 내 통합을 제어한다.
Liveness 프로브: 클라우드 플랫폼별 자동 복구 기능(예: AWS Auto Scaling, Azure VM 복구)을 반드시 활용해야 한다.
따라서 가상머신 운영 시 플랫폼의 헬스 체크 및 복구 메커니즘을 필수적으로 구성해야 한다.
가장 인기 있는 세 클라우드 프로바이더의 liveness 검사 및 자동 복구
이스티오의 가상머신 Readiness 프로브 구현 방식
가상머신의 readiness 프로브는 WorkloadGroup에 정의된 설정에 따라 istio-agent가 주기적으로 애플리케이션 상태를 검사해야 한다. 검사 결과는 istiod에 보고되며, 정상 상태일 때만 트래픽을 수신하도록 데이터 플레인을 구성한다.
핵심 메커니즘
상태 보고 및 라우팅 제어
istio-agent는 애플리케이션 헬스 체크 결과를 istiod에 전달해야 한다.
비정상 상태 감지 시 해당 VM 엔드포인트를 데이터 플레인에서 즉시 제거해야 한다.
프로브 구성 권장 사항
Readiness 프로브:
공격적 설정 적용이 필수적이다(예: 5초 주기 체크, 2회 연속 실패 시 제외).
오류 발생 시 트래픽 유입을 즉시 차단해야 한다.
Liveness 프로브:
클라우드 플랫폼(AWS/Azure/GCP)의 자동 복구 기능을 반드시 활용해야 한다.
보수적 설정 적용(예: 30초 주기 체크, 3회 연속 실패 후 인스턴스 재생성).
운영 주의사항
유예 기간 고려: 인스턴스 종료 전 진행 중인 요청 완료를 위해 grace period를 설정해야 한다.
실패 순서 관리: Readiness 프로브가 Liveness 프로브보다 먼저 실패하도록 설계해 불필요한 인스턴스 재생성을 방지해야 한다.
이스티오는 쿠버네티스의 헬스 체크 메커니즘을 확장해 VM 환경에 적용하며, 운영자 반드시 인프라 계층과의 연동을 검증해야 한다.
가상머신이 메시 내 서비스의 DNS를 해석하려면 로컬 DNS 프록시를 반드시 활용해야 한다. 쿠버네티스 외부 VM은 클러스터 내부 DNS에 접근할 수 없어 istio-agent에 내장된 DNS 프록시로 트래픽을 리다이렉트해야 한다.
DNS 쿼리 처리 메커니즘
애플리케이션의 DNS 요청은 iptables 규칙을 통해 DNS 프록시로 전달해야 한다.
DNS 프록시는 istiod가 제공하는 메시 서비스 정보(쿠버네티스 서비스, ServiceEntry)로 동적으로 업데이트되어야 한다.
NDS(Name Discovery Service) API
서비스 추가/변경 시 NDS API를 통해 DNS 프록시에 실시간으로 변경 사항을 반영해야 한다.
이를 통해 외부 DNS 서버(external-dns) 의존성을 제거하고 통합 관리가 가능하다.
장점
멀티 네트워크 환경에서도 DNS 해석이 자동화되어 VM-파드 간 원활한 통신을 보장해야 한다.
DNS 프록시는 TCP/UDP 트래픽 캡처 기능과 결합되어 애플리케이션 수정 없이 트래픽 제어가 가능하다.
이스티오 1.8 이상에서는 DNS 프록시가 기본 구성 요소로 포함되며, 운영자는 VM의 /etc/resolv.conf 설정을 DNS 프록시(127.0.0.1:15053)로 변경해야 한다.
Istio DNS Proxying은 DNS 라운드트립을 반드시 제거해야 응답 속도 개선과 CoreDNS 부하 감소를 동시에 달성할 수 있다.
트래픽 처리 방식
메시 내 Kubernetes 서비스, Pod, ServiceEntry 정보를 미리 동기화해 애플리케이션 변경 없이 DNS 해석을 수행해야 한다.
외부 서비스(예: AWS RDS)는 ServiceEntry 등록을 통해 VIP(Class E 대역)를 자동 할당해야 하며, 이를 통해 포트 충돌 없이 리스너를 구성해야 한다.
운영 효율성
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+ 버전 필수이며, 주기적인 컨트롤 플레인 업데이트가 권장된다.
이 설정은 메시 확장성을 위해 반드시 구현해야 하는 핵심 요소로, 온프레미스/멀티클라우드 환경에서 레거시 워크로드 통합에 적합하다.
WorkloadGroup 생성: VM 그룹의 공통 속성(포트, 레이블, 헬스 체크) 정의 필요
자동 등록 구성: VM 시작 시 WorkloadEntry 자동 생성되도록 설정
이 과정을 통해 멀티 네트워크 환경에서도 VM을 메시에 안정적으로 통합할 수 있다.
13.3.2 WorkloadGroup 으로 워크로드 그룹 나타내기* Representing a group of workloads with a WorkloadGroup
워크로드 그룹(WorkloadGroup)은 가상머신 집합의 공통 속성을 반드시 정의해야 한다.
레이블(labels): 쿠버네티스 서비스가 워크로드 엔트리를 선택하기 위한 기준으로 활용해야 한다.
네트워크(network): 동일 네트워크 시 IP 직접 접근, 다른 네트워크 시 이스트-웨스트 게이트웨이를 통해 트래픽을 라우팅해야 한다.
서비스 어카운트(serviceAccount): 워크로드 식별을 위해 반드시 지정해야 하며, VM은 해당 토큰으로 인증을 완료해야 한다.
헬스 체크(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을 반드시 활용해야 한다.
istioctl은 WorkloadGroup의 정보와 클러스터 쿼리 결과를 기반으로 호스트 파일, 루트 인증서, 서비스 어카운트 토큰, 메시 설정 파일을 자동 생성해야 한다.
생성된 hosts 파일은 이스트-웨스트 게이트웨이 IP를 통해 istiod 서비스 DNS를 반드시 매핑해야 한다.
**루트 인증서(root-cert.pem)**는 istiod와의 mTLS 통신 시 인증서 검증에 필수적으로 사용해야 한다.
**서비스 어카운트 토큰(istio-token)**은 WorkloadGroup 소속 인증을 위해 반드시 istio-agent에 제공해야 한다.
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단계로 수행된다.
DNS 쿼리 리다이렉트
애플리케이션의 DNS 요청은 iptables 규칙에 의해 Istio DNS 프록시로 전달되어야 한다.
DNS 프록시는 메시 내 서비스(쿠버네티스 서비스/ServiceEntry) 정보를 기반으로 IP 해석을 반드시 완료해야 한다.
아웃바운드 트래픽 캡처
해석된 IP로의 요청은 iptables가 Envoy 사이드카로 리다이렉트해야 한다.
이 단계에서 mTLS 암호화, 트래픽 정책 적용이 이루어진다.
이스트-웨스트 게이트웨이 경유
멀티 네트워크 환경에서는 Envoy가 트래픽을 east-west 게이트웨이로 라우팅해야 한다.
게이트웨이는 클러스터 네트워크와 VM 네트워크 간 보안 터널링을 반드시 수행한다.
최종 서비스 연결
게이트웨이는 트래픽을 webapp 서비스로 전달하며, webapp은 내부 catalog 서비스와 연동된다.
13.3.5 트래픽을 WorkloadEntry로 라우팅하기 Routing traffic to the WorkloadEntry
클러스터 내부에서 가상머신 워크로드로 트래픽을 라우팅할 때는 WorkloadEntry의 IP 주소를 직접 사용하지 않는다. 쿠버네티스 파드 IP를 직접 사용하지 않는 것과 동일한 원리로, 플랫폼 유연성과 인스턴스 교체 용이성을 위해 쿠버네티스 서비스를 반드시 활용해야 한다.
레이블 기반 선택:
쿠버네티스 서비스는 레이블 셀렉터를 통해 WorkloadEntry를 대상으로 지정해야 한다.
예를 들어 app: forum 레이블이 부여된 WorkloadEntry를 선택하는 서비스를 생성한다.
동적 라우팅:
이스티오는 서비스가 선택한 WorkloadEntry의 IP를 동적으로 설정해 트래픽을 전달해야 한다.
이를 통해 VM 인스턴스의 추가/제거 시 클라이언트 측 변경 없이 자동 조정이 가능하다.
운영 장점:
인스턴스 교체 용이: 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가 데이터 플레인을 설정한다.
이스티오 확장을 통한 맞춤형 네트워킹 구현 기본 기능으로 부족한 조직별 요구사항 충족을 위해 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 이후 적용되며, 엔보이 저수준 설정 변경으로 인한 메시 장애 리스크 존재해 신중한 구성이 필수다.
workloadSelector로 istioinaction 네임스페이스의 특정 워크로드에 적용 대상을 지정한다.
SIDECAR_INBOUND 리스너의 8080 포트 HTTP 필터 체인에서 envoy.filters.http.router 필터 앞에 tap 필터를 추가한다.
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 설정을 통해 외부 서버와 통합한다.
요청 경로에 속도 제한 걸기* CONFIGURING THE REQUEST PATH FOR RATE LIMITING
EnvoyFilter를 통해 /items 경로 요청의 x-loyalty 헤더를 포착하는 rate-limit action을 정의해, 특정 로열티 등급별로 속도 제한 서버에 전달할 descriptor 생성을 구성한다. VirtualService 라우트에 rate_limits 섹션 추가 없이, EnvoyFilter의 header_value_match 액션으로 헤더 기반 속성 추출 및 Redis 기반 글로벌 제한 정책을 적용한다.
Lua를 활용한 이스티오 데이터 플레인 확장 Lua 스크립트를 엔보이 필터 체인에 주입해 요청/응답 경로의 헤더 조작·바디 검사 등 커스텀 로직 구현이 가능하다. 예를 들어 A/B 테스트 그룹 분류를 위해 외부 서비스 호출 후 결과를 헤더에 추가하는 동적 라우팅을 구성할 수 있다. 단, 바디 전체 메모리 적재 등 무거운 연산은 성능 저하를 유발할 수 있으니 주의가 필요하다.
| 일전에 실 예로 내가 운영하던 서비스중 프론트/백 사이에서 데이터가 안들어온다는 개발자 확인 요청으로 Lua 활용해서 바디 전체를 로깅하는 루아 스크립트 작성했다가 뻗었던 기억이 있다.
#
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
다중 클러스터 서비스 메시는 장애 격리 및 고가용성을 통해 클러스터 장애 시 트래픽을 다른 클러스터로 자동 전환하며, 다중 클라우드 지원 및 지연 시간 최소화를 위해 다양한 환경에서 워크로드를 실행하고 지리적 근접성을 기반으로 라우팅할 수 있다.
다중 클러스터 서비스 메시는 클러스터 간 워크로드 디스커버리·연결성·공통 신뢰를 통해 트래픽 제어·보안·관측 기능을 유지하며, 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
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
# 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는 초당 설정 업데이트 수를 측정해 부하 정도를 파악한다. 이 메트릭들을 모니터링해 컨트롤 플레인 병목 현상을 식별하고, 디바운싱/스로틀링 설정을 조정해 성능을 최적화한다.
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
...
이제 테스트는 서비스를 반복적으로 만들어 부하를 생성하고, 프록시에 설정을 업데이트하는 데 걸리는 지연 시간과 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
이스티오는 기본적으로 모든 서비스 프록시에 메시 내 모든 워크로드 정보를 제공해, 불필요하게 설정 크기가 커지는 문제가 발생한다.
워크로드가 200개인 중간 클러스터만 돼도 엔보이 설정이 400MB로 늘어나며, 이로 인해 연산 성능, 네트워크 대역폭, 메모리가 더 많이 필요하다.
이 설정이 모든 사이드카 프록시에 저장되기 때문이다.
이스티오 Sidecar 리소스는 특정 워크로드의 트래픽 제어를 세밀하게 설정한다. workloadSelector로 대상 워크로드를 지정하고, egress 필드로 허용할 외부 서비스를 명시적으로 정의한다. outboundTrafficPolicy를 REGISTRY_ONLY로 설정하면 등록된 서비스만 접근 가능해 보안이 강화된다. 예시 YAML에서는 app: foo 워크로드가 bar.istioinaction과 istio-system 서비스만 접근하도록 제한한다. 이를 통해 불필요한 설정 전파를 막아 CPU/메모리 사용량과 네트워크 부하를 줄인다.
메시 범위 사이드카 설정으로 더 나은 기본값 정의하기 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를 설정해 불필요한 워크로드 이벤트를 필터링하면 컨트롤 플레인 부하를 크게 줄일 수 있다.
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_AFTER와 PILOT_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을 활용하면 연결 재분산을 유도해 오토스케일링 효율을 개선할 수 있다.
현재로서 오토스케일링을 구성하는 가장 좋은 방법은 점진적인 부하 증가에 맞추는 것이다.
며칠, 몇주, 심지어는 몇 달 단위에 걸쳐서 말이다.
이렇게 하면 성능을 지속적으로 모니터링하고 디폴리어먼트 스케일링 결정을 내려야 하는 인적 자원의 부담을 줄일 수 있다.
이스티오는 네트워크 통신 장애 발생 시 복원력 기능(타임아웃·재시도 등)을 통해 애플리케이션의 자동 대응을 지원한다. 데이터 플레인 동기화를 담당하는 istiod, 트래픽 허용을 위한 인그레스 게이트웨이, 트래픽 제어를 수행하는 서비스 프록시, 실제 요청을 처리하는 애플리케이션이 협력해 요청 흐름을 관리한다. 서비스 프록시의 비정상 동작 시 전체 시스템에 영향을 줄 수 있으므로 각 구성 요소의 정상 작동이 중요하다.
10.1 가장 흔한 실수: 잘못 설정한 데이터 플레인
이스티오는 VirtualService, DestinationRule 같은 CRD로 프록시 설정을 관리한다. 이 설정들은 엔보이 설정으로 변환되어 데이터 플레인에 적용된다. DestinationRule이 없으면 부분집합 정의가 없어 인그레스 게이트웨이에서 모든 요청이 실패한다.
데이터 플레인 문제 해결 시 컨트롤 플레인 동기화 상태를 먼저 확인해야 한다. 데이터 플레인 설정은 궁극적 일관성을 가지므로 환경 변화가 즉시 반영되지 않는다. 예를 들어 파드 장애 시 쿠버네티스가 비정상 엔드포인트를 감지하고 데이터 플레인에서 제거하는 데 지연이 발생할 수 있다. 컨트롤 플레인은 지속적으로 최신 설정을 데이터 플레인에 동기화하여 일관성을 복원한다. 이 과정에서 그림 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 라우트가 이를 처리한다.
이스티오는 VirtualService와 DestinationRule로 트래픽 라우팅 규칙을 정의하고, 이 설정들이 엔보이 프록시에 적용된다. 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 등)로 설정해 필요한 영역의 로그만 상세히 확인할 수 있다.
응답이 느린 업스트림의 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 실패 요청 비율 분석
클라이언트 측 성공률 70% (30% 실패) → 주요 원인은 504 Gateway Timeout
서버 측 성공률 100% → 실제 서버 문제는 없으나, Envoy 프록시가 응답 코드 0으로 처리
타임아웃 차이 → 클라이언트(istio-ingress)의 0.5초 타임아웃 설정이 서버(catalog) 응답보다 짧아 발생
트러블슈팅 포인트 : 워크로드(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 resolveedge cases where Kubernetes DNS resolution doesn’t suffice.
15001 : (엔보이 프로세스) 애플리케이션에서 나가는 트래픽은 Iptable 규칙에 의해 일단 이 포트로 리다이렉트되며, 이후 프록시가 트래픽을 서비스로 라우팅한다.
15006 : (엔보이 프로세스) 애플리케이션으로 들어오는 트래픽은 Iptable 규칙에 의해 일단 이 포트로 리다이렉트되며, 여기서 로컬 애플리케이션 라우팅된다.
에이전트 디버깅 및 내부 상태 조사에 유용한 포트 useful for debugging and introspecting the agent
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로 전체 메시의 동기화 상태를 한 번에 확인하거나, 특정 파드의 설정 차이를 비교해 문제 원인을 식별할 수 있다.
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 같은 도구가 엔드포인트를 활용하지만, 복잡한 문제 시 직접 접근해 세부 진단이 가능하다.
sleep 서비스는 레거시 워크로드로, 사이드카 프록시가 없어 ID가 부여되지 않는다. 이로 인해 webapp 프록시는 sleep 서비스의 요청 출처(네임스페이스 등)를 확인할 수 없다. 해결책은 sleep 서비스에 프록시를 주입해 ID와 상호 인증을 지원하는 것이며, 이 방식이 권장된다.
부득이한 경우에는 webapp에서 미인증 요청도 허용하는 인가 정책을 적용할 수 있지만, 이는 보안상 덜 안전하다.
외부 인가 서버 등과 연동된 CUSTOM 정책이 요청을 거부(Deny)하면, 즉시 요청이 거부
DENY 정책이 그다음 평가
CUSTOM 정책이 없거나 통과하면, DENY 정책이 요청과 일치하는지 확인
일치하는 DENY 정책이 있으면 요청이 거부
ALLOW 정책이 마지막으로 평가
DENY 정책도 없거나 통과하면, ALLOW 정책이 있는지 확인
ALLOW 정책이 하나라도 요청과 일치하면 요청이 허용
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)에 악용되는 것을 방지한다.
RequestAuthentication 리소스는 JWT 토큰을 검증하고, 유효한 토큰의 클레임을 추출해 필터 메타데이터에 저장한다. 이 필터 메타데이터는 인가 정책(AuthorizationPolicy)이 요청 허용/거부를 결정할 때 근거로 사용된다.
JWT가 있는 요청은 클레임이 메타데이터에 저장되지만, JWT가 없는 요청은 클레임 정보가 없다.
RequestAuthentication 리소스는 인가를 직접 강제하지 않으며, 인가를 위해서는 별도의 AuthorizationPolicy가 필요하다. 즉, 인증(토큰 검증 및 클레임 추출)과 인가(정책 적용)는 분리되어 있으며, 둘 다 설정해야 효과적으로 보안을 적용할 수 있다.
RequestAuthentication 리소스 만들기
다음 RequestAuthentication 리소스는 이스티오의 인그레스 게이트웨이에 적용된다. 이는 인그레스 게이트웨이가 auth@istioinaction.io 에서 발급한 토큰을 검증하도록 설정한다.
토큰이 없는 요청을 거부하려면, 실제로 다양한 무토큰 요청 상황이 존재하므로 추가적인 처리가 필요하다.
JWT가 없는 요청 거부하기
JWT가 없는 요청을 거부하려면 requestPrincipals가 없는 source를 명시적으로 거부하는 AuthorizationPolicy를 만들어야 하며, requestPrincipals는 JWT의 issuer와 subject 클레임을 ‘iss/sub’ 형태로 결합해 초기화된다. 이렇게 인증된 클레임은 RequestPrincipals 리소스를 통해 커넥션 메타데이터로 가공되어, AuthorizationPolicy 등에서 활용된다.
#일반 사용자가 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"] # 이 클레임을 포함한 요청만 허용.
이스티오가 새로운 외부 인가 서비스를 인식하도록 하려면, 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 이 CUSTOM 인 AuthorizationPolicy 를 만들고 정확히 어떤 외부 인가 서비스를 사용할지 지정해본다.
# 아래 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
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는 다양한 인증·인가 정책을 조합해 서비스 메시 내 트래픽의 신뢰성, 기밀성, 접근 제어를 유연하고 강력하게 구현할 수 있습니다.
# 패킷 모니터링 실행 해두기
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)를 발급·검증하는 표준화된 프레임워크다.
인증서에는 공개 키와 소유자 신원 정보가 포함되며, 서버는 이를 통해 자신의 정체를 증명한다.
#
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 프록시를 통해 자동으로 트레이스 메타데이터를 주입·전파해 코드 수정 없이 분산 트레이싱을 구현.
요청이 들어온다.
오! 트레이싱 헤더가 없는 것을 보니 새로운 요청이다.
요청이 서비스 사이를 오가는 과정을 추적할 수 있도록 트레이스 헤더를 생성해두자
트레이스 헤더가 요청 헤더에 추가됐다. x-request-id: c9421…
애플리케이션이 다른 서비스를 호출할 때 트레이스 헤더를 전파해야 한다.
트레이스 헤더를 전파한다. x-request-id: c9421…
이스티오 프록시는 기존 트레이스 헤더를 애플리케이션으로 전파한다.
만약 애플리케이션이 요청 헤더를 전파하지 않으면…
요청에 트레이스 헤더가 누락된다. 앱이 전파하지 않았기 때문이다.
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 서비스를 호출하고 요청 헤더를 표시하는 엔드포인트를 호출할 것이다.
#이렇게 라우팅하는 이스티오 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
...
키알리(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
###방법 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
이스티오 컨트롤 플레인과 워크로드를 긁어가도록 프로메테우스 오퍼레이터 설정하기
프로메테우스가 이스티오에서 메트릭을 수집하도록 설정하기 위해 프로메테우스 오퍼레이터의 커스텀 리소스 ServiceMonitor 와 PodMonitor 를 사용할 것이다.
##이스티오 컨트롤 플레인 구성 요소를 긁어오도록 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
...
#
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가 더 용이합니다.
이스티오 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 서비스를 도입할 것이다.
이는 엔드포인트 중 하나에 긴 가비지 컬렉션 이벤트 또는 기타 애플리케이션 지연 시간이 발생한 상황을 시뮬레이션한다.
우리는 로드 밸런싱 전략을 라운드 로빈, 랜덤, 최소 커넥션으로 바꿔가면서 차이점을 관찰할 것이다.
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
트래픽이 가용 영역을 넘어가는 것을 보기 위해 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 설정 부분 찾아 두자.
# 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) 장애를 명확히 구분할 수 있습니다.
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
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으로 연결되었어요.
...
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"
},
...
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 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 # 엔보이 통계(프로메테우스 레코드 형식)
특히 위와같은 접근으로 프로메테우스 형식의 메트릭을 접근할 수 있다.
이를 활용하여 이스티오 관련 모니터링 대시보드를 만들 수 있을것이다. 예를들면 다음과 같다.
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 계층에서 트래픽의 목적지와 라우팅 정책을 결정한다. 이 구조를 통해 이스티오는 외부 트래픽의 보안, 로드밸런싱, 가상 호스트 라우팅 등 다양한 네트워크 기능을 유연하게 제공한다.
두개의 버추어 서비스를 처리하는것을 확인 할 수 있다. 이를 처리하는 플로우는 클라이언트가 게이트웨이(엔보이)로 HTTPS 연결을 시작하면, TLS 핸드셰이크의 ClientHello 단계에서 SNI(서버 이름 표시) 확장에 접근하려는 서비스의 도메인을 명시해 전달하고, 엔보이는 이 SNI 정보를 기반으로 올바른 인증서를 선택해 제시하고 해당 서비스로 트래픽을 라우팅한다.
TCP 트래픽에 대한 실습이다.
이스티오 게이트웨이는 TCP 기반 서비스도 외부에 노출할 수 있지만, HTTP처럼 세밀한 트래픽 제어나 고급 기능은 사용할 수 없다. 이는 엔보이가 프로토콜을 이해하지 못해 단순 TCP 프록시만 가능하기 때문이다. 클러스터 외부에서 내부 TCP 서비스를 이용하려면 게이트웨이에서 해당 포트를 노출하면 된다.
# 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 실습
여러 인그레스 게이트웨이를 배포하면 서비스별 요구사항이나 팀별로 트래픽 경로와 설정을 독립적으로 관리·격리할 수 있다.
#
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 실습
이스티오 데모 프로필에서는 엔보이 프록시의 액세스 로그가 표준 출력으로 기록되어 컨테이너 로그를 통해 쉽게 확인할 수 있다.
운영 환경에서는 트래픽과 로그량이 많기 때문에, 텔레메트리 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 설정 잘못해서 서비스 연결이 제대로 안됐었는데, 디버깅 하기가 너무 힘들었다. 해당 설정때문에 앞으로 좀 더 수월해질듯 하다.
사내에서 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-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이 적용 돼 있다.
해당 설정에 의해 서비스타입이 로드밸런서인경우 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 서비스 매쉬 내 프록시의 상태를 확인(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
이제 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 으로 설정해줘야 한다.
그리고 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에서의 설정은 개발자가 해야 할 일이다.
5. 패키지 설치 후 빌드하는 과정에서 디펜던시 패키지가 또다시 패키지를 설치하는경우가 있음. 따라서 패키지 설치후 디펜던시 패키지내에
추가적으로기존 디폴트 퍼블릭 저장소를 넥서스 그룹 저장소로 변경해야 할 수 있음
앞서 깃헙과 함께 가장 골치 아픈 상황이였다. 특히 프론트개발환경에 React Native에서 많았다. 이건 뭐.. 어쩔 수 없다. 이것도 찾아서 다 바꿔야 한다. 빌드 도구별로 패치 기능이 또 있어서 잘 활용하면 된다.
패키지 설치 -> 패치 -> 빌드 가 가능한 환경이면 devops가 replace(기존 저장소를 내부 넥서스로)를 할 수 있다. 근데 애매한게... 빌드 하는 과정에서 패치가 필요한경우가 있다. 이때는 개발자가 소스코드에서 직접 수정해줬다. 예를들어 Gradle 같은 경우는 init.gradle에서 설정이 가능하다.
집합 자료형은 중복되지 않는 고유한 요소들의 모음을 저장하는 자료형으로, 여러 가지 실무 상황에서 유용하게 사용할 수 있다. 집합은 중괄호 {}를 사용하여 생성하며, 다양한 집합 연산을 지원한다. 아래에서는 집합 자료형을 실무에서 사용할 수 있는 몇 가지 상황을 설명해보겠다.
1. 중복 제거
가장 일반적인 집합의 사용 사례는 중복된 데이터를 제거하는 것이다. 예를 들어, 고객 이메일 목록에서 중복된 이메일 주소를 제거할 때 유용하다.
집합은 중복을 허용하지 않기 때문에, 데이터 무결성을 유지하는 데 유용하다. 예를 들어, 사용자 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바이트를 사용한다.
메모리 사용량 차이의 이유
가변성:
리스트는 가변적이어서 요소를 추가하거나 삭제할 수 있다. 이를 위해 리스트는 추가적인 메모리를 할당하여 데이터를 저장하고 관리해야 한다. 반면, 튜플은 불변적이어서 한 번 생성되면 변경할 수 없다. 따라서 튜플은 고정된 메모리만 할당하면 된다.
오버 할당:
리스트는 요소를 추가할 때마다 메모리를 재할당하는 비용을 줄이기 위해 오버 할당(over-allocation) 기법을 사용한다. 이는 리스트가 더 많은 메모리를 사용할 수 있게 한다. 반면, 튜플은 이러한 오버 할당이 필요 없으므로 더 적은 메모리를 사용한다[1][2].
구조적 차이:
리스트는 각 요소에 대한 포인터를 저장하는 데 추가 메모리를 사용한다. 반면, 튜플은 이러한 포인터를 저장하지 않아 더 적은 메모리를 사용한다[1].
리스트는 여러 데이터를 순서대로 저장할 수 있는 자료형이다. 리스트는 대괄호 []로 감싸서 만들고, 각 요소는 쉼표 ,로 구분한다. 리스트의 가장 큰 특징은 변경 가능하다는 점이다. 즉, 리스트에 있는 데이터를 수정, 추가, 삭제할 수 있다.
예시
# 리스트 생성
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차원 배열의 예시
예를 들어, 다음과 같은 표가 있다고 하자:
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은 간격을 의미한다.
인덱싱과 슬라이싱을 동시에 활용한 실무 예제 - 로그 파일에서 날짜와 오류 메시지 추출하기
문자열 인덱싱과 슬라이싱을 동시에 활용하면 문자열의 특정 부분을 효율적으로 추출하고 조작할 수 있다. 예를 들어, 로그 파일에서 특정 정보를 추출하거나, 텍스트 데이터에서 특정 패턴을 찾는 작업에 유용하다.
로그 파일의 각 줄에서 날짜와 오류 메시지를 추출하는 예제를 살펴보자. 로그 파일의 형식은 다음과 같다:
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
설명
날짜 추출: line[:10]을 사용하여 문자열의 처음 10글자를 추출한다. 이는 날짜를 의미한다.
오류 메시지 추출: line.index("ERROR:") + 7을 사용하여 "ERROR:" 문자열의 위치를 찾고, 그 이후의 문자열을 추출한다.
이 예시는 인덱싱과 슬라이싱을 동시에 활용하여 문자열에서 필요한 정보를 효율적으로 추출하는 방법을 보여준다. 이러한 기법은 로그 파일 분석, 데이터 전처리 등 다양한 실무 상황에서 유용하게 사용될 수 있다.
자료형이란 프로그래밍을 할 때 쓰이는 숫자, 문자열 등과 같이 자료 형태로 사용하는 모든 것을 뜻한다.
예시: 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 가이드라인을 따르며, "공개 리포지토리의 기본 브랜치에 필요한 파일을 참조하세요." 를 적는다.
로그 데이터는 라벨로 분류됩니다. 라벨은 로그 데이터를 구분하고 쿼리하기 위한 메타데이터로, 키/값의 쌍으로 이루어져 있습니다. 예를 들어, {component="printer", location="f2c16", level="error"}와 같은 라벨 세트가 로그 메시지에 할당됩니다.
라벨 세트는 해시되어 고유한 '스트림 ID'를 생성합니다. 이 ID는 특정 로그 스트림을 식별하는 데 사용됩니다. 이미지에는 해시된 결과의 예로 3b2cea09797978fc가 있습니다.
청크의 생성과 저장:
동일한 라벨 세트를 가진 추가적인 로그 메시지들은 같은 '청크'에 추가됩니다. 예를 들어, "Printing is not supported by this printer", "Out of paper", "Too much paper"와 같은 다양한 로그 메시지가 모두 같은 라벨을 공유하므로 같은 청크에 저장됩니다.
이러한 청크는 채워진 후에 압축되고 저장됩니다.
청크 조회를 위한 인덱스:
청크를 빠르게 찾기 위해, 별도의 작고 분리된 인덱스가 유지됩니다. 이 인덱스를 통해 청크를 빠르게 조회할 수 있습니다.
라벨 값의 변화와 새로운 청크 생성:
만약 라벨의 키 또는 값이 달라지면, 다른 해시 값을 가지는 새로운 스트림과 새로운 청크가 생성됩니다. 예를 들어, {component="printer", location="f2c16", level="info"} 라벨 세트는 "Consider the environment before printing this log message"라는 로그 메시지와 함께 새로운 청크를 형성합니다.
결과값 설명: 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분동안의 증가율만 결과값으로 반환하지만 이를 그래프로 표현했을때는 수집 시점부터의 수집값을 가지고 그래프를 그린다는것