개요
반드시 실습을 마치면 AWS의 CloudFormation으로 배포한 인스턴스를 삭제하고, /etc/hosts에 추가한 값들을 정리
Istio 간단 구축
•
k3s 1.30.4
•
Ubuntu 22.04
•
Flannel CNI
•
iptables proxy mode
•
istio 1.23.2 (Envoy 1.32.2)
istioctl 폴더 구조
istio
|____ bin
| |____ istioctl
|
|____ manifests
| |____ examples
| |____ profiles
| | |____ default.yaml
| | |____ demo.yaml
| | |____ empty.yaml
| | |____ minimal.yaml
| | |____ openshift.yaml
| | |____ preview.yaml
| | |____ remote.yaml
| |____ charts
|
|____ tools
|
|____ samples
|
|____ manifest.yaml
|
|____ README.md
Plain Text
복사
demo 프로필은 기본적으로 상세하게 로그 출력을 지원
istioctl 설치
컨트롤 플레인에서 istioctl을 설치
# 설치
export ISTIOV=1.23.2
echo "export ISTIOV=1.23.2" >> /etc/profile
curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV TARGET_ARCH=x86_64 sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
# 확인
tree istio-$ISTIOV -L 2
istioctl version --remote=false
Shell
복사
프로필 수정
실습 환경에서 복잡성을 낮취기 위해 프로필을 수정
# 프로필 목록 조회
istioctl profile list
# manifest를 생성
istioctl profile dump
# 특정 프로필의 manifest를 생성
# istioctl profile dump <profile>
istioctl profile dump default
istioctl profile dump demo
# 특정 config 경로만 확인
# istioctl profile dump --configu-path <path>
istioctl profile dump --config-path components.ingressGateways
istioctl profile dump --config-path values.gateways.istio-ingressgateway
# ---
# 프로필에 해당하는 manifest를 작성
istioctl profile dump demo > demo-profile.yaml
# egressGateways를 비활성화
# --------------------
# egressGateways:
# - enabled: false
# --------------------
vi demo-profile.yaml
# 수정된 프로필을 설치
istioctl install -f demo-profile.yaml -y
Shell
복사
# istio-ingressgateway를 NodePort로 변경
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"type":"NodePort"}}'
Shell
복사
설치 확인
# istio-system 네임스페이스 리소스 확인
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
Shell
복사
# istio CRD 확인
kubectl get crd | grep istio.io | sort
Shell
복사
# istio-ingressgateway의 Envoy 버전 확인
kubectl exec -it deploy/istio-ingressgateway -n istio-system -c istio-proxy -- envoy --version
Shell
복사
# istio-ingressgateway Service 확인
kubectl get svc,ep -n istio-system istio-ingressgateway
## istio-ingressgateway Service 포트 정보 확인
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath={.spec.ports[*]} | jq
Shell
복사
# istiod의 컨트롤 플레인 디플로이먼트 정보 확인
kubectl exec -it deployment.apps/istiod -n istio-system -- ss -tnlp
kubectl exec -it deployment.apps/istiod -n istio-system -- ss -tnp
kubectl exec -it deployment.apps/istiod -n istio-system -- ps -ef
# istio-ingressgateway 디플로이먼트 정보 확인
kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ss -tnlp
kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ss -tnp
kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ps -ef
Shell
복사
변수 지정
# kans-s 컨트롤 플레인 노드
# istio-ingressgateway의 NodePort 변수 지정
export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}')
echo $IGWHTTP
# 도메인 이름 변수 지정
# www.jseo.dev는 적절한 도메인으로 교체
# 192.168.10.102는 istio-ingressgateway Pod가 존재하는 워커 노드의 ip 주소
export MYDOMAIN=www.jseo.dev
echo -e "192.168.10.102 $MYDOMAIN" >> /etc/hosts
echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
# 접속 시도 시 아직 설정이 없어서 실패
curl -v -s $MYDOMAIN:$IGWHTTP
Shell
복사
# testpc 노드
# 컨트롤 플레인에서 확인했던 istio-ingressgateway의 NodePort 변수 지정
export IGWHTTP=<nodeport>
echo $IGWHTTP
# 도메인 이름 변수 지정
# www.jseo.dev는 적절한 도메인으로 교체
# 192.168.10.102는 istio-ingressgateway Pod가 존재하는 워커 노드의 ip 주소
export MYDOMAIN=www.jseo.dev
echo -e "192.168.10.102 $MYDOMAIN" >> /etc/hosts
echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
# 접속 시도 시 아직 설정이 없어서 실패
curl -v -s $MYDOMAIN:$IGWHTTP
Shell
복사
# 로컬 pc
# 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
# IP주소와 NodePort를 변수 지정
IGWHTTP=<nodeport>
echo $IGWHTTP
ISTIONODEIP=<control-plane-public-ip>
echo $ISTIONODEIP
# 도메인 이름 변수 지정
MYDOMAIN=www.jseo.dev
echo "$ISTIONODEIP $MYDOMAIN" | sudo tee -a /etc/hosts
# 접속 시도 시 아직 설정이 없어서 실패
curl -v -s $MYDOMAIN:$IGWHTTP
Shell
복사
외부 접속 설정
Auto Injection
# Auto Injection with Namespace Label
# Mutating Admission Webhook Controller를 사용하여 특정 네임스페이스 생성되는 모든 Pod들은 istio의 Sidecar가 자동으로 주입되도록 설정
# 아래 설정을 해주지 않으면 실습에서 Deployment를 생성하더라도 Pod 내애 컨테이너가 주입되지 않으므로 반드시 설정
kubectl label namespace default istio-injection=enabled
# 레이블 설정 확인
kubectl get ns -L istio-injection
Shell
복사
# ServiceAccount 및 Service manifest 생성
cat << EOT | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: kans-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-websrv
spec:
replicas: 1
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
serviceAccountName: kans-nginx
terminationGracePeriodSeconds: 0
containers:
- name: deploy-websrv
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 80
targetPort: 80
selector:
app: deploy-websrv
type: ClusterIP
EOT
Shell
복사
# Sidecar 확인
# Init 컨테이너가 istio-init 과정에서 Pod 내에 iptables 세팅 후 종료
# Pod 내에 istio-proxy라는 컨테이너가 생성
kubectl get po,svc,ep,sa -o wide
kc describe po/<deployment-pod-name> | grep "Container ID" -B3 -A3 | grep istio-proxy -A3
Shell
복사
Istio Gateway 및 VirtualService 설정
•
Gateway : 지정한 Ingress Gateway로부터 트래픽이 인입되도록 Protocol, Port, Hosts, Proxy 등 설정 가능
•
VirtualService : 인입 처리할 Hosts 설정하여 L7 PATH 별 라우팅을 지원하고, Envoy Route Config를 통해 목적지에 대한 정책을 설정 가능
# Gateway 및 VirtualService 생성
cat << EOT | kubectl create -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: test-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: nginx-service
spec:
hosts:
- "$MYDOMAIN"
gateways:
- test-gateway
http:
- route:
- destination:
host: svc-clusterip
port:
number: 80
EOT
Shell
복사
# Istio의 Gateway와 VirtualService 설명 확인
kc explain gateways.networking.istio.io
kc explain virtualservices.networking.istio.io
# istio api-resources를 확인
kubectl api-resources | grep istio
# gw, vs 조회
# vs는 특이하게도 다른 네임스페이스에 있는 Service를 참조할 수도 있음
kubectl get gw,vs
Shell
복사
# Mesh에 존재하는 각 Envoy와 istiod간의 xDS 동기화 관련 last sent, last acknowledged를 확인 가능
istioctl proxy-status
istioctl ps
Shell
복사
접속 확인
# 이전에는 curl 접속 실패했었지만 로컬 pc와 testpc노드에서 curl 시도 시 성공
curl -s $MYDOMAIN:$IGWHTTP | grep -o "<title>.*</title>"
# 퍼블릭 IP로 접속해도 정상 동작
curl -v -s <public-ip>:$IGWHTTP
Shell
복사
# proxy 이름 확인
istioctl proxy-status | grep deploy
# Envoy Config (all, cluster, endpoint, listener) 등을 dump
istioctl proxy-config all <proxy-name> -o json | jq
Shell
복사
# Deployment 내부에 istio-proxy 사용자가 존재함을 확인
kubectl exec -it deploy/deploy-websrv -c istio-proxy -- tail -n 3 /etc/passwd
# istiod와 istio-proxy의 커넥션 확인
# istiod와 istio-proxy가 사용하는 포트 정보는 아래 링크 확인
# https://istio.io/latest/docs/ops/deployment/application-requirements/
kubectl exec -it deploy/istiod -n istio-system -- netstat -antp
kubectl exec -it deploy/deploy-websrv -c istio-proxy -- netstat -antp
Shell
복사
istiod와 istio-proxy 포트
istiod / istio-proxy 포트 사용 테이블
istiod / istio-proxy의 구조적 관점 포트 사용 정보
UDS 통신 확인
istio-proxy와 Envoy 프로세스 간 UDS 통신을 수행하게 되는데, 이에 대해 확인
# Deployment 접속
kubectl exec -it deploy/deploy-websrv -c istio-proxy -- bash
# XDS는 소켓 타입
ls -al /etc/istio/proxy
# UDS의 Listener와 ESTAB 상태 정보 확인
ss -xpl
ss -xp
Shell
복사
Pod 디버깅
리소스 삭제
kubectl delete gw,vs,deploy,svc --all
Shell
복사
기능 확인
Product Page에서 요청을 받고 Reviews와 Details 결과를 사용자에게 반환
요청은 도서 리뷰를 보여주는 Reviews Service와 도서 상세 정보를 보여주는 Details Service로 전달
Reviews Service는 v1, v2, v3 세 개의 버전이 있고 v2, v3 버전의 경우 추가로 Ratings Service에 접속하여 도서에 대한 5단계 평가를 읽음
Reviews Service의 차이는, v1은 Rating 이 없고, v2는 검은색 별로 Ratings 가 표시되며, v3는 색깔이 있는 별로 Ratings를 표시
서버 구조
Service 구조에서 Istio를 적용하게 되면 이론과 실습으로 이해한 바대로 위와 같은 그림의 구조를 갖게 됨
Service 셋업
# istioctl 버전 변수 지정
export ISTIOV=1.23.2
echo $ISTIOV
# 실습 환경에 내장되어 있는 리소스 확인
cat ~/istio-$ISTIOV/samples/bookinfo/platform/kube/bookinfo.yaml
# 리소스 생성
kubectl apply -f ~/istio-$ISTIOV/samples/bookinfo/platform/kube/bookinfo.yaml
Shell
복사
Service 확인
# 리소스 및 ServiceAccount 조회
kubectl get all,sa
# 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>"
Shell
복사
외부 접속 설정
# Istio의 Gateway와 VirtualService 설정
# manifest 확인
cat ~/istio-$ISTIOV/samples/bookinfo/networking/bookinfo-gateway.yaml
# manifest 적용
kubectl apply -f ~/istio-$ISTIOV/samples/bookinfo/networking/bookinfo-gateway.yaml
# 리소스 확인
kubectl get gw,vs
Shell
복사
서버 구조의 Ingress Gateway에서 Product Page로 트래픽이 가기 전에 Gateway와 VirtualService를 거치게 되는데, 위 도표에 기재된 흐름으로 처리
bookinfo-gateway는 모든 호스트를 처리하고, 경로 매칭으로는 /productpage, /static, /login, /logout, /api/v1/products를 지원하며, 경로가 매칭되면 일단 productpage라는 Service의 9080 포트로 라우팅
# 로컬 pc
# 각 노드의 퍼블릭 주소를 확인
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
Shell
복사
# k3s-s 컨트롤 플레인에서 접속 확인
# NodePort를 변수에 할당
export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}')
echo $IGWHTTP
# 도메인 이름을 변수에 할당
export MYDOMAIN=www.jseo.dev
echo $MYDOMAIN
# /etc/hosts를 열고 클러스터의 각 노드를 퍼블릭 IP로 변경
# $MYDOMAIN의 엔트리도 istio-ingressgateway가 있는 워커 노드의 주소로 변경해야 정상 동작
vi /etc/hosts
# 클러스터의 각 노드로 접속 확인
# 로컬 호스트와 istio-ingressgateway가 존재하는 주소로는 응답이 정상적으로 오고, 그 외 주소로는 응답 실패
curl -s http://localhost:$IGWHTTP/productpage | grep "Book Details"
curl -s --max-time 3 http://192.168.10.101:$IGWHTTP/productpage | grep "Book Details"
curl -s --max-time 3 http://192.168.10.102:$IGWHTTP/productpage | grep "Book Details"
# 도메인 이름으로 접속 확인
# 설정해둔 퍼블릭 IP로 정상 요청되어 응답 확인
curl -s http://$MYDOMAIN:$IGWHTTP/productpage | grep "Book Details"
Shell
복사
# testpc 노드에서 접속 확인
# NodePort를 변수에 할당
export IGWHTTP=<nodeport>
echo $IGWHTTP
# 도메인 이름을 변수에 할당
export MYDOMAIN=www.jseo.dev
echo $MYDOMAIN
# /etc/hosts를 열고 클러스터의 각 노드를 퍼블릭 IP로 변경
# $MYDOMAIN의 엔트리도 istio-ingressgateway가 있는 워커 노드의 주소로 변경해야 정상 동작
vi /etc/hosts
# 도메인 이름으로 접속 확인
# 설정해둔 퍼블릭 IP로 정상 요청되어 응답 확인
curl -s http://$MYDOMAIN:$IGWHTTP/productpage | grep "Book Details"
Shell
복사
# 로컬 pc에서 접속 확인
# NodePort를 변수에 할당
export IGWHTTP=<nodeport>
echo $IGWHTTP
# 도메인 이름을 변수에 할당
export MYDOMAIN=www.jseo.dev
echo $MYDOMAIN
# /etc/hosts를 열고 클러스터의 각 노드를 퍼블릭 IP로 변경
# $MYDOMAIN의 엔트리도 istio-ingressgateway가 있는 워커 노드의 주소로 변경해야 정상 동작
vi /etc/hosts
# 도메인 이름으로 접속 확인
# 설정해둔 퍼블릭 IP로 정상 요청되어 응답 확인
curl -s http://$MYDOMAIN:$IGWHTTP/productpage | grep "Book Details"
Shell
복사
<node-ip>:<nodeport> 접속 시 버전에 따라 Reviews의 Ratings가 다른 것도 확인 가능
모니터링
Jaeger를 이용하는 경우 분산 트레이싱이 가능하며, Pod 자체의 헬스 체크는 Kiali가 직접 istiod에 TCP 15014 포트를 통해 수행
# 실습 확녕에 있는 애드온 파일을 확인하고, 이를 생성 및 배포
tree ~/istio-$ISTIOV/samples/addons/
kubectl apply -f ~/istio-$ISTIOV/samples/addons
kubectl rollout status deployment/kiali -n istio-system
Shell
복사
# 리소스 확인
kubectl get all,sa,cm -n istio-system
kubectl get svc,ep -n istio-system
# Kiali Service를 NodePort로 변경
kubectl patch svc -n istio-system kiali -p '{"spec":{"type":"NodePort"}}'
# Grafana Service를 Nodeport로 변경
kubectl patch svc -n istio-system grafana -p '{"spec":{"type":"NodePort"}}'
# Prometheus Service를 NodePort로 변경
kubectl patch svc -n istio-system prometheus -p '{"spec":{"type":"NodePort"}}'
Shell
복사
# Kiali 웹 접속 주소 확인
KIALINodePort=$(kubectl get svc -n istio-system kiali -o jsonpath={.spec.ports[0].nodePort})
echo -e "KIALI UI URL = http://$(curl -s ipinfo.io/ip):$KIALINodePort"
# Grafana 웹 접속 주소 확인
GRAFANANodePort=$(kubectl get svc -n istio-system grafana -o jsonpath={.spec.ports[0].nodePort})
echo -e "Grafana URL = http://$(curl -s ipinfo.io/ip):$GRAFANANodePort"
# Prometheus 웹 접속 주소 확인
PROMENodePort=$(kubectl get svc -n istio-system prometheus -o jsonpath={.spec.ports[0].nodePort})
echo -e "Prometheus URL = http://$(curl -s ipinfo.io/ip):$PROMENodePort"
Shell
복사
Kiali의 트래픽 그래프에서 Traffic Distribution과 Traffic Animation, Security를 켠 상태로 아래 명령어를 날려서 확인해보면 첨부된 스크린샷과 동일한 결과를 얻을 수 있음
while true; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; echo "--------------" ; sleep 0.1; done
Shell
복사
•
Application과 Service 측면에서의 정보 획득 가능
•
Workloads에서 istio-proxy와 app의 로그와 Envoy (Clusters, Listeners, Routes, Config) 정보 확인 가능
•
Istio Config에서 Istio 관련 설정 정보 확인 가능하며, Action으로 Istio 관련 오브젝트를 설정 및 삭제 가능
트래픽 컨트롤
트래픽 컨트롤은 VirtualService와 DestinationRule을 통해 동작
클라이언트 → Istio Ingress Gateway → Gateway, VirtualService + DestinationRule → Cluster (Endpoint to Pod)
DestinationRule은 실제 도착지 (Service와 1:1 연결)의 부하 분산, 커넥션 옵션, 서킷 브레이킹 등 정교한 정책 설정 가능
Request Routing은 Service로 들어오는 트래픽을 적절히 라우팅 하는 기능을 의미
# 샘플 파일 확인
cd ~/istio-$ISTIOV/samples/bookinfo/networking
# 기본 DestinationRule 적용
kubectl apply -f destination-rule-all.yaml
# DestinationRule 확인
kubectl get dr
Shell
복사
# 각 Service에 대해 v1의 서브셋이 되도록 VirtualService 적용
kubectl apply -f virtual-service-all-v1.yaml
# 아래 VirtualService에 따라 Kiali에서 Reviews의 v2, v3가 사라지는 것 확인
kubectl get vs
Shell
복사
# 특정 유저가 접속 시 v2로 라우팅 되도록 VirtualService 적용
kubectl apply -f virtual-service-reviews-test-v2.yaml
# 아래 VirtualService에 따라 ProductPage에서 jason으로 접속한 사용자는 v2로만 접속
kubectl get vs
Shell
복사
Fault Injection은 의도적으로 통신에 지연을 발생 시키거나 중단하는 것
# VirtualService 적용하여 jason 사용자 접속 시 7초 지연 발생
kubectl apply -f virtual-service-ratings-test-delay.yaml
Shell
복사
# VirtualService를 적용하여 jason 사용자 접속 시 500 응답 반환
kubectl apply -f virtual-service-ratings-test-abort.yaml
Shell
복사
# 리소스 삭제
kubectl delete -f virtual-service-all-v1.yaml
Shell
복사
Traffic Shifting은 카나리 배포처럼 비율 별 트래픽 전달을 수행
# 기본 VirtualService
kubectl apply -f virtual-service-all-v1.yaml
Shell
복사
# v1 50 - v3 50
kubectl apply -f virtual-service-reviews-50-v3.yaml
Shell
복사
# v1 80 - v2 20
kubectl apply -f virtual-service-reviews-80-20.yaml
Shell
복사
# v1 90 - v2 10
kubectl apply -f virtual-service-reviews-90-10.yaml
Shell
복사
# 리소스 삭제
kubectl delete -f virtual-service-all-v1.yaml
Shell
복사
Traffic Shifting은 TCP에 대해서도 지원하는데, 실습에서는 별도의 네임스페이스 만들어서 진행
# Istio 버전 설정
export ISTIO_VERSION=1.23.2
# 네임스페이스 생성
kubectl create namespace istio-io-tcp-traffic-shifting
# Auto Injection 설정
kubectl label namespace istio-io-tcp-traffic-shifting istio-injection=enabled
# 트래픽 전송에 Sleep이 걸려 있는 앱을 배포
kubectl apply -f ~/istio-$ISTIO_VERSION/samples/sleep/sleep.yaml -n istio-io-tcp-traffic-shifting
# v1, v2가 존재하는 TCP Echoing Micro Service
kubectl apply -f ~/istio-$ISTIO_VERSION/samples/tcp-echo/tcp-echo-services.yaml -n istio-io-tcp-traffic-shifting
Shell
복사
# 모든 TCP 트래픽이 v1으로 가도록 설정
kubectl apply -f ~/istio-$ISTIO_VERSION/samples/tcp-echo/tcp-echo-all-v1.yaml -n istio-io-tcp-traffic-shifting
# Ingress Host와 TCP Ingress Port를 변수 지정
export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="tcp")].nodePort}')
# 20개의 접속 시도 시 모든 트래픽이 v1으로 전송됨을 확인
for i in {1..20}; do \
kubectl exec "$(kubectl get pod -l app=sleep -n istio-io-tcp-traffic-shifting -o jsonpath={.items..metadata.name})" \
-c sleep -n istio-io-tcp-traffic-shifting -- sh -c "(date; sleep 1) | nc $INGRESS_HOST $TCP_INGRESS_PORT"; \
done
Shell
복사
# 20% 트래픽이 v2로 가도록 설정
kubectl apply -f ~/istio-$ISTIO_VERSION/samples/tcp-echo/tcp-echo-20-v2.yaml -n istio-io-tcp-traffic-shifting
# Ingress Host와 TCP Ingress Port를 변수 지정
export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="tcp")].nodePort}')
# 20개의 접속 시도 시 모든 트래픽이 v1으로 전송됨을 확인
for i in {1..20}; do \
kubectl exec "$(kubectl get pod -l app=sleep -n istio-io-tcp-traffic-shifting -o jsonpath={.items..metadata.name})" \
-c sleep -n istio-io-tcp-traffic-shifting -- sh -c "(date; sleep 1) | nc $INGRESS_HOST $TCP_INGRESS_PORT"; \
done
Shell
복사
# 리소스 삭제삭제
kubectl delete -f ~/istio-$ISTIO_VERSION/samples/tcp-echo/tcp-echo-all-v1.yaml -n istio-io-tcp-traffic-shifting
kubectl delete -f ~/istio-$ISTIO_VERSION/samples/tcp-echo/tcp-echo-services.yaml -n istio-io-tcp-traffic-shifting
kubectl delete -f ~/istio-$ISTIO_VERSION/samples/sleep/sleep.yaml -n istio-io-tcp-traffic-shifting
kubectl delete namespace istio-io-tcp-traffic-shifting
Shell
복사
Requests Timeouts는 타임아웃을 제어하는 기능
x-envoy-upstream-rq-timeout-ms가 헤더에 있다면 경로가 아니더라도 요청 별로 타임아웃 설정 가능
# 기본 VirtualService 설정
cd ~/istio-$ISTIOV/samples/bookinfo/networking
kubectl apply -f virtual-service-all-v1.yaml
# Reviews Service는 v2만 호출되도록 설정
kubectl apply -f - << EOT
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
EOT
# 가장 끝단인 Ratings Service 호출에 2초 지연을 설정함으로써 앞단의 모든 Service에 장애 영향
kubectl apply -f - << EOT
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 100
fixedDelay: 2s
route:
- destination:
host: ratings
subset: v1
EOT
Shell
복사
# HTTP 요청의 타임아웃은 기본적으로 비활성화이지만 Override하여 설정 가능
kubectl apply -f - << EOT
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
timeout: 0.5s
EOT
Shell
복사
0.5초로 타임아웃 설정했는데, 1초가 소요된 것은 Reviews 호출에 타임아웃 되었을 때 재시도가 설정되어 있기 때문
# 리소스 삭제
kubectl delete -f virtual-service-all-v1.yaml
Shell
복사
일반적으로 지칭하는 Circuit Breaking은 장애가 발생했을 때 전체 시스템에 영향을 주지 않도록 특정 Service를 제외하는 기능인데, k8s 관점에서의 Circuit Breaking은 Pod 안정성을 추구하는 기능
요청들이 대체로 Pod가 처리할 수 있는 수보다 높게 들어오게 되면 Pod는 재실행될 수 있는데, Circuit Breaking을 통해 Pod가 죽지 않도록 Pod에 들어오는 요청을 DestinationRule을 통해 정해두고 거절하는 것이 가능
# httpbin 배포
kubectl apply -f ~/istio-$ISTIOV/samples/httpbin/httpbin-nodeport.yaml
# 웹 접속 주소 확인
HTTPBHostIP=$(kubectl get pod -l app=httpbin -o jsonpath='{.items[0].status.hostIP}')
HTTPBNodePort=$(kubectl get svc httpbin -o jsonpath={.spec.ports[0].nodePort})
echo -e "HTTPBIN UI URL = http://$HTTPBHostIP:$HTTPBNodePort"
Shell
복사
# Circuit Breaking을 위한 DestinationRule 생성
# TCP 최대 연결 1개, HTTP 연결 당 요청 1개, 요청 대기 최대 1개
kubectl apply -f - << EOT
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1 # keepalive disable
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
EOT
Shell
복사
# httpbin으로 부하를 주입할 fortio 배포
kubectl apply -f ~/istio-$ISTIO_VERSION/samples/httpbin/sample-client/fortio-deploy.yaml
kubectl patch svc fortio -p '{"spec":{"type":"NodePort"}}'
# curl로 부하 주입
export FORTIO_POD=$(kubectl get pods -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get
# fortio control UI 웹 접속 주소 확인
FORTIOHostIP=$(kubectl get pod -l app=fortio -o jsonpath='{.items[0].status.hostIP}')
FORTIONodePort=$(kubectl get svc fortio -o jsonpath={.spec.ports[0].nodePort})
echo -e "FORTIO UI URL = http://$FORTIOHostIP:$FORTIONodePort/fortio"
Shell
복사
# 2 x 10회 반복
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
Code 200 : 13 (65.0 %)
Code 503 : 7 (35.0 %)
# 3 x10회 반복
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get
Code 200 : 14 (46.7 %)
Code 503 : 16 (53.3 %)
Shell
복사
# 리소스 삭제
kubectl delete destinationrule httpbin
kubectl delete deploy httpbin fortio-deploy
kubectl delete svc httpbin fortio
Shell
복사
보안
요구 사항
중간 공격을 방지하기 위해 트래픽의 암호화가 필요
Service의 유연한 접근 제어를 위해 mTLS와 Policy 필요
시간마다 어떤 일들이 있었는지 감지 수단 필요
목표
기본적으로 애플리케이션이나 인프라 변화 없이 적용
기존에 존재하는 여러 계층의 보안 시스템과 결합 가능
신뢰할 수 없는 네트워크에서도 보안 솔루션을 구축 (ZT)
아무것도 신뢰할 수 없다는 가정하에, 사용자 및 다양한 정보를 바탕으로 최소한의 권한과 세밀한 통제를 지속적으로 수행하는 보안 활동
구성 요소
키와 인증서를 위한 CA (Ceritificate Authority)
Proxy에 배포를 수행하는 Configuration API Server
Telemetry와 감지를 위한 Envoy Proxy Extensions
클라이언트와 서버 사이 통신을 보호하기 위한 PEP (Policy Enhancement Points)로 동작하는 Proxy
아키텍쳐
컨트롤 플레인이 설정 파일을 API Server로부터 관리하고, 데이터 플레인의 PEP를 설정 (PEP는 Envoy를 이용하여 구현)
TLS vs mTLS
TLS는 네트워크로 통신하는 과정에서 도청, 간섭, 위조를 방지하기 위해 설계되어, 암호화를 통해 인증하여 통신 기밀성을 제공
TLS는 3단계의 기본 절차로 이뤄지는데, 1) 지원 가능한 알고리즘을 서로 교환, 2) 키 교환 및 인증, 3) 대칭키 암호로 암호화하여 메시지를 인증
SSL은 Deprecated 되었는데, 용도 자체는 TLS와 비슷하니 참고
mTLS는 TLS와 달리 서버 측도 클라이언트 측에 대한 인증서를 확인하고, 접근 권한을 확인하는 방식
SPIFFE
Istio와 SPIFFE는 Service ID를 공유하게 되는데, 이를 통해 Istio Service들은 다른 SPIFFE 호환 시스템과 연결 설정 및 수락이 가능
Kiali에서도 Traffic Graph에서 Security 항목을 체크하여 커넥션에서 자물쇠를 클릭하면 Source / Destination Principal 정보를 확인 가능
Authentication, Authorization (mTLS)
Istio는 X.509 인증서를 이용하여 모든 워크로드에 강력한 Identity를 안전하게 프로비저닝 가능
Envoy Proxy와 함께 실행되는 istio-agent는 istiod와 함께 동작하여 키와 인증서 교체를 대규모로 자동화
1.
istiod는 CSR을 수행하기 위해 gRPC Service를 제공
2.
Envoy는 SDS (Secret Service Discovery) API를 이용하여 키와 인증서 요청을 전송
3.
istio-agent는 SDS 요청을 받았을 때 비공개 키와 CSR을 생성한 후, 자격증명과 함께 CSR을 istiod에 전송하여 서명을 요청
4.
CA는 CSR에 포함된 자격증명의 유효성을 검사하고 CSR에 서명하여 인증서 생성
5.
istio-agent는 istiod로부터 받은 인증서와 개인 키를 Envoy SDS API를 통해 Envoy에 전달
전반적으로 SSL 발급 과정을 Envoy 동작과 엮은 형태이며, 위 프로세스는 키와 인증서 교체를 위해 주기적으로 반복
Authentication에는 2가지 유형이 존재하는데, Peer Authentication과 Request Authentication으로 나뉨
•
Peer Authentication
클라이언트가 연결을 생성할 수 있도록 검증하기 위해 Service 간의 인증에 사용
Istio는 Service 코드 변경 없이 mTLS를 제공
•
Request Authentication
Request에 첨부된 자격증명을 확인하기 위해 엔드 사용자 인증에 사용
Istio는 JWT 유효성 검사를 통해 요청 수준의 인증을 지원
Authorization은 Mesh 내에 있는 워크로드를 대상으로 Mesh, 네임스페이스, 워크로드 범위의 접근 제어를 제공하고, 아래와 같은 장점이 존재
•
워크로드 - 워크로드 혹은 엔드 사용자 - 워크로드 간의 인증을 수행
•
사용과 관리가 쉬운 AuthorizationPolicy라는 CRD가 단순 API로 제공
•
Operator가 Istio 속성에 대해 커스텀 조건 (Custom Conditions)을 정의할 수 있고, CUSTOM, DENY, ALLOW 사용 가능
•
Envoy에서는 네이티브하게 Istio Authorization (ALLOW, DENY)를 적용하여 높은 성능을 보장
•
TCP, gRPC, HTTP, HTTPS, HTTP/2 등을 네이티브 하게 제공하여 높은 호환성을 지님
서버 측의 Envoy Proxy는 Inbound 트래픽에 대해 접근 제어 가능
HTTP Traffic을 이용하여 실습 가능
암묵적 활성화 사항으로는 1) 인증 미적용 시 모든 요청을 허용, 2) 권한 부여 정책은 ALLOW, DENY, CUSTOM로 동작, 3) 정책의 우선 순위가 있어서 CUSTOM, DENY, ALLOW 순서로 매칭 규칙을 확인
JWT는 X.509 인증서의 경량화된 JSON 버전으로, X.509 인증서와 마찬가지로 비공개 키를 이용하여 Token을 서명하고 공개 키를 이용하여 서명된 메시지를 검증
JWT는 JSON 형태로 Token의 형식을 정의한 스펙으로써 Header, Payload, Signature로 나뉘게 되는데, 각 파트들은 base64로 인코딩 되어 . 뒤에 합쳐지게 됨
Header - Token 형식과 암호화 아록리즘 선언
Payload - 전송하려는 데이터를 JSON 형태로 기입
Signature - Header와 Payload의 변조 가능성을 검증
트래픽 흐름 분석
외부 클라이언트에서 k8s로 접속하는 과정은 아래와 같은데, 클라이언트가 Nginx로 요청을 보내고, Nginx로부터 응답을 받으려고 하는 상황을 가정
간단하게는 호스트의 tcp/ip와 iptables를 거쳐 Pod 내의 iptables와 Envoy를 경유하는 식으로 동작하는데, 이를 Inbound / Outbound의 2가지 형태로 단계별 세세하게 탐색
(몇 없긴 하지만) 트래픽 흐름을 확인하기 위한 실습 환경
InTraffic - Inbound (클라이언트 → Pod)
sNAT + dNAT → dNAT → sNAT + dNAT
클라이언트 → istio-ingressgateway
클라이언트가 Nginx로 요청을 보내기 위해선 가장 먼저 Ingress Gateway로 인입
그림 상 1번 과정에 해당되며 일반적인 GET 요청을 보낸다고 가정했을 때, <client-ip>:<client-port> | <public-ip>:<public-port>와 같은 형태로 패킷을 구성
<public-port>의 경우 NodePort가 될 수도 있고, 그 외에 별도로 노출시킨 포트일 수 있음
istio-ingressgateway → 노드
Ingress Gateway에 요청이 인입되면, (externalTrafficPolicy: Local이 설정되었단 전제하에) Nginx Pod가 존재하는 노드로 전달 필요
이에 따라 Ingress Gateway에서는 클라이언트의 IP 주소를 XFF (X-Forwarded-For)에 담게 되고, sNAT과 dNAT되에 <ingress-gateway-ip>:<random-port> | <pod-ip>:<nginx-port> 형태로 노드에 전달
해당 과정에서의 패킷 캡쳐 시 예시로 아래와 같이 확인 가능
노드 → istio-proxy 컨테이너
노드로 전달되면 호스트 네임스페이스의 iptables를 경유하게 되는데, 이 때는 별도의 규칙 매칭 없이 바로 Pod의 네임스페이스로 전달
Pod의 네임스페이스는 Pause 컨테이너가 생성한 네트워크 네임스페이스이며, Nginx와 해당 네임스페이스를 공유하는 istio-proxy가 트래픽을 가로챌 수 있도록 Istio의 Init 컨테이너가 iptables 규칙을 수정
Pod 내부의 NAT 테이블을 통해 dNAT되어 <ingress-gateway-ip>:<random-port> | <pod-ip>:<istio-proxy-port> 형태로 패킷을 구성하게 되고, istio-proxy 컨테이너로 인입
istio-proxy에서 인입될 때 사용되는 필터들은 아래와 같은 과정으로 이뤄짐
PREROUTING → ISTIO_BOUNDED → ISTIO_REDIRECT
Istio의 Init 컨테이너가 설정하는 iptables 규칙은 구성된 실습 환경에서 아래 명령어로 확인 가능
kubectl logs nginx-pod -c istio-init
istio-proxy 컨테이너 → Nginx 컨테이너
istio-proxy에서는 요청을 전달 받게 되면 iptables 규칙에 의해 sNAT와 dNAT 되어 Nginx 컨테이너에 전달
sNAT되는 주소는 istio-proxy와 Nginx가 동일한 네트워크 네임스페이스를 사용하기 때문에 루프백으로 설정되므로, 127.0.0.6:<random-port> | <pod-ip>:<nginx-port>의 형태로 패킷을 구성
Nginx 컨테이너에는 클라이언트가 요청한 트래픽이 도착하게 되고, (externalTrafficPolicy: Local이므로) XFF 헤더에서 클라이언트의 주소를 확인 가능
istio-proxy에서 빠져나가면서 Nginx로 향할 때는 출발지 IP가 127.0.0.6임에 따라 최상단 규칙이 매칭되어 ISTIO_OUTPUT이 적용되는데, 전체적으로 아래와 같은 과정으로 Nginx에 전달
OUTPUT → ISTIO_OUTPUT → POSTROUTING
해당 과정에서의 패킷 캡쳐 시 예시로 아래와 같이 확인 가능
InTraffic - Outbound (Pod → 클라이언트)
Nginx 컨테이너에 도달한 요청은 리턴으로 200 응답을 클라이언트에 전달하는데, 마찬가지로 iptables의 규칙을 참고하여 Inbound와 동일한 동작 방식의 역순으로 트래픽을 전달
이 때 규칙은 별도 엔트리가 존재하는 것이 아니라, 기존에 존재하는 정보를 참고해서 역변환하는 방식으로 동작
sNAT + dNAT → sNAT → sNAT + dNAT
OutTraffic - Outbound (Pod → 웹 서버)
해당 예시는 외부의 요청을 받아내는 방식 보다는, Nginx가 업데이트 혹은 패치 등의 다운로드 과정에서 외부 웹 서버와의 연결을 위해 요청을 보내는 상황의 트래픽을 가정
나가는 트래픽도 들어오는 트래픽의 Outbound와 크게 다르지 않고 동일한 방식으로, Ingress Gateway를 거치지 않는다는 점만 빼면 거의 비슷하게 동작
dNAT → sNAT + dNAT → sNAT
Nginx 컨테이너 → istio-proxy 컨테이너
초기엔 <pod-ip>:<nginx-port> | <webserv-ip>:<webserv-port> 형태로 패킷이 구성되어 있는데, iptables의 OUTPUT → ISTIO_OUTPUT → ISTIO_REDIRECT 체인을 지나면서 istio-proxy로 접근하기 위해 <pod-ip>:<random-port> | 127.0.0.1:<istio-proxy-port> 형태로 패킷을 dNAT
해당 과정에서의 패킷 캡쳐 시 예시로 아래와 같이 확인 가능
istio-proxy 컨테이너 → 노드
외부로 트래픽을 전달하기 위해선 istio-proxy 컨테이너에서 나온 트래픽을 호스트 네트워크 네임스페이스가 있는 노드로 전달해야 하는데, 이 과정에서 Pause 컨테이너가 설정한 네트워크 네임스페이스를 거침
Pod의 네임스페이스에서는 iptables에 설정된 규칙에 따라 sNAT 및 dNAT 되어 <pod-ip>:<random-port + 2> | <webserv-ip>:<webserv-port> 형태로 패킷을 구성
InTraffic 때는 Pod의 네임스페이스를 지날 때 그냥 거쳐만 갔는데, OutTraffic에서는 iptables가 한 번 더 적용되는 것은 UID 1337 때문이고 자세한 내용은 여기를 참고 (따라서 Pod에서 동작하는 애플리케이션의 UID로 1337을 이용해선 안 됨)
해당 과정에서의 패킷 캡쳐 시 예시로 아래와 같이 확인 가능하고, 포트는 이전 그림과 달리 +2된 것도 확인 가능
노드 → 웹 서버
Pod의 네임스페이스를 거쳐 호스트의 네임스페이스까지 올라오면, 외부에 있는 웹 서버로 트래픽을 내보내기 위해 iptables의 규칙을 적용
<node-ip>:<random-port> | <webserv-ip>:<webserv-port>의 형태로 sNAT되어 패킷을 구성
OutTraffic - Inbound (웹 서버 → Pod)
웹 서버에서 반환되는 응답을 Pod에서 받기 위해 Inbound되면, Outbound의 역순으로 패킷을 처리
위에서 훑었던 흐름대로 Nginx로 도착하기 전에는랜덤 포트 + 2 값되었던 것도 인식하여 정상적으로 처리하여 istio-proxy를 거침