[Study 7주차] 쿠버네티스 Service Mesh - Envoy Proxy, Istio
- -
CoudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 7주차 스터디 내용 정리
이번 주차에서는 쿠버네티스 서비스 메시에 대해서 공부하였습니다.
그 중, 이스티오(Istio) 와 엔보이(Envoy) 에 대해서 동작 방식과 구성에 대해 집중적으로 공부했습니다.
1. Service Mesh
서비스 메시는 단어를 직역하면 서비스가 그물망 형태로 이루어져 있는 것을 의미합니다.
이는 서비스 어플리케이션들 간의 네트워크 통신이 무수히 많아지는 형태,
즉 마이크로서비스 아키텍처 환경에서의 서비스 간 통신을 제어하고 모니터링 하기 위해 생겨난 개념입니다.
1. 기존 통신 환경

가장 기본적인 어플리케이션 간 통신은 직접 통신입니다.
이런 어플리케이션 통신이 많아진다면 네트워크를 어떻게 모니터링하고 제어할 수 있을까요??
2. Proxy 통신 환경

서비스 메시를 구현하기 위해 나온 아이디어가 바로 Proxy 통신 방식입니다.
기존에 어플리케이션이 하던 직접 통신 패킷을 Proxy 가 가로채어 대신 통신하는 것입니다.
이 경우 Proxy 를 통해 어플리케이션 간 통신을 안되게 할 수도 있고, 어떤 통신이 이루어지고 있는 지 알 수 있게 됩니다.
이러한 방식을 사이드카 패턴이라고 하며, 해당 Proxy 구현 도구로 Envoy Proxy 가 있습니다.
3. Proxy 중앙 관리 환경

하지만 Proxy 를 직접 하나씩 관리하긴 어려우니 중앙 집중 관리 시스템이 필요합니다.
이를 위해서 Proxy 를 중앙에서 관리할 수 있는 API 를 가진 도구가 필요하게 되는데,
서비스 메시 구현체인 이스티오 (Istio) 가 이러한 동작을 합니다.
중앙 관리 환경에서는 아래와 같은 설정을 관리할 수 있어야 합니다.
a. 트래픽 모니터링
- 요청의 에러율, 레이턴시, 커넥션 개수, 요청 개수 등의 메트릭 모니터링
b. 트래픽 컨트롤
- 트래픽 시프팅 (Traffic Shifting)
- 서킷 브레이커 (Circuit Breaker) : 목적지 서비스에 문제가 있을 경우, 해당 서비스로 통신하는 것을 차단하는 기능
- 폴트 인젝션 (Fault Injection) : 의도적으로 요청 지연 및 실패 구현
- 속도 제한 (Rate Limit) : 요청 개수 제한
1.1. Istio
서비스 메시 구현체의 대표적인 도구로는 이스티오 (Istio), 링커드 (Linkerd) 가 있습니다.
이번 스터디는 이스티오를 사용하여 서비스 메시를 공부했습니다.

이스티오는 앞서 말한 Proxy 통신의 중앙 관리 도구로써 동작하며 크게 Pilot, Citadel, Galley 의 3 요소로 구성되어 있습니다.
현재는 이 3가지 요소가 'istiod' 라는 컴포넌트 안에 다 포함되어 있습니다.
* 파일럿 (Pilot)
- 모든 Envoy 사이드카 프록시 라우팅 규칙 관리
- 서비스 디스커버리와 로드밸런싱 설정 제공
* 갤리 (Galley)
- 이스티오와 쿠버네티스를 연결해주는 역할
- 서비스 메시 구성 데이터를 검증하고 변환
* 시타델 (Citadel)
- 보안 기능 담당
- TLS 인증서 발급 및 관리, 서비스 간 통신 암호화 수행
이스티오의 전체 동작 방식은 아래 아키텍처와 같습니다.
쿠버네티스에 이스티오를 구성하게 되면 파드에 사이드카로 엔보이 프록시가 들어가있는 형태가 되며,
엔보이 프록시가 들어간 파드 간 통신은 모두 이스티오를 통해 메트릭이 수집되고 트래픽을 컨트롤 할 수 있습니다.
1.2. Envoy
Envoy 는 L4/L7 프록시로 CNCF 에서 쿠버네티스, 프로메테우스에 이어 3번째로 졸업한 프로젝트인만큼 역사가 오래되고 검증된 도구라고 할 수 있습니다.
Envoy 용어
- Cluster : 엔보이가 트래픽을 포워드할 수 있는 논리적인 서비스, 실제 요청이 처리되는 IP 또는 엔드포인트 묶음
- Endpoint : IP 주소, 네트워크 노드로 그룹핑. 실제 접근이 가능한 엔드포인트를 의미
- Listener : 무엇을 받을 지, 어떻게 처리할 지에 대한 IP/Port 바인딩. 다운스트림 요청 처리
- Route : Listener 로 들어온 요청을 어디로 라우팅할 것인지 정의
- Filter : Listener 로부터 서비스에 트래픽을 전달하기 까지의 요청 처리 파이프라인
- Upstream : 엔보이 요청을 포워딩해서 연결하는 백엔드 네트워크 노드
- Downstream : 원격 클라이언트 요청

2. Envoy 프록시 실습
2.1. Envoy 프록시 실습 환경 구성
이번 서비스 메시 실습은 AWS EC2 를 생성하여 진행했습니다.
# CloudFormation 스택 다운로드 curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-7w.yaml STACK_NAME=kimalarm-stack AWS_REGION=ap-northeast-2 KEY_NAME=kimalarmkey aws cloudformation deploy \ --template-file kans-7w.yaml \ --stack-name ${STACK_NAME} \ --parameter-overrides KeyName=${KEY_NAME} SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \ --region ${AWS_REGION}
CloudFormation 으로 생성한 EC2 중 testpc 에 설치
wget -O- https://apt.envoyproxy.io/signing.key | sudo gpg --dearmor -o /etc/apt/keyrings/envoy-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/envoy-keyring.gpg] https://apt.envoyproxy.io jammy main" | sudo tee /etc/apt/sources.list.d/envoy.list sudo apt-get update && sudo apt-get install envoy -y
2.2. Envoy 데모 Config 구성
testpc 에 접근 후 구성 - Terminal 1번
해당 데모 Config 구성 파일은 아래와 같으며, 공식 문서에서 발췌했습니다.
'socket_address' 에 명시된 것처럼 모든 주소 (0.0.0.0) 의 10000 번 포트로 들어오는 트래픽을,
'route_config' 에 명시된 것처럼 service_envoy_proxy_io 로 전달하고, 호스트명을 www.envoyproxy.io 로 rewrite 합니다.
service_envoy_proxy_io 에는 endpoint 로 www.envoyproxy.io:443 으로 트래픽을 처리합니다.
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog http_filters: - name: envoy.filters.http.router route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: host_rewrite_literal: www.envoyproxy.io cluster: service_envoyproxy_io clusters: - name: service_envoyproxy_io type: LOGICAL_DNS # Comment out the following line to test on v6 networks dns_lookup_family: V4_ONLY connect_timeout: 5s load_assignment: cluster_name: service_envoyproxy_io endpoints: - lb_endpoints: - endpoint: address: socket_address: address: www.envoyproxy.io port_value: 443 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.envoyproxy.io
curl -O https://www.envoyproxy.io/docs/envoy/latest/_downloads/92dcb9714fb6bc288d042029b34c0de4/envoy-demo.yaml envoy -c envoy-demo.yaml
2.3. Envoy 접속 테스트
testpc 서버 - Terminal 2번
앞에서 엔보이를 실행한 터미널 외에 두 번째 터미널을 실행 시킨 후에 아래 명령어를 입력합니다.
# 엔보이 프록시 포트 확인 - 10000 번이 실제로 열려있는 지 ss -tnlp # Envoy 트래픽 통신 확인 curl -s http://127.0.0.1:10000 | grep -o "<title>.*</title>"

외부 접속 정보 확인 - Terminal 2번
# 서버의 공인 IP 출력 echo -e "http://$(curl -s ipinfo.io/ip):10000" # http://43.203.235.115:10000

Envoy 관리자 설정 페이지 덮어쓰기 - Terminal 1번
9902 포트로 접근 시, Envoy 관리자 페이지로 접근할 수 있도록 하는 설정을 덮어씁니다.
cat <<EOT> envoy-override.yaml admin: address: socket_address: address: 0.0.0.0 port_value: 9902 EOT envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)"
외부 접속 정보 확인 - Terminal 2번
# 서버의 공인 IP 출력 echo -e "http://$(curl -s ipinfo.io/ip):9902" # http://43.203.235.115:9902

해당 페이지에서는 Envoy 의 Cluster, Listner, Server 등의 여러 정보들을 확인할 수 있습니다.
3. Istio 실습
3.1. 실습 환경 구성
k3s-s 서버에서 실행
Istio 는 현재 가장 최신 버전인 1.23.2 버전을 설치했고,
Sidecar 모드와 Ambient 모드 중 Sidecar 방식으로 진행했습니다.
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 - # Istioctl 명령어를 사용할 수 있도록 복사 cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl istioctl version --remote=false # 디렉터리 구조 확인 tree istio-$ISTIOV -L 2 # sample yaml 포함

Istio 기본 구성
오퍼레이터를 통한 이스티오 설치는 프로파일 통해서 필요한 옵션과 설정을 다 맞춰줍니다.
다만 이스티오 1.23 버전 이후 부터는 오퍼레이터 API 가 deprecated 될 예정입니다.
실습은 편의를 위해 오퍼레이터 API 를 사용했습니다.
# Istio 프로파일 확인 istioctl profile list # 이스티오 프로파일 목록 Istio configuration profiles: ambient default demo empty minimal openshift openshift-ambient preview remote stable
편의를 위한 Default 프로파일 수정
istioctl profile dump demo > demo-profile.yaml # 아래와 같이 변경 egressGateways: - enabled: false # 이스티오 설치 istioctl install -f demo-profile.yaml -y

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

# Istio IngressGateway 구성 확인 - Envoy Proxy 버전 kubectl exec -it deploy/istio-ingressgateway -n istio-system -c istio-proxy -- envoy --version # Istio IngressGateway 를 NordPort 타입으로 변경 kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"type":"NodePort"}}' # istio-ingressgateway 서비스 포트 정보 확인 kubectl get svc -n istio-system istio-ingressgateway -o jsonpath={.spec.ports[*]} | jq # 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 kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- cat /etc/istio/proxy/envoy-rev.json kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ss -xnlp kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ss -xnp
Ingress Gateway 의 포트를 확인해보면, 각 서비스별 수신하고 있는 포트 정보를 확인할 수 있습니다.

이스티오의 중앙 관리 시스템(컨트롤 플레인)인 Istiod 를 확인해보면 내부적으로 소켓 통신을 하고 있는 것을 알 수 있으며,
앞서 보았던 Pilot 이 동작하고 있는 것을 확인할 수 있습니다.


3.2. Auto Injection 설정
네임스페이스에 특정 Label 이 있으면, 해당 네임스페이스에 파드가 배포될 때 Proxy 를 자동으로 주입하는 설정입니다.
default 네임스페이스에 'istio-injection=enabled' 라는 Label 을 주입합니다.
kubectl label namespace default istio-injection=enabled kubectl get ns -L istio-injection

3.3. Istio 테스트를 위한 환경 구성
k3s-s 서버 - Terminal 1번
# istio ingress gw NodePort(HTTP 접속용) 변수 지정 export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}') echo $IGWHTTP # 접속을 위한 /etc/hosts 파일 수정 - 아무 도메인 가능 export MYDOMAIN=www.kimalarm.com echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile

testpc 서버 - Terminal 2번
# k3s-s 서버에서 출력된 IGWHTTP 포트 IGWHTTP=<각자 출력된 NodePort> IGWHTTP=32251 # 접속을 위한 /etc/hosts 파일 수정 - k3s-s 서버에서 연결한 도메인 export MYDOMAIN=www.kimalarm.com echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
Local PC - Terminal 3번
# k3s-s 서버에서 출력된 IGWHTTP 포트 IGWHTTP=<각자 출력된 NodePort> IGWHTTP=32251 ISTIONODEIP=<k3s-s 의 유동 공인 IP> ISTIONODEIP=43.202.48.135 # 접속을 위한 /etc/hosts 파일 수정 - k3s-s 서버에서 연결한 도메인 export MYDOMAIN=www.kimalarm.com echo "$ISTIONODEIP $MYDOMAIN" | sudo tee -a /etc/hosts
3.4. Istio 외부 노출
앞서 3곳에서 Istio 접속을 위한 구성을 했습니다.
이제 실제 외부에서 Istio 접속을 시도하기 위한 Nginx 파드를 배포하겠습니다.
Nginx Deployment 생성
cat <<EOF | 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 EOF
kubectl get pod -o wide

# Pod 확인 kc describe pod ... 생략 ... # Init Container 를 통해 파드 실행 시, Istio iptable 을 설정해줌 Init Containers: istio-init: Container ID: containerd://d5120b8cb7d36a1f78522becaa98754dc51a6071e0a5f0200050cca716a4a5fe Image: docker.io/istio/proxyv2:1.23.2 Image ID: docker.io/istio/proxyv2@sha256:2876cfc2fdf47e4b9665390ccc9ccf2bf913b71379325b8438135c9f35578e1a Port: <none> Host Port: <none> Args: istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i * -x -b * -d 15090,15021,15020 --log_output_level=default:info ... 생략 ... # 자동으로 Istio-Proxy 파드가 주입되어 실행됨 (Auto-Injection) Containers: deploy-websrv: Container ID: containerd://ff5122cc32f6aebdb4f35936e31c9b881d0c03310a858d4ea01466a6e9c52f20 Image: nginx:alpine istio-proxy: Container ID: containerd://788948849ab175649d650e440ff269a427c416e3ef77ba40a7f956a5d5666623 Image: docker.io/istio/proxyv2:1.23.2 Image ID: docker.io/istio/proxyv2@sha256:2876cfc2fdf47e4b9665390ccc9ccf2bf913b71379325b8438135c9f35578e1a Port: 15090/TCP Host Port: 0/TCP Args: proxy sidecar --domain $(POD_NAMESPACE).svc.cluster.local --proxyLogLevel=warning --proxyComponentLogLevel=misc:error --log_output_level=default:info
Gateway 와 VirtualService 배포
이스티오의 트래픽을 처리해줄 Gateway 와 VirtualService 리소스를 배포합니다.
각 리소스는 다음과 같은 역할을 합니다.
* Gateway
지정한 인그레스 게이트웨이로부터 트래픽이 인입, 프로토콜 및 포트, HOSTS, Proxy 등 설정 가능
* VirtualService
인입 처리할 hosts 설정, L7 PATH 별 라우팅, 목적지에 대한 정책 설정 가능 (envoy route config)
k3s-s 서버에서 실행
cat <<EOF | 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 EOF # 리소스 확인 kubectl get gw,vs istioctl proxy-status


프록시 상태 용어
- CDS(Cluster Discovery Service) : 클러스터 상태
- LDS(Listener Discovery Servic) : 리스너 상태
- EDS(ndpoint Discovery) : 엔드포인트 상태
- RDS(Route Discovery Service) : 라우트 상태
외부 PC 에서 접속 테스트
1. testpc 서버 - Terminal 2번
2. Local PC - Terminal 3번
curl -v -s $MYDOMAIN:$IGWHTTP

Istio 프록시 정보 확인
아래 명령어를 입력하면, Nginx 파드의 사이드카로 주입된 이스티오 프록시가 어떤 통신을 하고 있는 지 알 수 있습니다.
# envoy 설정 정보 확인 kubectl exec -it deploy/deploy-websrv -c istio-proxy -- ss -nlp kubectl exec -it deploy/deploy-websrv -c istio-proxy -- ss -np kubectl exec -it deploy/deploy-websrv -c istio-proxy -- netstat -np
(⎈|default:N/A) root@k3s-s:~# kubectl exec -it deploy/deploy-websrv -c istio-proxy -- ss -nlp Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process nl UNCONN 0 0 0:0 * nl UNCONN 896 0 4:0 * nl UNCONN 4352 0 4:40 * nl UNCONN 0 0 9:0 * nl UNCONN 0 0 10:0 * nl UNCONN 0 0 12:0 * nl UNCONN 0 0 15:0 * nl UNCONN 0 0 16:0 * u_str LISTEN 0 4096 etc/istio/proxy/XDS 99549 * 0 users:(("pilot-agent",pid=1,fd=10)) u_str LISTEN 0 4096 var/run/secrets/workload-spiffe-uds/socket 99548 * 0 users:(("pilot-agent",pid=1,fd=8)) tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* tcp LISTEN 0 4096 127.0.0.1:15004 0.0.0.0:* users:(("pilot-agent",pid=1,fd=12)) tcp LISTEN 0 4096 127.0.0.1:15000 0.0.0.0:* users:(("envoy",pid=13,fd=18)) tcp LISTEN 0 4096 0.0.0.0:15090 0.0.0.0:* users:(("envoy",pid=13,fd=21)) tcp LISTEN 0 4096 0.0.0.0:15090 0.0.0.0:* users:(("envoy",pid=13,fd=20)) tcp LISTEN 0 4096 0.0.0.0:15001 0.0.0.0:* users:(("envoy",pid=13,fd=35)) tcp LISTEN 0 4096 0.0.0.0:15001 0.0.0.0:* users:(("envoy",pid=13,fd=34)) tcp LISTEN 0 4096 0.0.0.0:15006 0.0.0.0:* users:(("envoy",pid=13,fd=37)) tcp LISTEN 0 4096 0.0.0.0:15006 0.0.0.0:* users:(("envoy",pid=13,fd=36)) tcp LISTEN 0 4096 0.0.0.0:15021 0.0.0.0:* users:(("envoy",pid=13,fd=23)) tcp LISTEN 0 4096 0.0.0.0:15021 0.0.0.0:* users:(("envoy",pid=13,fd=22)) tcp LISTEN 0 511 [::]:80 [::]:* tcp LISTEN 0 4096 *:15020 *:* users:(("pilot-agent",pid=1,fd=3))
해당 명령어 입력 시 위와 같은 정보가 출력되며, 이스티오가 어떤 포트를 사용하는 지는 공식 문서에 잘 나와 있습니다.
Istio-Proxy

Istiod (컨트롤 플레인)

3.5. Istio 디버깅용 파드 생성
Istio-Proxy 컨테이너에 들어가 쉘 명령어를 입력할 경우 제한되는 부분이 종종 있습니다.
이러한 점을 방지하기 위해 해당 파드를 복제하여 디버깅용 파드를 생성할 수 있습니다.
kubectl debug $(kubectl get pod -l app=deploy-websrv -oname) -it --image=nicolaka/netshoot -c netdebug --share-processes --copy-to=websrv-debug --profile='sysadmin'

netshoot 파드가 주입되어 총 3개의 컨테이너가 확인됩니다.

프로세스를 전부 복제했기 때문에, Nginx 또한 접속이 가능합니다.
이러한 방법을 통해 이스티오 뿐만 아니라 다른 보안 내용이 적용된 파드도 디버깅이 가능합니다.
이번 글에서는 Istio 와 Envoy 의 간략한 설명과 구성에 대해서 알아보았습니다.
다음 글에서는 실제 데모 어플리케이션을 배포하고 다양한 Istio 의 기능에 대해서 알아보겠습니다.