Istio 란
Istio 는 Service Mesh 의 한 종류로 CNCF 에서 관리하는 Open Source 이다. Service Mesh 는 MSA 환경에서 애플리케이션 코드의 변경 없이 각 서비스에 대한 Observability(가시성, log/metrics/trace), 트래픽 관리, 보안 기능을 담당하는 인프라 계층이다. 애플리케이션에서 일일이 구현하던 기능을 인프라 계층으로 디커플링하기 위해 만들어진 것이다.
Istio 를 쓰는 이유
Kubernetes 환경에선 DNS 와 로드밸런서의 조합으로도 트래픽 관리가 충분히 가능하지만 Istio 를 활용하면 더 많은 기능을 쉽게 적용할 수 있다. 대표적으로 Observability, 트래픽 관리, 보안, 회복성, 계층 이점 등이 있다.
Observability, 가시성
애플리케이션 로그는 Datadog 과 같은 솔루션으로 취합할 수 있지만 마이크로서비스간 주고받는 트래픽에 대한 로그는 따로 확보하기 쉽지 않다. Istio 는 Envoy Proxy 를 Sidecar 형태로 애플리케이션과 함께 배포하여 트래픽을 앞단에서 먼저 받아 이에 대한 Log, Metrics, Trace 를 확보할 수 있다.
트래픽 관리
HTTP, gRPC, WebSocket 및 TCP 트래픽에 대한 로드 밸런싱이 가능하며 Retry, 라우팅 규칙, Fault Injection 등 트래픽 제어 역시 가능하다. Istio 가 제공하는 CustomResource 인 VirtualService 와 DestinationRule 을 통해서 원하는 서비스로 트래픽을 보내줄 수 있다. VirtualService 의 weight, subset 을 통해 Canary 배포를 수행할 수 있을 뿐 아니라 Header / Path 기반의 L7 라우팅도 쉽게 설정할 수 있다.
트래픽을 낚아채서 내 마음대로 할 수 있기 때문에 특정 트래픽만 카나리 버전에 흘려보내는 상황을 Application 수정 없이 Configuration 수정만으로 가능하게 해준다.
L4 뿐 아니라 L7 까지 제어하기 때문에 네트워크를 까서 HTTP Header 를 볼 수 있다. 헤더를 확인해서 요청이 아이폰에서 들어온 요청일 때 아이폰용 서비스로 라우팅해주는 등의 트래픽 스티어링이 가능하다. 예를 들어 gif 보다 압축률이 좋은 webp 같은 경우 chrome 과 android 는 지원하지만 아이폰은 지원하지 않는 상황에 사용할 수 있다.
보안
Kubernetes 환경에선 서비스 간 통신이 이루어지기 때문에 중간에 트래픽을 낚아채서 뜯어볼 수 있다. 이를 방지하기 위한 mTLS 를 Istio 를 통해 쉽게 설정할 수 있다. Istio Control Plane 이 모르는 서비스와는 아예 통신을 할 수 없게 할 수 있고, Policy 를 통해 어떤 서비스가 어떤 서비스와 통신할 수 있는지 코드화하여 관리할 수 있다.
Istio 아키텍처는 Envoy Proxy 를 통해 통신을 다 가로채고 모든 구간이 암호화되기 때문에 절대 못 열어보는 통신 보안을 제공한다 (Zero Trust)
Kubernetes 클러스터는 내부망 통신일텐데 왜 암호화해야할까? 최근 보안 업계 트렌드는 Zero Trust 아키텍처다. 내부망이어도 절대 신뢰하지 말고 항상 검증해야 된다는 개념이다. 왜 클러스터 내부망이 위험할 수 있을까? 내부자의 경우 네트워크를 뜯어볼 수 있기도 하고, Pod 하나가 취약점을 가지고 해커에게 장악될 경우 내부 네트워크에 접근이 가능하기에 외부자가 트래픽을 가로챌 수 있기도 하고, Compliance and Regulation 으로 전송 중 암호화를 법적으로 요구하는 경우가 많기도 하다.
회복성, Fault Tolerance
Timeout, Retry, Circuit Breaker 같은 고급 회복성 기능을 구성하여 서비스 장애에 대한 탄력성을 높일 수 있다.
Service 끼리 서로 통신하는 MSA 환경에서 호출되는 Service 에 장애가 발생하면 호출하는 Service 의 스레드는 응답을 기다리게 된다. 그렇게 모든 스레드가 응답대기 상태가 되면 문제가 없던 호출하는 Service 역시 장애가 발생한다. 즉, 장애가 전파되는 상황이 발생한다. 이를 Noisy Neighborhood 라고도 한다.
이런 상황을 막기 위해선 호출되는 Service 가 응답이 없으면 커넥션을 끊어줘야 한다. 과전류 시 퓨즈가 내려가는 것처럼 이를 Circuit Breaker 라고 한다.
이 외에도 모니터링, 서비스 간 의존성 등 MSA 환경에서 마주하는 다양한 문제들을 해결하기 위해 Envoy 라는 프록시를 이용하게 된다. Envoy 는 HAProxy 나 Nginx 같이 WAS 앞에 놓는 일반적인 프록시다. 단순하게 L4 레벨이 아니라 L7 레벨에서도 역할을 수행한다. 들어오는 트래픽양을 조절한다던지, 인증된 트래픽만 받는다던지, HTTP/1.0 뿐만 아니라 HTTP/2, gRPC, WebSocket 을 지원한다던지, Circuit Breaker 를 지원한다던지
계층 이점
애플리케이션 코드의 변경 없이 인프라 계층에서 위 모든 기능을 제공할 수 있다는 것이 가장 큰 장점이다. 비즈니스 로직과 Service Mesh 가 애플리케이션 계층, 인프라 계층으로 디커플링 되어 있기 때문에 istio 가 아닌 Consul, Linkerd 등 과 같은 다른 ServiceMesh 를 적용하더라도 애플리케이션 코드엔 변경이 필요 없어진다.
Istio 는 어떻게 작동하나?
Istio 는 내부적으로 Envoy Proxy 를 각 애플리케이션 Container 옆에 Sidecar 형태로 함께 배포해준다. Google 에서 Istio 프로젝트를 준비 중일 때 Nginx 를 Sidecar Proxy 로 사용하려다 Envoy 의 존재를 알게되고 Envoy 를 사용하기로 결정했다고 한다.
Envoy Proxy
- L4/L7 프록시
- 경량
- HTTP2, gRPC 등 지원
- Resilience 와 Scale 지원
- Health Check, Circuit Breaker, Timeout, Retry 지원
- API 를 통한 설정 변경, Nginx 와 가장 큰 차이점으로 Nginx 의 경우 설정을 변경할 때 conf 파일을 수정하고 reload 를 수행해야하지만 Envoy 는 API 를 통해 동적으로 설정을 변경할 수 있다. VirtualService, DestinationRule 과 같은 CustomResource 가 생성되면 Istio 가 이를 인지하고 Envoy Proxy 에 설정을 전파해주는 방식으로 작동한다.
Envoy 를 쓰면 인프라레벨에서 이런 MSA 패턴들을 풀어나갈 수 있다. 문제는 Envoy 가 많이 필요하다는 점이다. MSA 는 보통 컴포넌트가 천단위 이상으로 구성되기 때문에 모든 컴포넌트 앞단에 일일이 Envoy 를 붙여주려면.. 쉽지 않다. 그래서 중앙 컨트롤 솔루션인 Istio 를 사용할 수 있다.
Istio 는 WAS Container 옆에 항상 Envoy Container 가 같이 배포될 수 있도록 도와준다. 일반적으로 Kubernetes 에서 Pod 는 하나의 Container 를 포함한다. Envoy Proxy 는 다중 Container 패턴 중 하나인 Sidecar Pattern 으로 애플리케이션 Container 와 함께 배포된다. Sidecar Pattern 은 기존 Container 의 기능을 변경하지 않고 확장한다. 즉, 애플리케이션 코드 수정 없이 로깅이나 프록시를 붙이는 등 기능을 확장하기 위해 사용된다.
Pod 는 여러 Container 들을 어떻게 묶어줄까?
Kubernetes 에서 Pod 를 생성하면 pause 라는 Container 도 함께 생성된다. 이는 Pod 내부의 Container 들이 리눅스 namespace(PID, network, volume mount, UTS, IPC cgroup 등) 를 공유할 수 있도록 도와준다. 또한 PID 1 (init process)로서의 역할을하고 좀비 프로세스를 거둬들이기도 한다. pause Container 덕분에 Pod 내 Container 들은 동일한 네트워크 설정을 공유하게 되고 localhost 로 Container 간 통신도 가능하게 한다.
Istio 는 Kubernetes namespace 중 istio-enabled 설정이 적용된 namespace 에 생성되는 모든 Pod 에 Envoy Proxy 를 Sidecar 로 붙인다. Istio 의 Webhook 을 통해 istiod 의 API 를 호출하고 해당되는 Pod 에 init-container 와 istio-proxy Container 를 주입한다. init-container 는 다른 Container 들이 생성되기 전에 먼저 실행되며 트래픽 제어를 위한 iptable 작업을 수행한다. 이를 통해 해당 Pod 로 들어오는 트래픽을 모두 Envoy Proxy 로 전달하도록 설정한다.
Istio Architecture
Istio 는 컨트롤 플레인과 데이터 플레인으로 이루어진다. 데이터 플레인은 Envoy 들의 집합이다. 컨트롤 플레인은 Envoy 프록시들을 컨트롤해주는 역할을 한다.
Control Plane, istiod
컨트롤 플레인은 pilot, mixer, citadel 으로 구성되며 istiod 라는 싱글 바이너리로 묶여 배포된다. istiod 는 서비스 디스커버리, 프록시 구성 및 관리, 인증서 관리 등을 수행한다. mTLS 통신을 위한 인증서를 생성하는 CA 역할 역시 수행한다.
Pilot
컨트롤 플레인의 Pilot 이라는 컴포넌트는 configuration 정보를 갖고 있다. 예를 들면 Service A 의 IP Address 는 무엇인가, 받을 수 있는 트래픽은 어떻게 되는지 등.
Mixer
Mixer 는 Service 가 트래픽을 받을 때 1초당 얼마 받을 수 있는지, 나를 호출할 수 있는 Service 의 종류는 무엇인지, 또는 Request 의 우선순위 등의 정책을 확인하고, 로그도 수집한다.
Citadel
Citadel 이라는 컴포넌트는 보안을 담당한다. Istio 를 적용한 아키텍처에선 무조건 Envoy Proxy 를 통해 통신이 이루어진다. 이 통신 구간이 mTLS(Mutual TLS 또는 양방향 SSL) 로 암호화된다. 서버를 나가는 순간부터 모두 암호화가 된다. mTLS 를 사용하려면 인증서를 받아야되는데 Citadel 이 이를 관리하고 뿌려주는 역할을 한다.
Istio 1.26 버전업 후 istiod memory spike 이슈
- 기존 istiod 리소스를 requests.memory: 500Mi, limits.memory 2Gi 사용 중 OOMKilled 이슈로 requests.memory: 2500Mi, limits.memory 4Gi 로 증가하는 상황 발생
- RCA 중 1.26 로 버전업 이후 istiod 메모리 사용량이 증가한 것을 확인, istiod 를 부팅하는 순간부터 더 많은 메모리를 잡아먹었고 DEV, STG, PROD 환경마다 증가량이 달랐음, DEV 가 제일 많고, STG, PROD 순으로 메모리 사용량이 많아짐
- istiod 의 memory profile 을 확인한 결과 가장 많은 메모리를 잡아먹는 부분이 ConfigMap 과 관련있다는 것을 발견
- istiod 코드 중 ConfigMap 관련 코드를 확인한 결과 ConfigMap 을 위한 cluster-wide watcher 가 1.26 에 추가된 것을 확인
- 클러스터에서 ConfigMap 확인 결과 DEV 에 50000, STG 에 20000, PROD 에 9000 의 ConfigMap 이 존재하는 것을 확인