스터디/Istio Hands-on Study

Istio Hands-on Study [1기] - 2주차 - Envoy, Isto Gateway (4.2.3 이전)

Nowon9159 2025. 4. 15. 23:18
반응형

개요

CloudNet에서 주관하는 istio(Istio Hands-on Study) 1기 스터디 2주차 글이다. 아래의 글은 스터디의 내용을 기반으로 작성했으니 많은 관심 부탁 드린다. CloudNet@ Blog 에서 스터디 관련 글 및 모집 글을 확인 가능하다.

 

 

3장 Istio’s data plane: Envoy Proxy

이번 장은 아래와 같은 목표가 있다.

  • Understanding the standalone Envoy proxy and how it contributes to Istio : Istio 에 Envoy 동작 이해
  • Exploring how Envoy’s capabilities are core to a service mesh like Istio : Envoy 기능 알아보기
  • Configuring Envoy with static configuration : Envoy 정적 설정하기
  • Using Envoy’s Admin API to introspect and debug it : Envoy Admin API로 분석과 디버깅

 

3.1 What is the Envoy proxy?

엔보이 소개

엔보이란 무엇인가?

  • 엔보이는 분산 시스템을 구축할 때 발생하는 어려운 애플리케이션 네트워킹 문제를 해결하고자 리프트가 개발했다.
  • 엔보이는 다음 2가지 중요 원칙에 따라 만들어졌다.
    • 애플리케이션에게 네트워크는 투명해야 한다.
    • 네트워크 및 애플리케이션 문제가 발생할 때는 문제의 원인을 파악하기 쉬워야 한다.

엔보이는 프록시이다. 그럼 프록시라는 명제는 무엇인가?

  • 프록시는 클라이언트와 서버 간 통신의 중간에 위치한, 네트워크 아키텍터의 중개 구성 요소이다.
  • 프록시는 중간에 위치해 보안, 프라이버시, 정책 같은 기능을 추가할 수 있다.

https://excitingip.com/wp-content/uploads/2011/01/proxyserverarchitecture.jpeg

  • 프록시는 클라이언트와 서비스가 통신할 때 알아야 할 사항을 단순화 할수 있다.
    • 예를 들어 서비스가 동일한 인스턴스 집합(클러스터)으로 구현됐고, 각 인스턴스는 일정량의 부하를 처리할 수 있다고 가정해보자.
    • 클라이언트가 이 서비스에 요청할 때 어느 인스턴스 혹은 IP 주소인지 어떻게 알아야 하는가?
    • 프록시가 단일 식별자 혹은 IP주소를 갖고 중간에 위치할 수 있고, 클라이언트는 서비스 인스턴스들과 통신하는 데 프록시를 사용할 수 있다.
프록시가 서비스 인스턴스보다 윗 단에 위치해 클라이언트가 서비스로 접속하고자 할 때 프록시를 사용할 수 있다는 의미

 

프록시는 서비스 인스턴스에 로드밸런싱을 처리할 수 있다.

https://miro.medium.com/v2/resize:fit:720/format:webp/1*2LmccMiuHiqxbP8vQiq-Mg.png

  • 이런 리버스 프록시의 경우 클러스터의 인스턴스 상태를 검사하고 실패하거나 오동작하는 백엔드 인스턴스를 우회하도록 라우팅한다.
  • 이런 방식을 이용해 프록시는 클라이언트가 어느 백엔드 서비스 인스턴스가 과부하되어 있거나 장애가 발생했는지 파악할 필요 없도록 보호할 수 있다.
이와 같은 기능을 Health check라고 하며 대표적으로 AWS의 ELB에서 Target Group에 대한 Healthcheck 기능을 지원한다.

 

엔보이 프록시는 서비스 디스커버리, 로드 밸런싱, 헬스 체크 같은 기능을 제공하기 위해 애플리케이션 요청 경로 중간에 삽입할 수 있는 애플리케이션 수준 프록시이지만, 엔보이는 그 이상을 할 수 있다.

  • 애플리케이션이 다른 서비스와 통신할 때 사용하는 7계층 프로토콜(OSI 7 Layer 중 7번째 계층)을 이해할 수 있다.
  • 기본적으로 HTTP 1.1, HTTP 2, gRPC 등을 이해할 수 있고, 요청 수준 타임아웃, 재시도, 재시도별 타임아웃, 서킷 브레이커와 기타 복원력 기능 같은 동작을 추가할수 있다.
  • 커넥션만을 이해하는 기본적인 커넥션 수준(L3/L4) 프록시로는 이와 같은 작업을 수행할 수 없다.

엔보이는 기본 제공 프로토콜 외에도 다양한 프로토콜을 이해하도록 확장할 수 있다.

  • MongoDB, DynamoDB 같은 데이터베이스용 또는 AMQP 같은 비동기 프로토콜 용 필터도 작성 돼 왔다.
  • 애플리케이션에 투명하게 보이고 신뢰성을 갖추는 것도 중요하지만, 분산 아키텍처에서 일어나고 있는 일을 빠르게 이해할 수 있는 능력도 그만큼 중요하다.
  • 엔보이는 애플리케이션 수준 프로토콜을 이해할 수 있고, 애플리케이션 트래픽이 엔보이를 거쳐 흐르는 덕분에 통과하는 요청에 대한 텔레메트리를 수집할 수 있다.
    • 예를 들어 요청 처리에 얼마나 시간이 소요되는지, 특정 서비스가 얼마나 많은 요청을 처리하고 있는지(처리량 throughput), 서비스의 오류율은 어느 정도인지 등이다.

엔보이는 애플리케이션 외부에서 동작해 개발자가 네트워크 문제를 고려하지 않아도 되도록 설계됐다.

  • 어느 프로그래밍 언어로 작성하던, 어느 프레임워크를 사용하던 모든 애플리케이션이 엔보이 프록시의 모든 기능을 사용할 수 있다.
  • 엔보이는 클러스터 엣지 프록시, 서비스의 공유 프록시, 이스티오 처럼 서비스 별 프록시로도 사용 가능하다.
  • 이스티오에서는 엔보이 프록시를 서비스 인스턴스당 하나씩 배포해 유연성, 성능, 제어 능력을 높인다.
  • 사이드가 프록시와 에지 프록시를 구현하면 인프라를 운영하고 이해하기가 더 쉬워진다.
  • 엔보이는 서비스 메시의 진입점으로 사용돼 클러스터에 들어오는 트래픽을 완벽히 제어하고 관찰할 수 있다.

 

3.1.1 Envoy’s core features* 엔보이의 핵심 기능

상위 레벨에서의 엔보이는 아래와 같다.

  • 리스너 Listeners
    • 애플리케이션이 연결할 수 있는 외부 세계로 포트를 노출한다.
    • 예를 들어 포트 80에 대한 리스터는 트래픽을 받고, 설정된 동작을 해당 트래픽에 적용한다.
  • 루트(라우트) Routes
    • 리스너로 들어오는 트래픽을 처리하는 라우팅 규칙
    • 예를 들어 요청이 들어오고 /catalog 에 일치하면 그 트래픽을 catalog 클러스터로 보내는 식이다.
  • 클러스터 Cluster
    • 엔보이가 트래픽을 라우팅할 수 있는 특정 업스트림 서비스, 예를 들어 catalog-v1 과 catalog-v2 는 별도 클러스터일 수 있고,
    • 루트는 catalog 서비스의 v1이나 v2로 트래픽을 보내는 방법에 대한 규칙을 지정할 수 있다.

엔보이가 L7 트래픽에 수행하는 작업을 개념적으로 설명한 것이다.

 

엔보이는 트래픽 방향성을 나타낼 때 다른 프록시와 비슷한 용어를 사용한다.

  • 예를 들어 트래픽은 다운스트림 시스템에서 리스너로 들어온다.
  • 이 트래픽은 엔보이의 클러스터 중 하나로 라우팅되며, 클러스터는 트래픽을 업스트림 시스템으로 보내는 역할을 한다.
  • 트래픽은 엔보이를 거쳐 다운스트림에서 업스트림으로 흐른다.

 

서비스 디스커버리 SERVICE DISCOVERY

클라이언트 측 서비스 디스커버리 client-side service discovery를 구현하기 위해 런타임별로 전용 라이브러리를 사용할 필요 없이, 엔보이는 서비스 디스커버를 자동으로 수행할 수 있다.

  • 엔보이가 간단히 디스커버리 API에서 서비스 엔드포인트를 찾도록 설정하기만 하면, 애플리케이션은 서비스 엔드포인트를 찾는 방법을 몰라도 된다.
  • 디스커버리 API는 다른 일반적인 서비스 디스커버리 API(like HashiCorp Consul, Apache ZooKeeper, Netflix Eureka, and so on)를 래핑하는 데 사용할 수 있는 단순한 REST API 이다.
  • 이스티오의 컨트롤 폴레인은 이 API를 기본적으로 구현하고 있다.

 

이스티오엔보이서비스 디스커버리 메커니즘 설정을 조절하는 고수준 리소스들을 제공함으로써 세부적인 내용 대부분을 추상화한다.

https://www.notion.so/image/attachment%3A2d6e56f6-ae86-4ea6-97a9-8ef9a15bc62a%3Aimage.png?table=block&id=1d450aec-5edf-81fa-961a-c31b16179026&spaceId=a6af158e-5b0f-4e31-9d12-0d0b2805956a&width=1290&userId=63652e3a-ecd7-4baf-806b-78732b5cc206&cache=v2

 

로드 밸런싱 LOAD BALANCING

엔보이는 애플리케이션이 활용할 수 있는 고급 로드 밸런싱 알고리즘을 여러 가지 구현하고 있다.

  • 지역 인식 locality-aware 로드 밸런싱
    • 엔보이는 특정 기준을 충족하지 않으면 트래픽이 지역 경계를 넘지 않게 해 트래픽을 더 잘 분산시킬 수 있다.
    • 예를 들어 엔보이는 장애 상황으로 이어지는 것이 아닌 이상, 서비스 간 트래픽을 동일한 지역의 인스턴스로 라우팅되도록 한다.
  • 랜덤 Random
  • (가중치) 라운드 로빈 (Weighted) Round robin
  • 가중치를 적용한 최소 요청 Weighted, least request
  • 일관된 해싱 Consistent hashing (sticky) : Ring hash, Maglev

등등 여러 로드 밸런싱 알고리즘이 있다.

 

트래픽 및 요청 라우팅 TRAFFIC AND REQUEST ROUTING

  • 엔보이는 HTTP 1.1과 HTTP 2 같은 애플리케이션 프로토콜을 이해할 수 있으므로 정교한 라우팅 규칙을 사용해 트래픽을 특정 백엔드 클러스터로 보낼 수 있다. - Docs
  • 이를 통해 가상 호스트 매핑과 콘텍스트 경로 context-path 라우팅 같은 기본적인 리버스 프록시 라우팅을 처리할 수 있고, 또한 헤더 및 우선순위 기반 라우팅, 라우팅 재시도 및 타임아웃, 오류 주입까지도 수행할 수 있다.

 

트래픽 전환 및 섀도잉 기능 TRAFFIC SHIFTING AND SHADOWING CAPABILITIES

  • 비율 기반 트래픽 분할 및 전환 지원
    • 엔보이는 비율 기반(즉, 가중치 적용) 트래픽 분할 splitting / 전환 shifting 을 지원한다.
    • 이 기능을 활용해 애자일 팀은 카나리 릴리스와 같이 위험을 완화하는 CD 기술을 사용할 수 있다. 위험성을 더 작은 유저 풀로 완화하기는 하지만, 카나리 릴리스는 여전히 라이브 사용자 트래픽을 다룬다.
  • 트래픽 사본 생성 및 섀도잉
    • 엔보이는 트래픽의 사본을 만들어 ‘보내고 망각하는 fire and forget’ 방식으로 트래픽을 엔보이 클러스터에 섀도잉 shadowing 할 수 있다.
    • 이 섀도잉 기능을 트래픽 분할과 같은 것으로 생각할 수 있지만, 업스트림 클러스터(프록시 기준 수신 측)가 보는 요청은 라이브 트래픽의 복사본이다.
    • 이는 고객에게 영향을 주지 않고 운영 환경 트래픽으로 서비스 변경 사항을 테스트 할 수 있는 아주 강력한 기능이다.
섀도잉(미러링) 기능을 사용하면 최소한의 위험으로 변경 사항을 프로덕션에 반영할 수 있다. 예를 들어 v1 Pod 를 운영 중 아래 코드 처럼 v2로 트래픽을 100% 미러링 할수도 있다.
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
- httpbin
  http:
  - route:
- destination:
    host: httpbin
    subset: v1
  weight: 100
mirror:
  host: httpbin
  subset: v2
mirrorPercentage:
  value: 100.0
EOF

 

네트워크 복원력 NETWORK RESILIENCE

엔보이에게 특정 종류의 복원력 문제를 맡길 수는 있지만, 그 파라미터를 설정하고 잘 조정하는 것은 애플리케이션의 책임이라는 점을 유의해야 한다.

  1. 요청 타임아웃과 요청 수준 재시도 수행
    • 엔보이는 요청 타임아웃과 요청 수준 재시도(재시도별 타임아웃 포함)을 자동으로 수행할 수 있다. 이런 재시도 동작은 네트워크 불안정이 간간히 요청에 영향을 줄 때 매우 유용한다.
    • 반면에 재시도 증폭은 연쇄 장애로 이어질 수 있어 엔보이에서는 재시도 동작을 제한할 수 있다.
    • 또한 애플리케이션 수준 재시도는 여전히 필요할 수 있으며 엔보이가 완전히 대체할 수 없다.
  2. 커넥션 혹은 요청의 개수 제한 또는 임계값 기준 실패 처리
    1. 게다가 엔보이가 업스트림 클러스터를 호출할 때 진행 중인 커넥션 혹은 요청의 개수를 제한하고, 그 임계값을 넘어서는 것은 빠르게 실패시키도록 설정 할 수 있다. (임계값에는 지터 jitter 가 있을 수 있다).
  3. 이상값 감지
    • 마지막으로 엔보이는 서킷 브레이커처럼 동작하는, 이상값 감지 outlier detection 를 수행해 오동작하는 엔드포인트를 로드 밸런싱 풀에서 제거할 수 있다.

 

HTTP/2 AND gRPC

  • HTTP/2
    • HTTP/2 는 단일 커넥션에서 여러 요청을 처리하고 서버 푸시, 스트리밍, 요청 백프레셔 backpressure 를 지원하도록 HTTP 프로토콜을 크게 개선한 버전이다.
    • 엔보이는 처음부터 HTTP/1.1 과 HTTP/2 프록시로 개발돼 다운스트림과 업스트림 모두에게 각 프로토콜을 프록시할 수 있다. - Docs
    • 이를테면 엔보이는 HTTP/1.1 커넥션을 받아 HTTP/2로 프록시하거나 그 반대도 가능하며, HTTP/2를 받아 업스트림 HTTP/2 클러스터로 프록시 할 수도 있다.
  • gRPC
    • gRPC는 HTTP/2 위에서 구글 프로토콜 버퍼 Protobur 를 사용하는 RPC 프로토콜로, 역시 엔보이가 기본적으로 지원한다. - Docs
    • 이는 강력하지만 제대로 구현하기 어려운 기능이며 다른 서비스 프록시와 엔보이를 차별화한다.

 

메트릭 수집을 통한 관찰 가능성 OBSERVABILITY WITH METRICS COLLECTION

엔보이의 목표 중 하나는 네트워크를 이해할 수 있게 만드는 것이다. 이 목표를 위해 엔보이는 다양한 메트릭을 수집한다.

  • 엔보이는 서버에 호출하는 다운스트림 시스템, 서버 그 자체, 서버가 요청을 보내는 업스트림 클러스터에 대한 여러 측면(디멘션 dimentsion) 을 추적한다.
  • 엔보이의 통계는 카운터, 게이지, 히스토그램으로 추적된다.
  • 아래 표는 업스트림 클러스터에 대해 추적하는 통계 유형의 몇 가지 예시를 나열한다.
통계 설명
downstream_cx_total 커넥션 개수 Total connections
downstream_cx_http1_active 활성 HTTP/1.1 커넥션 개수 Total active HTTP/1.1 connections
downstream_rq_http2_total 총 HTTP/2 요청 개수 Total HTTP/2 requests
cluster.<name>.upstream_cx_overflow 클러스터의 커넥션 서킷 브레이커가 임계값을 넘겨 발동한 횟수
Total number of times that the cluster’s connection circuit breaker overflowed
cluster.<name>.upstream_rq_retry 총 요청 재시도 횟수 Total number of request retries
cluster.<name>.ejections_detected_consecutive_5xx 5xx 오류가 계속돼 퇴출된 횟수 (시행되지 않은 경우도 포함)
Number of detected consecutive 5xx ejections (even if unenforced)

엔보이는 설정 가능한 어댑터와 형식을 사용해 통계를 내보낼 수 있다. 기본적으로 지원하는 목록은 아래와 같다.

  • StatsD
  • Datadog; DogStatsD
  • Hystrix formatting
  • Generic metrics service

 

분산 트레이싱을 통한 관찰 가능성 OBSERVABILITY WITH DISTRIBUTED TRACING

  • 엔보이는 트레이스 스팬을 오픈트레이싱 OpenTracing 엔진에 보고해 호출 그래프 내 트래픽 흐름, 홉, 지연 시간을 시각화할 수 있다. 즉, 특별한 오픈트레이싱 라이브러리를 설치할 필요가 없다.
  • 한편 필요한 집킨 헤더를 전파하는 것은 애플리케이션의 역할이며, 이는 가벼운 래퍼 wrapper 라이브러리로 수행할 수 있다.
집킨(zipkin) 헤더
분산 추적(distributed tracing)을 위해 HTTP 요청이나 메시지에 추적 정보를 실어 보내는 메타데이터(header)
이는 서비스 간 호출이 있을 때 "이 요청이 어떤 트랜잭션의 일부인지", 또는 "어디서부터 어디까지 흘러왔는지"를 파악하기 위해 사용됨
  • 엔보이는 서비스 간 호출을 연관시킬 목적으로 x-request-id 헤더를 생성하며, 트레이싱이 시작될 때 initial x-b3* 헤더를 만들 수도 있다.
  • 애플리케이션이 전파해야 하는 헤더는 다음과 같다.
    • x-b3-traceid
    • x-b3-spanid
    • x-b3-parentspanid
    • x-b3-sampled
    • x-b3-flags
위 "애플리케이션이 전파애햐 하는 x-b3 헤더" 설명 링크

 

자동 TLS 종료 및 시작 AUTOMATIC TLS TERMINATION AND ORIGINATION

엔보이는 특정 서비스로 향하는 TLS 트래픽을 종료 terminate 시킬 수 있다. 클러스터의 에지와 서비스 메시 내부의 프록시 모두에서 가능하다.

  • 더 흥미로운 기능은 애플리케이션 대신 엔보이가 업스트림 클러스터로 TLS 트래픽을 시작할 수도 있다는 것이다.
  • 즉, 엔터프라이즈 개발자와 운영자가 언어별 설정과 키스토어 또는 트러스터 스토어를 만지작거리지 않아도 된다.

요청 경로에 엔보이가 있으면 TLS, 심지어 mTLS 까지도 자동으로 얻을 수 있다.

 

속도 제한 RATE LIMITING

  • 복원력이 중요한 측면, 보호받는 리소스로의 접근을 차단하거나 제한할 수 있는 기능이다.
  • 데이터베이스나 캐시, 공유 서비스 같은 리소스들은 다음과 같은 여러 이유로 보호받을 수 있다.
    • 호출 call 비용이 비쌈 (실행 invocation 당 비용)
    • 지연 시간이 길거나 예측 불가능
    • 기아 starvation 를 방지하기 위해 공정성 알고리즘이 필요.
  • 서비스가 재시도 하도록 설정한 경우 시스템 내에서 특정 장애의 영향이 과도하게 확대되지 않도록 요청을 제한하는 데 전역 속도 제한 서비스를 사용할 수 있다.
  • 엔보이는 네트워크(커넥션별)와 HTTP(요청별) 수준 모두에서 속도 제한 서비스와 통합할 수 있다.

 

엔보이 확장하기 EXTENDING ENVOY

  • 엔보이의 핵심은 프로토콜(7계층) 코덱(필터 filter 라고 부름)을 구축할 수 있는 바이트 처리 엔진이다.
  • 엔보이에서는 추가 필터를 구축하는 것을 주요 사용 사례로 삼고 있으며, 이는 필요에 맞게 엔보이를 확장할 수 있는 흥미로운 방법이다.
  • 엔보이 필터는 C++로 작성돼 엔보이의 바이너리로 컴파일된다.
  • 또한 엔보이는 루아 Lua 스크립트와 웹어셈블리 Wasm, WebAssembly를 지원하므로 덜 침습적인 invasive 방법으로도 엔보이 기능을 확장할 수 있다.

 

3.1.2 Comparing Envoy to other proxies 엔보이와 다른 프록시 비교

엔보이의 장점은 애플리케이션이나 서비스 프록시 역할을 한다는 데 있다. 프록시를 이용해 애플리케이션 간 통신을 원활하게 하며, 신뢰성 및 관찰 가능성 문제를 해결한다.

다른 프록시는 커뮤니티 중 

엔보이가 다른 프록시에 비해 특히 뛰어난 영역은 아래와 같다.

  • 웹어셈블리를 통한 확장성 Extensibility with WebAssembly
  • 공개 커뮤니팅 Open community
  • 유지 보수 및 확장이 용이하도록 구축한 모듈식 코드베이스 Modular codebase built for maintenance and extension
  • HTTP/2 지원 (업스트림 및 다운스트림) HTTP/2 support (upstream and downstream)
  • 심층 프로토콜 메트릭 수집 Deep protocol metrics collection
  • C++/가비지 수집 없음 C++ / non-garbage-collected
  • 동적 설정으로 핫 리스타트가 필요 없음 Dynamic configuration, no need for hot restarts

 

For a more specific and detailed comparison, see the following:

 

3.2 Configuring Envoy

들어가며

  • 엔보이는 JSON/YAML 형식 설정 파일로 구동된다.
  • 설정 파일은 리스너, 루트, 클러스터뿐 아니라 Admin API 활성화 여부, 액세스 로그 저장 위치, 트레이싱 엔진 설정 등 서버별 설정도 지정한다.
  • 초기 버전인 v1 과 v2 는 v3 로 대체돼 더 이상 사용하지 않는다.
  • 엔보이의 v3 설정 API는 gRPC를 사용한다.
  • 엔보이 및 v3 API 구현자들은 API 호출 시 스트리밍 기능을 사용해 엔보이 프록시가 올바른 설정으로 수렴하는 데 걸리는 시간을 줄 일 수 있다.
  • 실제로 이렇게 하면, 프록시가 주기적으로 폴링하는 대신 서버가 업데이트를 엔보이에 푸시할 수 있어 API를 폴링할 필요가 없어진다.
엔보이에 대한 형식 설정 파일은 예시 링크 참고하길 바란다.

 

3.2.1 Static configuration 정적 설정

엔보이의 설정 파일을 이용해 리스너, 라우팅 규칙, 클러스터를 지정할 수 있다. 아래는 간단한 엔보이 설정이다.

static_resources:
  listeners: # (1) 리스너 정의
  - name: httpbin-demo
    address:
      socket_address: { address: 0.0.0.0, port_value: 15001 }
    filter_chains:
    - filters:
      - name:  envoy.filters.network.http_connection_manager # (2) HTTP 필터
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          http_filters:
          - name: envoy.filters.http.router
          route_config: # (3) 라우팅 규칙
            name: httpbin_local_route
            virtual_hosts:
            - name: httpbin_local_service
              domains: ["*"] # (4) 와일드카드 가상 호스트
              routes:
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service # (5) 클러스터로 라우팅
  clusters:
    - name: httpbin_service # (6) 업스트림 클러스터
      connect_timeout: 5s
      type: LOGICAL_DNS
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: httpbin
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: httpbin
                  port_value: 8000
  • 위 설정 파일은 15001 포트에 소켓을 열고 필터 체인을 붙이는 리스너를 선언한다.
  • 필터는 엔보이의 http_connection_manager에 라우팅 지시문을 설정한다.
  • 이 예제의 간단한 라우팅 지시문은 와일드카드(*)로 모든 가상 호스트를 매칭하는 것으로, 모든 트래픽을 httpbin_service 클러스터로 라우팅한다.
  • 설정의 마지막 부분은 httpbin_service 클러스터에 커넥션 속성을 정의한다.
  • 이 예제는 업스트림 httpbin 서비스와 통신할 때 엔드포인트 서비스 디스커버리LOGICAL_DNS 를, 로드 밸런싱 알고리듬으로 ROUND_ROBIN 을 사용하도록 지정한다. - Docs
  • 이 설정 파일은 들어오는 트래픽이 연결할 수 있는 리스너를 만들고, 모든 트래픽을 httpbin 클러스터로 라우팅한다.
  • 또한 사용할 로드 밸런싱 설정과 커넥션 타임아웃 종류도 지정한다.
  • 이 프록시를 호출하면 요청이 httpbin 서비스로 라우팅될 것이다.
간단히 정리하자면 아래와 같다.
1. 15001 포트에 소켓을 열고 필터 체인 붙이는 리스너 선언
2. 모든 트래픽을 httpbin_service 클러스터로 라우팅하기 위해 http_connection_manager에 라우팅 지시문 설정
3. httpbin_service 클러스터에 커넥션 속성 정의
결국 들어오는 트래픽이 연결할 수 있는 리스너를 만들고, 모든 트래픽을 httpbin 클러스터로 라우팅

자세하게 보면 해당하는 내용을 지원하기 위한 여러 속성도 설정되어 있다.
connect_timeout -> 커넥션 타임아웃 시간 조절
lb_policy -> ROUND_ROBIN 으로 로드밸런싱 알고리즘 설정.
등등

 

  • 많은 설정들이 명시적으로 지정돼 있음을 유의하자. 이 예시는 완전히 정적인 설정 파일이다.
  • 앞 절에서 엔보이는 다양한 설정을 동적으로 설정할 수 있다는 점을 언급했다.
  • 엔보이 실습을 진행할 때는 정적 설정을 사용하겠지만, 먼저 동적 서비스를 살펴보고 엔보이가 어떻게 xDS API를 이용해 동적 설정을 하는지 알아본다.

 

3.2.2 Dynamic configuration 동적 설정

엔보이는 특정 API군을 사용해 다운타임이나 재시작 없이 설정을 실시간으로 업데이트할 수 있다. 올바른 디스커버리 서비스 API를 가리키는 간단한 부트스트랩 설정 파일만 있으면 나머지 설정은 동적으로 이뤄진다.

엔보이는 동적 설정에 다음과 같은 API를 사용한다. - Blog

  • Listener discovery service (LDS)
    • 엔보이가 자신이 어떤 리스너를 노출해야 한느지 쿼리할 수 있게 하는 API
  • Route discovery service (RDS)
    • 리스너 설정의 일부로, 사용할 루트를 지정한다. 정적 설정이나 동적 설정을 사용할 때 LDS의 부분집합이다.
  • Cluster discovery service (CDS)
    • 엔보이가 클러스터 목록과 각 클러스터용 설정을 찾을 수 있는 API
  • Endpoint discovery service (EDS)
    • 클러스터 설정의 일부로, 특정 클러스터에 어떤 엔드포인트를 사용해야 하는지 지정한다. CDS의 부분집합이다.
  • Secret discovery service (SDS)
    • 인증서를 배부하는 데 사용하는 API
  • Aggregate discovery service (ADS)
    • 나머지 API에 대한 모든 변경 사항을 직렬화된 스트림으로 제공한다. 이 API 하나로 모든 변경 사항을 순차적으로 가져올 수 있다.

이를 xDS 서비스라고 부른다. 이들 중 하나 이상을 조합해 설정할 수 있으며, 전부 사용해야만 하는 것은 아니다.

https://blog.christianposta.com/images/control-plane/xds-control-plane.png

한 가지 유념해야 할 점은 엔보이의 xDS API는 궁극적 일관성 eventual consistency 을 전제로 구축됐으며 궁극적으로는 올바른 구성으로 수렴한다는 것이다.

  • 예를 들어 엔보이가 트래픽을 클러스터 foo로 라우팅하는 새 루트가 RDS로 업데이트됐는데, 이 클러스터 foo를 포함한 CDS 업데이트는 아직 수행되지 않았을 수 있다.
  • 이 경우 CDS가 업데이트될 때까지 라우팅 오류가 발생할 수 있다.

https://blog.naver.com/alice_k106/222000680202 (원본 출처 EnvoyCon)

이런 순서에 따른 경쟁 상태 race condition 를 해결하기 위해 도입한 것이 ADS 이다.

ADS : 나머지 API에 대한 모든 변경 사항을 직렬화된 스트림으로 제공한다. 이 API 하나로 모든 변경 사항을 순차적으로 가져올 수 있다.

https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#aggregated-discovery-service

예를 들어 엔보이 프록시의 리스너를 동적으로 찾으려면 다음과 같은 설정을 사용할 수 있다.

dynamic_resources:
  lds_config: # Configuration for listeners (LDS) 리스너용 설정
    api_config_source:
      api_type: GRPC
      grpc_services:
        - envoy_grpc: # Go to this cluster for the listener API. 이 클러스터로 이동해 리스너 API를 확인하자
            cluster_name: xds_cluster

clusters: 
- name: xds_cluster # gRPC cluster that implements LDS. LDS를 구현하는 gRPC 클러스터
  connect_timeout: 0.25s
  type: STATIC
  lb_policy: ROUND_ROBIN
  http2_protocol_options: {}
  hosts: [{ socket_address: {
    address: 127.0.0.3, port_value: 5678 }}]

이 설정을 사용하면 설정 파일에 각 리스너를 명시적으로 설정하지 않아도 되며, 엔보이에게 LDS API를 사용해 런타임에 올바른 리스너 설정값을 찾으라고 지시하고 있다.

그렇지만 클러스터 하나는 명시적으로 설정하고 있는데, LDS API가 위치한 클러스터다. (이 예제에서는 xds_cluster로 명명했다)

dynamic_resources를 통해서 lds에 대한 리스너 설정을 하는 것이다. 그리고 결국 grpc를 이용해 cluster_name: xds_cluster로 가도록 설정이 되어 있다.

 

좀 더 구체적인 예를 들면, 이스티오는 서비스 프록시용으로 다음과 같이 부트스트랩 설정을 사용한다.

bootstrap:
  dynamicResources:
    ldsConfig:
      ads: {} # ADS for listeners 리스너용 ADS
    cdsConfig:
      ads: {} # ADS for clusters 클러스터용 ADS
    adsConfig:
      apiType: GRPC
      grpcServices:
      - envoyGrpc:
          clusterName: xds-grpc # Uses a cluster named xds-grpc 라는 클러스터를 사용
      refreshDelay: 1.000s

  staticResources:
    clusters:
    - name: xds-grpc # Defines the xds-grpc cluster 라는 클러스터를 정의
      type: STRICT_DNS
      connectTimeout: 10.000s
      hosts:
      - socketAddress:
          address: istio-pilot.istio-system
          portValue: 15010
      circuitBreakers: # Reliability and circuit-breaking settings 신뢰성 및 서킷 브레이커 설정
        thresholds:
        - maxConnections: 100000
          maxPendingRequests: 100000
          maxRequests: 100000
        - priority: HIGH
          maxConnections: 100000
          maxPendingRequests: 100000
          maxRequests: 100000
      http2ProtocolOptions: {}

 

 

3.3 Envoy in action (실습)

기본 실습

엔보이는 C++로 작성돼 플랫폼에 맞게 컴파일 된다. 엔보이를 시작하기 가장 좋은 방법은 도커를 사용해 컨테이너를 실행하는 것이다.

도커 이미지 가져오기 : citizenstig/httpbin 는 arm CPU 미지원 - Link

# 도커 이미지 가져오기
docker pull envoyproxy/envoy:v1.19.0
docker pull curlimages/curl
docker pull mccutchen/go-httpbin
docker pull citizenstig/httpbin

# 확인
docker images

 

 

httpbin은 호출하는 엔드포인트에 따라 호출할 때 사용한 헤더를 반환하거나, HTTP 요청을 지연시키거나, 오류를 발생시키는 등의 서비스를 제공한다.

httpbin 서비스를 만들어서 클라이언트를 통해 엔보이를 통해 httpbin 서비스로 가도록 프록시를 설정하는 과정을 진행해보자.

httpbin service는 잘 몰랐는데 나중에 네트워크 테스트 할때 사용하면 유용할 것 같다.
# mccutchen/go-httpbin 는 기본 8080 포트여서, 책 실습에 맞게 8000으로 변경
# docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin -p 8000:8000
docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin 
docker ps

# curl 컨테이너로 httpbin 호출 확인
docker run -it --rm --link httpbin curlimages/curl curl -X GET http://httpbin:8000/headers
{
  "headers": {
    "Accept": [
      "*/*"
    ],
    "Host": [
      "localhost:8000"
    ],
    "User-Agent": [
      "curl/8.7.1"
    ]
  }
}

위와 같이 Host, User-Agent 등 /headers 엔드포인트를 호출하는데 사용한 헤더가 반환된다

이제 엔보이 프록시를 실행하고 help 를 전달해 플래그와 명령줄 파라미터 중 일부를 살펴보자

#
docker run -it --rm envoyproxy/envoy:v1.19.0 envoy --help
...
   --service-zone <string> # 프록시를 배포할 가용 영역을 지정
     Zone name

   --service-node <string> # 프록시에 고유한 이름 부여
     Node name
     
...
   -c <string>,  --config-path <string> # 설정 파일을 전달
     Path to configuration file

 

엔보이 실행 : 프록시를 실행하려고 했지만 유효한 설정 파일을 전달하지 않았다.

#
docker run -it --rm envoyproxy/envoy:v1.19.0 envoy
[2021-11-21 21:28:37.347][1][info][main]
➥[source/server/server.cc:855] exiting
At least one of --config-path or --config-yaml or
➥Options::configProto() should be non-empty

위와 같이 --config-path 나 --config-yaml 을 제공해달라는 문구가 발생한다.

이를 앞서 봤던 예제 설정 파일을 통해 해결해보자.

admin:
  address:
    socket_address: { address: 0.0.0.0, port_value: 15000 }

static_resources:
  listeners:
  - name: httpbin-demo
    address:
      socket_address: { address: 0.0.0.0, port_value: 15001 }
    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
          http_filters:
          - name: envoy.filters.http.router
          route_config:
            name: httpbin_local_route
            virtual_hosts:
            - name: httpbin_local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
  clusters:
    - name: httpbin_service
      connect_timeout: 5s
      type: LOGICAL_DNS
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: httpbin
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: httpbin
                  port_value: 8000

 

#
cat ch3/simple.yaml

# 터미널1
docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple.yaml)"

# 터미널2
docker logs proxy
[2025-04-12 08:25:15.455][1][info][config] [source/server/listener_manager_impl.cc:834] all dependencies initialized. starting workers
[2025-04-12 08:25:15.456][1][info][main] [source/server/server.cc:804] starting main dispatch loo

위와 같이 "all dependencies initialized. starting workers" 나 "starting main dispatch loop" 로그를 통해 서버가 정상적으로 실행되어 리스닝을 하고 있음을 알 수 있다.

위의 yaml 파일에서 port를 15001로 설정했기 때문에 15001로 접근해보자

프록시의 15001로 호출했는데도 httpbin 서비스로 트래픽이 전송 되었다.

개중에 - X-Envoy-Expected-Rq-Timeout-Ms , X-Request-Id 헤더가 추가된 것을 볼 수 있다.

 

두 헤더는 모두 엔보이를 통해 생성된 헤더이다.

  • X-Request-Id는 클러스터 내 다양한 요청 사이의 관계를 파악하고 요청을 처리하기 위해 여러 서비스(여러 홉)를 거치는 동안을 추적하는 데 활용할 수 있다. 
  • X-Envoy-Expected-Rq-Timeout-Ms는 업스트림 서비스에 대한 힌트로, 요청이 15,000ms 후에 타임아웃될 것으로 기대한다는 의미다.
    • Timeout을 설정하게 되면 업스트림 시스템과 그 요청이 거치는 모든 홉은 이 힌트를 사용해 특정 데드라인을 구현해 데드라인이 넘으면 처리를 중단하게 할 수 있다. TTL과 같은 기능으로 이해가 된다.
    • 이렇게 하면 타임아웃된 후 묶여 있던 리소스가 풀려난다. 리소스가 풀려난다는 의미는 네트워크 연결 등 요청 처리에 사용되던 리소스가 즉시 해제된다는 의미.

 

다음 실습을 위해 proxy 컨테이너 삭제

 

위 타임아웃을 변경하기 위해서 구성을 변경해보자. 예상 요청 타임아웃을 1초로 설정해보자. (실제 명령어로는 config를 변환하지 않고 git clone 받았던 코드 집합에 다 있음.)

# 반영되어야 할 코드
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  timeout: 1s
admin:
  address:
    socket_address: { address: 0.0.0.0, port_value: 15000 }

static_resources:
  listeners:
  - name: httpbin-demo
    address:
      socket_address: { address: 0.0.0.0, port_value: 15001 }
    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
          http_filters:
          - name: envoy.filters.http.router
          route_config:
            name: httpbin_local_route
            virtual_hosts:
            - name: httpbin_local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  timeout: 1s ## 변경된 부분
  clusters:
    - name: httpbin_service
      connect_timeout: 5s
      type: LOGICAL_DNS
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: httpbin
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: httpbin
                  port_value: 8000

 

 

#
#docker run -p 15000:15000 --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_change_timeout.yaml)"
cat ch3/simple_change_timeout.yaml
docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_change_timeout.yaml)"
docker ps


# 타임아웃 설정 변경 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/headers
{
  "headers": {
    "Accept": [
      "*/*"
    ],
    "Host": [
      "httpbin"
    ],
    "User-Agent": [
      "curl/8.13.0"
    ],
    "X-Envoy-Expected-Rq-Timeout-Ms": [
      "1000" 1000ms초 = 1초
    ],
    "X-Forwarded-Proto": [
      "http"
    ],
    "X-Request-Id": [
      "dbff822d-17df-4d8c-bd4d-9c9d6f890cff"
    ]
  }
}

# 추가 테스트 : Envoy Admin API(TCP 15000) 를 통해 delay 설정
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/0.5
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/1
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/delay/2
upstream request timeout

아래 curl을 통해 반환된 내역을 확인하면 X-Envoy-Expected-Rq-Timeout-Ms 헤더가 1000ms 로 1s로 설정한 부분이 성공적으로 반영 되었음을 알 수 있다.

 

POST 요청을 이용하게 되면 log level도 변경 가능한것 같다. 아래는 로그 레벨을 debug로 변경한 것이고 위는 debug로 잘 반영 되었음을 알 수 있다.

 

delay를 이용해서 1초 이상의 request를 주니까 upstream에서 timeout이 난 것을 볼 수 있다.

 

2초도 동일하게 timeout으로 504 error 가 발생했다.

 

3.3.1 Envoy’s Admin API

엔보이의 Admin API를 사용하면 프록시 동작에 대한 통찰력을 향상시킬 수 있고, 메트릭과 설정에 접근할 수 있다.

 

#
docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_change_timeout.yaml)"

# admin API로 Envoy stat 확인 : 응답은 리스너, 클러스터, 서버에 대한 통계 및 메트릭
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats

# retry 통계만 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats | grep retry
cluster.httpbin_service.circuit_breakers.default.rq_retry_open: 0
cluster.httpbin_service.circuit_breakers.high.rq_retry_open: 0
cluster.httpbin_service.retry_or_shadow_abandoned: 0
cluster.httpbin_service.upstream_rq_retry: 0
cluster.httpbin_service.upstream_rq_retry_backoff_exponential: 0
cluster.httpbin_service.upstream_rq_retry_backoff_ratelimited: 0
cluster.httpbin_service.upstream_rq_retry_limit_exceeded: 0
cluster.httpbin_service.upstream_rq_retry_overflow: 0
cluster.httpbin_service.upstream_rq_retry_success: 0
...

# 다른 엔드포인트 일부 목록들도 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/certs # 머신상의 인증서
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/clusters # 엔보이에 설정한 클러스터
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/config_dump # 엔보이 설정 덤프
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/listeners # 엔보이에 설정한 리스너
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging # 로깅 설정 확인 가능
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug # 로깅 설정 편집 가능
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats # 엔보이 통계
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats/prometheus # 엔보이 통계(프로메테우스 레코드 형식)

 

위 curl -X GET으로 요청한 부분은 Envoy의 설정 및 상태를 확인 할 수 있다. 리스너, 클러스터, 서버에 대한 통계 및 메트릭을 확인 가능하다.

 

grep 명령어를 이용해 특정 통계만도 확인 가능하다.

위의 curl -X GET 또는 POST 코드를 보면 알겠지만 다양한 스탯을 확인 가능하다.

 

3.3.2 Envoy request retries 요청 재시도

httpbin 요청을 일부러 실패시켜서 엔보이가 요청을 자동으로 재시작하는지 살펴보자. retries 기능.

아까와 처럼 retry_policy를 사용하도록 설정 파일을 업데이트 한다. 

              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  retry_policy:
                      retry_on: 5xx  # 5xx 일때 재시도
                      num_retries: 3 # 재시도 횟수

 

#
docker rm -f proxy

#
cat ch3/simple_retry.yaml
docker run -p 15000:15000 --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_retry.yaml)"
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug

# /stats/500 경로로 프록시를 호출 : 이 경로로 httphbin 호출하면 오류가 발생
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/status/500

# 호출이 끝났는데 아무런 응답도 보이지 않는다. 엔보이 Admin API에 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats | grep retry
...
cluster.httpbin_service.retry.upstream_rq_500: 3
cluster.httpbin_service.retry.upstream_rq_5xx: 3
cluster.httpbin_service.retry.upstream_rq_completed: 3
cluster.httpbin_service.retry_or_shadow_abandoned: 0
cluster.httpbin_service.upstream_rq_retry: 3
...

 

또 log level을 debug로 변경

 

임의로 500 에러를 발생시켜보자

 

 

stats를 확인 해보면 cluster.httpbin_service.retry.upstream_rq_500 가 6으로 찍혀 있는 것을 볼 수 있고, cluster.httpbin_service.upstream_rq_retry: 가 6인 것을 볼 수 있다. 원래는 3이 맞는데 명령어를 두 번 날렸음

결국 엔보이는 재시도를 시도했음을 알 수 있고, 업스트림 클러스터 httpbin을 호출할 때 500 에러를 반환받아 재시도를 진행했음을 알 수 있다.

 

위의 httpbin 예시는 정적 설정 파일을 사용했지만, 앞 절에서 봤듯이 이스티오는 xDS 같은 컴포넌트를 이용해서 동적 설정 기능을 사용한다. 

동적 설정 예시

bootstrap:
  dynamicResources:
    ldsConfig:
      ads: {} # ADS for listeners 리스너용 ADS
    cdsConfig:
      ads: {} # ADS for clusters 클러스터용 ADS
    adsConfig:
      apiType: GRPC
      grpcServices:
      - envoyGrpc:
          clusterName: xds-grpc # Uses a cluster named xds-grpc 라는 클러스터를 사용
      refreshDelay: 1.000s

  staticResources:
    clusters:
    - name: xds-grpc # Defines the xds-grpc cluster 라는 클러스터를 정의
      type: STRICT_DNS
      connectTimeout: 10.000s
      hosts:
      - socketAddress:
          address: istio-pilot.istio-system
          portValue: 15010
      circuitBreakers: # Reliability and circuit-breaking settings 신뢰성 및 서킷 브레이커 설정
        thresholds:
        - maxConnections: 100000
          maxPendingRequests: 100000
          maxRequests: 100000
        - priority: HIGH
          maxConnections: 100000
          maxPendingRequests: 100000
          maxRequests: 100000
      http2ProtocolOptions: {}
# 다음 실습을 위해 Envoy 종료
docker rm -f proxy && docker rm -f httpbin

 

 

3.4 How Envoy fits with Istio 엔보이는 어떻게 이스티오에 적합한가?

엔보이는 프록시로서 서비스메시에 매우 적합하다. 하지만 엔보이를 최대한 활용하려면 보조 인프라나 구성 요소가 필요하다.

이스티오가 제공하는 사용자 설정, 보안 정책, 런타임 설정을 지원하는 구성 요소들컨트롤 플레인형성한다.

엔보이도 데이터 플레인에서 모든 작업을 혼자 수행하는 것은 아니고, 반드시 지원이 필요하다.

 

엔보이의 기능 덕에 정적 설정 파일이나 런타임에 리스너, 엔드포인트, 클러스터를 찾기 위한 xDS 디스커버리 서비스를 사용해 서비스 프록시를 설정할 수 있다.

이스티오는 istiod 컨트롤 플레인 구성 요소에서 이 xDS API 들을 구현한다.

위 그림은 istiod가 쿠버네티스 API를 사용해 VirtualService 등의 이스티오 설정을 읽은 다음 서비스 프록시를 동적으로 설정하는 모습을 보여준다.

관련 예로는 엔드포인트를 검색하기 위해 일종의 서비스 저장소에 의존하는 엔보이의 서비스 디스커버리가 있다.

  • istiod는 이 API를 구현하기도 하지만 엔보이에게 서비스 저장소의 구현을 추상화하기도 한다.
  • 이스티오를 쿠버네티스에 배포하면, 서비스 디스커버리에 쿠버네티스의 서비스 저장소를 사용한다.
  • 이런 구현 세부 사항은 엔보이 프록시에게 완벽히 감춰진다.

또 다른 예시가 있다. 엔보이는 많은 메트릭과 텔레메트리를 내보낼 수 있다. 

  • 이 텔레메트리는 어딘가로 이동해야 하며, 엔보이는 이를 보내도록 설정돼야 한다.
  • 이스티오는 프로메테우스 같은 시계열 시스템과 통합하도록 데이터 플레인을 설정한다.
  • 우리는 엔보이가 어떻게 분산 트레이싱 스팬을 오픈트레이싱 엔진에 보낼 수 있는지, 이스티오가 어떻게 스팬을 그 위치로 보낼 수 있는지도 봤다.
  • 예를 들어 이스티오는 예거 트레이싱 엔진과 통합할 수 있으며, 집킨도 사용할 수 있다.

마지막으로 엔보이는 메시 내의 서비스로 향하는 TLS 트래픽을 종료하고 시작할 수 잇다.

  • 그렇게 하려면 인증서를 생성, 서명, 로테이션하는 보조 인프라가 필요하다.
  • 이스티오는 이를 istiod 구성 요소를 통해 제공한다.

 

이스티오의 구성 요소와 엔보이 프록시는 강력한 서비스 메시를 구현하는 데 함께 기여한다.

이제부터 엔보이를 이스티오 프록시라 부르며, 그 기능들을 이스티오의 API를 통해 살펴볼 것이다. 그렇지만 실제로는 많은 것이 엔보이가 제공하고 구현하는 것임을 이해하자.

 

4장 Istio gateways: Getting traffic into a cluster

이번 장은 아래와 같은 목표가 있다.

  • Defining entry points into a cluster 클러스터 진입 지점 정의
  • Routing ingress traffic to deployments in your cluster 인입 트래픽을 클러스터 내 배포로 라우팅하기
  • Securing ingress traffic 인입 트래픽 보호하기 (HTTPS, x.509)
  • Routing non HTTP/S traffic HTTP아닌 트래픽 라우팅 (TCP)

 

 

먼저 실습 환경을 다시 구성해보자

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # New Gateway 
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /root/istio_gasida/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server
# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook # 마운트 안한 경우

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false

# default 프로파일 컨트롤 플레인 배포
istioctl install --set profile=default -y

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

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
kubectl get pod -n istio-system

# 빠져나오기
exit
-----------------------------------

# 실습을 위한 네임스페이스 설정
kubectl create ns istioinaction
kubectl label namespace istioinaction istio-injection=enabled
kubectl get ns --show-labels

# istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

# Kiali 접속 1 : NodePort
open http://127.0.0.1:30003

# (옵션) Kiali 접속 2 : Port forward
kubectl port-forward deployment/kiali -n istio-system 20001:20001 &
open http://127.0.0.1:20001

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004


# 접속 테스트용 netshoot 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot
spec:
  containers:
  - name: netshoot
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

4.1 Traffic ingress concepts 트래픽 인그레스 개념

ingress 인그레스

네트워킹 커뮤니티에는 잘 정의된 진입점으로 네트워크를 연결하는 상황을 설명하는 용어가 있는데, 바로 인그레스 포인트 ingress point 다.

인그레스 ingress 란 네트워크 외부에서 발원해 네트워크 내부 엔드포인트를 향하는 트래픽을 말한다.

  • 트래픽은 먼저 네트워크 내부로 향하는 트래픽에 문지기 역할을 하는 인그레스 포인트로 라우팅된다.
  • 인그레스 포인트는 어떤 트래픽을 로컬 네트워크로 허용할지에 대한 규칙과 정책을 집행한다.
  • 인그레스 포인트가 트래픽을 허용하면 로컬 네트워크의 올바른 엔드포인트로 트래픽을 프록시한다.
  • 트래픽이 허용되지 않으면 인그레스 포인트는 트래픽을 거부한다.

 

4.1.1 Virtual IPs: Simplifying services access 가상IP: 서비스 접근 단순화

  • 우리 카탈로그에 있는 상품 목록을 외부 시스템이 가져갈 수 있도록 `api.istioinaction.io/v1/products` 에 노출하고 싶은 서비스가 있다고 해보자.
  • 클라이언트가 그 엔드포인트를 쿼리하려고 할 때, 클라이언트의 네트워킹 스택은 먼저 api.istioinaction.io 도메인 이름을 IP 주소로 해석하려고 한다.
  • 이는 DNS 서버로 수행된다. 네트워킹 스택은 DNS 서버에 특정 호스트네임의 IP 주소를 물어본다.
  • 따라서 트래픽을 네트워크로 들여오는 작업의 첫 단계는 서비스 IP를 DNS의 호스트네임에 대응시키는 것이다.
  • 공개 주소의 경우에는 Amazon Route 53 이나 구글 Cloud DNS를 사용해 도메인 이름을 IP 주소로 대응시킬 수 있다.

자체 데이터센터에서는 내부 DNS 서버를 사용해 마찬가지 작업을 할 수 있다. 그런데 어떤 IP 주소를 이 이름에 대응시켜야 하는가?

위 그림은 왜 도메인을 서비스의 단일 인스턴스 엔드포인트(단일 IP)에 대응시키면 안 되는지를 그림으로 보여준다.

  • 이런 방식은 매우 취약할 수 있기 때문이다. 그 서비스 인스턴스가 다운되면 어떻게 되는가? 
  • 동작하는 엔드포인트의 IP 주소로 DNS 매핑이 변결될 때까지 클라이언트는 많은 오류를 볼 것이다.
  • 서비스가 다운될 때마다 그렇게 하는 것은 느리고, 오류가 발생하기 쉬우며, 가용성이 낮다.

아래 그림은 서비스를 대표하는 가상 IP 주소로 도메인 이름을 대응시키는 방법을 보여준다.

  • 가상 IP 주소가 실제 서비스 인스턴스로 트래픽을 전달하므로 가용성과 유연성을 높일 수 있다는 이점이 있으며, 가상 IP는 리버스 프록시라는 인그레스 포인트 유형에 바인딩 된다.
  • 리버스 프록시는 요청을 백엔드 서비스들에 분산시키는 역할을 하는 중간 구성 요소로, 특정 서비스에 해당하지 않는다.
  • 또한 리버스 프록시에는 로드 밸런싱 같은 기능도 있어 특정 백엔드 하나가 요청 때문에 과부하되지 않도록 할 수 도 있다.

 

4.1.2 Virtual hosting: Multiple services from a single access point 가상호스팅: 단일 접근 지점의 여러 서비스

서비스는 자체 IP를 지닌 서비스 인스턴스 여럿으로 구성될 수 있지만, 클라이언트는 오직 가상 IP만을 사용한다. 또한 가상 IP 하나로 여러 호스트네임을 나타낼 수도 있다.

  • 예를 들어 prod.istioinaction.io 와 api.istioinaction.io 가 모두 동일한 가상 IP로 해석되게 할 수 있다.
  • 즉, 호스트네임에 대한 요청 모두 동일한 가상 IP로 흐르게 되고, 그에 따라 동일한 인그레스 리버스 프록시가 그 요청을 라우팅하게 된다.
  • 리버스 프록시가 충분히 똑똑하다면, Host HTTP 헤더를 사용해 어느 요청이 어느 서비스 그룹으로 가야 하는지 기술할 수 있다.

서로 다른 서비스 여러 개를 진입점 하나로 호스팅하는 것을 가상 호스팅 virtual hosting 이라고 한다.

  • 특정 요청이 향하는 가상 호스트 그룹을 결정할 방법이 필요한데, HTTP/1.1 에서는 Host 헤더를 사용할 수 있고 HTTP/2에서는 :authority 헤더를 사용할 수 있다. - Blog1, Blog2, Blog3
  • 또한 TCP 커넥션에서는 TLS의 SNI server name indication 에 의존할 수 있다. SNI는 이 장의 뒷부분에서 자세히 살펴볼 것이다.
  • 여기서 중요한 사실은 이스티오에서 보이는 에지 인그레스 기능이 서비스 트래픽을 클러스터 안으로 라우팅하는 데 가상 IP 라우팅과 가상 호스팅을 사용한다는 것이다.

 

4.2 Istio ingress gateways 이스티오 인그레스 게이트웨이

소개 (실습) Gateway(L4/L5) , VirtualService(L7)

이스티오에는 네트워크 인그레스 포인트 역할을 하는 인그레스 게이트웨이라는 개념이 있다.

  • 이것은 클러스터 외부에서 시작한 트래픽이 클러스터에 접근하는 것을 방어하고 제어한다.
  • 로그 밸런싱과 가상 호스트 라우팅도 처리한다.

위 그림은 이스티오의 인그레스 게이트웨이가 클러스터로 트래픽을 허용하면서 리버스 프록시 기능도 수행하는 것을 보여준다.

  • 이스티오는 인그레스 게이트웨이로 단일 엔보이 프록시를 사용한다.
  • 3장에서 엔보이는 서비스 간 프록시이지만 서비스 메시 외부에서 내부의 서비스로 향하는 트래픽을 로드 밸런싱하고 라우팅하는 데 사용할 수도 있음을 확인했다.
  • 이전 장에서 이야기했던 엔보이의 모든 기능은 인그레스 게이트웨이에서도 사용할 수 있다.

이스티오 서비스 프록시(엔보이 프록시)가 이스티오 인그레스 게이트웨이에서 실제로 작동 중인지 확인해보자.

# 파드에 컨테이너 1개 기동 : 별도의 애플리케이션 컨테이너가 불필요.
kubectl get pod -n istio-system -l app=istio-ingressgateway
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-996bc6bb6-p6k79   1/1     Running   0          12m

# proxy 상태 확인
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                      VERSION
istio-ingressgateway-996bc6bb6-p6k79.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-7df6ffc78d-w9szx     1.17.8

# proxy 설정 확인
docker exec -it myk8s-control-plane istioctl proxy-config all deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
ADDRESS PORT  MATCH DESTINATION
0.0.0.0 15021 ALL   Inline Route: /healthz/ready*
0.0.0.0 15090 ALL   Inline Route: /stats/prometheus*

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
NAME     DOMAINS     MATCH                  VIRTUAL SERVICE
         *           /stats/prometheus*
         *           /healthz/ready*
         
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system

# 설정 참고
kubectl get istiooperators -n istio-system -o yaml


# pilot-agent 프로세스가 envoy 를 부트스트랩
kubectl exec -n istio-system deploy/istio-ingressgateway -- ps                                                  
    PID TTY          TIME CMD
      1 ?        00:00:00 pilot-agent
     23 ?        00:00:06 envoy
     47 ?        00:00:00 ps

kubectl exec -n istio-system deploy/istio-ingressgateway -- ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
istio-p+       1  0.1  0.3 753064 48876 ?        Ssl  03:57   0:00 /usr/local/bin/pilot-agent proxy router --domain istio-system.svc.cluster.local --proxyLogLevel=warning --proxyComponentLogLevel=misc:error --log_output_level=default:info
istio-p+      23  1.1  0.4 276224 57864 ?        Sl   03:57   0:06 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-strategy immediate --local-address-ip-version v4 --file-flush-interval-msec 1000 --disable-hot-restart --allow-unknown-static-fields --log-format %Y-%m-%dT%T.%fZ.%l.envoy %n %g:%#.%v.thread=%t -l warning --component-log-level misc:error
istio-p+      53  0.0  0.0   6412  2484 ?        Rs   04:07   0:00 ps aux

# 프로세스 실행 유저 정보 확인
kubectl exec -n istio-system deploy/istio-ingressgateway -- whoami
istio-proxy

kubectl exec -n istio-system deploy/istio-ingressgateway -- id
uid=1337(istio-proxy) gid=1337(istio-proxy) groups=1337(istio-proxy)

위와 같이 istio-ingressgateway pod 가 Running 상태인 것을 알 수 있고, proxy 관련된 설정 또한 확인 가능하다.

istio-ingressgateway를 확인하면 내부에 동작중인 프로세스로 pilot-agent와 envoy를 확인할 수 있다. pilot-agent 프로세스는 처음에 엔보이 프록시를 설정하고 부트스트랩한다.

 

인그레스 게이트웨이가 트래픽을 클러스터 내부로 허용하도록 설정하려면, 우선 이스티오의 리소스인 GatewayVirtualService 를 이해해야 한다. 이 두 리소스는 이스티오에서 트래픽의 흐름을 조절하는 데 핵심적인 역할을 한다. 하지만 여기서는 클러스터 내부로 트래픽을 수용하는 것과 관련된 맥락에서만 살펴본다. 

 

4.2.1 Specifying Gateway resources 게이트웨이 리소스 지정하기 (실습)

이스티오에서 인그레스 게이트웨이를 설정하려면, Gateway 리소스를 사용해 개방하고 싶은 포트와 그 포트에서 허용할 가상 호스트를 지정한다.

살펴볼 예시 -> Gateway 리소스는 매우 간단해 80 포트에서 가상 호스트 webapp.istioinaction.io 를 향하는 트래픽을 허용하는 HTTP 포트를 개방한다.

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: coolstore-gateway #(1) 게이트웨이 이름
spec:
  selector:
    istio: ingressgateway #(2) 어느 게이트웨이 구현체인가?
  servers:
  - port:
      number: 80          #(3) 노출할 포트 
      name: http
      protocol: HTTP
    hosts:
    - "webapp.istioinaction.io" #(4) 이 포트의 호스트

이 게이트웨이 리소스는 80포트를 리스닝하고 HTTP 트래픽을 기대하도록 엔보이를 설정한다

리소스를 만들고 아래와 같은 과정을 거친다.

# 신규터미널 : istiod 로그
kubectl stern -n istio-system -l app=istiod
istiod-7df6ffc78d-w9szx discovery 2025-04-13T04:50:04.531700Z	info	ads	Push debounce stable[20] 1 for config Gateway/istioinaction/coolstore-gateway: 100.4665ms since last change, 100.466166ms since last push, full=true
istiod-7df6ffc78d-w9szx discovery 2025-04-13T04:50:04.532520Z	info	ads	XDS: Pushing:2025-04-13T04:50:04Z/14 Services:12 ConnectedEndpoints:1 Version:2025-04-13T04:50:04Z/14
istiod-7df6ffc78d-w9szx discovery 2025-04-13T04:50:04.537272Z	info	ads	LDS: PUSH for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:1 size:2.2kB
istiod-7df6ffc78d-w9szx discovery 2025-04-13T04:50:04.545298Z	warn	constructed http route config for route http.8080 on port 80 with no vhosts; Setting up a default 404 vhost
istiod-7df6ffc78d-w9szx discovery 2025-04-13T04:50:04.545584Z	info	ads	RDS: PUSH request for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:1 size:34B cached:0/0

# 터미널2
cat ch4/coolstore-gw.yaml
kubectl -n istioinaction apply -f ch4/coolstore-gw.yaml

# 확인
kubectl get gw,vs -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   26m

#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
istio-ingressgateway-996bc6bb6-p6k79.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-w9szx     1.17.8

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
ADDRESS PORT  MATCH DESTINATION
0.0.0.0 8080  ALL   Route: http.8080
...

docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
NAME          DOMAINS     MATCH                  VIRTUAL SERVICE
http.8080     *           /*                     404
...     

# http.8080 정보의 의미는? 그외 나머지 포트의 역할은?
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
[
  {
    "name": "status-port",
    "nodePort": 31674,
    "port": 15021,
    "protocol": "TCP",
    "targetPort": 15021
  },
  {
    "name": "http2",
    "nodePort": 30000, # 순서1
    "port": 80,        
    "protocol": "TCP",
    "targetPort": 8080 # 순서2
  },
  {
    "name": "https",
    "nodePort": 30005,
    "port": 443,
    "protocol": "TCP",
    "targetPort": 8443
  }
]

# HTTP 포트(80)을 올바르게 노출했다. VirtualService 는 아직 아무것도 없다.
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system -o json
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system -o json --name http.8080
[
    {
        "name": "http.8080",
        "virtualHosts": [
            {
                "name": "blackhole:80",
                "domains": [
                    "*"
                ]
            }
        ],
        "validateClusters": false,
        "ignorePortInHostMatching": true
    }
]

 

gateway 리소스를 설정하는 yaml 파일을 확인 해보면 webapp.istioinaction.io 호스트로 들어온 경우 셀렉터에 해당하는 ingress gateway pod를 선택해서 트래픽을 전달함!

유의해야할 사항은 VirtualService가 없으면 라우팅 되지 않음

위 사진 처럼 route config for route http.8080 on port 80 with no vhosts 라고 로그가 발생한 것을 알수 있음.

8080으로 인입되지만 HTTP 404로 라우팅하는 기본 블랙홀 루트로 바인딩 돼 있다.

기본 istio-ingresgateway 는 권한 있는 privileged 접근이 필요하지 않다. 시스템 포트(HTTP의 경우 80)을 리스닝하지 않기 때문이다. istio-ingresgateway 는 기본적으로 8080 포트를 리스닝한다. 그러나 실제 포트는 서비스일지 로드밸런서일지는 모르겠지만 게이트웨이를 노출하는 데 사용하는 포트다.

 

4.2.2 Gateway routing with virtual services VirtualService로 게이트웨이 라우팅하기 (실습)*

지금까지는 이스티오 게이트웨이를 설정했다. 특정 포트를 노출하고, 그 포트에서 특정 프로토콜을 예상하고, 그 포트/프로토콜 쌍에서 서빙할 특정 호스트를 정의했다.

이제 트래픽이 게이트웨이로 들어오면 그 트래픽을 서비스 메시 내 특정 서비스로 가져올 방법이 필요하다. 이때 사용하는 것이 VirtualService 리소스다.

이스티오에서 VirtualService 리소스는 클라이언트가 특정 서비스와 통신하는 방법을 정의하는데, 구체적으로 FQDN, 사용 가능한 서비스 버전, 기타 라우팅 속성(재시도, 요청 타임아웃 등)이 있다.

가상 호스트 webapp.istioinaction.io 로 향하는 트래픽을 서비스 메시 내에 배포된 서비스로 라우팅하는 VirtualService 예제는 다음과 같다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webapp-vs-from-gw     #1 VirtualService 이름
spec:
  hosts:
  - "webapp.istioinaction.io" #2 비교할 가상 호스트네임(또는 호스트네임들)
  gateways:
  - coolstore-gateway         #3 이 VirtualService 를 적용할 게이트웨이
  http:
  - route:
    - destination:            #4 이 트래픽의 목적 서비스
        host: webapp
        port:
          number: 80

게이트웨이로 들어오는 트래픽에 대해 수행할 작업을 이 VirtualService 리소스로 정의한다. spec.gateway 필드에서 보듯이, 이 트래픽 규칙들은 이전 절에서 만든 coolstore-gateway 게이트웨이 정의를 통해 들어온 트래픽만 적용된다. 또한 이 규칙들은 가상 호스트 webapp.istioinaction.io 로 향하는 트래픽에만 적용된다.

kubectl apply 명령어로 VirtualService 리소스를 생성했다. 로그에도 webapp-vs-from-gw 리소스가 생성되어 config가 push 되었음을 알 수 있다.

 

아까완 다르게 http 8080 이 webapp-vs-from-gw VirtualService 와 매핑된 것을 알 수 있다. 결국 이스티오 게이트웨이에 엔보이 라우팅을 해주기 위해 설정한 것이다.

예제에서는 도메인이 webapp.istioinaction.io 와 일치하는 트래픽을 서비스 메시 내 webapp 으로 라우팅하는 엔보이 루트다.

현재 실제 애플리케이션은 없는 상태인데 실제 애플리케이션을 배포해보자

 

# 로그
kubectl stern -n istioinaction -l app=webapp
kubectl stern -n istioinaction -l app=catalog
kubectl stern -n istio-system -l app=istiod
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:52.736261Z	info	ads	LDS: PUSH request for node:webapp-7685bcb84-mck2d.istioinaction resources:21 size:50.1kB
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:52.749641Z	info	ads	RDS: PUSH request for node:webapp-7685bcb84-mck2d.istioinaction resources:13 size:9.9kB cached:12/13
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:54.076322Z	info	ads	CDS: PUSH for node:webapp-7685bcb84-mck2d.istioinaction resources:28 size:26.4kB cached:22/24
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:54.076489Z	info	ads	EDS: PUSH for node:webapp-7685bcb84-mck2d.istioinaction resources:24 size:4.0kB empty:0 cached:24/24
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:54.078289Z	info	ads	LDS: PUSH for node:webapp-7685bcb84-mck2d.istioinaction resources:21 size:50.1kB
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:54.078587Z	info	ads	RDS: PUSH for node:webapp-7685bcb84-mck2d.istioinaction resources:13 size:9.9kB cached:12/13
istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:59:54.185611Z	info	ads	Push debounce stable[37] 1 for config ServiceEntry/istioinaction/webapp.istioinaction.svc.cluster.local: 100.490667ms since last change, 100.490459ms since last push, full=false
...

# 배포
cat services/catalog/kubernetes/catalog.yaml
cat services/webapp/kubernetes/webapp.yaml 
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction -owide
NAME                     READY   STATUS    RESTARTS   AGE
catalog-6cf4b97d-nwh9w   2/2     Running   0          10m
webapp-7685bcb84-mck2d   2/2     Running   0          10m

# krew plugin images 설치 후 사용
kubectl images -n istioinaction
[Summary]: 1 namespaces, 2 pods, 6 containers and 3 different images
+------------------------+-------------------+--------------------------------+
|          Pod           |     Container     |             Image              |
+------------------------+-------------------+--------------------------------+
| catalog-6cf4b97d-nwh9w | catalog           | istioinaction/catalog:latest   |
+                        +-------------------+--------------------------------+
|                        | istio-proxy       | docker.io/istio/proxyv2:1.17.8 |
+                        +-------------------+                                +
|                        | (init) istio-init |                                |
+------------------------+-------------------+--------------------------------+
| webapp-7685bcb84-mck2d | webapp            | istioinaction/webapp:latest    |
+                        +-------------------+--------------------------------+
|                        | istio-proxy       | docker.io/istio/proxyv2:1.17.8 |
+                        +-------------------+                                +
|                        | (init) istio-init |                                |
+------------------------+-------------------+--------------------------------+

# krew plugin resource-capacity 설치 후 사용 : istioinaction 네임스페이스에 파드에 컨테이너별 CPU/Mem Request/Limit 확인
kubectl resource-capacity -n istioinaction -c --pod-count
kubectl resource-capacity -n istioinaction -c --pod-count -u
NODE                  POD                      CONTAINER     CPU REQUESTS   CPU LIMITS    CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL   POD COUNT

myk8s-control-plane   *                        *             200m (2%)      4000m (50%)   9m (0%)    256Mi (2%)        2048Mi (17%)    129Mi (1%)    2/110
myk8s-control-plane   catalog-6cf4b97d-m7jq9   *             100m (1%)      2000m (25%)   5m (0%)    128Mi (1%)        1024Mi (8%)     70Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-m7jq9   catalog       0m (0%)        0m (0%)       0m (0%)    0Mi (0%)          0Mi (0%)        22Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-m7jq9   istio-proxy   100m (1%)      2000m (25%)   5m (0%)    128Mi (1%)        1024Mi (8%)     48Mi (0%)
...

#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
istio-ingressgateway-996bc6bb6-p6k79.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-w9szx     1.17.8
catalog-6cf4b97d-nwh9w.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-w9szx     1.17.8
webapp-7685bcb84-mck2d.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-w9szx     1.17.8

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system | egrep 'TYPE|istioinaction'
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE           DESTINATION RULE
catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS            
webapp.istioinaction.svc.cluster.local                  80        -          outbound      EDS  

# istio-ingressgateway 에서 catalog/webapp 의 Service(ClusterIP)로 전달하는게 아니라, 바로 파드 IP인 Endpoint 로 전달함.
## 즉, istio 를 사용하지 않았다면, Service(ClusterIP) 동작 처리를 위해서 Node에 iptable/conntrack 를 사용했었어야 하지만,
## istio 사용 시에는 Node에 iptable/conntrack 를 사용하지 않아서, 이 부분에 대한 통신 라우팅 효율이 있다.
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.19:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.19:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

# 현재 모든 istio-proxy 가 EDS로 K8S Service(Endpoint) 정보를 알 고 있다
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.18:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.19:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

# netshoot로 내부에서 catalog 접속 확인
kubectl exec -it netshoot -- curl -s http://catalog.istioinaction/items/1 | jq
{
  "id": 1,
  "color": "amber",
  "department": "Eyewear",
  "name": "Elinor Glasses",
  "price": "282.00"
}

# netshoot로 내부에서 webapp 접속 확인 : 즉 webapp은 다른 백엔드 서비스의 파사드 facade 역할을 한다.
kubectl exec -it netshoot -- curl -s http://webapp.istioinaction/api/catalog/items/1 | jq
{
  "id": 1,
  "color": "amber",
  "department": "Eyewear",
  "name": "Elinor Glasses",
  "price": "282.00"
}

 

실제 애플리케이션을 배포하고 정상적으로 실행중인 상태이다.

 

kubectl images는 krew로 설치 가능하다. 컨테이너 별로 Image를 확인 가능하다. istio-proxy 컨테이너가 사이드카 형태로 붙어있는 것을 확인 가능하다.

kubectl resource-capacity 는 krew로 설치 가능하다. 

`docker exec -it myk8s-control-plane istioctl proxy-status` 명령어로 각 DS 서비스로의 SYNC 상태를 확인 가능하다.

EDS가 catalog 그리고 webapp의 엔드포인트 또한 모두 알고 있다.

엔드포인트를 확인해보면 각 Pod의 FQDN으로 직접 접속하고 있음을 확인할 수 있다. 이는 istio ingress gateway 가 Service를 통해 트래픽을 전송하는 것이 아니라 직접 전달하고 있음을 알 수 있다.

각기 다른 애플리케이션으로 접근 했음에도 불구하도 동일한 결과가 발생한다. webapp 애플리케이션으로 catalog가 노출되고 있음을 알 수 있다.

 

catalog, webapp 에 replicas=1 → 2로 증가 후 istio EDS(Endpoint) 정보 확인

# 로그 모니터링
kubectl stern -n istio-system -l app=istiod

# catalog, webapp 에 replicas=1 → 2로 증가
kubectl scale deployment -n istioinaction webapp --replicas 2
kubectl scale deployment -n istioinaction catalog --replicas 2

# 모든 istio-proxy 가 EDS로 해당 K8S Service의 Endpoint 목록 정보 동기화되어 알고 있음을 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|istioinaction'
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | egrep 'ENDPOINT|istioinaction'
ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
10.10.0.13:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
10.10.0.14:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
10.10.0.15:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
10.10.0.16:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local

# 다음 실습을 위해서 catalog, webapp 에 replicas=2 → 1로 감소 해두기
kubectl scale deployment -n istioinaction webapp --replicas 1
kubectl scale deployment -n istioinaction catalog --replicas 1

istio EDS 정보를 확인하면 엔드포인트가 각각 두개 씩 설정되어 있는 것을 확인할 수 있다. 

istio controlplane 는 어떻게 K8S Service(Endpoint)의 정보를 획득할 수 있었을까?

Service(Endpoint)가 빈번하게 변경될 경우, istio 가 역시 빈번하게 istio-proxy 들에게 전파가 될 것인데, 이를 최적화 하는 방법은 무엇이 있을까?

 

인증서 정보 확인

# 
kubectl exec -it deploy/webapp -n istioinaction -c istio-proxy -- curl http://localhost:15000/certs | jq
...
      "ca_cert": [ # 루트 인증서
        {
          "path": "<inline>",
          "serial_number": "530372db7c3cad87059b8d254c576f1a",
          "subject_alt_names": [],
          "days_until_expiration": "3649",
          "valid_from": "2025-04-15T10:33:56Z", # 만료 기간 10년
          "expiration_time": "2035-04-13T10:33:56Z"
        }
      ],
      "cert_chain": [ # 사용자 인증서
        {
          "path": "<inline>",
          "serial_number": "70eed2a1ca674593ec2cfac6fb254e12",
          "subject_alt_names": [
            {
              "uri": "spiffe://cluster.local/ns/istioinaction/sa/webapp"
            }
          ],
          "days_until_expiration": "0",
          "valid_from": "2025-04-15T10:33:03Z", # 만료 기간 1일
          "expiration_time": "2025-04-16T10:35:03Z"
...

#
istioctl proxy-config secret deploy/webapp.istioinaction -o json | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text
...
        Issuer: O=cluster.local
        Validity
            Not Before: Apr 15 10:33:03 2025 GMT
            Not After : Apr 16 10:35:03 2025 GMT
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                CA:11:18:3C:D2:34:4C:D4:69:94:BC:95:86:E2:64:DF:03:9B:82:18
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istioinaction/sa/webapp

#
istioctl proxy-config secret deploy/catalog.istioinaction -o json | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text
...
        Issuer: O=cluster.local
        Validity
            Not Before: Apr 15 10:32:57 2025 GMT
            Not After : Apr 16 10:34:57 2025 GMT
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                CA:11:18:3C:D2:34:4C:D4:69:94:BC:95:86:E2:64:DF:03:9B:82:18
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istioinaction/sa/catalog

#
istioctl proxy-config secret deploy/istio-ingressgateway.istio-system -o json | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text
...
        Issuer: O=cluster.local
        Validity
            Not Before: Apr 15 10:32:05 2025 GMT
            Not After : Apr 16 10:34:05 2025 GMT
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                CA:11:18:3C:D2:34:4C:D4:69:94:BC:95:86:E2:64:DF:03:9B:82:18
            X509v3 Subject Alternative Name: critical
                URI:spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account

루트 인증서와 사용자 인증서(애플리케이션)의 정보를 확인 가능하다. 루트의 경우 만료 기간 1년 사용자의 만료 기간 1일. 사용자 인증서의 경우 istio가 자동으로 재발급하기 때문에 보안적으로 강력함.

 

외부에서 호출해보기

# 터미널 : istio-ingressgateway 로깅 수준 상향
kubectl exec -it deploy/istio-ingressgateway -n istio-system -- curl -X POST http://localhost:15000/logging?http=debug
kubectl stern -n istio-system -l app=istio-ingressgateway
혹은
kubectl logs -n istio-system -l app=istio-ingressgateway -f
istio-ingressgateway-996bc6bb6-p6k79 istio-proxy 2025-04-13T06:22:13.389828Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1032	[C4403][S8504958039178930365] request end stream	thread=42
istio-ingressgateway-996bc6bb6-p6k79 istio-proxy 2025-04-13T06:22:13.390654Z	debug	envoy http external/envoy/source/common/http/filter_manager.cc:917	[C4403][S8504958039178930365] Preparing local reply with details route_not_found	thread=42
istio-ingressgateway-996bc6bb6-p6k79 istio-proxy 2025-04-13T06:22:13.390756Z	debug	envoy http external/envoy/source/common/http/filter_manager.cc:959	[C4403][S8504958039178930365] Executing sending local reply.	thread=42
istio-ingressgateway-996bc6bb6-p6k79 istio-proxy 2025-04-13T06:22:13.390789Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1687	[C4403][S8504958039178930365] encoding headers via codec (end_stream=true):
istio-ingressgateway-996bc6bb6-p6k79 istio-proxy ':status', '404'
...

# 외부(?)에서 호출 시도 : Host 헤더가 게이트웨이가 인식하는 호스트가 아니다
curl http://localhost:30000/api/catalog -v
* Host localhost:30000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:30000...
* Connected to localhost (::1) port 30000
> GET /api/catalog HTTP/1.1
> Host: localhost:30000
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 404 Not Found
< date: Sun, 13 Apr 2025 06:22:12 GMT
< server: istio-envoy
< content-length: 0
< 
* Connection #0 to host localhost left intact


# curl 에 host 헤더 지정 후 호출 시도
curl -s http://localhost:30000/api/catalog -H "Host: webapp.istioinaction.io" | jq
[
  {
    "id": 1,
    "color": "amber",
    "department": "Eyewear",
    "name": "Elinor Glasses",
    "price": "282.00"
...

 

기본적으로 아래와 같은 호출을 뱉는다.

이번엔 Host 헤더가 게이트웨이가 인식하는 호스트가 아닌 경우

Host: webapp.istioinaction.io 으로 호출한 경우 200 을 반환받고 정상적인 값을 반환받은 것을 아래에 확인 가능 (jq가 조금 깨진 정상적인 값 ㅋㅋ...)

반응형