[KANS 3기] Service Mesh : Istio - Mode (Sidecar, Ambient)
들어가며
이번주에는 Service Mesh와 대표적인 오픈소스 프로젝트인 Istio에 대해 알아 보겠습니다. KANS 3기 7주차 스터디를 시작하겠습니다.
Istio 소개
Istio는 서비스 메시를 구축하고 관리하기 위한 오픈소스 플랫폼입니다. Istio를 알아보기에 앞서 서비스 메시에 대해 알아보겠습니다.
서비스 메시 (Service Mesh)란?
- 서비스 메시는 서비스 간 통신을 제어하고 모니터링하는 레이어를 제공하는 인프라스트럭처 계층입니다.
-
등장배경 : MSA 환경에서 서비스가 많아지다 보니 서비스 간 통신이 복잡해지고, 이로 인해 서비스 간 통신을 관리하고 모니터링하는 것이 어려워졌습니다.
이로인해 장애가 발생하거나 병목 현상이 발생했을때 원인과 발생하는 구간을 찾기가 어려워졌습니다. 이것을 해결 하기 위해 등장한 것이 서비스 메시입니다.
- 개념 : 마이크로 서비스 간에 통신이나 경로를 제어 - 예) istio, linkerd, consul, envoy, …
-
기본 동작 : 파드간 통신경로에 프록시를 두고 트래픽을 모니터링하거나 컨트롤 합니다. 따라서 기존 어플리케이션을 수정하지 않고도 적용할 수 있습니다.
위의 그림 처럼 서비스 메시는 각 파드에 프록시를 두고 프록시를 통해 통신을 하도록한 다음 프록시를 통해 트래픽을 모니터링하거나 컨트롤 합니다.
- 이때 프록시는 Sidecar 모드로 동작하거나 Ambient 모드로 동작하며 대표적인 프록시로는 Envoys가 있습니다.
- Envoy는 구글, IBM, Lyft가 중심이 되어 개발하고 있는 오픈소스 프록시입니다.
- 네트워크 투명성을 목표로 다양한 필터 체인 지원(L3, L4, L7), 동적 Configuration API를 제공하고, hot reload를 지원합니다.
-
주요기능
- 트래픽 모니터링 : 요청의 에러율, 지연시간, 컨넥션 개수, 요청개수 등의 메트릭을 수집하여 모니터링하고, 서비스간 혹은 특정 요청 경로를 필터링 할 수 있습니다. => 원인 파악 용이
-
트래픽 컨트롤
- 트래픽 시프팅(traffic shifting) : 트래픽을 서비스간에 분산시키는 기능으로, 특정 단말/사용자는 신규 어플리케이션에 연결하도록 하는 카나리 배포등에 활용할 수도 있습니다.
- 서킷 브레이커(circuit breaker) : 특정 서비스에 문제가 있을때 접속을 차단하고, 출발지 서비스에 에러를 반환하도록 하는 기능입니다. (연쇄장애, 시스템 전체 장애 방지)
- 플트 인젝션(fault injection) : 의도적으로 요청을 지연시키거나 실패하도록 할 수 있습니다. (비정상 상황 테스트)
- 속도 제한(rate limiting) : 특정 서비스에 대한 요청 개수를 제한하는 기능입니다.
Envoy
- 지난주에 살짝 언급되었던 내용인데 이번 주에 좀 더 자세히 알아보겠습니다.
- Envoy는 구글, IBM, Lyft가 중심이 되어 개발하고 있는 오픈소스 프록시입니다.
- Istio의 핵심 기능들은 Envoy를 감싼 istio proxy를 통해 이루어지므로 Envoy에 대한 이해가 필요합니다.
- Envoy에서 사용하는 용어 들을 정리해 보았습니다.
용어 | 설명 |
---|---|
Cluster | envoy가 트래픽을 포워딩할 수 있는 논리적 서비스 (엔드포인트 셋트) |
Endpoint | IP 주소와 포트 번호로 구성된 서비스의 실제 인스턴스. 엔드포인트가 모여서 하나의 Cluster를 이룸 |
Listener | 클라이언트가 접속하는 포트, 유닉스 도메인 소켓 등을 노출하고, 다운스트림으로 부터 받은 요청을 처리 |
Route | Listener로 들어온 요청을 어떤 클러스터로 보낼지 정의 |
Filter | Listener로 부터 서비스에 트래픽 전달하기 전에 트래픽을 가공하거나 차단하는 역할을 하는 컴포넌트 |
UpStream | envoy 요청을 포워딩해서 연결하는 백엔드 네트워크 노드 - 사이드카일때는 application app, 아닐때는 원격 백엔드 |
DownStream | envoy로 연결하여 요청을 보내는 개체. 사이드카가 아닐때는 원격지의 클라이언트 |
Host | 네트워크 통신이 가능한 개체 (PC, 서버, 휴대폰, 네트워크 어플리케이션 등) |
- 많은 Service Mesh 솔루션이나, Gateway API 구현체들이 내부적으로 Envoy를 사용하고 있으며, Envoy가 제공하는 동적 구성을 위한 API(xDS Sync API)를 이용하여 다양한 네트워크 정책을 구성하게 됩니다.
- Envoy의 xDS Sync API는 아래와 같은 레이어에서 동작합니다.
- LDS - Listener Discovery Service
- RDS - Route Discovery Service
- CDS - Cluster Discovery Service
- EDS - Endpoint Discovery Service
Envoy 실습
- test pc에 Envoy 설치
# 설치
# echo "deb [signed-by=/etc/apt/keyrings/envoy-keyring.gpg] https://apt.envoyproxy.io focal main" | sudo tee /etc/apt/sources.list.d/envoy.list
$ 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
# 확인
$ envoy --version
# => envoy version: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9/1.32.0/Clean/RELEASE/BoringSSL
# 도움말
$ envoy --help
- Envoy proxy 실습 - Link
- envoy-demo.yml 작성
# envoy-demo.yml 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
-
실행
# (터미널1) 데모 config 적용하여 실행 $ curl -O https://www.envoyproxy.io/docs/envoy/latest/_downloads/92dcb9714fb6bc288d042029b34c0de4/envoy-demo.yaml $ envoy -c envoy-demo.yaml # => [2024-10-01 16:41:51.547][4479][info][main] [source/server/server.cc:426] initializing epoch 0 (base id=0, hot restart version=11.104) # ... # (터미널2) 정보 확인 $ ss -tnlp # => State Recv-Q Send-Q Local Address:Port Peer Address:Port Process # LISTEN 0 4096 0.0.0.0:10000 0.0.0.0:* users:(("envoy",pid=4479,fd=35)) # LISTEN 0 4096 0.0.0.0:10000 0.0.0.0:* users:(("envoy",pid=4479,fd=34)) # LISTEN 0 4096 0.0.0.0:10000 0.0.0.0:* users:(("envoy",pid=4479,fd=33)) # LISTEN 0 4096 0.0.0.0:10000 0.0.0.0:* users:(("envoy",pid=4479,fd=32)) # ... # 접속 테스트 $ curl -s http://127.0.0.1:10000 | grep -o "<title>.*</title>" # => <title>Envoy proxy - home</title> # 외부 접속 정보 출력 $ echo -e "http://$(curl -s ipinfo.io/ip):10000" # => http://54.123.42.212:10000 -------------------- # 자신의 PC 웹브라우저에서 외부 접속 정보 접속 확인! # k3s-m 에서 접속 테스트 $ curl -s http://192.168.56.104:10000 | grep -o "<title>.*</title>" # => <title>Envoy proxy - home</title> -------------------- # 연결 정보 확인 $ ss -tnp # (터미널1) envoy 실행 취소(CTRL+C) 후 (관리자페이지) 설정 덮어쓰기 - 링크 $ 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)" # envoy 관리페이지 외부 접속 정보 출력 $ echo -e "http://$(curl -s ipinfo.io/ip):9902" # => http://54.123.42.212:9902 -------------------- # 자신의 PC 웹브라우저에서 관리 페이지 외부 접속 정보 접속 확인!
PC에서 접속한 화면
- envoy-demo.yml 작성
Istio 소개
- Istio는 서비스 메시를 구축하고 관리하기 위한 오픈소스 플랫폼입니다.
-
Istio의 구성
- 파일럿(Pilot) : 모든 Envoy 사이드카에서 프록시 라우팅 규칙을 관리하며, 서비스 디스커버리, 로드밸런싱 설정을 제공합니다.
- 겔리(Galley) : Istio와 쿠버네티스를 연결하는 역할을 합니다. 서비스 메시 구성 데이터를 검증하고 변환합니다.
- 시타델(Citadel) : 서비스 간의 인증과 보안을 관리합니다. 서비스 간의 TLS 통신을 제공하고, 서비스 간의 인증을 관리합니다.
- Istio의 구성요소
- istiod : Istio의 중앙 제어 플레인으로, Pilot, Citadel, Galley를 포함합니다.
- istio proxy : Envoy 기반의 프록시로, istiod와 통신하며, 서비스 트래픽을 통제하고 옵저빌리티를 위한 메트릭을 제공합니다.
- 특징
- Istio는 각 파드안에서 사이드카로 동작하는 Envoy가 트래픽을 제어하고 모니터링합니다.
- 모든 마이크로 서비스간 통신은 Envoy를 통해 이루어지며, 이를 통해 메트릭을 수집하거나 컨트롤 할 수 있습니다.
- 트래픽을 컨트롤 하기 위해서 Envoy 프록시에 전송룰을 정의 합니다.
- 마이크로 서비스간의 통신을 mutual TLS 인증(mTLS)을 통해 보안합니다.
- 각 어플리케이션은 파드 내의 엔보이 프록시에 접속하기 위해 localhost에 TCP 접속을 합니다.
Istio 설치 (Sidecar 모드)
- Istio 공식 문서 : Link
- Istio 설치 ```bash
# 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 -
$ tree istio-$ISTIOV -L 2 # sample yaml 포함
# => istio-1.23.2
# ├── LICENSE
# ├── README.md
# ├── bin
# │ └── istioctl
# ├── manifest.yaml
# ├── manifests
# │ ├── charts
# │ └── profiles
# ├── samples
# │ ...
# └── tools
# ├── _istioctl
# ├── certs
# └── istioctl.bash
$ cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
$ istioctl version --remote=false
# => client version: 1.23.2
# (demo 프로파일) 컨트롤 플레인 배포 - 링크 Customizing
# The istioctl command supports the full IstioOperator API via command-line options for individual settings or for passing a yaml file containing an IstioOperator custom resource (CR).
$ istioctl profile list
# => Istio configuration profiles:
# ambient
# default
# demo
# empty
# minimal
# openshift
# openshift-ambient
# preview
# remote
# stable
$ istioctl profile dump default
$ istioctl profile dump --config-path components.ingressGateways
# => - enabled: true
# name: istio-ingressgateway
$ istioctl profile dump --config-path values.gateways.istio-ingressgateway
# => {}
$ istioctl profile dump demo
# => apiVersion: install.istio.io/v1alpha1
# kind: IstioOperator
# spec:
# components:
# base:
# enabled: true
# egressGateways:
# - enabled: true
# name: istio-egressgateway
# ingressGateways:
# - enabled: true
# name: istio-ingressgateway
# pilot:
# enabled: true
# hub: docker.io/istio
# profile: demo
# tag: 1.23.2
# values:
# defaultRevision: ""
# gateways:
# istio-egressgateway: {}
# istio-ingressgateway: {}
# global:
# configValidation: true
# istioNamespace: istio-system
# profile: demo
$ istioctl profile dump demo > demo-profile.yaml
$ vi demo-profile.yaml # 복잡성을 줄이게 실습 시나리오 환경 맞춤
--------------------
egressGateways:
- enabled: false
--------------------
$ istioctl install -f demo-profile.yaml -y
# => ✔ Istio core installed ⛵️
# ✔ Istiod installed 🧠
# ✔ Ingress gateways installed 🛬
# ✔ Installation complete
# Made this installation the default for cluster-wide operations.
# 설치 확인 : istiod, istio-ingressgateway
$ kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
# => NAME READY STATUS RESTARTS AGE
# pod/istio-ingressgateway-5f9f654d46-l7mqp 1/1 Running 0 14m
# pod/istiod-7f8b586864-8mc4c 1/1 Running 1 (82s ago) 14m
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/istio-ingressgateway LoadBalancer 10.10.200.171 192.168.10.101,192.168.10.102,192.168.10.103 15021:30953/TCP,80:31677/TCP,443:30737/TCP,31400:30617/TCP,15443:31668/TCP 14m
# service/istiod ClusterIP 10.10.200.215 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 14m
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/istio-ingressgateway 1/1 1 1 14m
# deployment.apps/istiod 1/1 1 1 14m
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/istio-ingressgateway-5f9f654d46 1 1 1 14m
# replicaset.apps/istiod-7f8b586864 1 1 1 14m
#
# NAME ENDPOINTS AGE
# endpoints/istio-ingressgateway 172.16.2.14:15443,172.16.2.14:15021,172.16.2.14:31400 + 2 more... 14m
# endpoints/istiod 172.16.3.16:15012,172.16.3.16:15010,172.16.3.16:15017 + 1 more... 14m
#
# NAME SECRETS AGE
# serviceaccount/istio-ingressgateway-service-account 0 14m
# serviceaccount/istio-reader-service-account 0 14m
# serviceaccount/istiod 0 14m
# ...
#
# NAME DATA AGE
# configmap/istio 2 14m
# configmap/istio-sidecar-injector 2 14m
# ...
#
# NAME TYPE DATA AGE
# secret/istio-ca-secret istio.io/ca-root 5 14m
#
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# poddisruptionbudget.policy/istio-ingressgateway 1 N/A 0 14m
# poddisruptionbudget.policy/istiod 1 N/A 0 14m
$ kubectl get crd | grep istio.io | sort
# => authorizationpolicies.security.istio.io 2024-10-01T05:26:47Z
# destinationrules.networking.istio.io 2024-10-01T05:26:47Z
# envoyfilters.networking.istio.io 2024-10-01T05:26:47Z
# gateways.networking.istio.io 2024-10-01T05:26:47Z
# peerauthentications.security.istio.io 2024-10-01T05:26:47Z
# proxyconfigs.networking.istio.io 2024-10-01T05:26:47Z
# requestauthentications.security.istio.io 2024-10-01T05:26:47Z
# serviceentries.networking.istio.io 2024-10-01T05:26:47Z
# sidecars.networking.istio.io 2024-10-01T05:26:47Z
# telemetries.telemetry.istio.io 2024-10-01T05:26:48Z
# virtualservices.networking.istio.io 2024-10-01T05:26:48Z
# wasmplugins.extensions.istio.io 2024-10-01T05:26:48Z
# workloadentries.networking.istio.io 2024-10-01T05:26:48Z
# workloadgroups.networking.istio.io 2024-10-01T05:26:48Z
# istio-ingressgateway 의 envoy 버전 확인
$ kubectl exec -it deploy/istio-ingressgateway -n istio-system -c istio-proxy -- envoy --version
# => envoy version: 6c72b2179f5a58988b920a55b0be8346de3f7b35/1.31.2-dev/Clean/RELEASE/BoringSSL
# istio-ingressgateway 서비스 NodePort로 변경
$ kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"type":"NodePort"}}'
# => service/istio-ingressgateway patched
# istio-ingressgateway 서비스 확인
$ kubectl get svc,ep -n istio-system istio-ingressgateway
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/istio-ingressgateway NodePort 10.10.200.171 <none> 15021:30953/TCP,80:31677/TCP,443:30737/TCP,31400:30617/TCP,15443:31668/TCP 16m
#
# NAME ENDPOINTS AGE
# endpoints/istio-ingressgateway 172.16.2.14:15443,172.16.2.14:15021,172.16.2.14:31400 + 2 more... 16m
## istio-ingressgateway 서비스 포트 정보 확인
$ kubectl get svc -n istio-system istio-ingressgateway -o jsonpath={.spec.ports[*]} | jq
# => {
# "name": "https",
# "nodePort": 30737,
# "port": 443,
# "protocol": "TCP",
# "targetPort": 8443
# }
# {
# "name": "tcp",
# "nodePort": 30617,
# "port": 31400,
# "protocol": "TCP",
# "targetPort": 31400
# }
# ...
## istio-ingressgateway 디플로이먼트 파드의 포트 정보 확인
$ kubectl get deploy/istio-ingressgateway -n istio-system -o jsonpath={.spec.template.spec.containers[0].ports[*]} | jq
# => ...
# {
# "containerPort": 8443,
# "protocol": "TCP"
# }
# {
# "containerPort": 31400,
# "protocol": "TCP"
# }
# ...
$ kubectl get deploy/istio-ingressgateway -n istio-system -o jsonpath={.spec.template.spec.containers[0].readinessProbe} | jq
# => {
# "failureThreshold": 30,
# "httpGet": {
# "path": "/healthz/ready",
# "port": 15021,
# "scheme": "HTTP"
# },
# "initialDelaySeconds": 1,
# "periodSeconds": 2,
# "successThreshold": 1,
# "timeoutSeconds": 1
# }
# 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
# => UID PID PPID C STIME TTY TIME CMD
# istio-p+ 1 0 0 05:39 ? 00:00:01 /usr/local/bin/pilot-discovery discovery --monitoringAddr=:15014 --log_output_level=default:info --domain cluster.local --ke
# 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
# => UID PID PPID C STIME TTY TIME CMD
# istio-p+ 1 0 0 05:27 ? 00:00:08 /usr/local/bin/pilot-agent proxy router --domain istio-system.svc.cluster.local
# istio-p+ 16 1 0 05:27 ? 00:00:04 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drai
# # <span style="color: green;">👉 pilot-agent와 envoy가 동작 중입니다.</span>
# envoy 설정 확인
$ 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
# => Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# u_str LISTEN 0 4096 var/run/secrets/workload-spiffe-uds/socket 32430 * 0 users:(("pilot-agent",pid=1,fd=9))
# u_str LISTEN 0 4096 etc/istio/proxy/XDS 32431 * 0 users:(("pilot-agent",pid=1,fd=10))
$ kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ss -xnp
# => Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# u_str ESTAB 0 0 * 39978 * 37977 users:(("envoy",pid=16,fd=19))
# u_str ESTAB 0 0 * 37501 * 37981 users:(("envoy",pid=16,fd=32))
# u_str ESTAB 0 0 etc/istio/proxy/XDS 37977 * 39978 users:(("pilot-agent",pid=1,fd=11))
# u_str ESTAB 0 0 var/run/secrets/workload-spiffe-uds/socket 37981 * 37501 users:(("pilot-agent",pid=1,fd=15))
- Auto Injection with namespace label
-
해당 네임스페이스에 생성되는 모든 파드들은 istio 사이드카가 자동으로 injection 됩니다.
# mutating Webhook admisstion controller 사용 $ kubectl label namespace default istio-injection=enabled # => namespace/default labeled $ kubectl get ns -L istio-injection # => NAME STATUS AGE ISTIO-INJECTION # default Active 7d enabled # ...
- Istio 접속 테스트를 위한 변수 지정 및 k3s-m에서 접속 테스트
# k3s-m)
# istio ingress gw NodePort(HTTP 접속용) 변수 지정
$ export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}')
$ echo $IGWHTTP
# => 31677
# /etc/hosts 파일 수정
# $ MYDOMAIN=<각자 자신의 www 도메인> # 단, 사용하고 있지 않는 공인 도메인을 사용 할 것
# $ echo "<istio-ingressgateway 파드가 있는 워커 노드> $MYDOMAIN" >> /etc/hosts
$ export MYDOMAIN=sweetlittlebird.com
$ echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts
$ echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
# istio ingress gw 접속 테스트 : 아직은 설정이 없어서 접속 실패가 됩니다.
$ curl -v -s $MYDOMAIN:$IGWHTTP
# => * Trying 192.168.10.10:31677...
# * connect to 192.168.10.10 port 31677 failed: Connection refused
# * Failed to connect to sweetlittlebird.com port 31677 after 3 ms: Connection refused
# * Closing connection 0
- testpc에서 접속 테스트
# 아래 변수는 각자 자신의 값을 직접 입력 할 것
# $ IGWHTTP=<각자 출력된 NodePort>
$ IGWHTTP=31677
$ export MYDOMAIN=sweetlittlebird.com
$ echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts
$ echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
# istio ingress gw 접속 테스트 : 아직은 설정이 없어서 접속 실패가 된다
$ curl -v -s $MYDOMAIN:$IGWHTTP
# => * Host sweetlittlebird.com:31677 was resolved.
# * ...
# * Failed to connect to sweetlittlebird.com port 31677 after 2 ms: Couldn't connect to server
# * ...
- 자신의 pc에서 접속 테스트
# 아래 변수는 각자 자신의 값을 직접 입력 할 것
# $ IGWHTTP=<각자 출력된 NodePort>
$ IGWHTTP=31677
# $ ISTIONODEIP=<k3s-m 의 유동 공인 IP>
$ ISTIONODEIP=54.123.42.212
$ export MYDOMAIN=sweetlittlebird.com
$ echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts
$ echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile
# istio ingress gw 접속 테스트 : 아직은 설정이 없어서 접속 실패가 된다
$ curl -v -s $MYDOMAIN:$IGWHTTP
# => * Host sweetlittlebird.com:31677 was resolved.
# * ...
# * Failed to connect to sweetlittlebird.com port 31677 after 2 ms: Couldn't connect to server
# * ...
Istio를 통한 외부 노출
- Nginx 디플로이먼트와 서비스 배포
# 로그 모니터링
$ kubectl get pod -n istio-system -l app=istiod
# => NAME READY STATUS RESTARTS AGE
# istiod-7f8b586864-8mc4c 1/1 Running 1 (60m ago) 73m
$ kubetail -n istio-system -l app=istiod -f
$ kubectl get pod -n istio-system -l app=istio-ingressgateway
# => NAME READY STATUS RESTARTS AGE
# istio-ingressgateway-5f9f654d46-l7mqp 1/1 Running 0 74m
$ kubetail -n istio-system -l app=istio-ingressgateway -f
$ 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
# => serviceaccount/kans-nginx created
# deployment.apps/deploy-websrv created
# service/svc-clusterip created
# 사이드카 컨테이너 배포 확인
$ kubectl get pod,svc,ep,sa -o wide
# => NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# pod/deploy-websrv-778ffd6947-cxf5k 2/2 Running 0 50s 172.16.1.13 k3s-w2 <none> <none>
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# service/kubernetes ClusterIP 10.10.200.1 <none> 443/TCP 6d16h <none>
# service/svc-clusterip ClusterIP 10.10.200.243 <none> 80/TCP 50s app=deploy-websrv
#
# NAME ENDPOINTS AGE
# endpoints/kubernetes 192.168.10.10:6443 6d16h
# endpoints/svc-clusterip 172.16.1.13:80 50s
#
# NAME SECRETS AGE
# serviceaccount/default 0 7d1h
# serviceaccount/kans-nginx 0 50s
$ kubectl describe pod
# => Name: deploy-websrv-778ffd6947-cxf5k
# Namespace: default
# Priority: 0
# Service Account: kans-nginx
# Node: k3s-w2/192.168.10.102
# Labels: app=deploy-websrv
# security.istio.io/tlsMode=istio
# ...
# Annotations: istio.io/rev: default
# sidecar.istio.io/status:
# {"initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["workload-socket","credential-socket","workload-certs","istio-env...
# Status: Running
# ...
# Controlled By: ReplicaSet/deploy-websrv-778ffd6947
# <span style="color: red;">Init Containers:</span> # <span style="color: green;">👉 init container가 파드내 iptables 셋팅</span>
# istio-init:
# Container ID: containerd://2a114fe0624581b35bda9ca257c6d3c831138e8a44900a6130e988bb51eb05da
# Image: docker.io/istio/proxyv2:1.23.2
# Image ID: docker.io/istio/proxyv2@sha256:2876cfc2fdf47e4b9665390ccc9ccf2bf913b71379325b8438135c9f35578e1a
# Port: <none>
# Host Port: <none>
# Args:
# <span style="color: red;">istio-iptables</span>
# -p
# 15001
# -z
# 15006
# -u
# 1337
# -m
# REDIRECT
# -i
# *
# -x
#
# -b
# *
# -d
# 15090,15021,15020
# --log_output_level=default:info
# State: Terminated
# Reason: Completed
# ...
# Containers:
# deploy-websrv:
# Container ID: containerd://8918e0bb760bce8d090e84818bc189ae3ababdf9e74eb7dd3fb9709b356891f9
# Image: nginx:alpine
# ...
# <span style="color: red;">istio-proxy:</span> # <span style="color: green;">👉 istio-proxy라는 컨테이너가 sidecar로 동작 중</span>
# Container ID: containerd://71d9e07a530dfce2ec34810d60a28dc3f9445b8eab714c2a7e204c459c59bcd3
# 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
# State: Running
# ...
- Istio Gateway/VirtualService 설정 - Host 기반 트래픽 라우팅 설정 링크
- 클라이언트 PC → (Service:NodePort) Istio ingressgateway 파드 → (Gateway, VirtualService, Service 는 Bypass) → Endpoint(파드 : 사이드카 - Application 컨테이너)
- Gateway : 지정한 인그레스 게이트웨이로부터 트래픽이 인입, 프로토콜 및 포트, HOSTS, Proxy 등 설정 가능 링크
- VirtualService : 인입 처리할 hosts 설정, L7 PATH 별 라우팅, 목적지에 대한 정책 설정 가능 (envoy route config)
- (참고) Introducing Istio v1 APIs - Blog
$ 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
# => gateway.networking.istio.io/test-gateway created
# virtualservice.networking.istio.io/nginx-service created
# Istio Gateway(=gw)/VirtualService(=vs) 설정 정보를 확인
$ kubectl explain gateways.networking.istio.io
$ kubectl explain virtualservices.networking.istio.io
$ kubectl api-resources | grep istio
# => wasmplugins extensions.istio.io/v1alpha1 true WasmPlugin
# destinationrules dr networking.istio.io/v1 true DestinationRule
# envoyfilters networking.istio.io/v1alpha3 true EnvoyFilter
# gateways gw networking.istio.io/v1 true Gateway
# proxyconfigs networking.istio.io/v1beta1 true ProxyConfig
# serviceentries se networking.istio.io/v1 true ServiceEntry
# sidecars networking.istio.io/v1 true Sidecar
# virtualservices vs networking.istio.io/v1 true VirtualService
# workloadentries we networking.istio.io/v1 true WorkloadEntry
# workloadgroups wg networking.istio.io/v1 true WorkloadGroup
# authorizationpolicies ap security.istio.io/v1 true AuthorizationPolicy
# peerauthentications pa security.istio.io/v1 true PeerAuthentication
# requestauthentications ra security.istio.io/v1 true RequestAuthentication
# telemetries telemetry telemetry.istio.io/v1 true Telemetry
# virtual service 는 다른 네임스페이스의 서비스(ex. svc-nn.<ns>)도 참조할 수 있다
$ kubectl get gw,vs
# => NAME AGE
# gateway.networking.istio.io/test-gateway 105s
#
# NAME GATEWAYS HOSTS AGE
# virtualservice.networking.istio.io/nginx-service ["test-gateway"] ["sweetlittlebird.com"] 105s
# Retrieves last sent and last acknowledged xDS sync from Istiod to each Envoy in the mesh
# istioctl proxy-status command was improved to include the time since last change, and more relevant status values.
$ istioctl proxy-status # 단축어 ps
$ istioctl ps
# => NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
# deploy-websrv-778ffd6947-cxf5k.default Kubernetes SYNCED (22m) SYNCED (22m) SYNCED (22m) SYNCED (22m) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# istio-ingressgateway-5f9f654d46-l7mqp.istio-system Kubernetes SYNCED (2m14s) SYNCED (2m14s) SYNCED (22m) SYNCED (2m14s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
- Istio를 통한 Nginx 파드 접속 테스트
- 외부 (자신의 PC, test pc)에서 접속 테스트
# istio ingress gw 를 통한 접속 테스트
$ curl -s $MYDOMAIN:$IGWHTTP | grep -o "<title>.*</title>"
# => <title>Welcome to nginx!</title>
$ curl -v -s $MYDOMAIN:$IGWHTTP
# => * Host sweetlittlebird.com:31677 was resolved.
# * IPv6: (none)
# * IPv4: 192.168.10.10, 192.168.10.10
# * Trying 192.168.10.10:31677...
# * Connected to sweetlittlebird.com (192.168.10.10) port 31677
# > GET / HTTP/1.1
# > Host: sweetlittlebird.com:31677
# > User-Agent: curl/8.5.0
# > Accept: */*
# >
# < HTTP/1.1 200 OK
# < server: istio-envoy
# < date: Sat, 19 Oct 2024 07:14:25 GMT
# < content-type: text/html
# < content-length: 615
# < last-modified: Wed, 02 Oct 2024 16:07:39 GMT
# < etag: "66fd6fcb-267"
# < accept-ranges: bytes
# < x-envoy-upstream-service-time: 1
# ...
# $ curl -v -s <유동공인이IP>:$IGWHTTP
$ curl -v -s 54.123.42.212:$IGWHTTP
- 출력 로그 정보 확인
$ kubetail -n istio-system -l app=istio-ingressgateway -f
# => [istio-ingressgateway-5f9f654d46-l7mqp] [2024-10-01T07:49:20.833Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 6 5 "172.16.0.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15" "6f246b4e-d675-971e-9a9d-dd809f560c6f" "sweetlittlebird.com:31677" "172.16.1.13:80"
# <span style="color: red;">outbound|80||svc-clusterip.default.svc.cluster.local</span> 172.16.2.14:60786 172.16.2.14:8080 172.16.0.0:8773 - -
$ kubetail -l app=deploy-websrv
# => [deploy-websrv-778ffd6947-cxf5k istio-proxy] [2024-10-01T07:49:20.866Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 2 1 "172.16.0.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15" "6f246b4e-d675-971e-9a9d-dd809f560c6f" "sweetlittlebird.com:31677" "172.16.1.13:80"
# <span style="color: red;">inbound|80||</span> 127.0.0.6:40337 172.16.1.13:80 172.16.0.0:0 invalid:outbound_.80_._.svc-clusterip.default.svc.cluster.local default
- istioctl 정보 확인
#
$ istioctl proxy-status
# => NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
# deploy-websrv-778ffd6947-cxf5k.default Kubernetes SYNCED (19m) SYNCED (19m) SYNCED (19m) SYNCED (19m) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# istio-ingressgateway-5f9f654d46-l7mqp.istio-system Kubernetes SYNCED (24m) SYNCED (24m) SYNCED (24m) SYNCED (24m) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# Envoy config dump : all, cluster, endpoint, listener 등
$ istioctl proxy-config --help
$ istioctl proxy-config all deploy-websrv-778ffd6947-cxf5k
$ istioctl proxy-config all deploy-websrv-778ffd6947-cxf5k -o json | jq
$ istioctl proxy-config route deploy-websrv-778ffd6947-cxf5k -o json | jq
- pilot : istio-proxy내 uds로 envoy와 grpc통신, istiod에서 받아온 dynamic config를 envoy에 전달
# istio-proxy 사용자 정보 확인 : uid(1337):gid(1337) 확인 -> iptables rule 에서 사용됨
$ kubectl exec -it deploy/deploy-websrv -c istio-proxy -- tail -n 3 /etc/passwd
# => ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
# tcpdump:x:100:102::/nonexistent:/usr/sbin/nologin
# istio-proxy:x:1337:1337::/home/istio-proxy:/bin/sh
# envoy 설정 정보 확인 : dynamic_resources , static_resources - listeners
$ kubectl exec -it deploy/deploy-websrv -c istio-proxy -- cat /etc/istio/proxy/envoy-rev.json
$ 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
# => Active Internet connections (w/o servers)
# Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
# tcp 0 0 127.0.0.1:33168 127.0.0.1:15020 ESTABLISHED 13/envoy
# tcp 0 0 127.0.0.1:33156 127.0.0.1:15020 ESTABLISHED 13/envoy
# tcp 0 0 172.16.1.13:15006 172.16.2.14:33006 ESTABLISHED 13/envoy
# tcp 0 0 172.16.1.13:59774 10.10.200.215:15012 ESTABLISHED 1/pilot-agent
# tcp 0 0 172.16.1.13:15006 172.16.2.14:60786 ESTABLISHED 13/envoy
# <span style="color: green;">👉 172.16.1.13 : deploy-websrv 파드 ip</span>
# <span style="color: green;"> 172.16.2.14 : istio-ingressgateway 파드 ip</span>
# <span style="color: green;"> 10.10.200.215 : istiod 서비스의 Cluster-IP</span>
# ...
# Active UNIX domain sockets (w/o servers)
# Proto RefCnt Flags Type State I-Node PID/Program name Path
# unix 3 [ ] STREAM CONNECTED 54162 13/envoy
# unix 3 [ ] STREAM CONNECTED 54709 1/pilot-agent var/run/secrets/workload-spiffe-uds/socket
# unix 3 [ ] STREAM CONNECTED 71034 1/pilot-agent etc/istio/proxy/XDS
# unix 3 [ ] STREAM CONNECTED 72979 13/envoy
# ...
#
$ kubectl exec -it deploy/deploy-websrv -c istio-proxy -- ps -ef
# => UID PID PPID C STIME TTY TIME CMD
# istio-p+ 1 0 0 06:43 ? 00:00:01 /usr/local/bin/pilot-agent proxy sidecar --domain default.svc.cluster.local --proxyLogLevel=warning --proxyComponentLogLevel=mi
# istio-p+ 13 1 0 06:43 ? 00:00:34 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-strategy immediate --local-address-ip-version
#
$ kubectl get pod,svc -A -owide
# => NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# default pod/deploy-websrv-778ffd6947-cxf5k 2/2 Running 0 91m 172.16.1.13 k3s-w2 <none> <none>
# istio-system pod/istio-ingressgateway-5f9f654d46-l7mqp 1/1 Running 0 167m 172.16.2.14 k3s-w3 <none> <none>
# istio-system pod/istiod-7f8b586864-8mc4c 1/1 Running 1 (154m ago) 167m 172.16.3.16 k3s-w1 <none> <none>
# ...
#
# NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# default service/kubernetes ClusterIP 10.10.200.1 <none> 443/TCP 6d17h <none>
# default service/svc-clusterip ClusterIP 10.10.200.243 <none> 80/TCP 91m app=deploy-websrv
# istio-system service/istio-ingressgateway NodePort 10.10.200.171 <none> 15021:30953/TCP,80:31677/TCP,443:30737/TCP,31400:30617/TCP,15443:31668/TCP 167m app=istio-ingressgateway,istio=ingressgateway
# istio-system service/istiod ClusterIP 10.10.200.215 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 167m app=istiod,istio=pilot
# ...
$ kubectl exec -it deploy/deploy-websrv -c istio-proxy -- netstat -antp
# => Active Internet connections (servers and established)
# Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
# tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
# ...
# tcp 0 0 127.0.0.1:33168 127.0.0.1:15020 ESTABLISHED 13/envoy
# tcp 0 0 127.0.0.1:33156 127.0.0.1:15020 ESTABLISHED 13/envoy
# tcp 0 0 172.16.1.13:15006 172.16.2.14:33006 ESTABLISHED 13/envoy
# tcp 0 0 172.16.1.13:59774 10.10.200.215:15012 ESTABLISHED 1/pilot-agent
# tcp 0 0 172.16.1.13:15006 172.16.2.14:60786 ESTABLISHED 13/envoy
# tcp6 0 0 127.0.0.1:15020 127.0.0.1:33168 ESTABLISHED 1/pilot-agent
# tcp6 0 0 127.0.0.1:15020 127.0.0.1:33156 ESTABLISHED 1/pilot-agent
# istiod 정보 같이 확인 : 출력되는 IP가 누구인지 확인 해보자
$ kubectl get pod,svc -A -owide
$ kubectl exec -it deploy/istiod -n istio-system -- ps -ef
$ kubectl exec -it deploy/istiod -n istio-system -- netstat -antp
$ kubectl exec -it deploy/istiod -n istio-system -- ss -nlp
$ kubectl exec -it deploy/istiod -n istio-system -- ss -np
# => Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# tcp ESTAB 0 0 172.16.3.16:33552 10.10.200.1:443 users:(("pilot-discovery",pid=1,fd=7))
# tcp ESTAB 0 0 [::ffff:172.16.3.16]:15012 [::ffff:172.16.2.14]:37102 users:(("pilot-discovery",pid=1,fd=13))
# tcp ESTAB 0 0 [::ffff:172.16.3.16]:15012 [::ffff:172.16.1.13]:56560 users:(("pilot-discovery",pid=1,fd=14))
- istio-proxy, istiod가 각각 사용하는 포트 정보 링크
-
istio-proxy
Port Protocol Description Pod-internal only 15000 TCP Envoy admin port (commands/diagnostics) Yes 15001 TCP Envoy outbound No 15004 HTTP Debug port Yes 15006 TCP Envoy inbound No 15008 HTTP2 HBONE mTLS tunnel port No 15020 HTTP Merged Prometheus telemetry from Istio agent, Envoy, and application No No 15021 HTTP Health checks No 15053 DNS DNS port, if capture is enabled Yes 15090 HTTP Envoy Prometheus telemetry No -
istiod (컨트롤플레인)
Port Protocol Description Pod-internal only 443 HTTPS Webhooks service port No 8080 HTTP Debug interface (deprecated, container port only) No 15010 GRPC XDS and CA services (Plaintext, only for secure networks) No 15012 GRPC XDS and CA services (TLS and mTLS, recommended for production use) No 15014 HTTP Control plane monitoring No 15017 HTTPS Webhook container port, forwarded from 443 No
-
- Istio - Istio proxy와 Envoy 프로세스간 유닉스 도메인 소켓 통신 확인
# 파드 확인
$ kubectl get pod
# => NAME READY STATUS RESTARTS AGE
# deploy-websrv-778ffd6947-cxf5k 2/2 Running 0 106m
# istio 컨테이너 접속
$ kubectl exec -it deploy/deploy-websrv -c istio-proxy -- bash
---------------------------------------------------------------
# SDS, XDS 는 소켓 타입
$ ls -al /etc/istio/proxy
# => total 24
# drwxrwxrwt 2 root root 100 Oct 19 06:43 .
# drwxr-xr-x 4 root root 4096 Oct 19 06:43 ..
# srw-rw-rw- 1 istio-proxy istio-proxy 0 Oct 19 06:43 XDS
# -rw-r--r-- 1 istio-proxy istio-proxy 13644 Oct 19 06:43 envoy-rev.json
# -rw-r--r-- 1 istio-proxy istio-proxy 2747 Oct 19 06:43 grpc-bootstrap.json
# .json 파일 확인
$ more /etc/istio/proxy/envoy-rev.json
# => {
# "node": {
# "id": "sidecar~172.16.1.13~deploy-websrv-778ffd6947-cxf5k.default~default.svc.cluster.local",
# "cluster": "deploy-websrv.default",
# ...
# "admin": {
# "access_log": [
# {
# "name": "envoy.access_loggers.file",
# "typed_config": {
# "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog",
# "path": "/dev/null"
# }
# }
# ],
# "profile_path": "/var/lib/istio/data/envoy.prof",
# "address": {
# "socket_address": {
# "address": "127.0.0.1",
# "port_value": 15000
# }
# }
# },
# ...
# "dynamic_resources": {
# "lds_config": {
# "ads": {},
# "initial_fetch_timeout": "0s",
# "resource_api_version": "V3"
# },
# "cds_config": {
# "ads": {},
# "initial_fetch_timeout": "0s",
# "resource_api_version": "V3"
# },
# "ads_config": {
# "api_type": "GRPC",
# "set_node_on_first_message_only": true,
# "transport_api_version": "V3",
# "grpc_services": [
# {
# "envoy_grpc": {
# "cluster_name": "xds-grpc"
# ...
# "static_resources": {
# "clusters": [
# {
# ...
# "name": "xds-grpc",
# "alt_stat_name": "xds-grpc;",
# "type" : "STATIC",
# "connect_timeout": "1s",
# "lb_policy": "ROUND_ROBIN",
# "load_assignment": {
# "cluster_name": "xds-grpc",
# "endpoints": [{
# "lb_endpoints": [{
# "endpoint": {
# "address":{
# "pipe": {
# "path": "./etc/istio/proxy/XDS"
# }
# ...
# "listeners":[
# ...
# "address": {
# "socket_address": {
# "protocol": "TCP",
# "address": "0.0.0.0",
# "port_value": 15090
# }
# },
# "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",
# "codec_type": "AUTO",
# "stat_prefix": "agent",
# "route_config": {
# "virtual_hosts": [
# {
# "name": "backend",
# "domains": [
# "*"
# ],
# "routes": [
# {
# "match": {
# "prefix": "/healthz/ready"
# },
# "route": {
# "cluster": "agent"
# ...
$ more /etc/istio/proxy/grpc-bootstrap.json
# => {
# "xds_servers": [
# {
# "server_uri": <span style="color: red;">"unix:///etc/istio/proxy/XDS",</span>
# ...
# }
# ],
# "node": {
# "id": "sidecar~172.16.1.13~deploy-websrv-778ffd6947-cxf5k.default~default.svc.cluster.local",
# "metadata": {
# "ANNOTATIONS": {
# "istio.io/rev": "default",
# "kubectl.kubernetes.io/default-container": "deploy-websrv",
# "kubectl.kubernetes.io/default-logs-container": "deploy-websrv",
# ...
# "sidecar.istio.io/status": "{\"initContainers\":[\"istio-init\"],\"containers\":[\"istio-proxy\"],\"volumes\":[\"workload-socket\",\"credential-socket\",\"workload-certs\"
# ,\"istio-envoy\",\"istio-data\",\"istio-podinfo\",\"istio-token\",\"istiod-ca-cert\"],\"imagePullSecrets\":null,\"revision\":\"default\"}"
# },
# ...
# "SERVICE_ACCOUNT": "kans-nginx",
# "WORKLOAD_NAME": "deploy-websrv"
# },
# "locality": {},
# "UserAgentVersionType": null
# },
# "server_listener_resource_name_template": "xds.istio.io/grpc/lds/inbound/%s"
# }
# display only Unix domain sockets : Listener 과 ESTAB 상태 정보 확인
$ ss -xpl
# => Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
# u_str LISTEN 0 4096 etc/istio/proxy/XDS 54694 * 0 users:(("pilot-agent",pid=1,fd=11))
# u_str LISTEN 0 4096 var/run/secrets/workload-spiffe-uds/socket 54693 * 0 users:(("pilot-agent",pid=1,fd=9))
$ ss -xp
# => Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# u_str ESTAB 0 0 etc/istio/proxy/XDS 79304 * 82228 users:(("pilot-agent",pid=1,fd=10))
# u_str ESTAB 0 0 var/run/secrets/workload-spiffe-uds/socket 54709 * 54162 users:(("pilot-agent",pid=1,fd=16))
# u_str ESTAB 0 0 * 82228 * 79304 users:(("envoy",pid=13,fd=19))
# u_str ESTAB 0 0 * 54162 * 54709 users:(("envoy",pid=13,fd=32))
# display only TCP sockets and display only IP version 4 sockets : TCP 상태 정보 확인
$ ss -4tpl
# => LISTEN 0 4096 0.0.0.0:15090 0.0.0.0:* users:(("envoy",pid=13,fd=21))
# LISTEN 0 4096 0.0.0.0:15090 0.0.0.0:* users:(("envoy",pid=13,fd=20))
# LISTEN 0 4096 0.0.0.0:15021 0.0.0.0:* users:(("envoy",pid=13,fd=23))
# LISTEN 0 4096 0.0.0.0:15021 0.0.0.0:* users:(("envoy",pid=13,fd=22))
# LISTEN 0 4096 0.0.0.0:15001 0.0.0.0:* users:(("envoy",pid=13,fd=35))
# LISTEN 0 4096 0.0.0.0:15001 0.0.0.0:* users:(("envoy",pid=13,fd=34))
# LISTEN 0 4096 0.0.0.0:15006 0.0.0.0:* users:(("envoy",pid=13,fd=37))
# LISTEN 0 4096 0.0.0.0:15006 0.0.0.0:* users:(("envoy",pid=13,fd=36))
# LISTEN 0 4096 127.0.0.1:15000 0.0.0.0:* users:(("envoy",pid=13,fd=18))
# LISTEN 0 4096 127.0.0.1:15004 0.0.0.0:* users:(("pilot-agent",pid=1,fd=13))
# LISTEN 0 511 0.0.0.0:http 0.0.0.0:*
Bookinfo 실습 및 Istio 기능 확인
Bookinfo
- Bookinfo는 istio의 기능을 설명하기위한 MSA(Microservices Architecture) 기반의 예제 어플리케이션입니다.
Bookinfo 어플리케이션 구성
- productpage, details, reviews, ratings 서비스로 구성됩니다. 소개 링크
- ProductPage 페이지에서 요청을 받으면, 도서 리뷰를 보여주는 Reviews 서비스와 도서 상세 정보를 보여주는 Details 서비스에 접속하고,
- ProductPage 는 Reviews 와 Details 결과를 사용자에게 응답합니다.
- Reviews 서비스는 v1, v2, v3 세 개의 버전이 있고 v2, v3 버전의 경우 Ratings 서비스에 접소갛여 도서에 대한 5단계 평가를 가져옵니다.
- Reviews 서비스의 차이는, v1은 Rating 이 없고, v2는 검은색 별로 Ratings 가 표시되며, v3는 색깔이 있는 별로 Ratings 가 표시됩니다.
- 설치 및 확인
# 모니터링
$ watch -d 'kubectl get pod -owide;echo;kubectl get svc'
# Bookinfo 애플리케이션 배포
$ echo $ISTIOV
# => 1.23.2
$ cat ~/istio-$ISTIOV/samples/bookinfo/platform/kube/bookinfo.yaml
$ kubectl apply -f ~/istio-$ISTIOV/samples/bookinfo/platform/kube/bookinfo.yaml
# => service/details created
# serviceaccount/bookinfo-details created
# deployment.apps/details-v1 created
# service/ratings created
# serviceaccount/bookinfo-ratings created
# deployment.apps/ratings-v1 created
# service/reviews created
# serviceaccount/bookinfo-reviews created
# deployment.apps/reviews-v1 created
# deployment.apps/reviews-v2 created
# deployment.apps/reviews-v3 created
# service/productpage created
# serviceaccount/bookinfo-productpage created
# deployment.apps/productpage-v1 created
# 확인
$ kubectl get all,sa
# => NAME READY STATUS RESTARTS AGE
# pod/details-v1-65cfcf56f9-8465x 2/2 Running 0 87s
# pod/productpage-v1-d5789fdfb-f8gdf 2/2 Running 0 86s
# pod/ratings-v1-7c9bd4b87f-s9gxs 2/2 Running 0 87s
# pod/reviews-v1-6584ddcf65-gnc9j 2/2 Running 0 87s
# pod/reviews-v2-6f85cb9b7c-cfc68 2/2 Running 0 87s
# pod/reviews-v3-6f5b775685-cw7tc 2/2 Running 0 87s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/details ClusterIP 10.10.200.54 <none> 9080/TCP 87s
# service/productpage ClusterIP 10.10.200.184 <none> 9080/TCP 87s
# service/ratings ClusterIP 10.10.200.163 <none> 9080/TCP 87s
# service/reviews ClusterIP 10.10.200.214 <none> 9080/TCP 87s
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/details-v1 1/1 1 1 87s
# deployment.apps/productpage-v1 1/1 1 1 86s
# deployment.apps/ratings-v1 1/1 1 1 87s
# deployment.apps/reviews-v1 1/1 1 1 87s
# deployment.apps/reviews-v2 1/1 1 1 87s
# deployment.apps/reviews-v3 1/1 1 1 87s
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/details-v1-65cfcf56f9 1 1 1 87s
# replicaset.apps/productpage-v1-d5789fdfb 1 1 1 86s
# replicaset.apps/ratings-v1-7c9bd4b87f 1 1 1 87s
# replicaset.apps/reviews-v1-6584ddcf65 1 1 1 87s
# replicaset.apps/reviews-v2-6f85cb9b7c 1 1 1 87s
# replicaset.apps/reviews-v3-6f5b775685 1 1 1 87s
#
# NAME SECRETS AGE
# serviceaccount/bookinfo-details 0 87s
# serviceaccount/bookinfo-productpage 0 86s
# serviceaccount/bookinfo-ratings 0 87s
# serviceaccount/bookinfo-reviews 0 87s
# product 웹 접속 확인
$ 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>"
# => <title>Simple Bookstore App</title>
# 로그
$ kubetail -l app=productpage -f
Istio를 통한 인입 기본 설정
Istio Gateway/VirtualService 설정
# Istio Gateway/VirtualService 설정
$ cat ~/istio-$ISTIOV/samples/bookinfo/networking/bookinfo-gateway.yaml
# => apiVersion: networking.istio.io/v1alpha3
# kind: Gateway
# metadata:
# name: bookinfo-gateway
# spec:
# # The selector matches the ingress gateway pod labels.
# # If you installed Istio using Helm following the standard documentation, this would be "istio=ingress"
# selector:
# istio: ingressgateway # use istio default controller
# servers:
# - port:
# number: 8080
# name: http
# protocol: HTTP
# hosts:
# - "*"
# ---
# apiVersion: networking.istio.io/v1alpha3
# kind: VirtualService
# metadata:
# name: bookinfo
# spec:
# hosts:
# - "*"
# gateways:
# - bookinfo-gateway
# http:
# - match:
# - uri:
# exact: /productpage
# - uri:
# prefix: /static
# - uri:
# exact: /login
# - uri:
# exact: /logout
# - uri:
# prefix: /api/v1/products
# route:
# - destination:
# host: productpage
# port:
# number: 9080
$ kubectl apply -f ~/istio-$ISTIOV/samples/bookinfo/networking/bookinfo-gateway.yaml
# => gateway.networking.istio.io/bookinfo-gateway created
# virtualservice.networking.istio.io/bookinfo created
# 확인
$ kubectl get gw,vs
$ istioctl proxy-status
# => NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
# deploy-websrv-778ffd6947-cxf5k.default Kubernetes SYNCED (5m9s) SYNCED (5m9s) SYNCED (4m24s) SYNCED (5m9s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# details-v1-65cfcf56f9-8465x.default Kubernetes SYNCED (4m43s) SYNCED (4m43s) SYNCED (4m24s) SYNCED (4m43s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# istio-ingressgateway-5f9f654d46-l7mqp.istio-system Kubernetes SYNCED (10s) SYNCED (10s) SYNCED (4m24s) SYNCED (10s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# productpage-v1-d5789fdfb-f8gdf.default Kubernetes SYNCED (4m53s) SYNCED (4m53s) SYNCED (4m24s) SYNCED (4m53s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# ratings-v1-7c9bd4b87f-s9gxs.default Kubernetes SYNCED (4m24s) SYNCED (4m24s) SYNCED (4m24s) SYNCED (4m24s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# reviews-v1-6584ddcf65-gnc9j.default Kubernetes SYNCED (4m47s) SYNCED (4m47s) SYNCED (4m24s) SYNCED (4m47s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# reviews-v2-6f85cb9b7c-cfc68.default Kubernetes SYNCED (4m41s) SYNCED (4m41s) SYNCED (4m24s) SYNCED (4m41s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# reviews-v3-6f5b775685-cw7tc.default Kubernetes SYNCED (4m43s) SYNCED (4m43s) SYNCED (4m24s) SYNCED (4m43s) IGNORED istiod-7f8b586864-8mc4c 1.23.2
# productpage 파드의 istio-proxy 로그 확인 Access log 가 출력 - Default access log format : 링크
$ kubetail -l app=productpage -c istio-proxy -f
- k3s-m NodePort 접속 확인
#
$ export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}')
$ echo $IGWHTTP
# => 31677
# 접속 확인
$ kubectl get svc -n istio-system istio-ingressgateway
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# istio-ingressgateway NodePort 10.10.200.171 <none> 15021:30953/TCP,80:31677/TCP,443:30737/TCP,31400:30617/TCP,15443:31668/TCP 3h31m
$ curl -s http://localhost:$IGWHTTP/productpage
$ curl -s http://192.168.10.101:$IGWHTTP/productpage
$ curl -s http://192.168.10.102:$IGWHTTP/productpage
# => <meta charset="utf-8">
# <meta http-equiv="X-UA-Compatible" content="IE=edge">
# <meta name="viewport" content="width=device-width, initial-scale=1.0">
#
# <title>Simple Bookstore App</title>
# ...
# 정보 확인
$ echo $MYDOMAIN
# => sweetlittlebird.com
$ cat /etc/hosts
# => ...
# 127.0.2.1 k3s-m k3s-m
# 192.168.10.10 k3s-m
# 192.168.10.101 k3s-w1
# 192.168.10.102 k3s-w2
# 192.168.10.103 k3s-w3
# 192.168.10.10 sweetlittlebird.com
#
$ curl -s http://$MYDOMAIN:$IGWHTTP/productpage
# => <meta charset="utf-8">
# <meta http-equiv="X-UA-Compatible" content="IE=edge">
# <meta name="viewport" content="width=device-width, initial-scale=1.0">
#
# <title>Simple Bookstore App</title>
# ...
- 자신의 PC에서 접속 확인
#
$ echo $MYDOMAIN $IGWHTTP
# => sweetlittlebird.com 31677
$ cat /etc/hosts
#
$ curl -v -s $MYDOMAIN:$IGWHTTP/productpage
Bookinfo 접속 결과 - 새로고침 할 때 마다 다른 파드에 접속되면서 리뷰가 달라짐
- testpc 에서 접속 실행
# istio ingress gw 를 통한 접속 테스트
$ curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>"
# => <title>Simple Bookstore App</title>
$ while true; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; echo "--------------" ; sleep 1; done
# => <title>Simple Bookstore App</title>
# --------------
# <title>Simple Bookstore App</title>
# --------------
# ...
$ for i in {1..100}; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; done
# => <title>Simple Bookstore App</title>
# <title>Simple Bookstore App</title>
# ...
모니터링
- 옵저빌리티 연동 문서 : 링크
Kiali (키알리) 소개
- Kiali는 Istio 서비스 메시의 모니터링 및 시각화 도구입니다.
- 주 데이터 소스는 Prometheus와 Jaeger 등입니다.
- 특히 Jaeger와 연동하여 서비스 간의 호출 관계를 시각화하여 볼 수 있습니다.
- Istiod의 health 상태를 확인하기 위해 istiod 파드를 직접 접속합니다. (기본 15014포트)
Kiali 설치 및 확인
# Install Kiali and the other addons and wait for them to be deployed. : Kiali dashboard, along with Prometheus, Grafana, and Jaeger.
$ tree ~/istio-$ISTIOV/samples/addons/
# => /root/istio-1.23.2/samples/addons/
# ├── README.md
# ├── extras
# │ ├── prometheus-operator.yaml
# │ ├── skywalking.yaml
# │ └── zipkin.yaml
# ├── grafana.yaml
# ├── jaeger.yaml
# ├── kiali.yaml
# ├── loki.yaml
# └── prometheus.yaml
$ kubectl apply -f ~/istio-$ISTIOV/samples/addons # 디렉터리에 있는 모든 yaml 자원을 생성
# => deployment.apps/grafana created
# deployment.apps/jaeger created
# deployment.apps/kiali created
# deployment.apps/prometheus created
# ...
$ kubectl rollout status deployment/kiali -n istio-system
# => deployment "kiali" successfully rolled out
# 확인
$ kubectl get all,sa,cm -n istio-system
$ kubectl get svc,ep -n istio-system
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/grafana ClusterIP 10.10.200.178 <none> 3000/TCP 69s
# service/istio-ingressgateway NodePort 10.10.200.171 <none> 15021:30953/TCP,80:31677/TCP,443:30737/TCP,31400:30617/TCP,15443:31668/TCP 5h48m
# service/istiod ClusterIP 10.10.200.215 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 5h48m
# service/jaeger-collector ClusterIP 10.10.200.121 <none> 14268/TCP,14250/TCP,9411/TCP,4317/TCP,4318/TCP 69s
# service/kiali ClusterIP 10.10.200.19 <none> 20001/TCP,9090/TCP 68s
# service/loki ClusterIP 10.10.200.227 <none> 3100/TCP,9095/TCP 68s
# service/loki-headless ClusterIP None <none> 3100/TCP 68s
# service/loki-memberlist ClusterIP None <none> 7946/TCP 68s
# service/prometheus ClusterIP 10.10.200.148 <none> 9090/TCP 68s
# service/tracing ClusterIP 10.10.200.133 <none> 80/TCP,16685/TCP 69s
# service/zipkin ClusterIP 10.10.200.29 <none> 9411/TCP 69s
#
# NAME ENDPOINTS AGE
# endpoints/grafana 172.16.2.17:3000 69s
# endpoints/istio-ingressgateway 172.16.2.14:15443,172.16.2.14:15021,172.16.2.14:31400 + 2 more... 5h48m
# endpoints/istiod 172.16.3.16:15012,172.16.3.16:15010,172.16.3.16:15017 + 1 more... 5h48m
# endpoints/jaeger-collector 172.16.3.19:9411,172.16.3.19:14250,172.16.3.19:4317 + 2 more... 69s
# endpoints/kiali 172.16.1.16:9090,172.16.1.16:20001 68s
# endpoints/loki 68s
# endpoints/loki-headless 68s
# endpoints/loki-memberlist 68s
# endpoints/prometheus 172.16.3.20:9090 67s
# endpoints/tracing 172.16.3.19:16685,172.16.3.19:16686 69s
# endpoints/zipkin 172.16.3.19:9411 69s
# kiali 서비스 변경
$ kubectl patch svc -n istio-system kiali -p '{"spec":{"type":"NodePort"}}'
# => service/kiali patched
# 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"
# => KIALI UI URL = http://54.123.42.212:31274
# Grafana 서비스 변경
$ kubectl patch svc -n istio-system grafana -p '{"spec":{"type":"NodePort"}}'
# => service/grafana patched
# Grafana 웹 접속 주소 확인 : 7개의 대시보드
$ 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"
# => Grafana URL = http://54.123.42.212:30266
# Prometheus 서비스 변경
$ kubectl patch svc -n istio-system prometheus -p '{"spec":{"type":"NodePort"}}'
# => service/prometheus patched
# 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"
# => Prometheus URL = http://54.123.42.212:30506
- Prometheus : Targets - 파드별로 tcp/15020의 /stats/prometheus를 통해 수집
- Grafana : 7개의 대시보드
- Kiali : 서비스간의 호출 관계를 시각화
Kiali (키알리) 대시보드 둘러보기
- Namespace 를 default 로 선택 후 Graph (Traffic, Versioned app graph) 에서 Display 옵션 중 ‘Traffic Distribution’과 ‘Traffic Animation’ 활성화, Security 체크 해서 확인해보겠습니다.
- 트래픽을 발생시켜서 Kiali 대시보드를 확인해보겠습니다.
# testpc 에서 아래 실행
# 반복 접속 테스트
$ while true; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; echo "--------------" ; sleep 1; done
$ while true; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; echo "--------------" ; sleep 0.1; done
$ while true; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; echo "--------------" ; sleep 0.5; done
$ for i in {1..100}; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; done
$ for i in {1..1000}; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -o "<title>.*</title>" ; done
- Traffic Graph에서는 트래픽 흐름을 확인할 수 있습니다.
- Workloads에서는 Log 등을 확인할 수 있고, Envoy 관련 설정 정보(Listener, Cluster, Route, Endpoint 등)를 확인할 수 있습니다.
- Istio Config에서 Istio 관련 설정을 볼 수 있고, Action 으로 Istio 관련 오브젝트를 설정/삭제 할 수 있습니다.
Traffic Management
-
동작 소개 : 클라이언트 PC → Istio ingressgateway 파드 → (Gateway, VirtualService + DestinationRule) → Cluster(Endpoint - 파드)
- Gateway : 지정한 인그레스 게이트웨이로부터 트래픽이 인입, 프로토콜 및 포트, HOSTS, Proxy 등 설정이 가능합니다.
-
VirtualService : 인입 처리할 hosts 설정, L7 PATH 별 라우팅, 목적지에 대한 정책 설정 가능합니다. (envoy route config) - 링크
- VirtualService 는 DestinationRule 에서 설정된 서브셋(subset)을 사용하여 트래픽 컨트롤을 할 수 있습니다.
- hosts 필드 : 목적지 주소 - IP address, a DNS name (FQDN), 혹은 k8s svc 이름, wildcard (”*”) prefixes
-
Routing rules : HTTP 경우 - Match 필드(예) 헤더), Destination(istio/envoy 에 등록된 대상, subnet 에 DestinationRule 활용)
- HTTPRoute : redirect , rewrite , fault(장애 주입) , mirror(복제, 기본 100%) , corsPolicy(CORS 삽입) , headers(헤더 조작) 등 - 링크
- Routing rule precedence : Routing rules are evaluated in sequential order from top to bottom - 위에서 순차적 적용
- DestinationRule : 실제 도착지(서비스와 1:1 연결)의 정교한 정책(부하분산, 연결 옵션, 서킷 브레이크, TLS 등)을 설정 - 링크
Request Routing 실습
- 실습전 기본 DestinationRule 적용
# 샘플 파일들 확인
$ tree ~/istio-$ISTIOV/samples/bookinfo/networking
# => /root/istio-1.23.2/samples/bookinfo/networking
# ├── bookinfo-gateway.yaml
# ├── certmanager-gateway.yaml
# ├── destination-rule-all-mtls.yaml
# ├── destination-rule-all.yaml
# ├── destination-rule-reviews.yaml
# ├── egress-rule-google-apis.yaml
# ├── fault-injection-details-v1.yaml
# ├── virtual-service-all-v1.yaml
# ├── virtual-service-details-v2.yaml
# ├── virtual-service-ratings-db.yaml
# ├── virtual-service-ratings-mysql-vm.yaml
# ├── virtual-service-ratings-mysql.yaml
# ├── virtual-service-ratings-test-abort.yaml
# ├── virtual-service-ratings-test-delay.yaml
# ├── virtual-service-reviews-50-v3.yaml
# ├── virtual-service-reviews-80-20.yaml
# ├── virtual-service-reviews-90-10.yaml
# ├── virtual-service-reviews-jason-v2-v3.yaml
# ├── virtual-service-reviews-test-v2.yaml
# ├── virtual-service-reviews-v2-v3.yaml
# └── virtual-service-reviews-v3.yaml
# 기본 DestinationRule 적용
$ kubectl apply -f ~/istio-$ISTIOV/samples/bookinfo/networking/destination-rule-all.yaml
# => destinationrule.networking.istio.io/productpage created
# destinationrule.networking.istio.io/reviews created
# destinationrule.networking.istio.io/ratings created
# destinationrule.networking.istio.io/details created
# DestinationRule 확인 dr(=destinationrules) : KIALI Services 확인 시 GW, VS, DR 확인
$ kubectl get dr
# => NAME HOST AGE
# details details 31s
# productpage productpage 31s
# ratings ratings 31s
# reviews reviews 31s
- virtual-service-all-v1.yaml : 4개 서비스 모두 v1 의 서브셋(subset) 에 전송하는 정책 테스트
# virtual-service-all-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage
http:
- route:
- destination:
host: productpage
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: details
spec:
hosts:
- details
http:
- route:
- destination:
host: details
subset: v1
---
# istio vs(virtualservices) 확인
$ kubectl get vs
# => NAME GATEWAYS HOSTS AGE
# bookinfo ["bookinfo-gateway"] ["*"] 3h30m
# 모든 마이크로서비스에 대해 v1 의 서브셋(subset) 에 전송되게 virtualservices 적용
$ kubectl apply -f virtual-service-all-v1.yaml
# => virtualservice.networking.istio.io/productpage created
# virtualservice.networking.istio.io/reviews created
# virtualservice.networking.istio.io/ratings created
# virtualservice.networking.istio.io/details created
# istio vs(virtualservices) 확인 >> KIALI 에서 reviews v2,v3 향하는 트래픽 경로가 사라진다!
$ kubectl get virtualservices
# => NAME GATEWAYS HOSTS AGE
# bookinfo ["bookinfo-gateway"] ["*"] 3h30m
# details ["details"] 10s
# productpage ["productpage"] 10s
# ratings ["ratings"] 10s
# reviews ["reviews"] 10s
-
모든 트래픽이 v1으로 향하게 되어서 브라우저를 새로고침해도 v1만 나오게 됩니다.
-
virtual-service-reviews-test-v2.yaml : User Identity 기반 라우팅, end-user 커스텀 헤더에 jason 매칭 시 reviews v2 로 전달
- Match 조건에는 완전 일치(exact) , 전방 일치(prefix) , 정규 표현(regex) - 3가지 패턴을 선택할 수 있다
# virtual-service-reviews-test-v2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
# 모든 마이크로서비스에 대해 v1 의 서브셋(subset) 에 전송되게 virtualservices 적용
$ kubectl apply -f virtual-service-reviews-test-v2.yaml
# => virtualservice.networking.istio.io/reviews configured
# jason 로그인 시 로그 확인
$ kubetail -l app=productpage -f
# => [productpage-v1-d5789fdfb-f8gdf productpage] DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): details:9080
# [productpage-v1-d5789fdfb-f8gdf productpage] send: b'GET /details/0 HTTP/1.1\r\nHost: details:9080\r\nuser-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nx-request-id: 3aa3c711-d014-930f-8c2a-563c3dbbd8b5\r\n\r\n'
# [productpage-v1-d5789fdfb-f8gdf productpage] reply: 'HTTP/1.1 200 OK\r\n'
# [productpage-v1-d5789fdfb-f8gdf productpage] header: content-type: application/json
# [productpage-v1-d5789fdfb-f8gdf productpage] header: server: envoy
# [productpage-v1-d5789fdfb-f8gdf productpage] header: date: Sat, 19 Oct 2024 14:32:09 GMT
# [productpage-v1-d5789fdfb-f8gdf productpage] header: content-length: 178
# [productpage-v1-d5789fdfb-f8gdf productpage] header: x-envoy-upstream-service-time: 6
# [productpage-v1-d5789fdfb-f8gdf productpage] DEBUG:urllib3.connectionpool:http://details:9080 "GET /details/0 HTTP/1.1" 200 178
# [productpage-v1-d5789fdfb-f8gdf productpage] DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): reviews:9080
# [productpage-v1-d5789fdfb-f8gdf productpage] send: b'GET /reviews/0 HTTP/1.1
# Host: reviews:9080
# user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15
# Accept-Encoding: gzip, deflate
# Accept: */*
# Connection: keep-alive
# x-request-id: 3aa3c711-d014-930f-8c2a-563c3dbbd8b5\r\n\r\n'
# [productpage-v1-d5789fdfb-f8gdf productpage] reply: 'HTTP/1.1 200 OK\r\n'
# [productpage-v1-d5789fdfb-f8gdf productpage] header: x-powered-by: Servlet/3.1
# [productpage-v1-d5789fdfb-f8gdf productpage] header: content-type: application/json
# [productpage-v1-d5789fdfb-f8gdf productpage] header: date: Sat, 19 Oct 2024 14:32:09 GMT
# [productpage-v1-d5789fdfb-f8gdf productpage] header: content-language: en-US
# [productpage-v1-d5789fdfb-f8gdf productpage] header: content-length: 358
# [productpage-v1-d5789fdfb-f8gdf productpage] header: x-envoy-upstream-service-time: 1
# [productpage-v1-d5789fdfb-f8gdf productpage] header: server: envoy
# [productpage-v1-d5789fdfb-f8gdf productpage] DEBUG:urllib3.connectionpool:http://reviews:9080 "GET /reviews/0 HTTP/1.1" 200 358
# [productpage-v1-d5789fdfb-f8gdf productpage] INFO:werkzeug:::ffff:127.0.0.6 - - [19/Oct/2024 14:32:09] "GET /productpage HTTP/1.1" 200 -
# ...
- 웹브라우저에서 productpage로 접속 후 새로고침을 해보겠습니다.
- 로그인 전에는 v1으로 접속 됩니다.
- 로그인 전에는 v1으로 접속 됩니다.
- 오른쪽 상단의 Sign in 클릭 후 jason으로 로그인 후 새로고침을 해보겠습니다.
- 로그인 후에는 v2로 접속 됩니다.
-
헤더에는 end-user:jason 이 추가되어 있습니다.
# 로그인 후 헤더헤더 # => [productpage-v1-d5789fdfb-f8gdf productpage] send: b'GET /reviews/0 HTTP/1.1 # Host: reviews:9080 # user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15 # Accept-Encoding: gzip, deflate # Accept: */* # Connection: keep-alive # <span style="color: red;">end-user: jason</span> # x-request-id: 03366677-7032-9291-a4b9-7009a6257394 # cookie: session=eyJ1c2VyIjoiamFzb24ifQ.ZxPUxA.3MJkXTFH8zJtg_YlXlvzq8xArpc'
- 로그인 후에는 v2로 접속 됩니다.
Fault Injection 실습
- virtual-service-ratings-test-delay.yaml : end-user 가 jason 는 ratings v1 에 7초 지연 발생, 그외 사용자는 ratings v1 정상 연결
# virtual-service-ratings-test-delay.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
delay:
percentage:
value: 100.0
fixedDelay: 7s
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
# virtualservices 적용
$ kubectl apply -f virtual-service-ratings-test-delay.yaml
# => virtualservice.networking.istio.io/ratings configured
# 로그 확인 : product 입장에서 접속 사용자(clinet) 연결을 끊어버림 0 DC downstream_remote_disconnect
$ kubetail -l app=productpage -f
- 웹브라우저에서 jason으로 로그인된 상태에서 접속시 6~7초 지연이 발생하는것을 확인할 수 있습니다.
- virtual-service-ratings-test-abort.yaml : end-user 가 jason 는 ratings v1 에 500 에러 리턴, 그외 사용자는 ratings v1 정상 연결
# virtual-service-ratings-test-abort.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
abort:
percentage:
value: 100.0
httpStatus: 500
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
# virtualservices 적용
$ kubectl apply -f virtual-service-ratings-test-abort.yaml
# => virtualservice.networking.istio.io/ratings configured
# 로그 확인
$ kubetail -l version=v2 -f
jason으로 로그인 했을때 Rating 서비스에 500 에러가 발생하는것을 확인할 수 있습니다.
또한 kiali에서도 어느 구간에서 오류가 발생했는지 확인할 수 있으며, Flags도 확인할 수 있습니다. 링크
(이경우 FI는 Fault Injection을 의미으로 일부러 오류를 일으킨 것을 확인 할 수 있습니다.)
Traffic Shifting 실습
-
카나라 배포 전략 등 활용 - 링크
-
virtual-service-reviews-50-v3.yaml : 가중치 (weight-based routing), reviews 에 v1(50%), v3(50%)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 50
- destination:
host: reviews
subset: v3
weight: 50
# virtualservices 적용
$ kubectl apply -f virtual-service-reviews-50-v3.yaml
# => virtualservice.networking.istio.io/reviews configured
$ for i in {1..100}; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -m 1 "reviews-v\?" ; done | sort | uniq -c
# => 53 reviews-v1-6584ddcf65-gnc9j
# 47 reviews-v3-6f5b775685-cw7tc
대략 50%의 확률로 v1과 v3로 접속되는것을 확인할 수 있습니다.
- virtual-service-reviews-80-20.yaml : 가중치 (weight-based routing), reviews 에 v1(80%), v2(20%)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 80
- destination:
host: reviews
subset: v2
weight: 20
# virtualservices 적용
$ kubectl apply -f virtual-service-reviews-80-20.yaml
# => virtualservice.networking.istio.io/reviews configured
$ for i in {1..100}; do curl -s $MYDOMAIN:$IGWHTTP/productpage | grep -m 1 "reviews-v\?" ; done | sort | uniq -c
# => 79 reviews-v1-6584ddcf65-gnc9j
# 21 reviews-v2-6f85cb9b7c-cfc68
대략 80%의 확률로 v1과 20%의 확률로 v2로 접속되는것을 확인할 수 있습니다.
kiali에서도 어느 구간에서 어떠한 비중으로 트래픽이 흘러가는지 확인할 수 있습니다.
Security (보안)
- 요구사항
- MITM (Man-In-The-Middle) 공격 방지를 위해 모든 트래픽은 mTLS로 암호화 되어야 합니다.
- 또한 접근 제어 정책이 필요하며, 감사 로깅을 통해 보안 이슈를 식별이 가능 해야 합니다.
- 목표
- 기본 셋팅을 안전하게 하기 : 별도의 셋팅이 없어도 보안을 유지할 수 있도록 설정
- 깊은 방어 : 기존에 존재하는 보안 시스템과 통합되어, 다층 방어를 구성
- Zero-trust network : 네트워크를 신뢰하지 않음으로써 보안 강화 https://genians.co.kr/genians-nac/zt/
- 구성요소
- Certification Authority (CA) : 인증서 발급, 관리, 갱신
- 보안 정책 (인증정책, 인가정책 등) 관련 설정을 각 프록시에 전달하는 API 서버
- 사이드카와 프록시를 정책 강제 지점(Policy Enforcement Point-PEPs)으로 사용하여 클라이언트와 서버간의 통신을 보호
- Envoy Proxy 확장 기능을 통해 telemetry와 감사 로깅 수집
istio 보안 아키텍쳐
- TLS와 mTLS
- TLS (Transport Layer Security) : 통신 보안을 위한 프로토콜로, 인터넷 상에서 데이터를 암호화하는 표준화된 방법입니다. 기본적으로 서버의 인증서만 확인하는 방식을 사용합니다.
- mTLS (mutual TLS) : 서버와 클라이언트가 서로 인증을 하고 서로 신뢰할 수 있는지 확인하는 방식입니다.
- Authentication (인증), Authorization (인가) (Auto mTLS)
- Istio는 모든 워크로드에 X.509 인증서를 부여하고, 서로 인증을 통해 통신을 보호합니다.
- Envoy proxy와 함께 실행되는 Istio agent는 istiod와 함께 동작하면서 자동으로 인증서를 갱신합니다.
- istiod는 CSR(인증서 서명 요청)을 수행하기 위해 gRPC 서비스를 제공합니다.
- Envoy는 SDS(Secret Discovery Serice) API를 통해 인증서와 키 요청을 보냅니다.
- istio-agent는 SDs 요청을 받으면 Private Key와 CSR을 생성한 후 자격증명 (credential)과 함께 CSR istiod에 전송하여 서명을 요청합니다.
- CA는 CSR에 포함된 자격증명(credential) 의 유효성을 검사하고 CSR에 서명하여 인증서를 생성합니다.
- istio-agent는 istiod로부터 받은 인증서(certiftcate)와 개인키 private Key)를 Envoy SDS API를 통해 Envoy에게 보냅니다.
- 위의 CSR 프로세스는 인증서 및 키 순환을 위해 주기적으로 반복됩니다
- Authentication (인증) : 2가지 타입 제공
- Peer Authentication :
- 서비스간 인증에 사용되며 Client가 연결을 확인하는데 사용. 서비스 코드 변경없이 mTLS 제공
- Request Authentication :
- Request에 첨부된 자격증명(Credential)을 통해 최종 사용자 인증에 사용
- istio는 JWT (JSON Web Token)을 지원하여 최종 사용자 인증을 제공
- 커스텀 인증 제공자를 비롯하여 OpenID Connect, Keycloak, Auth0 등 다양한 인증 방식을 지원
- Peer Authentication :
- Authorization (인가)
- istio는 mesh 단위, 네임스페이스 단위, 워크로드 단위로 접근을 제어 할 수 있는 인가 기능을 제공합니다. 다음과 같은 이점이 있습니다.
- 워크로드와 워크로드간, 또는 사용자와 워크로드간 인가 제공
- 단일한 AuthorizationPolicy CRD를 사용한 단순한 API 제공
- 유연한 정책 제공 : 커스텀 조건을 등록할 수 있고, CUSTOM, DENY, ALLOW 액션 지원
- 고성능 : Envoy Proxy를 통해 인가 정책을 적용하므로 성능 저하가 없음
- 높은 호환성 : gRPC, HTTP, HTTPS 등을 지원하며, 일반적인 TCP 프로토콜도 지원
- istio는 mesh 단위, 네임스페이스 단위, 워크로드 단위로 접근을 제어 할 수 있는 인가 기능을 제공합니다. 다음과 같은 이점이 있습니다.
Authentication (Auto mTLS) 실습
-
기존 파드에 로그에서 인증서 등 보안 관련 내용 확인
# CA Endpoint, CA(/var/run/secret/istio/root-cert,pem), citadelclient, SDS server 등등 $ kubectl logs ratings-v1-7c9bd4b87f-s9gxs -c istio-proxy -f $ kubetail # 인증정책 확인 $ kubectl get peerauthentications.security.istio.io # => No resources found # envoy 에 cert 정보 확인 : istio-proxy 에 admin페이지 접속 or kaila 에서 envoy 에서 확인
- bookinfo → kiali → product 계속 접속
- kiali 에서 Display(Security 체크) 후 자물쇠 클릭하면 오른쪽 창에서 보안설정을 확이할 수 있습니다. : mTLS Enabled, spiffe(Secure name)
- test 네임스페이스 생성 후 파드 생성(sidecar 미적용) 후 ratings 접속
# 네임스페이스 생성
$ kubectl create ns test
# => namespace/test created
# 파드 생성
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: netpod
namespace: test
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 확인 : sidecar 미적용
$ kubectl get pod -n test
# => NAME READY STATUS RESTARTS AGE
# netpod 1/1 Running 0 19s
# ratings 서비스 확인
$ kubectl get svc ratings
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# ratings ClusterIP 10.10.200.163 <none> 9080/TCP 8h
# ratings 접속 시도 : 성공
$ kubectl exec -it -n test netpod -- curl ratings.default:9080/health ;echo
$ {"status":"Ratings is healthy"}
# 로그 확인
$ kubetail -l app=ratings -f
NS(default, test 체크) netpod 에서 접속 시 unknown 으로 표기되며, 접근 성공(녹색) 확인
# Peer authentication 설정 변경 : PERMISSIVE(mTLS 사용/미사용 모두 허용) → STRICT(반드시 mTLS 사용, 미사용 시 거부)
$ cat <<EOF | kubectl create -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default-strict
spec:
mtls:
mode: STRICT
EOF
# ratings 접속 시도 : 실패!
$ kubectl exec -it -n test netpod -- curl ratings.default:9080/health ;echo
# => curl: (56) Recv failure: Connection reset by peer
# command terminated with exit code 56
$ kubetail -l app=ratings -f
# => [ratings-v1-7c9bd4b87f-s9gxs istio-proxy] [2024-10-01T17:21:12.708Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 172.16.3.17:9080 172.16.1.17:34938 - -
- 실습 자원 삭제
$ kubectl delete PeerAuthentication default-strict
# => peerauthentication.security.istio.io "default-strict" deleted
$ kubectl exec -it -n test netpod -- curl ratings.default:9080/health ;echo
# => {"status":"Ratings is healthy"}
# <span style="color: green;">👉 인증정책 강제 정책 삭제시 다시 통신이 됩니다.</span>
$ kubectl delete pod -n test netpod
$ kubectl delete ns test
Istio 통신 흐름
istio 사용시 트래픽은 호스트의 tcp/ip 스택과 iptables, 파드내의 iptables와 envoy를 경유하게 됩니다. istio는 강력하고 다양한 기능들을 제공하지만 비용(지연추가, 프로세서 사용량 추가, 복잡한 구조)이 필요합니다.
외부 클라이언트(PC 등)에서 파드로 접속되는 과정은 다음과 같습니다.
위의 그림에서 처럼 iptables를 여러번 거치고, DNAT등을 통해 포트번호등이 80 (http) => 15006 (istio-proxy) 로 변경되는 등의 작업을 여러번 거칩니다. 또한 파드와 호스트간 통신 envoy를 요청을 받을때와 응답할때 모두 거쳐가는 것을 확인할 수 있습니다.
- 파드 내 Iptables 적용 흐름
출처 : Jimmy song 블로그
다음 블로그에서 자세한 내용을 확인할 수 있습니다. Understanding the Sidecar Injection, Traffic Intercepting & Routing Process in Istio
마치며
1주차 컨테이너 격리 이후에 또 다시 뇌정지가 찾아온 주차였습니다.
이번주에 학습한것도 많은데, 이것이 일부만 살펴본것이라니 놀랍습니다.
정말 만든 분들도, 쓰는 분들도, 스터디를 진행해주시는 분들도 대단합니다.
인증이나 인가 등 gateway api에서 아쉬웠던 부분들이 나와서 좋았습니다. 찾던 기능인데 마침 이번주에 다루게 되어서 좋았습니다. Kiali를 통한 트래픽 시각화도 정말 유용하게 쓰일 것 같습니다. 복잡하긴 하지만 좋은 기능들이 많았습니다.
시간이 모자라서 미처 실습하지 못한 부분들과 Ambient Mesh도 바쁜일이 지나가면 다시 살펴봐야겠습니다.
스터디를 준비해주신 가시다님과 참여하신 분들 감사합니다.