5장 Traffic control: Fine-grained traffic routing
이번 장의 목표
- Traffic routing basics
- Shifting traffic during a new release
- Mirroring traffic to reduce the risk of a new release
- Controlling traffic as it leaves a cluster
들어가며 : 트래픽 세부 라우팅 필요 설명
이전 장에서 우리는 트래픽을 클러스터로 가져오는 방법과 이를 위해 고려해야 할 사항들을 살펴보았습니다.
요청이 클러스터에 들어오면, 요청을 처리하기 위해 적절한 서비스로 어떻게 라우팅되나요?
클러스터 내에 있는 서비스는 같은 클러스터 내 또는 클러스터 외부에 있는 다른 서비스와 어떻게 통신하나요?
마지막으로, 그리고 가장 중요한 것은 서비스를 변경하고 새로운 버전을 도입할 때, 고객과 고객이 이러한 변화에 안전하게 노출될 수 있도록 방해와 영향을 최소화하는 방법은 무엇일까요?
앞서 살펴본 바와 같이, Istio 서비스 프록시는 서비스 메시 클러스터 내의 서비스 간 통신을 차단하고 트래픽을 제어할 수 있는 지점을 제공합니다.
Istio를 사용하면 애플리케이션 간의 트래픽을 개별 요청까지 세밀하게 제어할 수 있습니다.
이 장에서는 왜 그렇게 하고 싶은지, 어떻게 해야 하는지, 그리고 이러한 기능을 활용할 때 어떤 이점을 얻어야 하는지 살펴봅니다.
[실습 환경 구성] k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)
#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
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'
# (옵션) 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
# (옵션) 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"
[실습 환경 구성] istio 1.17.8 설치 (addon 필수) - Docs , Install , profile
# 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
5.1 Reducing the risk of deploying new code
들어가며
1장에서는 ACME 회사의 시나리오를 소개했습니다.
ACME 회사의 시나리오는 클라우드 플랫폼으로 전환하여 회사의 코드 배포 위험을 줄이는 데 도움이 되는 관행을 채택했습니다.
ACME가 시도한 패턴 중 하나는 애플리케이션에 변경 사항을 도입하기 위한 blue / green deployments였습니다.
파란색/녹색 배포를 통해 ACME는 변경하고자 하는 서비스의 v2(녹색)를 가져와 v1(파란색) 옆에 프로덕션에 배포했습니다(그림 5.1 참조).
ACME는 고객에게 출시하고자 할 때 트래픽을 v2(녹색)로 줄였습니다.
이 접근 방식은 문제가 발생하면 ACME가 서비스의 v1(파란색)으로 축소될 수 있기 때문에 배포 중 중단을 줄이는 데 도움이 되었습니다.
파란색/녹색 배포가 도움이 되지만, v1에서 v2로 전환할 때는 여전히 모든 코드 변경 사항을 한 번에 해제하는 "빅뱅"을 경험합니다.
배포 위험을 더욱 줄일 수 있는 방법을 살펴보겠습니다.
ACME 회사의 사례는 배포를 진행하는 과정에서 코드 배포 위험을 줄이기 위해 blue/green 배포 방식을 도입하고, 실제로 사용하는 과정에서 모든 코드 변경 사항을 한 번에 해제하는 "빅뱅"을 경험함으로 더더욱 위험을 줄일수 있는 요구 사항을 찾기 시작함.
먼저, 배포와 릴리스가 무엇을 의미하는지 명확히 해야 합니다.
5.1.1 배포 Deployment vs. 릴리스 release → 발음
가상의 카탈로그 서비스를 사용하여 배포와 릴리스의 차이점을 설명해 보겠습니다.
catalog 서비스의 v1이 현재 운영 중이라고 가정해 보겠습니다.
catalog 서비스에 코드 변경 사항을 도입하려면, CI시스템으로 빌드하고, 새 버전(v1.1이라고 하자)으로 태그하고, 이를 스테이징 환경에 배포하고 테스트할 것이다.
이 변화를 스테이징에서 검증하고 필요한 승인을 받으면 새 버전 v1.1을 운영 환경으로 가져올 수 있다.
운영 환경에 배포할 때는 새 코드를 운영 환경 리소스(서버, 컨테이너 등)에 설치하지만, 트래픽은 보내지는 않는다.
운영 환경에 배포하는 행위는 운영 환경에서 실행 중인 사용자에게 영향을 미치면 안 된다. (아직 어떤 사용자 요청도 받지 않기 때문이다.)
이 시점에서는 운영 환경에서 실행 중인 새 배포에 테스트를 실행해 기대대로 작동하는지 검증할 수 있다. (그림 5.2 참조).
메트릭과 로그 수집을 활성화했을 것이므로, 이 신호들을 보고 새 배포가 기대한대로 행동하다고 확신할 수 있다.
일단 운영 환경에 코드를 배포하면, 사용자들에게 어떻게 릴리즈할지와 관련해 비즈니스 의사결정을 내릴 수 있다.
결론적으로 배포 Deployment는 새 코드를 운영 환경에 적용하기 위해 새로운 서버를 생성하는 것
코드를 릴리즈한다는 말은 실제 트래픽을 새 배포로 가져오는 것을 의미한다. 단, 릴리스가 꼭 한 번에 전부 가야 하는 것은 아니다.
새로운 코드를 운영 환경에 도입하는 위험성을 줄이려면 배포와 릴리스를 분리하는 것이 중요하다.
릴리스 Release 한다는 말은 신규 배포에 실제 트래픽을 전달 또는 이관하는 것
새로운 소프트웨어는 내부 직원에게만 먼저 공개하도록 결정할 수도 있다. (그림 5.3 참조).
내부 직원들은 트래픽을 제어해 신 버전을 사용할 수 있다. 내부 직원은 소프트웨어의 운영자로서 로그와 메트릭 수집을 활용해 코드 변경 효과가 의도대로인지 관찰하고 검증할 수 있다. 이제 실 트래픽의 대부분은 소프트웨어의 구 버전이 받고, 신 버전은 일부만을 받고 있다.
이 방식을 ‘카나리한다 canarying’ 고 말하거나 ‘카나리 릴리스 canary release’라고 부른다.
기본적으로는 신 버전을 노출할 소수의 사용자 그룹을 선택하고 신 버전이 어떻게 작동하는지 지켜본다. 신 버전이 의도치 않게 동작하면 릴리스를 철회하고 트래픽을 이전 버전으로 되돌릴 수 있다.
카나리한다 또는 카나리 릴리스라는 말은 새로 적용할 코드 배포를 트래픽 제어를 통해 소수의 사용자 그룹을 이용해 신 버전이 어떻게 작동하는 지 지켜보고, 해당 버전이 의도한 것과 다르게 동작하면 릴리스를 철회하고 이전 버전으로 트래픽을 다시 전달하도록 하는 것
코드 변경의 동작과 성능이 괜찮으면 릴리스의 범위를 더 넓힐 수 있다. (그림 5.4 참조). 이제 무과금 고객이나 실버 등급 고객(즉, 골드나 플래티넘 대비 우선순위가 낮은 고객)에게 릴리스를 할 수 있다.
새 코드를 모든 고객에게 노출할 때까지는 이렇게 릴리스하고 관찰하는 접근법을 계속 반복한다. (그림 5.5 참조). 이 과정 중 어느 시점에서라도 실제 사용자 상호작용 중에 새 코드가 예상하고 검증했던 기능이나 동작, 성능을 전달하지 않고 있다는 것을 발견할 수 있다.
이 경우 트래픽을 이전 버전으로 돌려 릴리스를 롤백할 수 있다.
과거에 ACME는 배포와 릴리스라는 두 가지 개념을 결합했다.
- 코드 변경을 운영 환경으로 가져오고자, 이 회사는 서비스 구 버전을 신 버전으로 효과적으로 교체하는 롤링 업그레이드를 시작했다.
신 버전은 클러스터에 도입되자마자 운영 환경 트래픽을 받았다.
- 이 방식은 코드 신 버전뿐 아니라 신 버전에 수반되는 버그나 이슈에도 사용자들을 노출했다.
- 배포와 릴리스를 분리하면 새 변화를 어떤 사용자들에게, 어떻게 내보일지 정밀하게 제어할 수 있다.
- 이를 통해 새 코드를 운영 환경에 가져오는 위험성을 줄일 수 있다.
이스티오가 시스템에 들어오는 요청을 기반으로 트래픽을 제어함으로써 릴리스 위험성을 낮추는 데 어떻게 도움이 되는지 살펴보자.
5.2 Routing requests with Istio (실습)
들어가며 : VirtualService
2장에서는 Istio를 사용하여 카탈로그 서비스로의 트래픽을 제어했습니다.Istio VirtualService 리소스를 사용하여 트래픽을 라우팅하는 방법을 지정했습니다.
어떻게 작동하는지 자세히 살펴보겠습니다. 요청의 내용에 따라 헤더를 평가하여 요청의 경로를 제어합니다.이러한 방식으로 다크 런치라는 기술을 사용하여 특정 사용자에게 배포를 제공할 수 있습니다.
어두운 실행(다크 런치)에서는 많은 사용자가 알려진 작동 중인 서비스 버전으로 보내지며, 특정 클래스의 사용자는 최신 버전으로 보내집니다. 따라서 우리는 다른 모든 사람에게 영향을 미치지 않고 특정 그룹에 통제된 방식으로 새로운 기능을 노출할 수 있습니다.
Notes:
다크 런치는 사용자에게는 보이지 않지만 백엔드에는 실제로 배포된 기능 또는 버전을 특정 사용자만 사용해보도록 제한적으로 노출하는 기법
4장 리소스 모두 삭제
# 4장 실습 리소스들 삭제
kubectl delete deployment,svc,gateway,virtualservice,destinationrule --all -n istioinaction
5.2.2 Deploying v1 of the catalog service
# Let’s deploy v1 of our catalog service. From the root of the book’s source code, run the following command
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
# 확인
kubectl get pod -n istioinaction -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
catalog-6cf4b97d-ftl77 2/2 Running 0 42s 10.10.0.14 myk8s-control-plane <none> <none>
# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1 catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3
# netshoot로 내부에서 catalog 접속 확인
kubectl exec -it netshoot -- curl -s http://catalog.istioinaction/items | jq
# 외부 노출을 위해 Gateway 설정
cat ch5/catalog-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: catalog-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "catalog.istioinaction.io"
kubectl apply -f ch5/catalog-gateway.yaml -n istioinaction
# 트래픽을 catalog 서비스로 라우팅하는 VirtualService 리소스 설정
cat ch5/catalog-vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: catalog-vs-from-gw
spec:
hosts:
- "catalog.istioinaction.io"
gateways:
- catalog-gateway
http:
- route:
- destination:
host: catalog
kubectl apply -f ch5/catalog-vs.yaml -n istioinaction
# 확인
kubectl get gw,vs -n istioinaction
NAME AGE
gateway.networking.istio.io/catalog-gateway 95s
NAME GATEWAYS HOSTS AGE
virtualservice.networking.istio.io/catalog-vs-from-gw ["catalog-gateway"] ["catalog.istioinaction.io"] 3s
# 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
echo "127.0.0.1 catalog.istioinaction.io" | sudo tee -a /etc/hosts
cat /etc/hosts | tail -n 3
# istio-ingressgateway Service(NodePort)에 포트 정보 확인
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
[
{
"name": "status-port",
"nodePort": 31674,
"port": 15021,
"protocol": "TCP",
"targetPort": 15021
},
{
"name": "http2",
"nodePort": 30000, # 순서1
"port": 80,
"protocol": "TCP",
"targetPort": 8080 # 순서2
},
{
"name": "https",
"nodePort": 30005,
"port": 443,
"protocol": "TCP",
"targetPort": 8443
}
]
# 호스트에서 NodePort(Service)로 접속 확인
curl -v -H "Host: catalog.istioinaction.io" http://localhost:30000
kubectl stern -l app=catalog -n istioinaction
open http://localhost:30000
open http://catalog.istioinaction.io:30000
open http://catalog.istioinaction.io:30000/items
# 신규 터미널 : 반복 접속 실행 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items/ ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 0.5; echo; done
호스트 외부에서 호출 경로 : 아래 그림 맨 왼쪽(curl client)는 외부라고 생각하면 됨.
#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
catalog-6cf4b97d-ftl77.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-fl492 1.17.8
istio-ingressgateway-996bc6bb6-zvtdc.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-fl492 1.17.8
# istio-ingressgateway
## LDS - Listener Discovery Service
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
## RDS - Route Discovery Service
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
## CDS - Cluseter Discovery Service
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
## EDS - Endpoint Discovery Service
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
# 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
특정 파드의 istio-proxy 에 Envoy 에 Admin 웹 접속
# 신규 터미널 : istio-ingressgateway 파드
kubectl port-forward deploy/istio-ingressgateway -n istio-system 15000:15000
#
open http://127.0.0.1:15000
5.2.3 Deploying v2 of the catalog service
istio 트래픽 제어 기능 동작을 알아보기 위해, catalog 서비스 v2 를 배포해보자. ⇒ 버전1,2 호출 된다. v2 호출되지 않게 할 수 없을까?
# catalog 서비스 v2 를 배포 : v2에서는 imageUrl 필드가 추가
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
#
kubectl get deploy -n istioinaction --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
catalog 1/1 1 1 30m app=catalog,version=v1
catalog-v2 1/1 1 1 34s app=catalog,version=v2
kubectl get pod -n istioinaction -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
catalog-6cf4b97d-ftl77 2/2 Running 0 43m 10.10.0.14 myk8s-control-plane <none> <none>
catalog-v2-6df885b555-6hmcl 2/2 Running 0 13m 10.10.0.15 myk8s-control-plane <none> <none>
docker exec -it myk8s-control-plane istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
catalog-6cf4b97d-ftl77.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-fl492 1.17.8
catalog-v2-6df885b555-6hmcl.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-fl492 1.17.8
istio-ingressgateway-996bc6bb6-zvtdc.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-fl492 1.17.8
# 호출 테스트 : v1 , v2 호출 확인
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
# istio-ingressgateway proxy-config 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
"name": "outbound|80||catalog.istioinaction.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
},
"connectTimeout": "10s",
"lbPolicy": "LEAST_REQUEST",
"circuitBreakers": {
"thresholds": [
{
"maxConnections": 4294967295,
"maxPendingRequests": 4294967295,
"maxRequests": 4294967295,
"maxRetries": 4294967295,
"trackRemaining": true
}
]
},
"commonLbConfig": {
"localityWeightedLbConfig": {}
},
...
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
...
{
"name": "outbound|80||catalog.istioinaction.svc.cluster.local",
"addedViaApi": true,
"hostStatuses": [
{
"address": {
"socketAddress": {
"address": "10.10.0.14",
"portValue": 3000
}
},
"stats": [
{
"name": "cx_connect_fail"
},
{
"value": "8",
"name": "cx_total"
},
{
"name": "rq_error"
},
{
"value": "315",
"name": "rq_success"
},
{
"name": "rq_timeout"
},
{
"value": "315",
"name": "rq_total"
},
{
"type": "GAUGE",
"value": "8",
"name": "cx_active"
},
{
"type": "GAUGE",
"name": "rq_active"
}
],
"healthStatus": {
"edsHealthStatus": "HEALTHY"
},
"weight": 1,
"locality": {}
},
{
"address": {
"socketAddress": {
"address": "10.10.0.15",
"portValue": 3000
}
},
"stats": [
{
"name": "cx_connect_fail"
},
{
"value": "8",
"name": "cx_total"
},
{
"name": "rq_error"
},
{
"value": "308",
"name": "rq_success"
},
{
"name": "rq_timeout"
},
{
"value": "308",
"name": "rq_total"
},
{
"type": "GAUGE",
"value": "8",
"name": "cx_active"
},
{
"type": "GAUGE",
"name": "rq_active"
}
],
"healthStatus": {
"edsHealthStatus": "HEALTHY"
},
"weight": 1,
"locality": {}
}
],
"circuitBreakers": {
"thresholds": [
{
"maxConnections": 4294967295,
"maxPendingRequests": 4294967295,
"maxRequests": 4294967295,
"maxRetries": 4294967295
},
{
"priority": "HIGH",
"maxConnections": 1024,
"maxPendingRequests": 1024,
"maxRequests": 1024,
"maxRetries": 3
}
]
},
"observabilityName": "outbound|80||catalog.istioinaction.svc.cluster.local",
"edsServiceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
},
...
# catalog proxy-config 도 직접 확인해보자
5.2.4 Routing all traffic to v1 of the catalog service
모든 트래픽을 catalog v1 으로 라우팅. 이것이 다크런치를 시작하기 전의 일반적인 트래픽 패턴이다.
어느 워크로드가 v1, v2 인지 이스티오에게 힌트를 줘야한다.
catalog v1은 deployment 리소스에서 레이블 app:catalog, version:v1 을 사용한다.
catalog v2은 deployment 리소스에서 레이블 app:catalog, version:v2 을 사용한다.
이스티오에게는 이렇게 다른 버전들의 부분집합 subset 으로 지정하는 DestinationRule 을 만들어준다.
#
kubectl get pod -l app=catalog -n istioinaction --show-labels
NAME READY STATUS RESTARTS AGE LABELS
catalog-6cf4b97d-ftl77 2/2 Running 0 56m app=catalog,...,version=v1
catalog-v2-6df885b555-6hmcl 2/2 Running 0 26m app=catalog,...,version=v2
#
cat ch5/catalog-dest-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: catalog
spec:
host: catalog.istioinaction.svc.cluster.local
subsets:
- name: version-v1
labels:
version: v1
- name: version-v2
labels:
version: v2
kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
# 확인
kubectl get destinationrule -n istioinaction
NAME HOST AGE
catalog catalog.istioinaction.svc.cluster.local 8s
# catalog proxy-config 확인 : SUBSET(v1, v2, -) 확인
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
catalog.istioinaction.svc.cluster.local 80 - outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v1 outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v2 outbound EDS catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
"name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
},
"connectTimeout": "10s",
"lbPolicy": "LEAST_REQUEST",
...
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v2 -o json
...
"name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"serviceName": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local"
},
"connectTimeout": "10s",
"lbPolicy": "LEAST_REQUEST",
...
#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
...
"name": "outbound|80||catalog.istioinaction.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
},
...
#
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
10.10.0.16:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local' -o json
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
# VirtualService 수정 (subset 추가)
cat ch5/catalog-vs-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: catalog-vs-from-gw
spec:
hosts:
- "catalog.istioinaction.io"
gateways:
- catalog-gateway
http:
- route:
- destination:
host: catalog
subset: version-v1
kubectl apply -f ch5/catalog-vs-v1.yaml -n istioinaction
# 호출 테스트 : v1
for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
# 세부 정보 확인
# routes 에 virtualHosts 항목에 routes.route 에 cluster 부분이 ...version-v1... 설정 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
...
"virtualHosts": [
{
"name": "catalog.istioinaction.io:80",
"domains": [
"catalog.istioinaction.io"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
"timeout": "0s",
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
...
# cluster
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
catalog.istioinaction.svc.cluster.local 80 version-v1 outbound EDS catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
...
"name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"initialFetchTimeout": "0s",
"resourceApiVersion": "V3"
},
"serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
},
...
"metadata": {
"filterMetadata": {
"istio": {
"config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
"default_original_port": 80,
"services": [
{
"host": "catalog.istioinaction.svc.cluster.local",
"name": "catalog",
"namespace": "istioinaction"
}
],
"subset": "version-v1"
...
# endpoint
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
...
# istio-proxy (catalog)
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --subset version-v1 -o json
...
"metadata": {
"filterMetadata": {
"istio": {
"config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
"default_original_port": 80,
"services": [
{
"host": "catalog.istioinaction.svc.cluster.local",
"name": "catalog",
"namespace": "istioinaction"
}
],
"subset": "version-v1"
}
}
},
...
- 이 시점에서 모든 트래픽이 v1 라우팅 된다.
- 이제 특정 요청들을 통제된 방식으로 v2 라우팅하고 싶다. 다음 절에서 알아보자.
5.2.5 Routing specific requests to v2
HTTP 요청 헤더 x-istio-cohort: internal 을 포함한 트래픽(통제된 방식)은 catalog 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... /*
5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)
- 지금까지 이스티오를 사용해 요청을 라우팅하는 방법을 살펴봤지만, 라우팅 수행 위치가 에지/게이트웨이뿐이었다.
- 이런 트래픽 규칙은 호출 그래프 내 깊은 곳에서도 적용할 수 있습니다. (그림 5.9 참조).
- 2장에서 이 작업을 수행했으므로, 프로세스를 다시 만들고 기대대로 동작하는지 확인해보자.
# 초기화
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
GW, VS 설정 후 호출 테스트 : webapp → catalog 는 k8s service(clusterIP) 라우팅 사용
# 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으로 연결되었어요.
...
Let’s create VirtualService and DestinationRule resources that route all traffic to v1 of the catalog service
모든 트래픽을 카탈로그 서비스의 v1로 라우팅하는 VirtualService 및 DestinationRule 리소스를 만들어 보겠습니다.
#
ch5/catalog-dest-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: catalog
spec:
host: catalog.istioinaction.svc.cluster.local
subsets:
- name: version-v1
labels:
version: v1
- name: version-v2
labels:
version: v2
kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
# istio-proxy
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
catalog.istioinaction.svc.cluster.local 80 - outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v1 outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v2 outbound EDS catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
10.10.0.16:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.18:8080 HEALTHY OK outbound|80||webapp.istioinaction.svc.cluster.local
#
cat ch5/catalog-vs-v1-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: catalog
spec:
hosts:
- catalog
gateways: # 만약, gateways 부분을 제외하고 배포하면 암묵적으로 mesh gateways가 적용됨.
- mesh # VirtualService는 메시 내의 모든 사이드카(현재 webapp, catalog)에 적용된다. edge는 제외.
http:
- route:
- destination:
host: catalog
subset: version-v1
kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
# VirtualService 확인 : GATEWAYS 에 mesh 확인
kubectl get vs -n istioinaction
NAME GATEWAYS HOSTS AGE
catalog ["mesh"] ["catalog"] 12s
webapp-virtualservice ["coolstore-gateway"] ["webapp.istioinaction.io"] 28s
# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 해두기 >> 현재는 v1만 라우팅 처리
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
# webapp → catalog 호출도 istio 의 DestinationRule 라우팅 전달 처리! : 신규터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-18T13:52:54.772Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "172.18.0.1" "beegoServer" "2035962f-144f-4d07-9102-4e3ab7ea3484" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80|version-v1|catalog.istioinaction.svc.cluster.local 10.10.0.18:52458 10.200.1.254:80 172.18.0.1:0 - -
=> 이 로그는 webapp이 내부적으로 catalog 서비스를 호출하는 로그이고, version-v1이라는 **서브셋(subset)**으로 요청이 라우팅되었어요. 이는 DestinationRule에서 subset: version-v1으로 정의된 엔드포인트로 라우팅이 잘 되었다는 뜻이에요.
# proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 정보는 없었는데, 추가됨을 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
NAME DOMAINS MATCH VIRTUAL SERVICE
80 catalog, catalog.istioinaction + 1 more... /* catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json | jq
...
"virtualHosts": [
{
"name": "catalog.istioinaction.svc.cluster.local:80",
"domains": [
"catalog.istioinaction.svc.cluster.local",
"catalog",
"catalog.istioinaction.svc",
"catalog.istioinaction",
"10.200.1.254" # 해당 IP는 catalog service(clusterIP)
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
"timeout": "0s",
...
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
catalog.istioinaction.svc.cluster.local 80 - outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v1 outbound EDS catalog.istioinaction
catalog.istioinaction.svc.cluster.local 80 version-v2 outbound EDS catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --subset version-v1 -o json
...
"name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
"type": "EDS",
...
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.10.0.16:3000 HEALTHY OK outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
10.10.0.16:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
10.10.0.17:3000 HEALTHY OK outbound|80||catalog.istioinaction.svc.cluster.local
# proxy-config (catalog) : gateway.mesh 이므로, 메시 내에 모든 사이드카에 VirtualService 적용됨을 확인. 아래 routes 부분
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 | egrep 'NAME|catalog'
NAME DOMAINS MATCH VIRTUAL SERVICE
80 catalog, catalog.istioinaction + 1 more... /* 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
#
cat ch5/catalog-vs-v2-request-mesh.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: catalog
spec:
hosts:
- catalog
gateways:
- mesh
http:
- match:
- headers:
x-istio-cohort:
exact: "internal"
route:
- destination:
host: catalog
subset: version-v2
- route:
- destination:
host: catalog
subset: version-v1
kubectl apply -f ch5/catalog-vs-v2-request-mesh.yaml -n istioinaction
# 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 >> v1, v2 각기 라우팅 확인
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
# proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 추가된 2개 항목 확인
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
NAME DOMAINS MATCH VIRTUAL SERVICE
80 catalog, catalog.istioinaction + 1 more... /* catalog.istioinaction
80 catalog, catalog.istioinaction + 1 more... /* catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
cat webapp-routes.json | jq
...
"virtualHosts": [
{
"name": "catalog.istioinaction.svc.cluster.local:80",
"domains": [
"catalog.istioinaction.svc.cluster.local",
"catalog",
"catalog.istioinaction.svc",
"catalog.istioinaction",
"10.200.1.254"
],
"routes": [
{
"match": {
"prefix": "/",
"caseSensitive": true,
"headers": [
{
"name": "x-istio-cohort",
"stringMatch": {
"exact": "internal"
}
}
]
},
"route": {
"cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
....
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
...
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'
# proxy-config (catalog) : mesh 이므로 VS가 아래 routes(catalog)에도 적용됨
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
5.3 Traffic shifting (실습)
Manual Canary release
이 절에서는 배포를 ‘카나리 canary’하거나 점진적으로 릴리스 incrementally release 하는 또 다른 방법을 살펴본다.
- 이전 절에서는 헤더 비교를 기반해 특정 사용자 그룹에 다크 런치 dark launch 를 수행하는 라우팅을 살펴봤다.
- 이 절에서는 가중치를 기반으로 특정 서비스의 여러 버전에 라이브 트래픽을 분배해본다.
- 예를 들어 catalog 서비스 v2를 내부 직원에게 다크 런치했고 이 버전을 모두에게 천천히 릴리스하고 싶다면, v2의 라우팅 가중치를 10%로 지정할 수 있다.
- catalog로 향하는 전체 트래픽의 10%는 v2로 가고 90%는 여전히 v1으로 갈 것이다.
- 이렇게 하면, 전체 트래픽 중 얼마만큼이 v2 코드에 부정적 영향을 받을지 제어함으로써 릴리스의 위험성을 더욱 줄일 수 있다.
- 다크 런치와 마찬가지로 서비스에 오류가 있는지 모니터링하고 관찰하려고 하며, 문제가 있으면 릴리스를 롤백하려고 한다.
- 이 경우, 롤백은 catalog v2 서비스로 가는 트래픽 가중치를 줄이는 것만큼(필요한 경우 다시 0%까지) 간단하다.
- 이스티오로 가중치 기반의 트래픽 전환을 수행하는 방법을 살펴보자.
# 이전 절부터 다음 서비스가 실행 중이다 : 파드에 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
트래픽 중 10%를 catalog v2 로 라우팅해보자
#
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
...
트래픽을 50:50 반반으로 라우팅 해보자
#
cat ch5/catalog-vs-v2-50-50-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: 50
- destination:
host: catalog
subset: version-v2
weight: 50
kubectl apply -f ch5/catalog-vs-v2-50-50-mesh.yaml -n istioinaction
# 호출 테스트 : 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)
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": 50
},
{
"name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
"weight": 50
}
],
"totalWeight": 100
...
- 각 서비스 버전의 트래픽 가중치를 1에서 100 사이로 바꿀 수 있지만, 가중치 총합은 반드시 100이어야 한다.
- 그렇지 않으면 예기치 못한 트래픽 라우팅이 발생할 수 있다.
- v1, v2 외 다른 버전이 있을 경우, DestinationRule 에서 subnet 으로 선언해야 한다는 것도 유념하자.
- 예시로는 ch5/catalog-dest-rule.yaml 을 참조하자.
이 절에서는 여러 버전 사이에서 트래픽을 단계적으로 옮기는 작업을 수작업으로 수행했다.
이상적으로는 이 트래픽 전환을 어떤 도구나 CI/CD 파이프라인의 배포 파이프라인에서 자동화하고 싶다.
다음 절에서는 이런 카나리 릴리스 프로세스 자동화를 돕는 도구를 살펴본다.
Notes:
소프트웨어 새 버전을 천천히 출시할 때는 구 버전과 신 버전을 모두 모니터링하고 관찰해 안정성, 성능, 정확성 등을 확인해야 한다. 문제를 발견하면 가중치를 변경해 구 버전으로 쉽게 롤백할 수 있다. 이 방식을 사용할 때는 여러 버전을 동시에 실행할 수 있도록 서비스를 구축해야 한다는 점도 명심하자. 서비스가 더 많은 상태를 갖고 있을 수록(심지어 외부 상태에 의존한다 하더라고) 이런 작업은 더 어려워질 수 있다. 이 주제에 대한 자세한 내용은 우리의 블로그 포스트를 참조하자.
https://blog.christianposta.com/microservices/traffic-shadowing-with-istio-reduce-the-risk-of-code-release/
https://blog.christianposta.com/microservices/advanced-traffic-shadowing-patterns-for-microservices-with-istio-service-mesh/
5.3.1 (Automating) Canary releasing with Flagger (Progressive Delivery Operator for Kubernetes) - Link
- 이전 절 에서 본 것처럼 이스티오는 트래픽 라우팅을 제어하는 강력한 기능을 운영자에게 제공하지만, 라우팅 변경과 새로운 설정 적용은 CLI에서 수동으로 해야 한다. 또한 여러 버전의 설정을 만들었기 때문에 작업도 더 많았고 설정이 잘못될 가능성도 있었다. 수백 개의 릴리스가 동시에 진행될 수도 있으므로, 카나리를 수행할 때 사람에 의한 이와 같은 수작업을 피하길 원하며 실수 가능성도 줄이고 싶다.
- Flagger 같은 것을 사용하면 서비스 릴리스를 자동화할 수 있다. Flagger는 스테판 프로단 Stefan Prodan 이 만든 카나리 자동화 도구로, 릴리스를 어떻게 수행할지, 언제 더 많은 사용자에게 릴리스를 개방할지, 릴리스가 문제를 일으킬 경우 언제 롤백할지 등에 관련된 파라미터를 지정할 수 있다. Flagger는 릴리스를 수행하는 데 필요한 작절한 설정을 모두 만든다.
- 이스티오와 Flagger를 함께 사용하는 방법을 살펴보자.
- 이전 절에서는 catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 배포했다.
- 이 둘을 제거하고 Flagger가 라우팅과 배포 변경을 처리하게 하자.
# 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를 설치하려고 한다. helm 사용. https://docs.flagger.app/install/flagger-install-on-kubernetes
# 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 Canara 리소스를 사용해 카나리 릴리스의 파라미터를 지정하고, Flagger가 적절한 리로스를 만들어 이 릴리스를 주관하게 할 것이다. - Docs
# 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
- 이 Canary 리소스에서는 어떤 쿠버네티스 Deployment가 카나리 대상인지, 어떤 쿠버네티스 Service와 이스티오 VirtualService가 자동으로 만들어져야 하는지, 카나리를 어떻게 진행해야 하는지 등을 지정한다.
- Canary 리소스의 마지막 부분은 카나리를 얼마나 빨리 진행할지, 생존을 판단하기 위해 지켜볼 메트릭은 무엇인지, 성공을 판단할 임계값은 얼마인지를 기술하고 있다.
- 45초마다 카나리의 각 단계를 평가하고, 단계별로 트래픽을 10%씩 늘린다. 트래픽이 **50%에 도달하면 100%**로 바꾼다.
- 성공률 메트릭의 경우 1분 동안의 성공률이 99% 이상이어야 한다. 또한 P99(상위 99%) 요청 시간은 500ms까지 허용한다.
- 이 메트릭들이 연속으로 5회를 초과해 지정한 범위와 다르면, 롤백한다.
위 설정을 적용하고 catalog 서비스를 자동으로 v2로 카나리하는 절차를 시작해보자
# 반복 호출테스트 : 신규터미널
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
# 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
...
위 VS catalog 등 설정을 보면, catalog 서비스로 향하는 트래픽이 catalog-primary 서비스로는 100%, catalog-canary 로는 0% 라우팅될 것임을 알 수 있다.
- 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않았다.
- Flagger는 원본 디폴로이먼트 대상(여기서는 catalog 디플로이먼트
?catalog-primary가 아닌가?)의 변경 사항을 지켜보고, 카나리 디폴로이먼트(catalog-canary) 및 서비스(catalog-canary)를 생성하고, VirtualService 의 가중치를 조정한다.
이제 catalog v2 를 도입하고 Flagger가 어떻게 릴리스에서 이를 자동화하는지, 어떻게 메트릭에 기반해 의사결정을 내리는지 살펴보자.
- 또한 Flagger가 정상 메트릭 기준선을 가져올 수 있도록 이스티오를 통해 서비스에 대한 부하를 만들어보자.
- 카나리는 Carary 오브젝트에 설정한 대로 45초마다 진행될 것이다.
- 트래픽의 50%가 카나리로 이동할 때까지는 단계별로 10%씩 증가한다.
- flagger가 메트릭에 문제가 없고 기준과 차이가 없다고 판단되면, 모든 트래픽이 카나리로 이동해 카나리가 기본 서비스로 승격 될 때까지 카나리가 진행된다.
- 만약 문제가 발생하면 flagger는 자동으로 카나리 릴리스를 롤백할 것이다.
# 반복 호출테스트 : 신규터미널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
카나리 과정 중 kiali 확인
프로메테우스에서 확인 : flagger_canary_weight - Link
- Flagger를 사용해 이스티오의 API로 카나리 릴리스를 자동 제어함으로써, 리소스를 직접 설정하는 등 설정 오류를 일으킬 수 있는 수작업의 필요성을 없앴다.
- 또한 Flagger는 다크 런치 테스트, 트래픽 미러링(다음 절에서 설명한다) 등도 할 수 있다.
- 관련 내용은 웹 사이트를 참고하자. https://docs.flagger.app/usage/deployment-strategies
실습을 정리하고 이 장을 계속 진행할 수 있는 상태로 만들기 위해 Flagger Canary 리소스를 제거한다.
# Canary 삭제 : Flagger가 만든 service (catalog, catalog-canary, catalog-primary), destinationrule (catalog-canary, catalog-primary), deployment (catalog-primary) 를 제거함
kubectl delete canary catalog-release -n istioinaction
# catalog 삭제
kubectl delete deploy catalog -n istioinaction
# Flagger 삭제
helm uninstall flagger -n istio-system
5.4 Reducing risk even further: Traffic mirroring (실습)
트래픽 미러링 소개
- 앞서 살펴본 요청 수준 라우팅과 트래픽 전환이라는 두 가지 기술을 사용하면 릴리스의 위험성을 낮출 수 있다.
- 두 기술 모두 라이브 트래픽과 요청을 사용하므로, 영향의 파급 범위를 아무리 제어하더라도 사용자에게 영향을 줄 수 있었다.
- 또 다른 접근법은 운영 환경 트래픽을 새 디플로이먼트로 미러링하는 것으로, 그림 5.10처럼 운영 환경 트래픽을 복사해 고객 트래픽 범위 외부의 새 디플로이먼트로 보내는 것이다.
- 미러링 방식을 사용하면, 실제 운영 환경 트래픽을 배포로 보냄으로써 사용자에게 영향을 주지 않고 새 코드가 어떻게 동작할지에 대한 실제 피드백을 얻을 수 있다.
- 이스티오는 트래픽 미러링을 지원하며, 이는 배포 및 릴리스 수행의 위험성을 다른 두 방식보다 휠씬 더 줄일 수 있다. 한번 살펴보자.
미러링 Mirroring vs. 복제 Replication 의 용어의 차이? 동작의 차이는?
트래픽 미러링은 네트워크 트래픽의 '실시간 복사'로, 분석 및 모니터링 목적에 한정되며, 서비스에는 영향을 주지 않습니다.
복제는 데이터의 '복사'로, 서비스의 가용성, 확장성, 장애 대비 등 실제 서비스 운영에 직접적으로 활용됩니다.
미러링은 트래픽(패킷) 레벨의 실시간 복사 및 분석, 복제는 데이터 레벨의 서비스 목적 복사라는 점에서 구분된다.
실습 환경 초기화 (실습)
# catalog 디플로이먼트를 초기 상태로 돌리고, catalog-v2 를 별도의 디플로이먼트로 배포
kubectl apply -f services/catalog/kubernetes/catalog-svc.yaml -n istioinaction
kubectl apply -f services/catalog/kubernetes/catalog-deployment.yaml -n istioinaction
kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
# 확인
kubectl get deploy -n istioinaction -o wide
kubectl get svc,ep -n istioinaction -owide
kubectl get gw,vs -n istioinaction
# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
# catalog v1 으로만 접속 확인
for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
0
트래픽 미러링 (실습)
미러링 수행이 필요한 VS 확인
# 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
- 이 VS는 라이브 트래픽을 전부 catalog(v1)으로 보내지만, 동시에 v2로도 미러링한다.
- 미러링은 요청의 복사복을 만들어 미러링된 클러스터(여기서는 catalog-v2)로 전송하는 이른바 ‘보내고 잊는 방식’으로 수행된다.
- 미러링된 요청은 실제 요청에는 영향을 줄 수 없는데, 미러링을 수행하는 이스티오 프록시가 미러링된 클러스터에서 오는 응답을 모두(성공인든, 실패든) 무시해버리기 때문이다.
VS 리소스를 만들어보자
# 반복 접속
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
...
(심화) webapp 과 catalog v2 파드에서 패킷 덤프로 확인
# 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"
},
...
- 트래픽 미러링은 릴리스의 위험성을 낮추는 방법 중 하나다.
- 요청 라우팅 및 트래픽 전환과 마찬가지로 애플리케이션은 상황을 인지해 실제와 미러링 모드 모두로 동작하거나, 여러 버전으로 동작하거나, 혹은 둘 다 모두 할 수 있어야 한다.
- 자세한 내용은 저자의 블로그 게시물을 참조하자.https://blog.christianposta.com/microservices/advanced-traffic-shadowing-patterns-for-microservices-with-istio-service-mesh/
- https://blog.christianposta.com/microservices/traffic-shadowing-with-istio-reduce-the-risk-of-code-release/
5.5 Routing to services outside your cluster by using Istio’s service discovery
들어가며
- 기본적으로, 이스티오는 트래픽이 서비스 메시 밖으로 향하는 것을 허용한다.
- 예를 들어, 애플리케이션이 서비스 메시가 관리하지 않는 외부의 웹 사이트나 서비스와 통신하려고 시도하면 이스티오는 트래픽이 나가도록 허용한다.
- 모든 트래픽은 먼저 서비스 메시 사이드카 프록시(이스티오 프록시)를 거치므로 트래픽 라우팅을 제어할 수 있고, 이스티오의 기본 정책을 바꿔 어떤 트래픽도 메시를 떠날 수 없게 거부할 수 있다.
- 어떤 트래픽도 메시를 떠날 수 없게 막는 것은, 메시 내 서비스나 애플리케이션이 손상됐을 때 악의적인 공격자가 자신의 집으로 연락하는 것을 방지하기 위한 기본적인 심층 방어 태세다.
- 그렇지만 외부 트래픽이 이스티오를 사용할 수 없게 하는 것만으로는 충분하지 않다.
- 손상된 파드는 프록시를 우회할 수 있기 때문이다.
- 그러므로 3계층 및 4계층 보호 같은 추가적인 트래픽 차단 매커니즘을 갖춘 심층 방어 접근법이 필요하다.
- 예를 들어 취약점 때문에 공격자가 특정 서비스를 제어할 수 있다면, 공격자는 자신이 제어하는 서버에 도달할 수 있도록 코드 주입이나 서비스 조작을 시도할 수 있다.
- 공격자가 자신이 제어하는 서버에 도달할 수 있고 손상된 서비스를 더 제어할 수 있다면, 회사의 민감 데이터와 지적 재산권을 탈취할 수 있다.
외부 트래픽을 차단하도록 이스티오를 설정해 메시에 간단한 보호 계층을 더해보자. (그림 5.11참조). (실습)
# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
"meshConfig": {
"defaultConfig": {
"proxyMetadata": {}
},
"enablePrometheusMerge": true
},
...
# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
# webapp 로그 : 신규 터미널
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -
# 다음 명령을 실행해 이스티오의 기본값을 ALLOW_ANY 에서 REGISTRY_ONLY 로 바꾸자.
# 이느 서비스 메시 저장소에 명시적으로 허용된 경우(화이트 리스트)에만 트래픽이 메시를 떠나도록 허용하겠다는 의미다.
# 아래 설정 방법 이외에도 IstioOperator 로 설정을 변경하거나, istio-system 의 istio configmap 을 변경해도 됨.
# outboundTrafficPolicy 3가지 모드 : ALLOW_ANY (default) , REGISTRY_ONLY , ALLOW_LIST
docker exec -it myk8s-control-plane bash
----------------------------------------
istioctl install --set profile=default --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
y
exit
----------------------------------------
# 배포 확인
docker exec -it myk8s-control-plane istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
catalog-6d5b9bbb66-vzg4j.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-6w77x 1.17.8
catalog-v2-6df885b555-n9nxw.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-6w77x 1.17.8
istio-ingressgateway-6bb8fb6549-s4pt8.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-6w77x 1.17.8
webapp-7685bcb84-skzgg.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-6w77x 1.17.8
# webapp 파드에서 외부 다운로드
kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
Connecting to raw.githubusercontent.com (185.199.109.133:443)
wget: error getting response: Connection reset by peer
command terminated with exit code 1
# webapp 로그 : BlackHoleCluster 차단
# UH : NoHealthyUpstream - No healthy upstream hosts in upstream cluster in addition to 503 response code.
# https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -
# proxy-config : webapp
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn BlackHoleCluster -o json
[
{
"name": "BlackHoleCluster",
"type": "STATIC",
"connectTimeout": "10s"
}
]
# 현재 istiooperators meshConfig 설정 확인
kubectl get istiooperators -n istio-system -o json
...
"meshConfig": {
"accessLogFile": "/dev/stdout",
"defaultConfig": {
"proxyMetadata": {}
},
"enablePrometheusMerge": true,
"extensionProviders": [
{
"envoyOtelAls": {
"port": 4317,
"service": "opentelemetry-collector.istio-system.svc.cluster.local"
},
"name": "otel"
},
{
"name": "skywalking",
"skywalking": {
"port": 11800,
"service": "tracing.istio-system.svc.cluster.local"
}
}
],
"outboundTrafficPolicy": {
"mode": "REGISTRY_ONLY"
}
},
ServiceEntry 소개 : 이스티오의 서비스 디스커버리 기능을 사용해 클러스터 외부의 서비스로 라우팅하기
- 모든 서비스가 서비스 메시 내에 있는 것은 아니므로, 메시 내부의 서비스가 메시 외부의 서비스와 통신할 방법이 필요하다.
- 메시 외부의 서비스는 기존 HTTP 서비스일 수도 있고, 데이터베이스나 캐시 같은 인프라 서비스일 수도 있다.
- 이스티오 외부에 위치한 서비스에 대해서도 정교한 라우팅을 구현할 수 있는데, 먼저 ServiceEntry 라는 개념부터 소개해야 한다.
- 이스티오는 내부 서비스 저장소 internal service registry 를 구축하는데, 여기에는 메시에서 인지하고 접근할 수 있는 모든 서비스가 들어 있다.
- 이 저장소가 메시 내 서비스가 다른 서비스를 찾는 데 사용 할 수 있는 서비스 디스커버리 저장소의 공식적인 표현이라고 생각해도 좋다.
- You can think of this registry as the canonical representation of a service-discovery registry that services within the mesh can use to find other services.
- 이스티오는 컨트롤 플레인을 배포한 플랫폼을 보고 내부 저장소를 구축한다.
- Istio builds this internal registry by making assumptions about the platform on which the control plane is deployed.
- 예를 들어 이 책에서는 컨트롤 플레인을 쿠버네티스에 배포하고 있다.
- 이스티오는 기본 쿠버네티스 API를 사용해 자신의 서비스 카탈로그를 구축한다. 이는 그림 5.12에서 보여지는 것과 같다.
- 메시 내부의 서비스가 메시 외부의 서비스와 통신하려면, 이스티오의 서비스 디스커버리 저장소가 이 외부 서비스를 인지하게 해야 한다.
- Istio uses the default Kubernetes API to build its catalog of services (based on Kubernetes Service objects)
⇒ istiod는 k8s api (Service 리소스)를 통해서 서비스/엔드포인트 정보를 동적으로 발견/획득
- 우리의 가상 상점에서는 최고의 고객 서비스를 제공하고, 고객이 직접 피드백을 하거나 서로 생각을 나눌 수 있도록 하고 싶다.
- 이를 위해 서비스 메시 클러스터 외부에 구축하고 배포한 온라인 포럼에 사용자를 연결할 것이다.
- 여기서 포럼은 jsonplaceholder.typicode.com URL에 있다. Free fake and reliable API for testing and prototyping.
- API 개발 및 테스트를 쉽게 할 수 있도록 가짜 데이터(Fake Data) REST API 형태로 제공해주는 무료 서비스.
- 각 엔드포인트는 GET, POST, PUT, DELETE 같은 메서드도 흉내낼 수 있지만 실제 데이터는 저장되지는 않음. (가상의 응답만 리턴)
- 다음과 같은 리소스 엔드포인트를 제공
리소스 | 설명 | 예시 요청 |
/posts | 게시글 목록 | GET /posts |
/comments | 댓글 목록 | GET /comments |
/albums | 앨범 목록 | GET /albums |
/photos | 사진 정보 | GET /photos |
/todos | 할일 목록 | GET /todos |
/users | 사용자 정보 | GET /users |
이스티오 ServiceEntry 리소스는 이스티오의 서비스 저장소에 항목을 삽입하는 데 사용 할 수 있는 저장소 메타데이터를 캡슐화한다.
# cat ch5/forum-serviceentry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: jsonplaceholder
spec:
hosts:
- jsonplaceholder.typicode.com
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
location: MESH_EXTERNAL
- 이 ServiceEntry 리소스는 항목을 이스티오 서비스 저장소에 삽입하는데, 이는 메시 내 클라이언트가 호스트 jsonplaceholder.typicode.com 를 사용해 JSON 플레이스홀더를 호출할 수 있음을 명시한다.
- JSON 플레이스홀더 서비스는 클러스터 외부에 있는 서비스와의 통신을 실험하는 데 사용할 수 있도록 예제 REST API를 노출한다.
- 이 ServiceEntry를 만들기 전에 jsonplaceholder.typicode.com REST API를 호출하는 서비스를 설치하고, 이스티오가 실제로 밖으로 나가는 모든 트래픽을 막는지 관찰해보자.
ServiceEntry (실습)
jsonplaceholder.typicode.com 을 사용하는 예시 포럼 애플리케이션을 설치하자.
# forum 설치
cat services/forum/kubernetes/forum-all.yaml
kubectl apply -f services/forum/kubernetes/forum-all.yaml -n istioinaction
# 확인
kubectl get deploy,svc -n istioinaction -l app=webapp
docker exec -it myk8s-control-plane istioctl proxy-status
# webapp 웹 접속
open http://webapp.istioinaction.io:30000/
웹 브라우저에서 http://webapp.istioinaction.io:30000/ 접속 → 새로 고침
메시 안에서 새로운 포럼 서비스를 호출해보자
# 메시 안에서 새로운 포럼 서비스를 호출
curl -s http://webapp.istioinaction.io:30000/api/users
error calling Forum service
# forum 로그 확인
kubectl logs -n istioinaction -l app=forum -c istio-proxy -f
[2025-04-19T10:35:23.526Z] "GET /users HTTP/1.1" 502 - direct_response - "-" 0 0 0 - "172.18.0.1" "Go-http-client/1.1" "04bef923-b182-94e9-a58d-e2d9f957693b" "jsonplaceholder.typicode.com" "-" - - 104.21.48.1:80 172.18.0.1:0 - block_all
# 클러스터 내부 서비스에서 외부 도메인(jsonplaceholder.typicode.com) 으로 나가려 했지만, Istio가 요청을 막아서 502 오류와 함께 직접 응답 처리한 상황
## direct_response : Envoy가 요청을 외부로 보내지 않고 자체적으로 차단 응답을 반환했음을 의미
## block_all : Istio에서 egress(외부) 요청이 전면 차단됨을 나타내는 메시지
[2025-04-19T10:35:23.526Z] "GET /api/users HTTP/1.1" 500 - via_upstream - "-" 0 28 0 0 "172.18.0.1" "beegoServer" "04bef923-b182-94e9-a58d-e2d9f957693b" "forum.istioinaction:80" "10.10.0.31:8080" inbound|8080|| 127.0.0.6:60487 10.10.0.31:8080 172.18.0.1:0 - default
- 이 호출을 허용하기 위해 jsonplaceholder.typicode.com 호스트에 이스티오 ServiceEntry 리소스를 만들 수 있다.
- ServiceEntry 리소스를 만들면 이스티오의 서비스 저장소에 항목이 삽입되고, 서비스 메시가 이를 알 수 있다.
# istio proxy (forum)
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-1.json
#
cat ch5/forum-serviceentry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: jsonplaceholder
spec:
hosts:
- jsonplaceholder.typicode.com
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
location: MESH_EXTERNAL
kubectl apply -f ch5/forum-serviceentry.yaml -n istioinaction
#
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-2.json
diff forum-1.json forum-2.json
25a26
> jsonplaceholder.typicode.com 80 - outbound STRICT_DNS
96a98
> 80 jsonplaceholder.typicode.com /*
#
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction | grep json
80 jsonplaceholder.typicode.com /*
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction --fqdn jsonplaceholder.typicode.com -o json
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction | grep json
jsonplaceholder.typicode.com 80 - outbound STRICT_DNS
# 목적 hosts 의 도메인 질의 응답 IP로 확인된 엔드포인트들 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction --cluster 'outbound|80||jsonplaceholder.typicode.com'
ENDPOINT STATUS OUTLIER CHECK CLUSTER
104.21.112.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.16.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.32.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.48.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.64.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.80.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
104.21.96.1:80 HEALTHY OK outbound|80||jsonplaceholder.typicode.com
# 메시 안에서 새로운 포럼 서비스를 호출 : 사용자 목록 반환 OK
curl -s http://webapp.istioinaction.io:30000/api/users
# 반복 접속
while true; do curl -s http://webapp.istioinaction.io:30000/ ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
# webapp 웹 접속
open http://webapp.istioinaction.io:30000/
Summary
- DestinationRule 로 워크로드를 v1, v2 버전과 같이 더 작은 부분집합들로 분리할 수 있다.
- VirtualService는 이런 부분집합들을 사용해 트래픽을 세밀하게 라우팅한다.
- VirtualService는 HTTP 헤더 같은 애플리케이션 계층 정보를 기반으로 라우팅 결정을 설정한다.
- 이 덕분에 베타 테스터 같은 특정 사용자 집합을 서비스의 신 버전으로 보내 테스트하는 다크 런치 기법을 사용할 수 있다.
- 가중치 라우팅(VirtualService 리소스로 설정)을 사용하는 서비스 프록시는 트래픽을 점진적으로 새 배포로 라우팅할 수 있는데, 덕분에 카나리 배포(트래픽 전환이라고도 함) 같은 방법을 사용할 수 있다.
- 트래픽 전환은 Flagger를 사용해 자동화할 수 있다. Flagger는 수집한 메트릭을 사용해 새 배포로 라우팅되는 트래픽을 점진적으로 늘리는 오픈소스 솔루션이다.
- outboundTrafficPolicy 를 REGISTRY_ONLY로 설정하면 어떤 트래픽도 클러스터를 떠나지 못하게 함으로써 악의적인 사용자가 외부로 정보를 전송하는 것을 방지할 수 있다.
- outboundTrafficPolicy 를 REGISTRY_ONLY로 설정했을 때는 ServiceEntry로 외부로 향하는 트래픽을 허용할 수 있다.
실습 후 삭제
kind delete cluster --name myk8s
'스터디 > Istio Hands-on Study' 카테고리의 다른 글
Istio Hands-on Study [1기] - 4주차 - Observability - 8장 (0) | 2025.05.03 |
---|---|
Istio Hands-on Study [1기] - 2주차 - Envoy, Isto Gateway (4.2.3 이후) (1) | 2025.04.20 |
Istio Hands-on Study [1기] - 2주차 - Envoy, Isto Gateway (4.2.3 이전) (1) | 2025.04.15 |
Istio Hands-on Study [1기] - 1주차 - Istio 소개, 첫걸음 (0) | 2025.04.13 |