공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/Service Mesh

Deview 2021 - istio/Envoy로 Multi-IDC L7 로드밸런서 만들기

inspirit941 2022. 10. 22. 23:36
반응형

Deview 2021: https://deview.kr/2021/sessions/502

 

당신의 대문을 책임집니다. Istio/Envoy로 Multi-IDC L7 로드밸런서 만들기

발표자 : 김동경

deview.kr

영상: https://tv.naver.com/v/23651554


스크린샷 2022-10-22 오전 11 34 15

 

스크린샷 2022-10-22 오전 11 34 19스크린샷 2022-10-22 오전 11 37 26

 
Google에서 Multi-Cluster Application을 제공하는 플랫폼의 형태

  • 여러 Region에 배포된 클러스터를 향해 트래픽이 전달됨
  • Fleet이라는 product - 물리적으로 나뉘어 있는 클러스터를 하나의 logical cluster처럼 만드는 기능
  • cluster API - 하나의 API로 여러 region에 클러스터를 배포할 수 있음.

스크린샷 2022-10-22 오전 11 37 49

 
멀티 클러스터로 트래픽을 분산하는 Load balancer에는 앞단에서 사용될 수 있는 여러 routing / 인증처리와 같은 기능이 필요함.

  • AWS / GCP의 경우 L7 Load balancer에서 위와 같은 기능이 제공되고 있음.

왜 Istio?

스크린샷 2022-10-22 오전 11 41 06스크린샷 2022-10-22 오전 11 41 16

 
Load balancer로 사용되는 여러 SW가 있지만, 필요로 하는 여러 요구사항을 충족하지는 못함
 

스크린샷 2022-10-22 오전 11 42 51

 

스크린샷 2022-10-22 오전 11 42 59

 

  • Envoy는 mesh로 들어오는 traffic의 proxy 역할
  • istio는 proxy 설정을 On-the-fly 형태로 변경할 수 있도록 control plane역할 지원
  • GCP의 LoadBalancer도 envoy를 사용중

 

Load balancer 클러스터 구축

스크린샷 2022-10-22 오전 11 45 42

  • Load balancer를 위한 클러스터를 두 개의 region에 구축

 

스크린샷 2022-10-22 오전 11 49 35스크린샷 2022-10-22 오전 11 51 42

 

  • 각각의 클러스터는 2-tier Gateway architecture로 구성
    • LB 클러스터에 접근하기 위한 1-tier Gateway (클러스터의 External IP)
    • 각각의 서비스로 연결하기 위한 2-tier Gateway
    • 1-tier와 2-tier를 연결하기 위한 VirtualService가 존재함
    • 2-tier gateway와 외부 서비스의 연결을 위한 ServiceEntry / VirtualService 존재

그래서 LB 클러스터는 최종적으로 아래의 아키텍처 형태.

  • 1-tier Gateway의 경우, 여러 서비스가 virtualService / ServiceEntry 형태로 연결되도록 만들었음.
  • public IP 리소스 아끼기 / 가급적 많은 서비스가 1-tier Gateway 사용할 수 있도록

 

스크린샷 2022-10-22 오전 11 54 31

성능 이슈 발생

 

스크린샷 2022-10-22 오전 11 57 01스크린샷 2022-10-22 오전 11 56 57스크린샷 2022-10-22 오후 12 00 34

 

  • 하나의 gateway 안에 연결된 서비스가 2~3천개 되니까 성능 이슈 발생 (2-tier gateway 개수 증가로 발생)
  • VirtualService 설정을 추가할 때, 설정이 반영되는 시간이 선형적으로 증가함
  • default 설정 기준으로는 2500개 넘어가면 OOM 발생
  • gateway에 연결된 virtualService 개수를 모니터링하면서, 한쪽이 과도해지면 다른 쪽으로 연결해주는 migration이 필요함.

Dynamic Migration을 위한 API서버 개발

 

스크린샷 2022-10-22 오후 12 03 57

  • virtualService를 동적으로 배포하고 관리하기 위한 API서버 개발, LB 클러스터에 배포.

 

스크린샷 2022-10-22 오후 12 05 47

 
일반적으로 API서버는 동기 방식. 하지만 k8s 클러스터에서는 대부분의 API가 비동기 방식임. 따라서 API서버도 Declarative API 형태로 개발해야 함

  • 목표 상태 정의 - event 형태로 받아서 동작 수행
  • 목표상태에 실패할 수 있는 상황을 감안해 Retry (Reconcile loop) 필요
  • 동작 중에 변경된 목표 상태 (예컨대 생성 -> 삭제)가 들어올 경우, 변경된 요청사항 반영

 

스크린샷 2022-10-22 오후 12 09 22

 

  • 따라서 API서버의 요구사항은 위와 같다.
    • 선언형 API
    • 이벤트 처리를 위한 Producer / Consumer 구조
    • 재시도 로직 (Reconcilation Loop)

API 서버 모델 디자인

스크린샷 2022-10-22 오후 12 11 00

 

  • Service : 정의한 다른 모델 (Routing, backend 등)을 하나의 그룹으로 묶기 위한 일종의 Container / Namespace
  • DNS : DNS서버의 특정 도메인이 어떤 IP와 연결되는지 확인하는 용도
  • Routing : 여러 조건에 맞게 traffic Routing 기능을 담당함
  • Backend : 외부의 PM 또는 다른 클러스터의 service (pod)를 지칭함. health check / Load balancing 담당

 
각 모델이 실제로 생성하는 k8s Object들은 아래와 같다.
 

스크린샷 2022-10-22 오후 12 16 58스크린샷 2022-10-22 오후 12 17 58스크린샷 2022-10-22 오후 12 18 03스크린샷 2022-10-22 오후 12 18 11

 
Golang으로 구현된 해당 모델의 Struct는 아래와 같다.

  • Service
    • 사용자가 원하는 상태를 선언하기 위한 필드.
    • 해당 서비스에 어떤 Domain이 매핑되어 있는지 모델링되어 있음
  • Routing
    • httpRoutes struct가 따로 있고, 해당 route에 적용될 rule을 정의할 수 있다.
    • RouteRule의 경우 uri나 header, query parameter와 같은 조건을 정의할 수 있다.
  • Backend
    • endpoint로 실제 쓰이는 address, port 지정
    • backendOption의 경우 ConnectionSetting, LB setting, healthCheck 세팅 등이 포함된다.

 

스크린샷 2022-10-22 오후 7 32 14스크린샷 2022-10-22 오후 7 32 20스크린샷 2022-10-22 오후 7 32 27스크린샷 2022-10-22 오후 7 32 32스크린샷 2022-10-22 오후 7 32 41


 
멀티 클러스터 환경에서 DNS를 체크하기 위해 etcd는 global clustering 방식을 사용함. 다른 클러스터의 DNS 정보도 확인할 수 있어야 하므로.

  • kafka같은 message Queue도 있지만, 이 경우 message Queue의 Availability / Scalability 도 보장해야만 함.

 

스크린샷 2022-10-22 오후 7 40 31스크린샷 2022-10-22 오후 7 40 40

 

  • 하나의 API server에서 데이터를 선언
  • 다른 API server에서는 해당 데이터를 받아서 작업 처리.
  • 요청이 들어오면, etcd 클러스터로부터 변경사항을 받아서 처리한 후에야 클라이언트에게 응답. -> 비동기 처리.

고려해야 할 장애사항

 

스크린샷 2022-10-22 오후 7 47 01스크린샷 2022-10-22 오후 7 47 06스크린샷 2022-10-22 오후 7 47 16

예컨대

  • API 서버에서 특정 k8s Object를 삭제 요청했으나 삭제되지 않을 경우 - 재시작
  • API 서버 자체에 문제가 생겨 생성 / 삭제 요청을 제대로 수행하지 못할 경우
  • Backend struct를 토대로 istio Object 생성 중 일부가 실패할 경우

등등..
핵심: 생성 / 삭제와 같은 로직이 멱등성 있게 동작해야 한다.
 

스크린샷 2022-10-22 오후 7 49 26

 
Loop를 GoRoutine으로 구현.

  • 실행해야 할 연산이 정해진 목표에 도달할 때까지 반복.
  • 처리 과정에서 ETCD / k8s Object 상태 체크 -> 최신 상태와 맞는지 확인

특정 이벤트 요청이 들어왔을 때 / Bootup 시점에 Loop 동작하도록 구성. API에 멱등성이 있으므로 데이터 복구용 sync로도 사용 가능.


스크린샷 2022-10-22 오후 8 21 56

 

Reconciliation Loop

  • 함수 output으로 loop연산 / 종료 연산을 반환함.
  • loop key 획득 -> ACID 연산 사용
  • 종료 여부 판단 -> 최신 상태 아닐 경우 재시도
    • 너무 오래 재시도할 경우 timeout 처리
  • 마지막 함수 input으로 실행해야 할 함수를 추가 -> 기본 로직을 공통으로 활용 가능.

스크린샷 2022-10-22 오후 8 27 53스크린샷 2022-10-22 오후 8 28 48스크린샷 2022-10-22 오후 8 29 40

 

동시성 처리

하나의 클러스터 내에 여러 API server가 배포되어 있음. ETCD에 접근해서 DNS 정보를 확인할 때의 동시성 처리.

  • ACID txn 사용.

스크린샷 2022-10-22 오후 8 30 43스크린샷 2022-10-22 오후 8 38 04

 

  • 데이터 선언 시 Revision Check -> ETCD에 transaction 요청. transaction 성공한 API만 데이터 선언한 내용을 반영할 수 있음.
  • 선언 완료 시 클라이언트에 응답. (비동기)
  • Loop도 선언형으로 상태를 변경하는 것이므로 transcation을 확보해야 Loop 생성 가능.
    • loop 과정에서 장애가 발생할 수 있으므로 etcd의 Lease 기능 포함.
    • keepAlive 메시지를 일정 시간 보내지 않을 경우 장애상황으로 간주 - 작업내용 삭제.

상태 관리

스크린샷 2022-10-22 오후 8 45 29

 
다른 클러스터에서는 전부 정상적으로 동작이 완료되었는데, 하나의 클러스터에서만 재시도중인 상황

  • 사용자가 API server에서 상태 조회요청을 하면, 모든 클러스터의 상태를 볼 수 있어야 한다.
  • 실패했다면 어떤 이유로 실패했는지 확인이 필요

= 히스토리 관리가 필요함

스크린샷 2022-10-22 오후 8 48 56

 
히스토리 추적을 고려한 ETCD key 모델링
 

multi-domain / 인증서 관리

스크린샷 2022-10-22 오후 8 50 50스크린샷 2022-10-22 오후 8 51 00

 

  • DNS에서 사용하는 default Domain.
  • 사용자는 중간의 io prefix 없이 public domain (i.e. cafe.naver.com ) 형태로 사용함
  • 즉 대부분의 경우 suffix가 유사함 -> 비슷한 Service의 경우 domain Conflict 발생 가능

스크린샷 2022-10-22 오후 8 54 55

 
예컨대 두 개의 서비스가 1-tier gateway (아마도 같은 클러스터 내부)에 연결되어 있고, 같은 wildcard cname 인증서를 가지고 있을 경우

  • 최초에 브라우저가 serviceA로 connection을 맺었을 경우
  • serviceB 도메인으로 요청해도 내부적으로는 이미 생성된 serviceA connection을 재사용하게 됨
  • 원하는 endpoint가 없으므로 404 not found.

스크린샷 2022-10-22 오후 8 55 00

 
서로 다른 인증서를 사용하더라도 cname이 같은 인증서일 경우 발생할 수 있음.

  • 현재 http 2.0 spec에 따른 이슈이므로 우회하는 식으로 해결해야 함.

스크린샷 2022-10-22 오후 9 00 52

 
따라서 충돌이 나지 않도록 모델링 - Cert, Domain struct 추가. 충돌이 있을 경우 다른 Gateway로 연결되도록 변경
 

스크린샷 2022-10-22 오후 9 01 59스크린샷 2022-10-22 오후 10 24 36스크린샷 2022-10-22 오후 10 27 02

 

스크린샷 2022-10-22 오후 10 26 10

 


Health Check

스크린샷 2022-10-22 오후 10 28 26스크린샷 2022-10-22 오후 10 28 33

 
클러스터에 장애가 있거나 maintenance mode로 전환하는 등 특정 클러스터를 제외해야 하는 경우.
 

스크린샷 2022-10-22 오후 10 29 58스크린샷 2022-10-22 오후 10 31 38

 

Cluster

예컨대 특정 클러스터 (cluster4)를 모든 서비스에서 제외해야 하는 경우라면

  • 모든 DNS record를 확인한 뒤 해당 클러스터의 DNS를 제외해야 한다.
  • maintenance Mode 활성화 / 비활성화 API 추가, DNSRecord 모델에 zonesToExclude 필드 추가해서 제외해야 할 zone 지정 가능.

 

스크린샷 2022-10-22 오후 10 32 57

 
maintenance mode 기능을 활용한 health check.

  • gateway의 상태를 확인
  • 문제가 있을 경우 maintenance mode -> 클러스터 제외.
  • 최소 두 개 이상의 health checker가 장애로 인지 / gateway 실패가 threshold 기준 초과해야 제외.

Service

스크린샷 2022-10-22 오후 10 35 43

 
연결된 서비스의 health check endpoint (i.e. /health) 확인.


Global Rate Limit

스크린샷 2022-10-22 오후 10 37 58

 
istio(envoy)의 ratelimit 기능 활용.

  • 보통 service mesh 내에 여러 개의 envoy proxy가 돌고 있고, envoy proxy에서 rateLimit Service에 요청을 보내 값을 확인하는 방식.
  • istio에선 ratelimit 인터페이스를 Json / gRPC 형태로 제공
    • 어떤 도메인에 ratelimit을 적용할 것인지 (string domain)
    • 하나의 요청에 몇 개의 limit을 증가시킬지 결정 (hit_addend)
  • ratelimit service는 redis의 값을 토대로 ratelimit response를 응답한다.

 

스크린샷 2022-10-22 오후 10 42 16

 
자세한 설정을 위해 Descriptor 사용

  • entry : kv pair
  • RateLimitOverride : unit을 정의하고, 해당 unit동안 얼마의 트래픽을 허용할 것인지

 

스크린샷 2022-10-22 오후 10 44 41

 
Envoy 공식문서의 예제. ratelimit으로도 복잡한 시나리오를 정의할 수 있음.
 

스크린샷 2022-10-22 오후 10 46 49

 
모든 envoy가 요청이 들어올 때마다 ratelimit 값을 확인한 뒤 요청에 응답할지를 결정하기 때문에 성능에 영향을 미치게 됨.

  • 50개 client -> 4 ratelimit pod에 요청을 보낼 시 Latency 2ms
  • 400 client -> 4 ratelimit pod에 요청을 보낼 시 Latency 40ms

사용자의 요청 중간에 끼어드는 형태이므로 latency 고려가 필요함


스크린샷 2022-10-22 오후 10 51 04




벤치마크 결과

  • 20 client : 1 pod 정도까지는 latency 1ms 정도.
  • 40 client일 경우 2.5ms. 이후 linear하게 증가하는 경향.

따라서 latency 요구사항에 맞는 ratelimit pod 비율을 조정 - scale out 비율을 정의할 수 있음.
 

스크린샷 2022-10-22 오후 10 51 31

 
최종 아키텍처

  • 사용자의 namespace마다 ratelimit service, 동적으로 service 옵션 변경을 위한 configmap 배포
  • ingress gateway에서 ratelimit 활성화할 수 있도록 envoyFilter 적용
  • ratelimit service에서 사용하는 redis는 system ns에 배포
  • ingress gateway -> 자신의 ns에 배포된 ratelimit 서비스에 요청을 보내고 받는 구조.

단, ratelimit 자체는 하나의 클러스터에서만 동작하도록 구성되어 있음. 따라서 redis는 clustering 수행 - 여러 클러스터에서 동일한 값을 받을 수 있도록.
 

스크린샷 2022-10-22 오후 11 23 00

 


Logging

 

스크린샷 2022-10-22 오후 11 24 32스크린샷 2022-10-22 오후 11 24 53

 
사용자의 namespace에 loki 배포, ingress gateway에 Promtail agent을 sidecar로 배포.

  • 클러스터마다 loki 인스턴스가 존재
  • 사용자의 fetch api 요청
    • api에서 각 클러스터의 loki 인스턴스에 요청 전달
    • 응답받은 뒤 aggregate -> 전달.

특정 서비스가 로그를 많이 생성하는 경우 -> 한 서비스가 다른 서비스에 영향을 주게 된다.

  • 서비스 간 영향을 최소화하기 위해 loki를 사용자의 namespace마다 배포함.

Monitoring

스크린샷 2022-10-22 오후 11 29 38

 
사용자의 namespace에 prometheus / grafana 배포

Autoscaling

스크린샷 2022-10-22 오후 11 29 51

 
모든 컴포넌트는 HPA 기반 authoscaling

반응형