마이크로서비스 아키텍처의 단점을 해결해줄 수 있는 Service Mesh와 Istio에 대해 학습하고 정리합니다.
학습할 내용은 다음과 같습니다.
- 마이크로 서비스 아키텍처 단점
- 서킷 브레이커(Circuit breaker)
- 서비스 메쉬 (Service mesh)
- Envoy Proxy
- Istio
- Istio 기능
Reference
마이크로서비스는 장점만큼 단점도 존재한다.
그 중 하나가 잘개쪼갠 서비스가 광범위하게 많아질 수 있고 이로인해 서비스간 통신이 복잡해질 수 있다.
서비스간 전체 연결 구조를 알기 어려워서 장애가 났을 때 어디서 났는지 알기 힘들다.
예를들어 클라이언트 -> 서비스 A -> 서비스 B로 통신을 한다고 했을 때 서비스 B가 느려지거나 장애가 난다면 서비스 A에서는 이 상황을 알기 어렵다.
즉 서비스 A는 계속해서 응답을 기다리는 대기상태가 되고 이로인해 서비스 A도 장애가 된다. 이런 현상을 전파 현상이라고 한다.
이 문제를 서비스 메쉬로 해결할 수 있다.
이런 문제들이 생겨나면서 이를 해결하는 디자인 패턴이 등장했다. 그게 바로 서킷 브레이커(Circuit breaker)다.
아까 예제를 계속해서 이용해보면 서비스 A와 서비스 B가 통신할 수 있다.
이 구성에서 서킷 브레이커를 서비스 A와 서비스 B 사이에 둔다.
그래서 정상적인 상황에선 서비스 A에서 시작해 서킷 브레이커를 통과해서 서비스 B에 요청이 잘 전송된다.
하지만 서비스 B에서 장애가 발생난다면 서킷 브레이커는 서비스 B와의 통신을 끊어버리고 서비스 A에게 에러를 반환한다.
그로인해 서비스 A는 더이상 서비스 B를 기다리지 않고 에러 로직을 처리하도록 한다.
이런 패턴을 구현하기는 굉장히 어려웠는데 넷플릭스에서 이러한 마이크로서비스 패턴을 오픈소스화 했다.
그리고 그걸 스프링에서 그대로 추상화해서 쉽게 사용할 수 있는데 문제는 스프링은 자바기반이라 언어의 한계가 있다.
넷플릭스에서는 이런 마이크로서비스의 단점을 소프트웨어 계층에서 해결했다면 서비스 메쉬는 이를 인프라 단계에서 해결할려는 노력을 했다.
서비스 메쉬의 아키텍처 패턴은 서비스와 서비스간의 통신이 있을 때 서비스마다 프록시를 둔다
즉 서비스간의 통신은 프록시를 통해서 해결한다. 그러므로 한쪽 서버에서 장애가 났을 때 프록시가 서킷 브레이커 역할을 해줄 수 있다.
또한 이런 프록시는 L7 계열의 지능형 라우터 기능도 지원하기 떄문에 HTTP 헤더기반이나 메시지 본문을보고 다른 서비스로 라우팅을 해줄 수 있다.
프록시로 이런 기능들을 지원할 수 있지만 서비스가 엄청나게 많아진다면 어떻게 다른 모든 서비스들의 경로를 알 수 있을까?
그래서 이런 프록시들에게 설정 정보를 넣어줄 수 있는 컨트롤 플레인(Control Plane)이란 영역이 존재한다.
프록시는 이런 설정 정보 값을 바탕으로 트래픽을 제어하는 데이터 플레인(Data Plane)이란 영역이다.
서비스 메쉬의 구현체인 Istio를 나중에 살펴볼건데 Istio에 사용하는 Envoy 프록시에 대해 먼저 알아보겠다.
Envoy Proxy는 기존 프록시의 기능인 L4 뿐 아니라 L7 기능도 지원하면서 HTTP 뿐 아니라 HTTP 2.0 TCP gRPC등 다양한 기능을 제공한다.
이런 Envoy Proxy는 다양한 기능은 다음과 같다.
HTTP, TCP, gRPC 프로토콜 지원
HTTP2 지원
서비스간 통신에 TLS client certification 지원
Auto retry, circuit breaker, 부하량 제한 등 다양한 로드 밸런싱 기능 지원
모니터링 및 추적 기능 제공 및 마이크로서비스간 분산 트랜잭션 성능 측정 제공해서 서비스 간 가시성(Visibility)를 제공
Dynamic Configuration 지원을 통해서 중앙 레파지토리에서 설정 정보를 읽어와서 서버의 재시작 없이 라우팅 기능을 제공해준다.
L7 라우팅 지원
Envoy 프록시는 배포 위치에 따라 다양한 기능을 지원할 수 있다.
특정 서비스가 아니라 전체 시스템 앞에 위치한 프록시로 클라이언트에서 들어오는 요청을 각각의 서비스로 라우팅 해주는 기능이다.
URL 기반으로 라우팅을 하는 기능 이외에도 TLS(SSL) 처리 기능도 지원한다.
통상적으로 nginx나 apache가 reverse proxy의 기능을 위해 이 방법을 많이 사용했다.
특정 서비스 앞에 위치하는 프록시로 트래픽을 제어하거나 서킷 브레이커 역할을 해준다.
특정 서비스 뒤에있는 프록시로 서비스로 부터 나가는 트래픽을 제어하는 용도로 쓰인다. 서비스로 부터 호출 대상이 되는 서비스에 대한 로드 밸런싱과 호출 횟수 통제(Rate limiting) 역할을 수행해준다.
내부 서비스에서 외부 서비스로 요청을 보낼 때 대신 해주는 역할을 한다.
Envoy 프록시는 서비스의 앞 뒤에 붙을 수 있는 구조라서 배포할 때 두개의 Envoy 프록시를 배포해야지 라고 생각할 수 있지만 실제로는 한개의 Envoy 프록시를 배포한 후 ingress/egress 용으로 어떻게 쓰일지 설정하는 방식으로 사용한다.
Envoy 프록시는 크게 Listener, Filter, Cluster 이렇게 구성되어있다.
리스너는 클라이언트로부터 요청을 받는 부분으로 HTTP Listener, TCP Listener 이렇게 있다.
필터는 라우터로 부터 나가기전에 메시지를 중간 처리하는 역할을 한다. 압축이나 들어오는 트래픽에 대한 제어를 한다.
클러스터는 실제로 라우팅 될 서비스를 지정한다.
요약하면 리스너를 통해서 요청을 받아서 필터를 통해 처리한 후 라우팅에 따라 적절한 클러스터가 선택되서 적절한 서비스로 요청을 보내게 된다.
Envoy Proxy를 설치하는 방법은 다양하게 있지만 모든 플랫폼에서 사용할 수 있는 도커 이미지 방식이 가장 많이 사용된다.
다음 도커 명령어를 통해서 Envoy 프록시의 최신 이미지를 가져와서 실행시킬 수 있다.
$ docker pull envoyproxy/envoy:latest
$ docker run --rm -d -p 10000:10000 envoyproxy/envoy:latest
$ curl -v localhost:10000
Envoy 프록시를 이용해서 서비스 메쉬를 구현하기 위해서는 Envoy 프록시 같은 데이터 플레인을 통제해주는 컨트롤 플레인이 있어야 한다.
이를 위한 솔루션이 Istio다.
Istio의 아키텍처는 데이터 플레인과 컨트롤 플레인으로 나뉜다.
데이터 플레인은 Envoy를 서비스 옆에 사이드카 형식으로 배포해서 서비스로 들어오고 나가는 요청을 Envoy를 통해서 처리한다.
Envoy는 요청할 서비스의 주소를 알아야 하는데 이를 서비스 디스커버리(Service Discovery)라고 한다.
Envoy는 서비스의 주소를 컨트롤 플레인의 컴포넌트인 Pilot을 통해서 안다.
컨트롤 플레인은 데이터 플레인에 배포된 Envoy를 컨트롤 하는 역할을 하고 파일럿(Pilot), 믹서(Mixer), 시타델(Citadel) 3개의 모듈로 구성되어있다.
파일럿 (Pilot)
파일럿은 Envoy가 서비스의 엔드포인트를 알 수 있도록 서비스 디스커버리 기능을 제공해준다.
그리고 파일럿은 Envoy의 통신 즉 서비스간의 트래픽의 경로를 통제해주는 역할을 해줄 수 있다.
이 외에도 Envoy가 하는 기능인 retry, timeout, circuit breaker 같은 설정 정보를 제공해주는 역할을 한다.
믹서(Mixer)
믹서가 하는 일은 요청에 대한 정책을 통제하거나 모니터링 지표의 수집을 한다.
서비스가 처리량 이상만큼 요청을 못받도록 설정하거나 특정 헤더값이 일치해야 요청을 받도록 하는 정책 기능을 제공해준다.
또한 서비스의 응답 시간이나 평균 처리량 같은 다양한 지표를 수집한다.
시타델(Citadel)
시타델은 보안에 관련된 기능을 제공한다.
서비스간 인증이나 인가를 담당하고 서비스간 통신에 TLS을 이용해서 암호화 하도록 제공해준다.
대략적인 구조를 이해했으니 Istio의 기능을 이해해보자
트래픽 분할은 서로 다른 버전의 서비스를 배포했을 때 서비스 별로 가는 트래픽을 제어할 수 있도록 하는 기능이다.
이전 버전에서는 95%의 트래픽을 받도록 하고 새로운 버전에서는 5%의 트래픽을 받도록 설정할 수 있다. (카나리 테스트)
단순하게 커넥션 기반으로 트래픽을 분할하는게 아니라 네트워크 패킷의 내용을 기반으로 트래픽을 분할하는게 가능하다
예를 들어 HTTP User-agent 필드에 따라서 클라이언트가 안드로이드인 경우 ios인 경우에 따라 트래픽을 분할하는게 가능하다.
파일럿은 트래픽 통제를 통해서 서비스 호출에 대한 안정성 제공한다.
파일럿은 대상 서비스에 인스턴스가 여러개 있을 경우 로드 밸런싱 기능을 지원하고 각 서비스를 찾을 수 있도록 서비스 디스커버리 기능을 제공해준다.
그리고 각 서비스에 대한 주기적인 헬스 체크를 해서 살아있는지 확인한다.
서비스간 호출에 안정성을 제공해주기 위해서 retry 횟수와 timeout 설정을 제공해준다. 그리고 장애가 났다고 판단되면 서킷 브레이커 기능을 지원해준다.
기본적으로 envoy를 통한 통신은 TLS를 이용해서 암호화한다. 즉 서비스간의 통신이 디폴트로 암호화된다.
Istio는 서비스에 대한 인증을 제공한다. 서비스와 서비스간의 호출에 대한 서비스를 인증하는 기능과 서비스와 클라이언트간의 직접 인증 기능을 제공해준다.
인증뿐만 아니라 서비스에 대한 접근 권한을 통제 가능하다. 기본적으로 역할 기반의 권한 인증을 지원한다.
마이크로서비스에서 문제점은 서비스간의 호출이 많아지면서 각 서비스를 모니터링 하기가 어렵다는 뜻이다.
Istio는 네트워크 트래픽을 모니터링 할 수 있고 서비스간의 응답시간과 처리량 같은 지표를 수집할 수 있다.
이 지표는 Envoy로 부터 믹서에게 전달된다
Istio Gateway는 Istio 서비스를 외부로 노출시킨다.
그치만 쿠버네티스의 ingress나 service는 사용하지 않고 Istio의 Gateway를 통해서 노출시킨다.
Istio Gateway는 CRD(Custom Resource Definition) 타입으로 Istio에 들어오는 트래픽을 받아주는 엔드포인트 역할을 한다.
Istio Gateway는 pod 형식으로 배포되어 LoadBalancer 형식으로 동작한다.
Istio Gateway를 등록한 후에 서비스할 호스트를 Virtual Service로 등록하면 된다.
다음은 Istio 예제인 bookinfo의 istio-gateway yaml 파일이다.
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
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
selector를 이용해서 gateway 타입을 istio에서 제공해주는 ingressgateway로 설정했다.
그리고 http 프로토콜을 80으로 받도록 했고 여기서 게이트웨이로 들어오는 트래픽은 VirtualService로 간다.
Virtual Service에서는 게이트웨이에서 받을 서비스를 입력해야한다.
여기서는 /productpage, /static, /login, /logout, /api/v1/products/*로 들어오는 모든 트래픽은 쿠버네티스 서비스인 productpage의 9080로 간다.