WellSpring

Istio 2주차 - Envoy, Istio Gateway 본문

ISTIO

Istio 2주차 - Envoy, Istio Gateway

daniel00324 2025. 4. 14. 21:46

 

 

목차

     

     

     

    ※ 본 게재 글은 gasida님의 'AEWS' 스터디 강의내용과 실습예제 및 istio 공식 사이트와 관련 Blog 를 참고하여 작성하였습니다.


    Chap3. Istio’s data plane: Envoy Proxy

    Envoy 1.19.0 공식 문서 https://www.envoyproxy.io/docs/envoy/v1.19.0/

     

    This chapter covers

    더보기
    • 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?

    ▶ 엔보이 소개

    더보기
    • 엔보이는 분산 시스템을 구축할 때 발생하는 어려운 애플리케이션 네트워킹 문제를 해결하고자 리프트가 개발했다.
    • 엔보이는 2016년 9월 오픈소스 프로젝트로 공개됐으며, 1년 후인 2017년 9월에는 CNCF에 합류했다.
    • 엔보이는 **C++**로 작성됐는데, 그 목표는 성능을 늘리는 것, 특히 높은 부하에서도 더 안정적이고 결정론적일 수 있도록 만드는 것이었다.
    • 엔보이는 다음 2가지 중요 원칙에 따라 만들어졌다.

     

    애플리케이션에게 네트워크투명해야 한다. 네트워크 및 애플리케이션 문제가 발생할 때는 문제의 원인을 파악하기 쉬워야 한다. The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem. - Envoy announcement

     

    엔보이프록시이므로, 더 나아가기 전에 프록시란 무엇인지 명확히 해야 한다. 앞서 프록시란 클라이언트와 서버 간 통신의 중간에 위치한, 네트워크 아키텍터의 중개 구성 요소라고 언급한 바 있다. 프록시는 중간에 위치함으로써 보안, 프라이버시, 정책 같은 기능을 추가할 수 있다.

     

    • 프록시는 클라이언트가 서비스와 통신할 때 알아야 할 사항을 단순화할 수 있다.
    • 예를 들어 서비스가 동일한 인스턴스 집합(클러스터)으로 구현됐고, 각 인스턴스는 일정량의 부하를 처리할 수 있다고 가정해보자.
    • 클라이언트가 이 서비스에 요청할 때 어느 인스턴스 혹은 IP 주소인지 어떻게 알아야 하는가?
    • 프록시가 단일 식별자 혹은 IP주소를 갖고 중간에 위치할 수 있고, 클라이언트는 서비스 인스턴스들과 통신하는 데 프록시를 사용할 수 있다.
    • 아래 그림은 클라이언트가 실제 서비스 인스턴스 배포 방식을 자세히 알지 못하는 상태에서 프록시가 서비스 인스턴스 간에 로드 밸런싱을 처리하는 것을 보여준다.
    • 이런 리버스 프록시 유형의 또 다른 기능은 클러스터의 인스턴스 상태를 검사하고 실패하거나 오동작하는 백엔드 인스턴스를 우회하도록 라우팅하는 것이다. 이런 방식으로, 프록시는 클라이언트가 어느 백엔드가 과부화돼 있거나 장애가 발생했는지 파악할 필요가 없도록 보호 할 수 있다.
    • 엔보이 프록시는 특히 서비스 디스커버리, 로드 밸런싱, 헬스 체크 같은 기능을 제공하기 위해 애플리케이션 요청 경로 중간에 삽입할 수 있는 애플리케이션 수준 프록시이지만, 엔보이는 그 이상을 할 수 있다.
    • 이전 장에서 이런 강화된 기능 중 일부를 언급했는데, 이번 장에서 더 자세히 다룰 것이다.
    • 엔보이는 애플리케이션이 다른 서비스와 통신할 때 사용하는 7계층 프로토콜을 이해할 수 있다.
    • 예를 들어, 엔보이는 기본적으로 HTTP 1.1, HTTP 2, gRPC 등을 이해할 수 있고, 요청 수준 타임아웃, 재시도, 재시도별 타임아웃, 서킷 브레이커와 기타 복원력 기능 같은 동작을 추가할 수 있다.
    • 커넥션만을 이해하는 기본적인 커넥션 수준(L3/L4) 프록시로는 이와 같은 작업을 수행할 수 없다.

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

    • 프록시로서, 엔보이는 애플리케이션 외부에서 동작함으로써 개발자가 네트워크 문제를 고려하지 않아도 되도록 설계됐다.
    • 즉, 어느 프로그래밍 언어로 작성됐든, 어느 프레임워크를 사용하든 모든 애플리케이션이 이 기능들을 사용할 수 있다.
    • 게다가 서비스 아키텍처(SOA)가 표준 아키텍처이기는 하지만, 마이크로서비스든, 어떤 언어로 작성된 모놀리스 및 레거시 애플리케이션이든 엔보이는 상관없다.
    • 엔보이가 이해할 수 있는 프로토콜(HTTP 등)을 사용하는 한, 엔보이의 이점을 누릴 수 있다.

    • 엔보이는 매우 다재다응한 프록시로 다양한 역할을 할 수 있다. 클러스터 에지 프록시(진입 지점으로), 서비스의 공유 프록시로, 심지어는 이스티오처럼 서비스별 프록시로도 사용할 수 있다.
    • 이스티오에서는 엔보이 프록시를 서비스 인스턴스당 하나씩 배포해 유연성, 성능, 제어 능력을 높인다.
    • 사이드가 프록시와 에지 프록시를 구현하면 인프라를 운영하고 이해하기가 더 쉬워진다.
    • 엔보이는 서비스 메시의 진입점으로 사용돼 클러스터에 들어오는 트래픽을 완벽히 제어하고 관찰할 수 있다.

     

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

    더보기

    Envoy concepts as a high level

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

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

     

    • 엔보이는 트래픽 방향성을 나타낼 때 다른 프록시와 비슷한 용어를 사용한다.
    • 예를 들어 트래픽은 다운스트림 시스템에서 리스너로 들어온다.
    • 이 트래픽은 엔보이의 클러스터 중 하나로 라우팅되며, 클러스터는 트래픽을 업스트림 시스템으로 보내는 역할을 한다.
    • 트래픽은 엔보이를 거쳐 다운스트림에서 업스트림으로 흐른다.

    [ 엔보이가 제공하는 기능 ]

     

    A. 서비스 디스커버리 SERVICE DISCOVERY

    • 클라이언트 측 서비스 디스커버리 client-side service discovery를 구현하기 위해 런타임별로 전용 라이브러리를 사용할 필요 없이, 엔보이는 서비스 디스커버를 자동으로 수행할 수 있다.
    • 엔보이가 간단히 디스커버리 API에서 서비스 엔드포인트를 찾도록 설정하기만 하면, 애플리케이션은 서비스 엔드포인트를 찾는 방법을 몰라도 된다.
    • 디스커버리 API는 다른 일반적인 서비스 디스커버리 API(like HashiCorp Consul, Apache ZooKeeper, Netflix Eureka, and so on)를 래핑하는 데 사용할 수 있는 단순한 REST API 이다.
    • 이스티오의 컨트롤 폴레인은 이 API를 기본적으로 구현하고 있다.
    • 엔보이서비스 디스커버리 카탈로그의 업데이트가 궁극적으로는 일관성을 가질 것이라는 가정에 기반해 설계됐다.
    • 이는 분산 시스템에서는 통신할 모든 서비스의 정확한 상태와 사용 가능 여부를 실시간으로 파악할 수 없다는 것을 의미한다.
    • 따라서 우리가 할 수 있는 최선은 당면한 지식을 활용하고, 능동적이고 수동적인 헬스 체크를 수행하며, 그 결과들이 최선이 아닐 수 있음을 인식하는 것이다. (최선일 수도 없다)
    • 이스티오엔보이서비스 디스커버리 메커니즘 설정을 조절하는 고수준 리소스들을 제공함으로써 세부적인 내용 대부분을 추상화한다.

     

    B. 로드 밸런싱 LOAD BALANCING

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

     

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

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

     

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

    • 엔보이는 비율 기반(즉, 가중치 적용) 트래픽 분할 splitting / 전환 shifting 을 지원한다.
    • 이 기능을 활용해 애자일 팀은 카나리 릴리스와 같이 위험을 완화하는 CD 기술을 사용할 수 있다.
    • 위험성을 더 작은 유저 풀로 완화하기는 하지만, 카나리 릴리스는 여전히 라이브 사용자 트래픽을 다룬다.
    • 엔보이는 트래픽의 사본을 만들어 ‘보내고 망각하는 fire and forget’ 방식으로 트래픽을 엔보이 클러스터에 섀도잉 shadowing 할 수 있다.
    • 이 섀도잉 기능을 트래픽 분할과 같은 것으로 생각할 수 있지만, 업스트림 클러스터가 보는 요청은 라이브 트래픽의 복사본이다.
    • 따라서 라이브 운영 환경 트래픽에 영향을 주지 않고 섀도잉한 트래픽을 서비스의 새 버전으로 라우팅할 수 있다.
    • 이는 고객에게 영향을 주지 않고 운영 환경 트래픽으로 서비스 변경 사항을 테스트 할 수 있는 아주 강력한 기능이다.

     

    E. 네트워크 복원력 NETWORK RESILIENCE

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

     F. HTTP/2 AND gRPC

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

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

    • 엔보이의 목표 중 하나는 네트워크를 이해할 수 있게 만드는 것이다. 이 목표를 위해 엔보이는 다양한 메트릭을 수집한다.
    • 엔보이는 서버에 호출하는 다운스트림 시스템, 서버 그 자체, 서버가 요청을 보내는 업스트림 클러스터에 대한 여러 측면(디멘션 dimentsion) 을 추척한다.
    • 엔보이의 통계는 카운터, 게이지, 히스토그램으로 추적된다.
    • 아래 표는 업스트림 클러스터에 대해 추적하는 통계 유형의 몇 가지 예시를 나열한다.
    • 엔보이는 설정 가능한 어댑터와 형식을 사용해 통계를 내보낼 수 있다. 기본적으로 지원하는 목록은 아래와 같다.
      • StatsD
      • Datadog; DogStatsD
      • Hystrix formatting
      • Generic metrics service

     

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

    • 엔보이는 트레이스 스팬을 오픈트레이싱 OpenTracing 엔진에 보고해 호출 그래프 내 트래픽 흐름, 홉, 지연 시간을 시각화할 수 있다.
    • 즉, 특별한 오픈트레이싱 라이브러리를 설치할 필요가 없다.
    • 한편 필요한 집킨 헤더를 전파하는 것은 애플리케이션의 역할이며, 이는 가벼운 래퍼 wrapper 라이브러리로 수행할 수 있다.
    • 엔보이는 서비스 간 호출을 연관시킬 목적으로 x-request-id 헤더를 생성하며, 트레이싱이 시작될 때 initial **x-b3*** 헤더를 만들 수도 있다.
    • 애플리케이션이 전파해야 하는 헤더는 다음과 같다.
      • x-b3-traceid
      • x-b3-spanid
      • x-b3-parentspanid
      • x-b3-sampled
      • x-b3-flags

     

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

    • 엔보이는 특정 서비스로 향하는 TLS 트래픽을 종료 terminate 시킬 수 있다. 클러스터의 에지와 서비스 메시 내부의 프록시 모두에서 가능하다.
    • 더 흥미로운 기능은 애플리케이션 대신 엔보이가 업스트림 클러스터로 TLS 트래픽을 시작할 수도 있다는 것이다.
    • 즉, 엔터프라이즈 개발자와 운영자가 언어별 설정과 키스토어 또는 트러스터 스토어를 만지작거리지 않아도 된다.
    • 요청 경로에 엔보이가 있으면 TLS, 심지어 mTLS 까지도 자동으로 얻을 수 있다.

     

    J. 속도 제한 RATE LIMITING

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

     

    K. 엔보이 확장하기 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 설정만을 살펴볼 것인데, 최신 버전이자 이스티오가 사용하는 버전이기 때문이다.
    • 엔보이의 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 서비스로 라우팅될 것이다.

    • 많은 설정들이 명시적으로 지정돼 있음을 유의하자. 이 예시는 완전히 정적인 설정 파일이다.
    • 앞 절에서 엔보이는 다양한 설정을 동적으로 설정할 수 있다는 점을 언급했다.
    • 엔보이 실습을 진행할 때는 정적 설정을 사용하겠지만, 먼저 동적 서비스를 살펴보고 엔보이가 어떻게 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 하나로 모든 변경 사항을 순차적으로 가져올 수 있다.

    • 이 API들을 통틀어 xDS 서비스라고 부른다.
    • 이들 중 하나 이상을 조합해 설정할 수 있으며, 전부 사용해야만 하는 것은 아니다.
    • 한 가지 유념해야 할 점은 엔보이의 xDS API는 궁극적 일관성 eventual consistency 을 전제로 구축됐으며 궁극적으로는 올바른 구성으로 수렴한다는 것이다.
    • 예를 들어 엔보이가 트래픽을 클러스터 foo로 라우팅하는 새 루트가 RDS로 업데이트됐는데, 이 클러스터 foo를 포함한 CDS 업데이트는 아직 수행되지 않았을 수 있다.
    • 이 경우 CDS가 업데이트될 때까지 라우팅 오류가 발생할 수 있다.
    https://blog.naver.com/alice_k106/222000680202 (원본 출처 EnvoyCon)

     

    • 이런 순서에 따른 경쟁 상태 race condition 를 해결하기 위해 도입한 것이 ADS 이다.
    • 이스티오는 프록시 설정 변경을 위해 ADS를 구현한다.
    https://blog.naver.com/alice_k106/222000680202 (원본 출처 EnvoyCon)

     

    출처 : 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로 명명했다)
    • 좀 더 구체적인 예를 들면, 이스티오는 서비스 프록시용으로 다음과 같이 부트스트랩 설정을 사용한다.
    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++로 작성돼 플랫폼에 맞게 컴파일 된다. 엔보이를 시작하기 가장 좋은 방법은 도커를 사용해 컨테이너를 실행하는 것이다.

     

    Step1. 도커 이미지 가져오기 : 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

     

    Step2. 간단한 httpbin 서비스 만들어보자.

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

    • 예를 들어 http://httpbin.org/headers 로 이동해보자. httpbin 서비스를 시작하면, 그 다음에는 엔보이를 시작하고 모든 트래픽이 httpbin 서비스로 가도록 프록시를 설정할 것이다.
    • 그러고 나서 클라이언트 앱을 시작해 프록시를 호출할 것이다. 아래 그림은 이 예제의 아키텍처를 단순화한 것을 보여준다.

     

    ☞ httpbin 서비스 실행

    # 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"
        ]
      }
    }
    • /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

     

    ☞ 이를 수정하고 앞서 봤던 예제 설정 파일을 전달해보자.

    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
    • 기본적으로 15001 포트에 단일 리스너를 노출하고 모든 트래픽을 httpbin 클러스터로 라우팅할 것이다.

    ☞ 다시 엔보이 시작!!

    #
    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
    • 프록시가 성공적으로 시작해 15001 포트를 리스닝하고 있다.

     

    ☞ curl 로 프록시를 호출해보자!!

    # 터미널2
    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": [
          "15000" # 15000ms = 15초
        ],
        "X-Forwarded-Proto": [
          "http"
        ],
        "X-Request-Id": [
          "8d08bd8e-7899-42e1-bf74-7a3381a2494a"
        ]
      }
    }
    • 프록시를 호출했는데도 트래픽이 httpbin 서비스로 정확하게 전송됐다. 또 다음과 같은 새로운 헤더도 추가됐다.
      • X-Envoy-Expected-Rq-Timeout-Ms
      • X-Request-Id
    Envoy 정적 설정에 의한 서비스 연동과정

    • 사소하게 보일 수 있지만, 이미 엔보이는 우리를 위해 많은 일을 하고 있다.
    • 엔보이는 새 X-Request-Id를 만들었는데, X-Request-Id는 클러스터 내 다양한 요청 사이의 관계를 파악하고 요청을 처리하기 위해 여러 서비스를 거치는 동안(즉, 여러 홉)을 추적하는 데 활용할 수 있다.
    • 두 번째 헤더인 X-Envoy-Expected-Rq-Timeout-Ms는 업스트림 서비스에 대한 힌트로, 요청이 15,000ms 후에 타임아웃될 것으로 기대한다는 의미다.
    • 업스트림 시스템과 그 요청이 거치는 모든 홉은 이 힌트를 사용해 데드라인을 구현할 수 있다. 데드라인을 사용하면 업스트림 시스템에 타임아웃 의도를 전달할 수 있으며, 데드라인이 넘으면 처리를 중단하게 할 수 있다.
    • 이렇게 하면 타임아웃된 후 묶여 있던 리소스가 풀려난다.
    • 다음 실습을 위해 Envoy 종료 docker rm -f proxy

    • 이제 이 구성을 살짝 변경해 예상 요청 타임아웃1초로 설정해보자.
    • 설정 파일에 라우팅 규칙을 업데이트하자.
                  - match: { prefix: "/" }
                    route:
                      auto_host_rewrite: true
                      cluster: httpbin_service
                      timeout: 1s

     

    실행  ( 정적설정 파일 수정 반영 )

    #
    #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

     

    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 # 엔보이 통계(프로메테우스 레코드 형식)

     

    [ 실행 결과 - 한 눈에 보기 ]

    3.3.2 Envoy request retries 요청 재시도

    더보기
    • httpbin 요청을 일부러 실패시켜 엔보이가 어떻게 요청을 자동으로 재시작하는지 살펴보자.
    • 먼저 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
    ...
    • 엔보이는 업스트림 클러스터 httpbin 호출할 때 HTTP 500 응답을 받았다.
    • 엔보이는 요청을 재시도했으며, 이는 통계값에 cluster.httpbin_service.upstream_rq_retry: 3 으로 표시돼 있다.

    • 애플리케이션 네트워킹에 자동으로 신뢰성을 부여하는 엔보이의 아주 기본적인 기능을 시연해봤다.
    • 이 기능들을 추론하고 시연하는 데 정적 설정 파일을 사용했지만, 앞 절에서 봤듯이 이스티오는 동적 설정 기능을 사용한다.
    • 그렇게 해서 이스티오는 각각의 설정을 복잡할 수 있는 대규모 엔보이 프록시 집합을 관리할 수 있다.

    ▶ 다음 실습을 위해 Envoy 종료

    docker rm -f proxy && docker rm -f httpbin

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

    더보기
    • 엔보이는 이 책에서 다루는 이스티오의 기능 대부분에서 핵심 역할을 맡는다. 엔보이는 프록시로서 서비스 메시에 매우 적합하다.
    • 하지만 엔보이를 최대한 활용하려면 보조 인프라나 구성 요소가 필요하다.
    • 이스티오가 제공하는 사용자 설정, 보안 정책, 런타임 설정을 지원한느 구성 요소들이 컨트롤 플레인을 형성한다.
    • 엔보이도 데이터 플레인에서 모든 작업을 혼자 수행하는 것은 아니며 지원이 필요하다. 자세한 내용은 부록 B 참고.

     

    • 몇 가지 예를 들어 보조 구성 요소의 필요성을 설명해보자.
    • 엔보이의 기능 덕분에 정적 설정 파일이나 런타임에 리스너, 엔드포인트, 클러스터를 찾기 위한 xDS 디스커버리 서비스를 사용해 서비스 프록시를 설정할 수 있다는 사실을 확인했다.
    • 이스터오는 istiod 컨트롤 플레인 구성 요소에서 이 xDS API들을 구현한다.

    • 아래 그림은 istiod가 쿠버네티스 API를 사용해 VirtualService 등의 이스티오 설정을 읽은 다음 서비스 프록시를 동적으로 설정하는 모습을 보여준다.
    • 관련 예로는 엔드포인트를 검색하기 위해 일종의 서비스 저장소에 의존하는 엔보이의 서비스 디스커버리가 있다.
    • A related example is Envoy’s service discovery, which relies on a service registry of some sort to discover endpoints.
    • istiod는 이 API를 구현하기도 하지만 엔보이에게 서비스 저장소의 구현을 추상화하기도 한다.
    • 이스티오를 쿠버네티스에 배포하면, 서비스 디스커버리에 쿠버네티스의 서비스 저장소를 사용한다.
    • When Istio is deployed on Kubernetes, Istio uses Kubernetes’ service registry for service discovery.
    • 이런 구현 세부 사항은 엔보이 프록시에게 완벽히 감춰진다.

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

    • 마지막으로 엔보이는 메시 내의 서비스로 향하는 TLS 트래픽을 종료하고 시작할 수 있다.
    • 그렇게 하려면 인증서를 생성, 서명, 로테이션하는 보조 인프라가 필요하다.
    • 이스티오는 이를 istiod 구성 요소를 통해 제공한다.

     

    • 이스티오의 구성 요소와 엔보이 프록시는 강력한 서비스 메시를 구현하는 데 함께 기여한다.
    • 이제부터 엔보이이스티오 프록시라 부르며, 그 기능들을 이스티오의 API를 통해 살펴볼 것이다.
    • 그렇지만 실제로는 많은 것이 엔보이가 제공하고 구현하는 것임을 이해하자.

     

     

    [ Summary ]

    더보기
    • Envoy는 애플리케이션이 애플리케이션 수준의 동작에 사용할 수 있는 프록시입니다.
    • Envoy는 이스티오의 데이터 플레인입니다.
    • Envoy는 클라우드 신뢰성 문제(네트워크 장애, 토폴로지 변경, 탄력성)를 일관되고 정확하게 해결하는 데 도움을 줄 수 있습니다.
    • Envoy는 런타임 제어를 위해 동적 API를 사용합니다(Istio는 이를 사용합니다).
    • Envoy는 애플리케이션 사용 및 프록시 내부에 대한 강력한 지표와 정보를 많이 노출합니다.

    [도전과제1] Envoy 공식문서에 Quick start 전체 내용을 실습 및 정리 - Docs

    [도전과제2] Envoy 공식문서에 Sandboxes 내용을 실습 및 정리 - Docs

    [도전과제3] solo academy 에 Get Started with Envoy Proxy 온라인 실습 렙 정리 - Link

     


    Chap4. Istio gateways: Getting traffic into a cluster

    • 스터디 실습 환경 : docker (kind - k8s 1.23.17 ‘23.2.28 - Link) , istio 1.17.8(’23.10.11) - Link

    k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)

    더보기
    #
    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: /Users/gasida/Downloads/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

     

     

    istio 1.17.8 설치 - Docs , Install , profile

    더보기
    # 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

    [ 실행 결과 - 한 눈에 보기 ]


    Istio 1.17 공식 문서 https://istio.io/v1.17/docs/

     

    This chapter covers

    더보기
    • 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)

     

     


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

     

     

    • 서로 다른 서비스 여러 개를 진입점 하나로 호스팅하는 것을 가상 호스팅 virtual hosting 이라고 한다.
    • 특정 요청이 향하는 가상 호스트 그룹을 결정할 방법이 필요한데, HTTP/1.1 에서는 Host 헤더를 사용할 수 있고 HTTP/2에서는 :authority 헤더를 사용할 수 있다. - Blog1, Blog2, Blog3
    • 또한 TCP 커넥션에서는 TLSSNI 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)

     

    • 실행 결과, 이스티오 서비스 프록시에서 동작 중인 프로세스로 pilot-agent 와 envoy가 보여야 한다.
    • pilot-agent 프로세스는 처음에 엔보이 프록시를 설정하고 부트스트랩한다.
    • 그리고 13장에서 보겠지만, DNS 프록시도 구현한다.
    • 인그레스 게이트웨이가 트래픽을 클러스터 내부로 허용하도록 설정하려면, 우선 이스티오의 리소스인 Gateway 와 VirtualService 를 이해해야 한다.
    • 이 두 리소스는 이스티오에서 트래픽의 흐름을 조절하는 데 핵심적인 역할을 한다. 하지만 여기서는 클러스터 내부로 트래픽을 수용하는 것과 관련된 맥락에서만 살펴본다.

     

    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
        }
    ]
    • 리스너는 모든 것을 HTTP 404로 라우팅하는 기본 블랙홀 루트로 바인딩돼 있다.
    • 다음 절에서 트래픽을 80 포트에서 서비스 메시 내의 서비스로 라우팅하도록 가상 호스트를 설정할 것이다.

    • 기본 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 로 향하는 트래픽에만 적용된다.
    • 실행해보자!
    # 신규터미널 : istiod 로그
    kubectl stern -n istio-system -l app=istiod
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:15:16.485143Z	info	ads	Push debounce stable[21] 1 for config VirtualService/istioinaction/webapp-vs-from-gw: 102.17225ms since last change, 102.172083ms since last push, full=true
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:15:16.485918Z	info	ads	XDS: Pushing:2025-04-13T05:15:16Z/15 Services:12 ConnectedEndpoints:1 Version:2025-04-13T05:15:16Z/15
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:15:16.487330Z	info	ads	CDS: PUSH for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:23 size:22.9kB cached:22/22
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:15:16.488346Z	info	ads	LDS: PUSH for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:1 size:2.2kB
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T05:15:16.489937Z	info	ads	RDS: PUSH for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:1 size:538B cached:0/0
    
    
    #
    cat ch4/coolstore-vs.yaml
    kubectl apply -n istioinaction -f ch4/coolstore-vs.yaml
    
    # 확인
    kubectl get gw,vs -n istioinaction
    NAME                                            AGE
    gateway.networking.istio.io/coolstore-gateway   26m
    
    NAME                                                   GATEWAYS                HOSTS                         AGE
    virtualservice.networking.istio.io/webapp-vs-from-gw   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   49s
    
    #
    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
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
    NAME          DOMAINS     MATCH                  VIRTUAL SERVICE
    http.8080     webapp.istioinaction.io     /*                     webapp-vs-from-gw.istioinaction
    ...     
    
    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": "webapp.istioinaction.io:80",
                    "domains": [
                        "webapp.istioinaction.io" #1 비교할 도메인
                    ],
                    "routes": [
                        {
                            "match": {
                                "prefix": "/"
                            },
                            "route": { #2 라우팅 할 곳
                                "cluster": "outbound|80||webapp.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                    "numRetries": 2,
                                    "retryHostPredicate": [
                                        {
                                            "name": "envoy.retry_host_predicates.previous_hosts",
                                            "typedConfig": {
                                                "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                            }
                                        }
                                    ],
                                    "hostSelectionRetryMaxAttempts": "5",
                                    "retriableStatusCodes": [
                                        503
                                    ]
                                },
                                "maxGrpcTimeout": "0s"
                            },
                            "metadata": {
                                "filterMetadata": {
                                    "istio": {
                                        "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/virtual-service/webapp-vs-from-gw"
                                    }
                                }
                            },
                            "decorator": {
                                "operation": "webapp.istioinaction.svc.cluster.local:80/*"
                            }
                        }
                    ],
                    "includeRequestAttemptCount": true
                }
            ],
            "validateClusters": false,
            "ignorePortInHostMatching": true
        }
    ]
    
    # 실제 애플리케이션(서비스)를 배포 전으로 cluster 에 webapp, catalog 정보가 없다.
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system
    ...
    • 루트 출력은 다른 속성 및 정보를 포함할 수 있지만 이전과 비슷해야 한다.
    • 핵심은 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"
    }

     

     

    (추가) 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 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

     

    ☞ 이제 외부(?)에서 호출 해보자!

    # 터미널 : 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"
    ...

     

     

    (추가) 전체적인 트래픽 흐름 실습으로 알아보기

    더보기

    사전 준비 : 임시로 Istio 내부망에서 mTLS 통신 기능을 끄기 - Docs → 즉, http 평문 통신 환경으로 동작.

     

    # Istio 메시 내부망에서 모든 mTLS 통신 기능 끄기 설정 : (참고) 특정 네임스페이스 등 세부 조절 설정 가능 
    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: istio-system
    spec:
      mtls:
        mode: DISABLE
    EOF
    
    kubectl get PeerAuthentication -n istio-system
    NAME      MODE      AGE
    default   DISABLE   6h13m
    
    # 설정 적용 후, 실제로 인증서도 없음을 확인
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system
    No active or warming secrets found.
    
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/webapp.istioinaction
    No active or warming secrets found.
    
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/catalog.istioinaction
    No active or warming secrets found.

     

     


    Step1.  자신의 PC에서 localhost(127.0.0.1)로 30000 포트로 웹 호출

    소스IP:소스포트 = 127.0.0.1:랜덤포트 + 목적지IP:목적지포트 = 127.0.0.1:30000

    Docker 컨테이너가 30000 포트를 호스트로 Listen(Expose)하고 있어서, Docker Network(kind)로 진입으로 2번 처럼 NAT 변환

     

    Step2. myk8s-control-plane(Master Node) 컨테이너에 NodePort 30000 포트로 호출

    소스IP:소스포트 = 172.18.0.1:랜덤포트 + 목적지IP:목적지포트 = 172.18.0.2:30000

    172.18.0.1 는 Docker Network(kind) 172.18.0.0/16에서 gateway ip임

    docker inspect kind | jq
    docker inspect kind | grep -i gateway
                        "Gateway": "172.18.0.1"
                        "Gateway": "fc00:f853:ccd:e793::1"

     

    172.18.0.2 는 myk8s-control-plane(Master Node) 컨테이너에 할당된 IP임

    docker exec -it myk8s-control-plane ifconfig eth0
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 65535
            inet 172.18.0.2  netmask 255.255.0.0  broadcast 172.18.255.255
    ...

     

    ☞ 이후 아래 3번 처럼 Service(NodePort 30000)에 Endpoint인 istio-ingressgateway 파드 IP(10.10.0.6)로 전달

    kubectl get pod -n istio-system -l app=istio-ingressgateway -o jsonpath='{.items[*].status.podIP}'
    10.10.0.6

     

    [ packet 확인 및 추출 방법 ] 

    # 트래픽 발생
    curl -s http://localhost:30000/api/catalog -H "Host: webapp.istioinaction.io"
    
    # ngrep 툴로 패캣 덤프 확인 : control-plane 노드(?)의 NodePort 30000 대상
    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d eth0 '' 'tcp port 30000'"
    ####
    GET /api/catalog HTTP/1.1.
    Host: webapp.istioinaction.io.
    User-Agent: curl/8.7.1.
    Accept: */*.
    .
    
    ##
    T 2025/04/14 08:34:13.824436 172.18.0.2:30000 -> 172.18.0.1:64898 [AP] #6
    HTTP/1.1 200 OK.
    content-length: 357.
    content-type: application/json; charset=utf-8.
    date: Mon, 14 Apr 2025 08:34:13 GMT.
    x-envoy-upstream-service-time: 15.
    server: istio-envoy.
    
    ---
    
    # tcpdump 로 확인
    docker exec -it myk8s-control-plane tcpdump -i eth0 tcp port 30000 -nn
    
    # 트래픽 발생
    curl -s http://localhost:30000/api/catalog -H "Host: webapp.istioinaction.io"
    
    ---
    
    # tcpdump 로 패킷 추출 : control-plane 노드(?)의 /root 디렉터리에 test1.pcap 파일 생성됨
    docker exec -it myk8s-control-plane tcpdump -i eth0 tcp port 30000 -nn -w /root/test1.pcap
    # 아래 트래픽 발생 이후 CTRL+C로 취소
    
    # 트래픽 발생
    curl -s http://localhost:30000/api/catalog -H "Host: webapp.istioinaction.io"
    
    # 파일 확인
    docker exec -it myk8s-control-plane ls /root
    test1.pcap
    
    # 노드(실제로는 컨테이너)에서 로컬에 현재 디렉터리에 파일 복사
    docker cp myk8s-control-plane:/root/test1.pcap ./
    
    # 와이어샤크로 pcap 파일 확인
    wireshark

     

    Step3. istio-ingressgateway 파드 IP로 호출 전달

    소스IP:소스포트 = 172.18.0.1:랜덤포트 + 목적지IP:목적지포트 = 10.10.0.6:8080

    istio-ingressgateway Service 에 externalTrafficPolicy: Local 이므로 소스IP는 172.18.0.1 유지

    목적지 IP는 파드 IP로 + 목적지 포트는 istio-ingressgateway Service 의 target port 인 8080으로 NAT 변환

    kubectl get svc -n istio-system istio-ingressgateway  -o yaml
      - name: http2
        nodePort: 30000  #1 NodePort 서비스로 트래픽 인입하여
        port: 80         # 참고로 해당 포트는 내부망에서 직접 Service IP/Domain으로 접속 시 사용. NodePort 접속 시 미사용
        protocol: TCP
        targetPort: 8080 #2 서비스의 엔드포인트인 istio-ingressgateway 파드의 IP로 전달시 목적지 Port

     

    이후 아래 4번 처럼 istio-ingressgateway 파드에서 목적지 대상 애플리케이션 파드로 직접 호출 전달

    [ wireshark 패킷으로 확인 ]

     

    [ packet 확인 및 추출 방법 ]

    --------------------------------------------------------------------------------
    # istio-ingresgateway 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
    ## istio-ingresgateway 파드 IP 확인
    IGWIP=$(kubectl get pod -n istio-system -l app=istio-ingressgateway -o jsonpath='{.items[*].status.podIP}')
    echo $IGWIP
    
    ## veth 이름 확인
    docker exec -it myk8s-control-plane ip -c route | grep $IGWIP
    docker exec -it myk8s-control-plane ip -c route | grep $IGWIP | awk '{ print $3 }'
    IGWVETH=$(docker exec -it myk8s-control-plane ip -c route | grep $IGWIP | awk '{ print $3 }')
    echo $IGWVETH
    veth3df9536e <- 해당 이름을 메모해두기
    --------------------------------------------------------------------------------
    
    # 트래픽 발생
    curl -s http://localhost:30000/api/catalog -H "Host: webapp.istioinaction.io"
    
    
    # ngrep 확인(메모 정보 직접 기입!) : control-plane 노드(?)에서 istio-ingressgw의 vpair의 veth 8080 대상
    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d veth3df9536e '' 'tcp port 8080'"
    ####
    T 2025/04/14 08:45:52.508224 172.18.0.1:57536 -> 10.10.0.6:8080 [AP] #4
    GET /api/catalog HTTP/1.1.
    Host: webapp.istioinaction.io.
    User-Agent: curl/8.7.1.
    Accept: */*.
    .
    
    #####
    T 2025/04/14 08:45:52.509665 10.10.0.6:46612 -> 10.10.0.9:8080 [AP] #9
    GET /api/catalog HTTP/1.1.
    host: webapp.istioinaction.io.
    user-agent: curl/8.7.1.
    accept: */*.
    x-forwarded-for: 172.18.0.1.
    x-forwarded-proto: http.
    x-envoy-internal: true.
    x-request-id: 4cdf5faa-2eb2-425e-90cd-3152e376a7ca.
    x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuNgoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi4KBE5BTUUSJhokaXN0aW8taW5ncmVzc2dhdGV3YXktOTk2YmM2YmI2LXpuNTlqChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ==.
    x-envoy-peer-metadata-id: router~10.10.0.6~istio-ingressgateway-996bc6bb6-zn59j.istio-system~istio-system.svc.cluster.local.
    x-envoy-attempt-count: 1.
    x-b3-traceid: 672472dde754a886d33204c7d68908d8.
    x-b3-spanid: d33204c7d68908d8.
    x-b3-sampled: 0.
    ...
    
    # x-b3-sampled: This denotes whether a request should be traced or not. A value of 1 means yes and 0 means prohibited.
    # x-b3-traceid: This is 8 or 16 bytes in length, indicating the overall ID of the trace. 
    # x-b3-parentspanid: This is 8 bytes in length and represents the position of the parent operation in the trace tree. Every span will have a parent span unless it is the root itself.
    # x-b3-spanid: This is 8 bytes in length and represents the position of the current operation in the trace tree.
    ---
    
    # tcpdump 로 확인
    docker exec -it myk8s-control-plane tcpdump -i veth3df9536e tcp port 8080 -nn
    
    ---
    
    # tcpdump 로 패킷 추출 : control-plane 노드(?)의 /root 디렉터리에 test2.pcap 파일 생성됨
    docker exec -it myk8s-control-plane tcpdump -i veth3df9536e tcp port 8080 -nn -w /root/test2.pcap
    # 트래픽 발생 이후 CTRL+C로 취소
    
    # 파일 확인
    docker exec -it myk8s-control-plane ls /root
    test1.pcap test2.pcap
    
    # 노드(실제로는 컨테이너)에서 로컬에 현재 디렉터리에 파일 복사
    docker cp myk8s-control-plane:/root/test2.pcap ./
    
    # 와이어샤크로 pcap 파일 확인
    wireshark

     

    Step4. istio-ingressgateway 파드 EDS 정보를 확인하여, 해당 서비스의 Endpoint인 파드의 IP로 직접 호출 전달

     

    소스IP:소스포트 = 1010.0.6:랜덤포트 + 목적지IP:목적지포트 = 10.10.0.9:8080

    10.10.10.9:8080은 Istio EDS(Endpoint) 정보로 알게된, 목적지 애플리케이션의 Service의 Endpoint 목록 정보

    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.8:3000                                          HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.9:8080                                          HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local

     

    아래 처럼 webapp 서비스의 ClusterIP/Domain을 경유하지 않고, 바로 Endpoint인 10.10.0.9:8080으로 직접 호출 전달

    # 
    kubectl get svc,ep -n istioinaction webapp                                                                                                        
    NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
    service/webapp   ClusterIP   10.200.1.51   <none>        80/TCP    4h6m
    
    NAME               ENDPOINTS        AGE
    endpoints/webapp   10.10.0.9:8080   4h6m

     

    그리고 istio-ingressgateway 는 envoy 동작 중으로, 전달 시 xff 헤더, x-envoy 헤더, x-request-id 헤더, x-b3-* 등 추가해서 전달

    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d veth3df9536e '' 'tcp port 8080'"
    interface: veth3df9536e (10.10.0.1/255.255.255.255)
    filter: ( tcp port 8080 ) and ((ip || ip6) || (vlan && (ip || ip6)))
    ####
    T 2025/04/14 05:31:05.196680 172.18.0.1:62346 -> 10.10.0.6:8080 [AP] #4
    GET /api/catalog HTTP/1.1.
    Host: webapp.istioinaction.io.
    User-Agent: curl/8.7.1.
    Accept: */*.
    .
    
    #####
    T 2025/04/14 05:31:05.199269 10.10.0.6:37886 -> 10.10.0.9:8080 [AP] #9
    GET /api/catalog HTTP/1.1.
    host: webapp.istioinaction.io.
    user-agent: curl/8.7.1.
    accept: */*.
    x-forwarded-for: 172.18.0.1.
    x-forwarded-proto: http.
    x-envoy-internal: true.
    x-request-id: 48303117-36b9-4849-a986-1985a202ddaa.
    x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuNgoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi4KBE5BTUUSJhokaXN0aW8taW5ncmVzc2dhdGV3YXktOTk2YmM2YmI2LXpuNTlqChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ==.
    x-envoy-peer-metadata-id: router~10.10.0.6~istio-ingressgateway-996bc6bb6-zn59j.istio-system~istio-system.svc.cluster.local.
    x-envoy-attempt-count: 1.
    x-b3-traceid: 36c65fbfc0eb90e77b80e483cd159800.
    x-b3-spanid: 7b80e483cd159800.
    x-b3-sampled: 0.

     

    [ wireshark 패킷으로 확인 ] 

     

     

    • 전체 Flow에 대한 정보 : 아래 첨부 pcap 파일 다운로드해서 확인하셔도 됩니다.
    출처 : [Wireshark] Statistics - Flow Graph
    • 172.18.0.1 : docker network 중 kind 에서 사용하는 gateway ip
    • 172.18.0.2 : myk8s-control-plane 노드(실제로는 컨테이너) eth0 IP
    • 10.10.0.6 : istio-ingressgateway 파드 IP
    • 10.10.0.9 : webapp 파드 IP
    • 2번째가 마치 172.18.0.1 에서 10.10.10.6 연결 처럼 보이지만, 실제로는 172.18.0.2가 IP/Port NAT를 할때, 소스IP 유지로 표현됨

    실습 완료 후 4.3 TLS 실습을 위해서 mTLS 활성화

    # PeerAuthentication 설정 제거
    kubectl delete PeerAuthentication -n istio-system default
    kubectl get PeerAuthentication -n istio-system
    
    # 다시 인증서 확인
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system
    RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
    default           Cert Chain     ACTIVE     true           155102144044256685798584897642001908841     2025-04-15T01:17:56Z     2025-04-14T01:15:56Z
    ROOTCA            CA             ACTIVE     true           161890271456473083200656107031149708324     2035-04-12T01:17:47Z     2025-04-14T01:17:47Z

     

    4.2.3 Overall view of traffic flow 전체적인 트래픽 흐름 개요

    • 이스티오 게이트웨이 리소스는 서비스 메시 클러스터의 에지에서 리스닝하고자 하는 포트, 프로토콜, 가상 호스트를 정의한다.
    • VirtualService 리소스는 에지에서 허용된 트래픽이 가야 할 곳을 정의한다.
    더보기

      

     

    4.2.4 Istio ingress gateway vs. Kubernetes Ingress

    더보기
    • 쿠버네티스를 실행할 때 ‘왜 이스티오는 그냥 쿠버네티스 인그레스 v1 리소스를 인그레스로 지정하지 않는가?’ 라는 의문이 생길 수 있다. 이스티오는 쿠버네티스 인그레스 v1 리소스를 지원하지만, 쿠버네티스 인그레스 v1 사양에는 상당한 한계가 있다.
    • 첫 번째 한계는 쿠버네티스 인그레스 v1 의 사양이 HTTP 워크로드에 맞춰져 매우 단순하다는 점이다.
      • 실제로 인그레스 사양은 80포트와 443 포트만 인그레스 포인트로 간주한다. 이는 클러스터 운영자가 서비스 메시로 허용할 수 있는 트래픽 유형을 심각하게 제한한다.
      • 예를 들어 카프카나 NATS.io 워크로드가 있다면, 이들 메시지 시스템에 TCP 커넥션직접 노출하고 싶을 수 있다. 쿠버네티스 인그레스는 이를 허용하지 않는다.
    • 두 번째로, 쿠버네티스 인그레스 v1 리소스는 사양심각하게 부족하다는 점을 지적할 수 있다.
      • 복잡한 트래픽 라우팅 규칙, 트래픽 분할, 트래픽 섀도잉 같은 것들을 지정하는 일반적인 방법이 없다.
      • 이런 영역에서 사양이 부족한 까닭에 각 벤더는 인그레스 구현체(HAProxy, Nginx 등)에서 설정의 최적 구현법을 재창조해야했다.
    • 마지막으로, 사양이 부족하다 보니 벤더 대부분의 설정이 디플로이먼트의 맞춤형 애노테이션으로 노출하는 방식을 택했다는 점을 꼽을 수 있다.
      • 이 애노테이션은 벤더마다 상이하고 호환되지 않으므로, 이스티오가 이런 추세에 따랐다면 에지 게이트웨이로서 엔보이의 모든 기능을 반영하기 위해 애노테이션이 더 많이 필요했을 것이다.
    • 결국 이스티오는 인그레스 패턴백지 상태에서 시작해 구축하기로 결정했고, 4계층(전송)과 5계층(세션) 속성을 7계층(애플리케이션) 라우팅 문제로부터 명확하게 분리해냈다.
    • 이스티오 GatewayL4/L5 문제를 처리하고, VirtualServiceL7 문제를 처리한다.

    ☞ 쿠버네티스 Gateway API - 이 책을 저술하는 시점(’21~22년)에 쿠버네티스 커뮤니티는 인그레스 v1 API를 대체하고자 Gateway API를 열심히 준비하고 있다. - 이 API는 책에서 다룬 Istio Gateway 및 VirtualService 리소스와 다른다. - Istio의 구현체와 리소스는 Gateway API 보다 먼저 등장했고, 여러 면에서 Gateway API에 영감을 줬다.  

    4.2.5 Istio ingress gateway vs. API gateways

    더보기
    • API 게이트웨이는 조직이 네트워크나 아키텍처 경계의 서비스를 사용하는 클라이언트에게 서비스 구현의 세부 사항을 추상화활 수 있게 해준다.
    • 예컨대 클라이언트는 잘 문서화된 API들을 호출하게 되고, 이 API들은 앞뒤 호환성을 지니면서 발전하며, 셀프 서비스와 기타 사용 메커니즘을 제공할 수 있다.
    • 이를 달성하려면 API 게이트웨이는 여러 보안 기술(OIDC, OAuth 2.0, LDAP)을 사용해 클라이언트를 식별할 수 있어야 하고, 메시지를 변형할 수 있어야 하며(SOAP → REST, gRPC → REST, 바디 및 헤더 텍스트 기반 변환 등), 비즈니스 수준으로 정교하게 속도를 제한할 수 있어야 하고, 자체 가입 기능이나 개발자 포털을 제공할 수 있어야 한다.
    • 이스티오의 인그레스 게이트웨이는 이런 것들을 기본적으로는 제공하지 않는다.
    • 메시 안팎에서 이런 역할을 수행하는 더욱 강력한 API 게이트웨이를 찾는다면, 엔보이 프록시를 기반으로 하는 Solo.io Gloo Edge 같은 것을 살펴볼만 한다.

     


    4.3 Securing gateway traffic 게이트웨이 트래픽 보안

    더보기

    [ 들어가며 ]

    • 지금까지 Gateway, VirtualService 리소스를 사용해 이스티오 게이트웨이에서 기본 HTTP 서비스를 노출하는 방법을 살펴봤다.
    • 클러스터 외부(퍼블릭 인터넷 등)의 서비스를 클러스터 내부에서 실행 중인 서비스에 연결할 때, 인그레스 게이트웨이의 기본 기능 중 하나는 트래픽을 보호해 시스템의 신뢰 구축을 돕는 것이다.
    • 트래픽 보호는 클라이언트가 통신하려는 서버의 진위를 가리는 것에서 시작할 수 있다. 자신이 그 서비스라고 주장하는 서비스가 정말 통신하려는 서비스가 맞는지 검증하는 것이다. 또한 누군가가 도청하는 일을 막고 싶으므로 트래픽을 암호화해야 한다.
    • 이스티오의 게이트웨이 구현을 사용하면 들어오는 TLS/SSL 트래픽을 종료하고, 백엔드 서비스로 전달하고, TLS가 아닌 트래픽을 적절한 TLS 포트로 리다이렉트하고, mTLS를 구현할 수 있다.

     

    4.3.1 HTTP traffic with TLS (실습)

    더보기
    • MITM 공격을 방지하고 서비스 메시로 들어오는 모든 트래픽을 암호화하기 위해 이스티오 게이트웨이에 TLS를 설정할 수 있다.
    • 들어오는 트래픽 모두가 HTTPS로 제공되도록 하는 것이다.
    • MITM 공격이란 클라이언트가 어떤 서비스에 연결하려고 하지만, 그 서비스가 아닌 사칭 서비스에 언결할 때를 말한다.
    • 사칭 서비스는 통신에 접근할 수 있게 되며, 여기에는 민감 정보도 포함된다. TLS는 이 공격을 완화하는 데 도움을 준다.

    • 수신 트래픽에 HTTPS를 활성화하려면 게이트웨이가 사용할 비공개 키와 인증서를 올바르게 지정해야 한다.
    • 서버가 제시하는 인증서는 서버가 클라이언트에게 자신의 정체를 알리는 방법이다.
    • 인증서란 기본적으로 서버의 공개 키이며, 신뢰할 수 있는 기관인 인증 기관 (CA, Certificate Authority)에서 서명한 것이다.
    • 아래 그림은 클라이언트가 서버 인증서의 유효성을 판단하는 방법을 도식화한 것이다.
    • 먼저 클라이언트에 CA 발급자의 인증서가 설치돼 있어야 한다.
    • 이는 이 발급자가 신뢰할 수 있는 CA이며 발급자가 발급한 인증서 역시 신뢰할 수 있다는 의미다.
    • CA 인증서가 설치돼 있으면, 클라이언트는 인증서가 신뢰할 수 있는 CA가 서명한 것인지 검증할 수 있다.
    • 그런 다음, 클라이언트는 인증서 내 공개 키를 사용해 서버로 보내는 트래픽을 암호화 한다.
    • 서버는 비밀 키로 트래픽을 복호화 할 수 있다.

     

    ☞ 앞 선 설명은 사실 정확하지 않다. TLS handshake 에는 더 정교한 프로토콜이 포함되는데, 최초 통신에는 공개/비공개 키(비대칭)를 조합하고, TLS 세션에서 트래픽 암호화/복호화에 사용할 세션 키(대칭)을 생성한다. TLS를 더 자세히 알고 싶다면 부록 C를 참조하자.


    • 기본 istio-ingressgateway 가 인증서와 키를 사용하도록 설정하려면 먼저 인증서/키를 쿠버네티스 시크릿으로 만들어야 한다.  
    #
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system
    RESOURCE NAME     TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
    default           Cert Chain     ACTIVE     true           5384840145379860934113500875842526824       2025-04-14T11:51:01Z     2025-04-13T11:49:01Z
    ROOTCA            CA             ACTIVE     true           218882737649632505517373517490277818381     2035-04-11T11:50:53Z     2025-04-13T11:50:53Z
    
    
    # 파일 정보 확인
    cat ch4/certs/3_application/private/webapp.istioinaction.io.key.pem # 비밀키(개인키)
    cat ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem
    openssl x509 -in ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem -noout -text
    ...
            Issuer: C=US, ST=Denial, O=Dis, CN=webapp.istioinaction.io
            Validity
                Not Before: Jul  4 12:49:32 2021 GMT
                Not After : Jun 29 12:49:32 2041 GMT
            Subject: C=US, ST=Denial, L=Springfield, O=Dis, CN=webapp.istioinaction.io
            ...
            X509v3 extensions:
                X509v3 Basic Constraints: 
                    CA:FALSE
                Netscape Cert Type: 
                    SSL Server
                Netscape Comment: 
                    OpenSSL Generated Server Certificate
                X509v3 Subject Key Identifier: 
                    87:0E:5E:A4:4C:A5:57:C5:6D:97:95:64:C4:7D:60:1E:BB:07:94:F4
                X509v3 Authority Key Identifier: 
                    keyid:B9:F3:84:08:22:37:2C:D3:75:18:D2:07:C4:6F:4E:67:A9:0C:7D:14
                    DirName:/C=US/ST=Denial/L=Springfield/O=Dis/CN=webapp.istioinaction.io
                    serial:10:02:12
                X509v3 Key Usage: critical
                    Digital Signature, Key Encipherment
                X509v3 Extended Key Usage: 
                    TLS Web Server Authentication
    ...
    
    # webapp-credential 시크릿 만들기
    kubectl create -n istio-system secret tls webapp-credential \
    --key ch4/certs/3_application/private/webapp.istioinaction.io.key.pem \
    --cert ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem
    
    # 확인 : krew view-secret
    kubectl view-secret -n istio-system webapp-credential --all

     

    • istio-system 네임스페이스에 시크릿을 만든다. 이 책을 저술하는 시점에서(istio 1.13.0) 게이트웨이의 TLS에서 사용하는 시크릿은 이스티오 인그레스 게이트웨이와 동일한 네임스페이스에 있을 때만 가져올 수 있다.
    • 운영 환경에서는 인그레스 게이트웨이를 istio-system과 분리해 자체 네임스페이스에서 실행해야 한다.

    ☞ 이제 이스티오 게이트웨이 리소스가 인증서와 키를 사용하도록 설정할 수 있다.  

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: coolstore-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80  #1 HTTP 트래픽 허용
          name: http
          protocol: HTTP
        hosts:
        - "webapp.istioinaction.io"
      - port:
          number: 443 #2 HTTPS 트래픽 허용
          name: https 
          protocol: HTTPS
        tls:
          mode: SIMPLE #3 보안 연결
          credentialName: webapp-credential #4 TLS 인증서가 들어 있는 쿠버네티스 시크릿 이름
        hosts:
        - "webapp.istioinaction.io"
    • Gateway 리소스에서 인그레스 게이트웨이의 443 포트를 열고, 이를 HTTPS로 지정한다.
    • 또한 게이트웨이 설정에 tls 부분을 추가해 TLS에 사용할 인증서와 키의 위치를 지정한다.
    • 이 위치가 앞서 istio-ingressgateway 에 마운트한 위치와 동일하다는 것을 확인하자.

    ☞ 설정해보자!  

    #
    cat ch4/coolstore-gw-tls.yaml
    kubectl apply -f ch4/coolstore-gw-tls.yaml -n istioinaction
    gateway.networking.istio.io/coolstore-gateway configured
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system
    RESOURCE NAME                      TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
    default                            Cert Chain     ACTIVE     true           135624833228815846167305474128694379500     2025-04-14T03:57:41Z     2025-04-13T03:55:41Z
    kubernetes://webapp-credential     Cert Chain     ACTIVE     true           1049106                                     2041-06-29T12:49:32Z     2021-07-04T12:49:32Z
    ROOTCA                             CA             ACTIVE     true           236693590886065381062210660883183746411     2035-04-11T03:57:31Z     2025-04-13T03:57:31Z
    
    
    # 호출 테스트 1
    curl -v -H "Host: webapp.istioinaction.io" https://localhost:30005/api/catalog
    * Host localhost:30005 was resolved.
    * IPv6: ::1
    * IPv4: 127.0.0.1
    *   Trying [::1]:30005...
    * Connected to localhost (::1) port 30005
    * ALPN: curl offers h2,http/1.1
    * (304) (OUT), TLS handshake, Client hello (1):
    *  CAfile: /etc/ssl/cert.pem
    *  CApath: none
    * LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:30005
    * Closing connection
    curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:30005
    # 서버(istio-ingressgateway 파드)에서 제공하는 인증서는 기본 CA 인증서 체인을 사용해 확인할 수 없다는 의미다.
    # curl 클라이언트에 적절한 CA 인증서 체인을 전달해보자.
    # (호출 실패) 원인: (기본 인증서 경로에) 인증서 없음. 사설인증서 이므로 “사설CA 인증서(체인)” 필요
    
    #
    kubectl exec -it deploy/istio-ingressgateway -n istio-system -- ls -l /etc/ssl/certs
    ...
    lrwxrwxrwx 1 root root     23 Oct  4  2023  f081611a.0 -> Go_Daddy_Class_2_CA.pem
    lrwxrwxrwx 1 root root     47 Oct  4  2023  f0c70a8d.0 -> SSL.com_EV_Root_Certification_Authority_ECC.pem
    lrwxrwxrwx 1 root root     44 Oct  4  2023  f249de83.0 -> Trustwave_Global_Certification_Authority.pem
    lrwxrwxrwx 1 root root     41 Oct  4  2023  f30dd6ad.0 -> USERTrust_ECC_Certification_Authority.pem
    lrwxrwxrwx 1 root root     34 Oct  4  2023  f3377b1b.0 -> Security_Communication_Root_CA.pem
    lrwxrwxrwx 1 root root     24 Oct  4  2023  f387163d.0 -> Starfield_Class_2_CA.pem
    lrwxrwxrwx 1 root root     18 Oct  4  2023  f39fc864.0 -> SecureTrust_CA.pem
    lrwxrwxrwx 1 root root     20 Oct  4  2023  f51bb24c.0 -> Certigna_Root_CA.pem
    lrwxrwxrwx 1 root root     20 Oct  4  2023  fa5da96b.0 -> GLOBALTRUST_2020.pem
    ...
    
    #
    cat ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    openssl x509 -in ch4/certs/2_intermediate/certs/ca-chain.cert.pem -noout -text
    ...
               X509v3 Basic Constraints: critical
                    CA:TRUE, pathlen:0
    ...
    
    
    # 호출 테스트 2
    curl -v -H "Host: webapp.istioinaction.io" https://localhost:30005/api/catalog \
    --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    * Host localhost:30005 was resolved.
    * IPv6: ::1
    * IPv4: 127.0.0.1
    *   Trying [::1]:30005...
    * Connected to localhost (::1) port 30005
    * ALPN: curl offers h2,http/1.1
    * (304) (OUT), TLS handshake, Client hello (1):
    *  CAfile: ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    *  CApath: none
    * LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:30005 
    * Closing connection
    curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:30005 
    # (호출 실패) 원인: 인증실패. 서버인증서가 발급된(issued) 도메인 “webapp.istioinaction.io”로 호출하지 않음 (localhost로 호출함)
    
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 1
    
    # 호출 테스트 3
    curl -v https://webapp.istioinaction.io:30005/api/catalog \
    --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    
    open https://webapp.istioinaction.io:30005
    open https://webapp.istioinaction.io:30005/api/catalog
    
    # http 접속도 확인해보자
    curl -v http://webapp.istioinaction.io:30000/api/catalog
    open http://webapp.istioinaction.io:30000

     

    • 아래 그림은 종단 간 end-to-end 암호화를 달성했음을 보여준다.
    • 우리는 이스티오 게이트웨이를 향하는 트래픽을 암호화함으로써 보호한다.
    • 이스티오 인그레스 게이트웨이는 TLS 커넥션을 종료하고, 트래픽을 서비스 메시 내에서 실행 중인 뒷단의 webapp 서비스로 보낸다.
    • istio-ingressgateway 와 webapp 서비스 사이의 홉은 서비스의 ID를 사용해 암호화한다. 9장에서 살펴볼 것이다.

     

     

    ☞ 인증서 워크플로우를 외부 CA 또는 자체적인 내부 PKI와 통합하고 싶을 수 있다. 통합하는 데는 cert-manager 등을 사용할 수 있으며, 자세한 정보는 공식 문서 참고하자. https://cert-manager.io/docs/


    [ 실행 결과 - 한 눈에 보기 ]

     

    4.3.2 HTTP redirect to HTTPS (실습)

    더보기

    Step1. 모든 트래픽 항상 TLS를 쓰도록 강제, HTTP 트래픽을 강제로 리다이렉트하게 Gateway 수정

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: coolstore-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "webapp.istioinaction.io"
        tls:
          httpsRedirect: true 
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: webapp-credential
        hosts:
        - "webapp.istioinaction.io"

     

     Step2. 실행

    #
    kubectl apply -f ch4/coolstore-gw-tls-redirect.yaml
    
    # HTTP 301 리다이렉트
    curl -v http://webapp.istioinaction.io:30000/api/catalog
    * Host webapp.istioinaction.io:30000 was resolved.
    * IPv6: (none)
    * IPv4: 127.0.0.1
    *   Trying 127.0.0.1:30000...
    * Connected to webapp.istioinaction.io (127.0.0.1) port 30000
    > GET /api/catalog HTTP/1.1
    > Host: webapp.istioinaction.io:30000
    > User-Agent: curl/8.7.1
    > Accept: */*
    > 
    * Request completely sent off
    < HTTP/1.1 301 Moved Permanently
    < location: https://webapp.istioinaction.io:30000/api/catalog
    < date: Sun, 13 Apr 2025 08:01:31 GMT
    < server: istio-envoy
    < content-length: 0
    < 
    * Connection #0 to host webapp.istioinaction.io left intact

     

    ☞ 이 리다이렉트는 클라이언트에게 API의 HTTPS 버전을 호출하라고 지시한다.

    4.3.3 HTTP traffic with mutual TLS (실습)

    더보기
    • 앞 절에서는 표준 TLS를 사용해 서버가 자신의 정체를 클라이언트에게 증명하게 했다.
    • 그런데 만약 클러스터 외부의 트래픽을 받아들이기 전에 클라이언트가 누군지 검증하고 싶다면 어떻게 해야 할까?
    • 단순 TLS 시나리오에서는 서버가 자신의 공개 인증서를 클라이언트에게 보냈고, 클라이언트는 서버의 인증서에 서명한 CA를 신뢰할 수 있음을 검증했다.
    • 우리는 클라이언트가 자신의 공개 인증서를 보내게 하고 서버가 인증서를 신뢰하는지 검증하게 하고 싶다.
    • 아래 그림은 mTLS 프로토콜에서 클라이언트와 서버 모두가 어떻게 서로의 인증서를 검증하는지, 즉 어떻게 서로 인증하는지 보여준다.
    • 그리고 인증서는 트래픽을 암호화하는 데 사용한다.
    출처 : https://www.youtube.com/watch?v=4sJd6PIkP_s

     

    • 기본 istio-ingressgateway 가 mTLS 커넥션에 참여하도록 구성하려면, 클라이언트의 인증서를 검증하는 데 사용할 수 있도록 CA 인증서 집합을 제공해야 한다.
    • 알 절에서 했던 것처럼, 쿠버네티스 시크릿으로 istio-ingressgateway 에세 이 CA 인증서(더 구체적으로 인증서 체인)를 사용할 수 있게 해야 한다.
    • 적절한 CA 인증서 체인으로 istio-ingressgateway-ca-cert 시크릿을 구성하는 것으로 시작하겠다.
    # 인증서 파일들 확인
    cat ch4/certs/3_application/private/webapp.istioinaction.io.key.pem
    cat ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem
    cat ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    openssl x509 -in ch4/certs/2_intermediate/certs/ca-chain.cert.pem -noout -text
    
    # Secret 생성 : (적절한 CA 인증서 체인) 클라이언트 인증서
    kubectl create -n istio-system secret \
    generic webapp-credential-mtls --from-file=tls.key=\
    ch4/certs/3_application/private/webapp.istioinaction.io.key.pem \
    --from-file=tls.crt=\
    ch4/certs/3_application/certs/webapp.istioinaction.io.cert.pem \
    --from-file=ca.crt=\
    ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    
    # 확인
    kubectl view-secret -n istio-system webapp-credential-mtls --all

    ☞ 이제 CA 인증서 체인의 위치를 가리키도록 이스티오 Gateway 리소스를 업데이트하고, expected 프로토콜을 mTLS로 구성하자.

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: coolstore-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "webapp.istioinaction.io"
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: MUTUAL # mTLS 설정
          credentialName: webapp-credential-mtls # 신뢰할 수 있는 CA가 구성된 자격 증명
        hosts:
        - "webapp.istioinaction.io"

     

    ☞ 실행

    #
    kubectl apply -f ch4/coolstore-gw-mtls.yaml -n istioinaction
    gateway.networking.istio.io/coolstore-gateway configured
    
    # (옵션) SDS 로그 확인
    kubectl stern -n istio-system -l app=istiod
    istiod-7df6ffc78d-w9szx discovery 2025-04-13T08:14:14.405397Z	info	ads	SDS: PUSH request for node:istio-ingressgateway-996bc6bb6-p6k79.istio-system resources:0 size:0B cached:0/0 filtered:2
    ...
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config secret deploy/istio-ingressgateway.istio-system
    RESOURCE NAME                                  TYPE           STATUS     VALID CERT     SERIAL NUMBER                               NOT AFTER                NOT BEFORE
    default                                        Cert Chain     ACTIVE     true           135624833228815846167305474128694379500     2025-04-14T03:57:41Z     2025-04-13T03:55:41Z
    ROOTCA                                         CA             ACTIVE     true           236693590886065381062210660883183746411     2035-04-11T03:57:31Z     2025-04-13T03:57:31Z
    kubernetes://webapp-credential                 Cert Chain     ACTIVE     true           1049106                                     2041-06-29T12:49:32Z     2021-07-04T12:49:32Z
    kubernetes://webapp-credential-mtls            Cert Chain     ACTIVE     true           1049106                                     2041-06-29T12:49:32Z     2021-07-04T12:49:32Z
    kubernetes://webapp-credential-mtls-cacert     CA             ACTIVE     true           1049106                                     2041-06-29T12:49:29Z     2021-07-04T12:49:29Z
    
    
    # 호출 테스트 1 : (호출실패) 클라이언트 인증서 없음 - SSL 핸드섀이크가 성공하지 못하여 거부됨
    curl -v https://webapp.istioinaction.io:30005/api/catalog \
    --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    
    # 웹브라우저에서 확인 시 클라이언트 인증서 확인되지 않아서 접속 실패 확인
    open https://webapp.istioinaction.io:30005
    open https://webapp.istioinaction.io:30005/api/catalog
    
    
    # 호출 테스트 2 : 클라이언트 인증서/키 추가 성공!
    curl -v https://webapp.istioinaction.io:30005/api/catalog \
    --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem \
    --cert ch4/certs/4_client/certs/webapp.istioinaction.io.cert.pem \
    --key ch4/certs/4_client/private/webapp.istioinaction.io.key.pem

     

    • 이스티오 게이트웨이는 istio-proxy 를 시작하는 데 사용하는 istio-agent 프로세스에 내장된 SDS에서 인증서를 가져온다.
    • SDS는 업데이트를 자동으로 전파해야 하는 동적 API이다. 서비스 프록시도 마찬가지다.

    [ 실습 결과 - 한 눈에 보기 ]

    4.3.4 Serving multiple virtual hosts with TLS (실습)

    더보기
    • 이스티오의 인그레스 게이트웨이는 동일한 HTTPS 포트(443)에서 자체 인증서와 비밀 키가 있는 여러 가상 호스트를 서빙할 수 있다.
    • 이를 위해 동일 포트 및 프로토콜에 여러 항목을 추가한다.
    • 예를 들어 자체 인증서와 키 쌍이 있는 webapp.stioinaction.iocatalog.istioinaction.io 서비스를 둘 다 추가할 수 있다.
    • HTTPS로 서빙하는 가상 호스트를 여럿 기술하고 있는 이스티오 Gateway 리소스는 다음과 같다.
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: coolstore-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 443
          name: https-webapp
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: webapp-credential
        hosts:
        - "webapp.istioinaction.io"
      - port:
          number: 443
          name: https-catalog
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: catalog-credential
        hosts:
        - "catalog.istioinaction.io"

    ※  둘 다 443 포트 리스닝하고 HTTPS 를 서빙하지만, 호스트 이름이 다르다는 것을 유의하자.

     

    • 각 항목에는 서빙하는 가상 호스트용으로 사용하는 고유한 인증서와 키가 있다. 이를 위해 인증서와 키를 추가하자.
    #
    cat ch4/certs2/3_application/private/catalog.istioinaction.io.key.pem
    cat ch4/certs2/3_application/certs/catalog.istioinaction.io.cert.pem
    openssl x509 -in ch4/certs2/3_application/certs/catalog.istioinaction.io.cert.pem -noout -text
    ...
            Issuer: C=US, ST=Denial, O=Dis, CN=catalog.istioinaction.io
            Validity
                Not Before: Jul  4 13:30:38 2021 GMT
                Not After : Jun 29 13:30:38 2041 GMT
            Subject: C=US, ST=Denial, L=Springfield, O=Dis, CN=catalog.istioinaction.io
    ...
    
    #
    kubectl create -n istio-system secret tls catalog-credential \
    --key ch4/certs2/3_application/private/catalog.istioinaction.io.key.pem \
    --cert ch4/certs2/3_application/certs/catalog.istioinaction.io.cert.pem
    
    # Gateway 설정 업데이트
    kubectl apply -f ch4/coolstore-gw-multi-tls.yaml -n istioinaction
    
    
    # Gateway 로 노출한 catalog 서비스용 VirtualService 리소스 생성
    cat ch4/catalog-vs.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog-vs-from-gw
    spec:
      hosts:
      - "catalog.istioinaction.io"
      gateways:
      - coolstore-gateway
      http:
      - route:
        - destination:
            host: catalog
            port:
              number: 80
    
    #          
    kubectl apply -f ch4/catalog-vs.yaml -n istioinaction
    kubectl get gw,vs -n istioinaction
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 2
    
    
    # 호출테스트 1 - webapp.istioinaction.io
    curl -v https://webapp.istioinaction.io:30005/api/catalog \
    --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    
    # 호출테스트 2 - catalog.istioinaction.io (cacert 경로가 ch4/certs2/* 임에 유의)
    curl -v https://catalog.istioinaction.io:30005/items \
    --cacert ch4/certs2/2_intermediate/certs/ca-chain.cert.pem

     

    • 두 호출 모두 동일한 응답으로 성공했다. 그런데 발신자마다 어떤 인증서를 제시해야 하는지를 이스티오 인그레스가 어떻게 아는지가 궁금할 수 있다.
    • 이 커넥션들이 열린 포트는 하나인데, 게이트웨이는 클라이언트가 접근하려는 서비스가 어느것이고 그 서비스에 해당하는 인증서가 어느 것인지를 알 수 있을까?
    • 해답은 SNI(서버 이름 표시)라는 TLS 확장에 있다.
    • 기본적으로 HTTPS 커넥션이 만들어지면 클라이언트는 먼저 TLS 핸드셰이크의 ClientHello 부분에서 어느 서비스에 접근해야 하는지부터 전달한다.
    • 이스티오의 게이트웨이(엄밀히는 엔보이)가 TLS의 SNI를 구현하는 덕분에 올바른 인증서를 제시할 수 있고 올바른 서비스로 라우팅 할 수 있는 것이다.

     

    • HTTPS 경우 TLS CliendHello 의 SNI의 Server Name 에서 주소를 확인 후 통제합니다.
      • Squid v3.5 부터 SslPeekAndSplice 기능으로 TLS ClientHello 의 SNI 값(URL 주소) 기반 통제 가능
    출처 : [그림 2-1-2-2] HTTPS 의 TLS 협상 과정
    출처 : [그림 2-1-2-3] TLS Client Hello 패킷 SNI 확장헤더의 Server Name 의 주소로 통제

    (추가) https 트래픽 패킷 확인 : TLSv1에 Handshake Type(Client Hello) 에 Extension 에 SNI 부분에 도메인 확인  

    • 이 절에서는 인그레스 게이트웨이에서 서로 다른 가상 호스트들을 노출했고, 동일한 HTTPS 포트에서 각자를 고유한 인증서로 서비스해봤다.
    • 다음 절에서는 TCP 트래픽을 살펴본다.

     

     


    4.4 TCP traffic

    [ 들어가며 ]

    더보기
    • 이스티오 게이트웨이는 HTTP/HTTPS 트래픽뿐 아니라 TCP로 접근하는 모든 트래픽을 처리할 수 있다.
    • 예를 들어, 인그레스 게이트웨이로 데이터베이스(MongoDB 등)나 메시지 큐(카프카 등)를 노출할 수 있다.
    • 이스티오가 트래픽을 단순 TCP로 다루면 재시도, 요청 수준 서킷 브레이킹, 복잡한 라우팅 등과 같은 유용한 기능을 사용할 수 없다.
    • 단순히 이스티오가 어떤 프로토콜을 사용하는지 분간할 수 없기 때문이다. (MongoDB 처럼 이스티오-엔보이가 이해하는 프로토콜을 사용하는 것이 아닌 이상).
    • 클러스터 외부의 클라이언트가 클러스터 내에서 실행 중인 서비스와 통신할 수 있도록 이스티오 게이트웨이에 TCP 트래픽을 노출하는 방법을 알아보자.

    4.4.1 Exposing TCP ports on an Istio gateway (실습)

      → 실습 컨테이너 이미지를 istio-echo-server 변경 만들어 둘것 - Link, Dockerhub

    더보기
    • 가장 먼저 해야 할일은 서비스 메시 내에서 TCP 기반 서비스를 만드는 것이다.
    • 이 예제는 깃허브의 에코 서비스를 사용한다. https://github.com/cjimti/go-echo
    • 이 TCP 서비스는 Telnet 같은 간단한 TCP 클라이언트로 접속해 명령어를 입력하면 그대로 되돌려준다.

     

    Step1. 배포 실행! - dockerhub

    #
    cat ch4/echo.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: tcp-echo-deployment
      labels:
        app: tcp-echo
        system: example
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: tcp-echo
      template:
        metadata:
          labels:
            app: tcp-echo
            system: example
        spec:
          containers:
            - name: tcp-echo-container
              image: cjimti/go-echo:latest
              imagePullPolicy: IfNotPresent
              env:
                - name: TCP_PORT
                  value: "2701"
                - name: NODE_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: spec.nodeName
                - name: POD_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
                - name: POD_NAMESPACE
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.namespace
                - name: POD_IP
                  valueFrom:
                    fieldRef:
                      fieldPath: status.podIP
                - name: SERVICE_ACCOUNT
                  valueFrom:
                    fieldRef:
                      fieldPath: spec.serviceAccountName
              ports:
                - name: tcp-echo-port
                  containerPort: 2701
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: "tcp-echo-service"
      labels:
        app: tcp-echo
        system: example
    spec:
      selector:
        app: "tcp-echo"
      ports:
        - protocol: "TCP"
          port: 2701
          targetPort: 2701
    
    kubectl apply -f ch4/echo.yaml -n istioinaction
    
    #
    kubectl get pod -n istioinaction
    NAME                                   READY   STATUS    RESTARTS   AGE
    tcp-echo-deployment-584f6d6d6b-xcpd8   2/2     Running   0          27s
    ...

     

    Step2. 실습

    # tcp 서빙 포트 추가 : 편집기는 vi 대신 nano 선택 <- 편한 툴 사용
    KUBE_EDITOR="nano"  kubectl edit svc istio-ingressgateway -n istio-system
    ...
      - name: tcp
        nodePort: 30006
        port: 31400
        protocol: TCP
        targetPort: 31400
    ...
    
    # 확인
    kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.name=="tcp")]}'
    {"name":"tcp","nodePort":30006,"port":31400,"protocol":"TCP","targetPort":31400}
    
    
    # 게이트웨이 생성
    cat ch4/gateway-tcp.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: echo-tcp-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 31400
          name: tcp-echo
          protocol: TCP
        hosts:
        - "*"
        
    kubectl apply -f ch4/gateway-tcp.yaml -n istioinaction
    kubectl get gw -n istioinaction
    
    # 에코 서비스로 라우팅하기 위해 VirtualService 리소스 생성
    cat ch4/echo-vs.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: tcp-echo-vs-from-gw
    spec:
      hosts:
      - "*"
      gateways:
      - echo-tcp-gateway
      tcp:
      - match:
        - port: 31400
        route:
        - destination:
            host: tcp-echo-service
            port:
              number: 2701
    
    #
    kubectl apply -f ch4/echo-vs.yaml -n istioinaction
    kubectl get vs -n istioinaction
    NAME                  GATEWAYS                HOSTS                          AGE
    catalog-vs-from-gw    ["coolstore-gateway"]   ["catalog.istioinaction.io"]   44m
    tcp-echo-vs-from-gw   ["echo-tcp-gateway"]    ["*"]                          6s
    webapp-vs-from-gw     ["coolstore-gateway"]   ["webapp.istioinaction.io"]    3h59m
    
    
    # mac 에 telnet 설치
    brew install telnet
    
    #
    telnet localhost 30006
    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
    Welcome, you are connected to node myk8s-control-plane.
    Running on Pod tcp-echo-deployment-584f6d6d6b-xcpd8.
    In namespace istioinaction.
    With IP address 10.10.0.20.
    Service default.
    hello istio! # <-- type here
    hello istio! # <-- echo here
    
    # telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit

    [ 실행 결과 - 한 눈에 보기 ]

    4.4.2 Traffic routing with SNI passthrough (실습)

    더보기
    • 이번 절에서는 두 기능의 조합. 즉 이스티오 인그레스 게이트웨이에서 트래픽을 종료하지 않고 SNI 호스트네임에 따라 TCP 트래픽을 라우팅하는 방법을 다룬다.
    • 게이트웨이가 하는 일은 SNI 헤더를 살펴보고 트래픽을 특정 백엔드로 라우팅하는 것 뿐이다. TLS 커넥션 종료는 그 후 백엔드에서 처리한다.
    • 커넥션은 게이트웨이를 ‘통과 passthrough’ 하고, 처리는 게이트웨이가 아닌 실제 서비스담당하게 된다.

    • 이런 방식은 서비스 메시에 참여할 수 있는 애플리케이션의 범위를 휠씬 넓혀준다.
    • 데이터베이스, 메시지 큐, 캐시 등과 같은 TLS 상의 TCP 서비스는 물론, HTTPS/TLS 트래픽을 직접 처리하고 종료할 것이라고 예상되는 레거시 애플리케이션까지도 포함될 수 있다.
    • Gateway 살펴보자  ( Mode - 총3가지 : Simple, Mutual, Passthrough )
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: sni-passthrough-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 31400 #1 HTTP 포트가 아닌 특정 포트 열기
          name: tcp-sni
          protocol: TLS
        hosts:
        - "simple-sni-1.istioinaction.io" #2 이 호스트를 포트와 연결
        tls:
          mode: PASSTHROUGH #3 통과 트래픽으로 처리
    • 예제 애플리케이션에서 TLS를 종료하도록 설정한다. 즉, 인그레스 게이트웨이는 커넥션에 아무것도 하지 않아도 된다.
    • 앞 절에서 했던 것처럼 게이트웨이에 인증서를 설정할 필요가 없다.

     

    Step1. TLS를 종료하는 예제 에플리케이션을 배포하는 것부터 시작하자.

    # TLS 인증을 직접 처리하는 앱 배포. (gw는 route 만 처리, pass through )
    cat ch4/sni/simple-tls-service-1.yaml
    kubectl apply -f ch4/sni/simple-tls-service-1.yaml -n istioinaction
    kubectl get pod -n istioinaction
    
    # 기존 Gateway 명세(echo-tcp-gateway) 제거 : istio-ingressgateway의 동일한 port (31400, TCP)를 사용하므로 제거함
    kubectl delete gateway echo-tcp-gateway -n istioinaction
    
    # 신규 Gateway 설정
    kubectl apply -f ch4/sni/passthrough-sni-gateway.yaml -n istioinaction
    kubectl get gw -n istioinaction

     

     Step2. VirtualService 리소스 라우팅 규칙 지정하자

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-sni-1-vs
    spec:
      hosts:
      - "simple-sni-1.istioinaction.io"
      gateways:
      - sni-passthrough-gateway
      tls: 
      - match: #1 특정 포트와 호스트의 비교 부분
        - port: 31400
          sniHosts:
          - simple-sni-1.istioinaction.io
        route:
        - destination: #2 트래픽이 일치하는 경우 라우팅 목적지
            host: simple-tls-service-1
            port:
              number: 80 #3 서비스 포트로 라우팅
    # 
    kubectl apply -f ch4/sni/passthrough-sni-vs-1.yaml -n istioinaction
    kubectl get vs -n istioinaction
    
    
    # 호출테스트1
    echo "127.0.0.1       simple-sni-1.istioinaction.io" | sudo tee -a /etc/hosts
    
    curl https://simple-sni-1.istioinaction.io:30006/ \
     --cacert ch4/sni/simple-sni-1/2_intermediate/certs/ca-chain.cert.pem

     

    ☞ curl 호출은 이스티오 인그레스 게이트웨이로 갔다가, 종료 없이 예제 서비스 simple-tls-service-1 에 도달한다.

     

    Step3. 라우팅을 더 명확하게 하기 위해 인증서가 다르고 SNI 호스트에 기반해 라우팅을 하는 두 번째 서비스를 배포해보자.

    # 두 번째 서비스 배포
    cat ch4/sni/simple-tls-service-2.yaml
    kubectl apply -f ch4/sni/simple-tls-service-2.yaml -n istioinaction
    
    # gateway 설정 업데이트
    cat ch4/sni/passthrough-sni-gateway-both.yaml
    kubectl apply -f ch4/sni/passthrough-sni-gateway-both.yaml -n istioinaction
    
    # VirtualService 설정
    cat ch4/sni/passthrough-sni-vs-2.yaml
    kubectl apply -f ch4/sni/passthrough-sni-vs-2.yaml -n istioinaction
    
    # 호출테스트2
    echo "127.0.0.1       simple-sni-2.istioinaction.io" | sudo tee -a /etc/hosts
    
    curl https://simple-sni-2.istioinaction.io:30006 \
    --cacert ch4/sni/simple-sni-2/2_intermediate/certs/ca-chain.cert.pem

     

    ☞ 이 요청을 simple-tls-sni-2 서비스가 서빙했음을 body 필드의 응답이 어떻게 알려주는지 주목하자.


    [ 실행 결과 - 한 눈에 보기 ]

     


    4.5 Operational tips 운영 팁

    실습 참고 https://netpple.github.io/docs/istio-in-action/Istio-ch4-4-tips

     

    4.5.1 Split gateway responsibilities 게이트웨이 책임 나누기 (실습)

    더보기
    • 인그레스 게이트웨이를 여럿(다른 진입점) 둘 수 있다.
    • 다른 진입점을 배포해 트래픽을 나누고 다양한 서비스 간 트래픽 경로를 격리하고 싶을 수도 있다.
    • 어떤 서비스는 성능에 더 민감하거나, 고가용성이 더 필요하거나, 컴플라이언스를 이유로 격리돼야 할 수도 있다.
    • 어떤 떄는 다른 팀에 영향을 주지 않도록 각 팀이 각자의 게이트웨이 및 설정을 소유하게 하고 싶을 수도 있다.

     

    출처 : https://www.youtube.com/watch?v=ftFHZwyUN38

     

    https://www.youtube.com/watch?v=ftFHZwyUN38

     

    • 새 커스텀 게이트 웨이 정의 예시
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    metadata:
      name: my-user-gateway-install
      namespace: istioinaction
    spec:
      profile: empty
      values:
        gateways:
          istio-ingressgateway:
            autoscaleEnabled: false
      components:
        ingressGateways:
        - name: istio-ingressgateway
          enabled: false    
        - name: my-user-gateway
          namespace: istioinaction
          enabled: true
          label:
            istio: my-user-gateway
          k8s:
            service:
              ports:
                - name: tcp  # my-user-gateway 에서 사용할 포트 설정
                  port: 30007
                  targetPort: 31400
    #
    docker exec -it myk8s-control-plane bash
    ------------------------------------------
    # istioinaction 네임스페이스에 Ingress gateway 설치
    cat <<EOF > my-user-gateway-edited.yaml
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    metadata:
      name: my-user-gateway-install
      namespace: istioinaction
    spec:
      profile: empty
      values:
        gateways:
          istio-ingressgateway:
            autoscaleEnabled: false
      components:
        ingressGateways:
        - name: istio-ingressgateway
          enabled: false    
        - name: my-user-gateway
          namespace: istioinaction
          enabled: true
          label:
            istio: my-user-gateway
          k8s:
            service:
              ports:
                - name: tcp  # my-user-gateway 에서 사용할 포트 설정
                  port: 31400
                  targetPort: 31400
                  nodePort: 30007 # 외부 접속을 위해 NodePort Number 직접 설정
    EOF
    
    # istioctl manifest generate -n istioinaction -f my-user-gateway-edited.yaml
    istioctl install -y -n istioinaction -f my-user-gateway-edited.yaml
    
    exit
    ------------------------------------------
    
    # IstioOperator 확인
    kubectl get IstioOperator -A
    NAMESPACE       NAME                      REVISION   STATUS   AGE
    istio-system    installed-state                               5h48m
    istioinaction   my-user-gateway-install                       17s
    
    #
    kubectl get deploy my-user-gateway -n istioinaction
    NAME              READY   UP-TO-DATE   AVAILABLE   AGE
    my-user-gateway   1/1     1            1           65s
    
    # 포트 확인
    kubectl get svc my-user-gateway -n istioinaction -o yaml
    ...
      - name: tcp
        nodePort: 30007
        port: 31400
        protocol: TCP
        targetPort: 31400
    ...

     

    • 실습 : my-user-gateway를 경유하여 TCP 통신을 해봅시다
    # Gateway
    cat <<EOF | kubectl apply -n istioinaction -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: echo-tcp-gateway
    spec:
      selector:
        istio: my-user-gateway  #  New gateway
      servers:
      - port:
          number: 31400
          name: tcp-echo
          protocol: TCP
        hosts:
        - "*"
    EOF
    
    # VirtualService 명세
    cat ch4/echo-vs.yaml
    kubectl apply -f ch4/echo-vs.yaml -n istioinaction
    
    # 앱 배포
    cat ch4/echo.yaml
    kubectl apply -f ch4/echo.yaml -n istioinaction
    
    # 호출 테스트 : NodePort 30007로 접속 테스트
    telnet localhost 30007
    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
    ..
    Service default.
    hello Istio    # <-- type here
    hello Istio    # <-- echo here
    
    # telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit

     


    [ 실습 결과 - 한 눈에 보기 ]

     

    4.5.2 Gateway injection 게이트웨이 주입 (실습)

    더보기
    • 사용자에게 IstioOperator 리소스(기존 이스티오 설치를 변경할 수 있는)에 대한 전체 접근 권한을 부여하지 않고도 자체 게이트웨이를 만들 수 있게 허용하는 방법은 게이트웨이 주입 gateway injection 을 사용하는 것이다.
    • 게이트웨이 주입을 사용하며, 미완성 subbed-out 게이트웨이 디플로이먼트를 배포하고 이스티오가 나머지를 채우는 방식이 된다.
    • 사이드카 주입이 이뤄지는 방식과 비슷하다.
    • 이런 방식으로, 팀에서 미완성 게이트웨이 Deployment 리소스를 주고 이스티오가 나머지를 자동 설정하게 할 수 있다.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-user-gateway-injected
      namespace: istioinaction
    spec:
      selector:
        matchLabels:
          ingress: my-user-gateway-injected
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "true" #1 주입 활성화
            inject.istio.io/templates: gateway #2 gateweay 템플릿  
          labels:
            ingress: my-user-gateway-injected
        spec:
          containers:
          - name: istio-proxy #3 반드시 이 이름이어야 한다
            image: auto #4 미완성 이미지
    ...
    #
    cat ch4/my-user-gw-injection.yaml
    kubectl apply -f ch4/my-user-gw-injection.yaml
    
    #
    kubectl get deploy,svc,ep my-user-gateway-injected -n istioinaction

     

     

     

    4.5.3 Ingress gateway access logs 인그레스 게이트웨이 액세스 로그 (실습)

    더보기
    • 프록시에서 일반적인 기능은 처리하는 모든 요청을 기록하는 것이다. 이 액세스 로그는 문제 해결에 유용한다.
    • 이스티오의 프록시(엔보이)도 액세스 로그를 만들 수 있다.
    • 데모 설치 프로필에서는 인그레스 게이트웨이와 서비스 프록시가 액세스 로그를 표준 출력 스트림에 출력하도록 설정된다.
    • 액세스 로그를 보려면 컨테이너 로그를 출력하기만 하면 된다.
    #
    kubectl logs -f deploy/istio-ingressgateway -n istio-system
    ...

     

    • 위 명령은 인그레스 게이트웨이의 액세스 로그를 출력한다. 따라서 앞선 예제들에서 만들어낸 트래픽들의 기록을 볼 수 있다.
    • 기본 프로필을 사용한 운영 환경 이스티오에서는 액세스 로깅이 비활성화돼 있다는 사실을 알면 놀랄 수도 있다.
    # 반복 호출
    watch -d -n 1 curl -s -v https://webapp.istioinaction.io:30005/api/catalog --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem
    
    # 애플리케이션 컨테이너는 실시간 로그 출력
    kubectl stern -n istioinaction -l app=webapp -c webapp
    webapp-7685bcb84-h5knf webapp 2025/04/14 09:28:40.248 [M] [router.go:1014]  172.18.0.1 - - [14/Apr/2025 09:28:40] "GET /api/catalog HTTP/1.1 200 0" 0.004840  curl/8.7.1
    ...
    
    # 초기 부팅 로그 이외에 별다른 로그 출력이 없음
    kubectl stern -n istioinaction -l app=webapp -c istio-proxy
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002358Z	info	FLAG: --concurrency="2"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002374Z	info	FLAG: --domain="istioinaction.svc.cluster.local"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002376Z	info	FLAG: --help="false"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002377Z	info	FLAG: --log_as_json="false"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002378Z	info	FLAG: --log_caller=""
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002379Z	info	FLAG: --log_output_level="default:info"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002380Z	info	FLAG: --log_rotate=""
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002381Z	info	FLAG: --log_rotate_max_age="30"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002382Z	info	FLAG: --log_rotate_max_backups="1000"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002383Z	info	FLAG: --log_rotate_max_size="104857600"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002384Z	info	FLAG: --log_stacktrace_level="default:none"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002386Z	info	FLAG: --log_target="[stdout]"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002387Z	info	FLAG: --meshConfig="./etc/istio/config/mesh"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002388Z	info	FLAG: --outlierLogPath=""
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002389Z	info	FLAG: --proxyComponentLogLevel="misc:error"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002390Z	info	FLAG: --proxyLogLevel="warning"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002390Z	info	FLAG: --serviceCluster="istio-proxy"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002391Z	info	FLAG: --stsPort="0"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002392Z	info	FLAG: --templateFile=""
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002393Z	info	FLAG: --tokenManagerPlugin="GoogleTokenExchange"
    webapp-7685bcb84-h5knf istio-proxy 2025-04-14T01:21:58.002395Z	info	FLAG: --vklog="0"
    ...
    
    
    #

     

    • 그러나 표준 출력 스트림으로 출력하도록 accessLogFile 속성을 변경해 이를 변경할 수 있다.
    #
    kubectl get cm -n istio-system istio -o yaml
    data:
      mesh: |-
        defaultConfig:
          discoveryAddress: istiod.istio-system.svc:15012
          proxyMetadata: {}
          tracing:
            zipkin:
              address: zipkin.istio-system:9411
        enablePrometheusMerge: true
        rootNamespace: istio-system
        trustDomain: cluster.local
      meshNetworks: 'networks: {}'
    ..
    
    #
    docker exec -it myk8s-control-plane bash
    ------------------------------------------
    # 표준 출력 스트림으로 출력하도록 accessLogFile 속성을 변경
    istioctl install --set meshConfig.accessLogFile=/dev/stdout
    y 입력
    
    exit
    ------------------------------------------
    
    # configmap 에 mesh 바로 아래에 accessLogFile 부분 추가됨
    kubectl get cm -n istio-system istio -o yaml
    ...
      mesh: |-
        accessLogFile: /dev/stdout
    ...
    
    # 애플리케이션 호출에 대한 로그도 출력됨!
    kubectl stern -n istioinaction -l app=webapp -c istio-proxy
    webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:47.764Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 1 1 "172.18.0.1" "beegoServer" "4c10dba4-f49a-4b58-9805-ac30515dd417" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60052 10.200.1.251:80 172.18.0.1:0 - default
    webapp-7685bcb84-h5knf istio-proxy [2025-04-14T09:30:48.805Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 6 6 "172.18.0.1" "beegoServer" "7733f4d0-6e3a-4138-a126-fe25c30e51f4" "catalog.istioinaction:80" "10.10.0.8:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.9:60898 10.200.1.251:80 172.18.0.1:0 - default
    ...

     

    • 액세스 로그는 기본적으로 꺼져 있는데, 운영 환경 클러스터에서 수백 또는 수천 개의 워크로드가 있고 각 워크로드가 많은 트래픽을 처리한다는 점을 고려하면 합리적이다.
    • 또한 각 요청은 한 서비스에서 다른 서비스로 여러 홉을 이동하기 때문에 생성되는 액세스 로그량은 어떤 로깅 시스템에든 부담을 줄 수 있다.
    • 더 나은 접근 방식은 관심 있는 워크로드에 대해서만 액세스 로그를 켜는 것이며, 이는 텔레메트리 API를 사용해 가능하다.
    • 예를 들어 인그레스 게이트웨이 워크로드의 액세스 로그만 표시하려면 다음과 같은 텔레메트리 설정을 사용할 수 있다.
    apiVersion: telemetry.istio.io/v1alpha1
    kind: Telemetry
    metadata:
      name: ingress-gateway
      namespace: istio-system
    spec:
      selector:
        matchLabels:
          app: istio-ingressgateway #1 레이블과 일치하는 파드는 텔레메트리 설정을 가져온다
      accessLogging:
      - providers:
        - name: envoy #2 액세스 로그를 위한 프로바이더 설정
        disabled: false #3 disable 를 false 로 설정해 활성화한다

     

    • 토스 사례 : istio-proxy 에서 gRPC로 직접 Acess log collector 로 수집(Go로 직접 개발) → Kafka → Logstash → ES
    출처 : https://www.youtube.com/watch?v=ftFHZwyUN38

     

     

    4.5.4 Reducing gateway configuration 게이트웨이 설정 줄이기

    더보기
    • 기본적으로, 이스티오는 모든 프록시가 메시 내의 모든 서비스를 알도록 구성한다. 메시에 서비스가 많다면 데이터 플레인 프록시의 설정이 매우 커질 수 있다. 그리고 설정이 크면 리소스 증폭, 성능 문제, 확장성 문제로 이어질 수 있다.
    • 이 문제를 해결하기 위해 데이터 플레인과 컨트롤 플레인 모두에게 설정을 최적화할 수 있다.
    • Sidecar 리소스를 사용해 이 설정을 줄이는 방법은 11장에서 자세히 다룬다.

     

    출처 : https://www.youtube.com/watch?v=4sJd6PIkP_s

     

    • 그러나 Sidecar 리소스는 게이트웨이에는 적용되지 않는다.
    • 새 게이트웨이(인그레스 게이트웨이 등)를 배포하면 프록시에는 메시 내 라우팅할 수 있는 모든 서비스가 설정된다.
    • 상술했듯이, 이렇게 하면 설정이 아주 커져 게이트웨이에 부담이 될 수 있다.

    • 요령은 프록시에서 필요 이상의 설정은 제거하고 게이트웨이 관련 설정만 포함되도록 하는 것이다.
    • 최근까지 이 기능은 기본적으로 꺼져 있었는데, 최신 버전에서는 활성화 여부를 확인 할 수 있다.
    • 어느 버전이든 다음 설정으로 게이트웨이의 설정 잘라내기를 명시적으로 활성화할 수 있다.
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    metadata:
      name: control-plane
    spec:
      profile: minimal
      components:
        pilot:
          k8s:
            env:
            - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG
              value: "true"
      meshConfig:
        defaultConfig:
          proxyMetadata:
            ISTIO_META_DNS_CAPTURE: "true"
        enablePrometheusMerge: true
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/my-user-gateway.istioinaction
    SERVICE FQDN                                                 PORT      SUBSET     DIRECTION     TYPE           DESTINATION RULE
    BlackHoleCluster                                             -         -          -             STATIC
    agent                                                        -         -          -             STATIC
    catalog.istioinaction.svc.cluster.local                      80        -          outbound      EDS
    ...
    
    # 현재 37개 cluster 정보
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/my-user-gateway.istioinaction | wc -l
          37
          
    # 아래 추가
    KUBE_EDITOR="nano"  kubectl edit IstioOperator -n istioinaction installed-state-my-user-gateway-install
    ...
        pilot:
          enabled: false
          k8s:
            env:
            - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG
              value: "true"
    ...
    
    # 동일... 해당 gw pod 삭제 후 재시작 되어도 동일.. 다른 설정 방법이 있나?... kubectl edit 대신 IstioOperator 로 설정해야할지도..
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/my-user-gateway.istioinaction | wc -l
          37

     

    • 이 설정의 핵심은 PILOT_FILTER_GATEWAY_CLUSTER_CONFIG 기능 플래그이다. - Docs
      • If enabled, Pilot will send only clusters that referenced in gateway virtual services attached to gateway
    • 이 플래그는 게이트웨이 프록시 설정 내에 있는 클러스터를 해당 게이트웨이에 적용한 VirtualService 에서 실제로 참조하는 클러스터로만 좁힌다.

     

     


    [ Summary ]

    더보기
    • 인그레스 게이트웨이는 서비스 메시에 들어오는 트래픽을 세밀하게 제어합니다.
      • Ingress gateways provide fine-grained control over what traffic enters our service mesh.
    • 게이트웨이 리소스를 사용하면 메시 내로 허용하는 트래픽 종류를 호스트별로 설정할 수 있다.
      • Using the Gateway resource, we can configure the type of traffic admitted into the mesh for a specific host.
    • 메시 내의 다른 서비스와 마찬가지로, 트래픽을 라우팅하기 위해 VirtualService 리소스를 사용합니다.
      • Just as with any service in the mesh, it uses VirtualService resources to route the traffic.
    • TLS 모드는 호스트마다 다음 모드 중 하나로 구성할 수 있습니다. TLS mode is configurable per host with one of the following modes:
      • Encrypt and prevent man-in-the-middle attacks with the SIMPLE TLS mode.
      • Mutually authenticate both server and client with the MUTUAL TLS mode.
      • Admit and reverse proxy encrypted traffic using the SNI header with the PASSTHROUGH TLS mode.
    • 현재 지원되지 않는 L7 프로토콜의 경우 Istio에서 순수 TCP 트래픽이 지원됩니다. 그러나 재시도, 복잡한 라우팅 등 일반 TCP에서는 많은 기능을 사용할 수 없습니다.
      • Plain TCP traffic is supported in Istio for L7 protocols that are currently not supported. However, many features are not possible with plain TCP, such as retries, complex routing, and so on.
    • 게이트웨이 주입을 사용하여 팀이 자체 게이트웨이를 관리할 수 있도록 할 수 있습니다.
      • We can enable teams to manage their own gateways by using gateway injection.

    ▶ 실습 후 자원 삭제

    • kind delete cluster --name myk8s
    • /etc/hosts 파일에 추가했던 도메인 제거

    [ 마무리 ]

    Istio 기능 관련 정적설정과 동적설정 시 동작하는 Auto Discovery Service에 대해 볼 수 있는 과정이었다. Istio Gateway 내부적으로 처리하는 auto retry 나 mTls 통신 기능 등 실습을 통해 다양한 Istio 의 기능에 대해 체험해 볼 수 있었고, 다양한 기능 만큼 적절한 사용을 위해 시간을 가지고 충분한 테스트를 기반한 이해가 필요한 것 같다.


    [ 참고 링크 모음 ]

    A. 추천 영상 모음

    더보기
    • 토스ㅣSLASH 23 - 고객 불안을 0으로 만드는 토스의 Istio Zero Trust* - Link , PDF
    • 토스ㅣSLASH 22 - 은행 앱에도 Service Mesh 도입이 가능한가요?* - Link , PDF

    • [추천 영상] [코맹탈출] RSA 작동원리 - Youtube , [NHN] 웹 서버 인증서 - Youtube
    • [현실판 MiTM] 중고거래 사기 - Youtube
    • 호다닥 공부해보는 x509와 친구들 - Blog
    • SSL과 인증서 구조 이해하기 : 인증서 계층 구조 설명 - Blog
    즉 서버와 클라이언트 간에 처음 연결을 수립할 때에만 비대칭키를 사용하고, 보안 연결 수립 뒤에는 대칭 키로 패킷을 암호화해 전송

     

     

    B. 도전 과제 모음

    더보기
    • [도전과제1] Envoy 공식문서에 Quick start 전체 내용을 실습 및 정리 - Docs
    • [도전과제2] Envoy 공식문서에 Sandboxes 내용을 실습 및 정리 - Docs
    • [도전과제4] Istio ingress 이외에 egress-gateway 를 추가 배포해보고, 외부에서 서비스 메시 내부 app 접속 후 리턴까지 E2E 호출 경로 확인
    • [도전과제5] Istio v1.17 공식 문서Ingress 에 각 기능과 실습 시나리오를 직접 테스트 후 정리 - Docs
      • Secure Gateways - Docs
      • Ingress Gateway without TLS Termination - Docs
      • Ingress Sidecar TLS Termination - Docs
    • [도전과제6] k8s / istio 최신 버전 설치 후 Istio API 대신 k8s Gateway API 를 이용하여 위 실습 시나리오를 구성해보기 

    [ 도전과제 3 ] solo academy 에 Get Started with Envoy Proxy 온라인 실습 렙 정리 - Link

    더보기
    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 19000
    version: "3.7"
    
    services:
      envoy:
        image: envoyproxy/envoy:v1.20-latest
        volumes:
        - ./served-config/envoy.yaml:/etc/envoy/config.yaml 
        - ./configs/security/tls.crt:/etc/certs/tls.crt
        - ./configs/security/tls.key:/etc/certs/tls.key
        entrypoint: /usr/local/bin/envoy -c /etc/envoy/config.yaml 
        networks:
        - network
        ports:
        - "8000:8000"
        - "8443:8443"
        - "19000:19000"
      upstream:
        build:
          context: ./upstream
          dockerfile: service/Dockerfile
        networks:
        - network
      auth:
        build:
          context: ./auth
          dockerfile: service/Dockerfile
        networks:
        - network
    
      prometheus:
        image: prom/prometheus
        volumes:
        - ./configs/monitoring/prometheus.yaml:/etc/prometheus/prometheus.yml
        networks:
        - network
        ports:
        - "9090:9090"
    
      grafana:
        image: grafana/grafana:8.5.4
        volumes:
        - ./configs/monitoring/grafana.ini:/etc/grafana/grafana.ini
        - ./configs/monitoring/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yaml
        - ./configs/monitoring/dashboard.json:/usr/share/grafana/public/dashboards/home.json
        
        ports:
          - 3000:3000
        links:
          - prometheus    
    
    networks:
      network: {}
    #
    docker-compose up -d --build
    
    #
    docker ps --format '{{ .Names }}'
    envoy-workshop-basics_grafana_1
    envoy-workshop-basics_prometheus_1
    envoy-workshop-basics_envoy_1
    envoy-workshop-basics_auth_1
    envoy-workshop-basics_upstream_1
    
    # envoy.yaml 파일 내용 아래 확인 : 맨위에서 차이점 확인
    git checkout lab1-2
    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 19000
    static_resources:
      listeners:
      - name: envoy-listener-8000
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 8000
        filter_chains:
        - filters:
          - name: envoy.filters.network.direct_response
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.direct_response.v3.Config
              response: 
                inline_string: hello world
    listeners:
      - name: envoy-listener-8000
        address:
          socket_address: { address: 0.0.0.0, port_value: 8000 }
        filter_chains:
        - filters:
          - name: envoy.filters.network.direct_response
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.direct_response.v3.Config
              response:
                inline_string: hello world
    # Apply the filter configuration.
    docker-compose restart envoy
    
    # Call the listener with cURL.
    curl --http0.9  host:8000
    hello world
    
    # 참고로 바로 위 envoy 설정 에 hello world 수정 후 curl 해보 바로 반영 안됨. docker restart 필요.
    
    #
    git checkout lab1-3
    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 19000
    static_resources:
      listeners:
      - name: envoy-listener-8000
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 8000
        filter_chains:
        - filters:
          - name: envoy.filters.network.tcp_proxy
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
              cluster: upstream
              stat_prefix: passthrough
    
      clusters:
      - name: upstream
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: upstream
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: upstream
                    port_value: 8080
    #
    docker-compose restart envoy
    
    #
    curl localhost:8000/hello
    Hello back!
    
    #
    curl -s http://localhost:19000/clusters | grep upstream
    upstream::observability_name::upstream
    upstream::default_priority::max_connections::1024
    upstream::default_priority::max_pending_requests::1024
    upstream::default_priority::max_requests::1024
    upstream::default_priority::max_retries::3
    upstream::high_priority::max_connections::1024
    upstream::high_priority::max_pending_requests::1024
    upstream::high_priority::max_requests::1024
    upstream::high_priority::max_retries::3
    upstream::added_via_api::false
    upstream::172.18.0.2:8080::cx_active::0
    upstream::172.18.0.2:8080::cx_connect_fail::0
    upstream::172.18.0.2:8080::cx_total::5
    upstream::172.18.0.2:8080::rq_active::0
    upstream::172.18.0.2:8080::rq_error::0
    upstream::172.18.0.2:8080::rq_success::0
    upstream::172.18.0.2:8080::rq_timeout::0
    upstream::172.18.0.2:8080::rq_total::5
    upstream::172.18.0.2:8080::hostname::upstream
    upstream::172.18.0.2:8080::health_flags::healthy
    upstream::172.18.0.2:8080::weight::1
    upstream::172.18.0.2:8080::region::
    upstream::172.18.0.2:8080::zone::
    upstream::172.18.0.2:8080::sub_zone::
    upstream::172.18.0.2:8080::canary::false
    upstream::172.18.0.2:8080::priority::0
    upstream::172.18.0.2:8080::success_rate::-1.0
    upstream::172.18.0.2:8080::local_origin_success_rate::-1.0
    
    #

     

    Understanding the Http Connection Manager  

    #
    cd $HOME/envoy-workshop-basics
    
    #
    git checkout lab2-1

     

    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 19000
    static_resources:
      listeners:
      - name: envoy-listener-8000
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 8000
        filter_chains:
    #########################################
    # Envoy HTTP Connection Manager Filter
    # The HTTP Connection Manager (HCM) is used in Envoy to handle the HTTP traffic, allowing users to route to certain endpoints, apply policies, manage the timeouts and more.      
    #########################################     
        - 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: ingress_http
              route_config:
                name: local_route
                # Defining a Virtual Host we will configure the matching domains, and under a Virtual Host we can define the maching routes.   
                virtual_hosts:
                - name: upstream
                  domains:
                  - "hello.envoyproxy.io"
                  # The following section mean that any call on the path '/hello' will be routed to the cluster 'upstream'
                  routes:
                  - match:
                      prefix: "/hello"
                    route:
                      cluster: upstream
              # The HTTP Connection Manager allows you to define multiple http filters, to transform or authorize an HTTP request for example. 
              # The last filter in this filter chain is the 'router', it will route traffic to the corresponding Envoy Clusters.  
              http_filters:
              - name: envoy.filters.http.router
    #########################################
      clusters:
      - name: upstream
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: upstream
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: upstream
                    port_value: 8080

     

    #
    docker-compose restart envoy
    
    #
    curl http://localhost:8000/hello -H "host: hello.envoyproxy.io"
    Hello back
    
    #
    curl -s http://localhost:19000/stats/prometheus | grep envoy_cluster_upstream_rq_completed
    # TYPE envoy_cluster_upstream_rq_completed counter
    envoy_cluster_upstream_rq_completed{envoy_cluster_name="upstream"} 1
    #
    curl http://localhost:8000/hello -H "host: hello.envoyproxy.io" -H "unreliable: true" -i
    HTTP/1.1 500 Internal Server Error
    server: envoy
    date: Sat, 12 Apr 2025 04:00:20 GMT
    content-type: text/html; charset=utf-8
    content-length: 265
    x-envoy-upstream-service-time: 1
    
    <!doctype html>
    <html lang=en>
    <title>500 Internal Server Error</title>
    <h1>Internal Server Error</h1>
    <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
    
    #
    ... snip ...
        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: ingress_http
              route_config:
                name: local_route
                # Defining a Virtual Host we will configure the matching domains, and under a Virtual Host we can define the maching routes.
                virtual_hosts:
                - name: upstream
                  domains:
                  - "hello.envoyproxy.io"
                  # The following section mean that any call on the path '/hello' will be routed to the cluster 'upstream'
                  routes:
                  - match:
                      prefix: "/hello"
                    route:
                      cluster: upstream
                  # ------ This service endpoint is unreliable - retry on 5xx internal server errors ------
                  retry_policy:
                    retry_on: "5xx"
                    num_retries: 10
                  # ---------------------------------------------------------------------------------------
    
              # The HTTP Connection Manager allows you to define multiple http filters, to transform or authorize an HTTP request for example.
              # The last filter in this filter chain is the 'router', it will route traffic to the corresponding Envoy Clusters.
              http_filters:
              - name: envoy.filters.http.router
    ... snip ...
    
    #
    docker-compose restart envoy
    
    #
    cd $HOME/envoy-workshop-basics
    docker-compose logs -f upstream
    upstream_1    | 172.18.0.4 - - [12/Apr/2025 04:00:20] "GET /hello HTTP/1.1" 500 -
    upstream_1    | 172.18.0.4 - - [12/Apr/2025 04:01:42] "GET /hello HTTP/1.1" 500 -
    
    #
    curl -s http://localhost:19000/stats | grep upstream_rq | grep cluster | head -29
    cluster.upstream.external.upstream_rq_200: 1
    cluster.upstream.external.upstream_rq_2xx: 1
    cluster.upstream.external.upstream_rq_500: 2
    cluster.upstream.external.upstream_rq_5xx: 2
    cluster.upstream.external.upstream_rq_completed: 3
    cluster.upstream.upstream_rq_200: 1
    cluster.upstream.upstream_rq_2xx: 1
    cluster.upstream.upstream_rq_500: 2
    cluster.upstream.upstream_rq_5xx: 2
    cluster.upstream.upstream_rq_active: 0
    cluster.upstream.upstream_rq_cancelled: 0
    cluster.upstream.upstream_rq_completed: 3
    cluster.upstream.upstream_rq_maintenance_mode: 0
    cluster.upstream.upstream_rq_max_duration_reached: 0
    cluster.upstream.upstream_rq_pending_active: 0
    cluster.upstream.upstream_rq_pending_failure_eject: 0
    cluster.upstream.upstream_rq_pending_overflow: 0
    cluster.upstream.upstream_rq_pending_total: 3
    cluster.upstream.upstream_rq_per_try_idle_timeout: 0
    cluster.upstream.upstream_rq_per_try_timeout: 0
    cluster.upstream.upstream_rq_retry: 0
    cluster.upstream.upstream_rq_retry_backoff_exponential: 0
    cluster.upstream.upstream_rq_retry_backoff_ratelimited: 0
    cluster.upstream.upstream_rq_retry_limit_exceeded: 0
    cluster.upstream.upstream_rq_retry_overflow: 0
    cluster.upstream.upstream_rq_retry_success: 0
    cluster.upstream.upstream_rq_rx_reset: 0
    cluster.upstream.upstream_rq_timeout: 0
    cluster.upstream.upstream_rq_total: 3
    
    
    #

     

    Securing Traffic Using Envoy

    #
    cd $HOME/envoy-workshop-basics
    
    #
    git checkout lab3-1
    ... snip ...
    static_resources:
      listeners:
      - name: envoy-listener-8443
        address:
          socket_address:
            address: 0.0.0.0
            # ------ Modified from http port 8080 to https port 8443 -----
            port_value: 8443
            # ------------------------------------------------------------
        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: ingress_http
              route_config:
                name: local_route
                virtual_hosts:
                - name: upstream
                  domains:
                  - "hello.envoyproxy.io"
                  routes:
                  - match:
                      prefix: "/hello"
                    route:
                      cluster: upstream
              http_filters:
              - name: envoy.filters.http.router
          # ------ Add transport_socket to filter chain with TLS credentials ------
          transport_socket:
            name: envoy.transport_sockets.tls
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
              require_client_certificate: true
              common_tls_context:
                tls_certificates:
                - certificate_chain:
                    filename: /etc/certs/tls.crt
                  private_key:
                    filename: /etc/certs/tls.key
          # ------------------------------------------------------------------------
    ... snip ...
    
    #
    docker-compose restart envoy
    
    #
    curl http://localhost:8000/hello -H "host: hello.envoyproxy.io"
    curl: (56) Recv failure: Connection reset by peer
    
    curl http://localhost:8443/hello -H "host: hello.envoyproxy.io"
    curl: (52) Empty reply from server
    
    curl https://localhost:8443/hello -H "host: hello.envoyproxy.io"
    curl: (60) SSL certificate problem: self signed certificate
    
    curl https://localhost:8443/hello -H "host: hello.envoyproxy.io" -k
    Hello back!

     

    #
    git checkout lab3-2
    ... snip ...
        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: ingress_http
              route_config:
                name: local_route
                virtual_hosts:
                - name: upstream
                  domains:
                  - "hello.envoyproxy.io"
                  routes:
                  - match:
                      prefix: "/hello"
                    route:
                      cluster: upstream
              http_filters:
              # ----- Add ext_authz filter to force authorization with external service -----
              - name: envoy.filters.http.ext_authz
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
                  transport_api_version: V3
                  http_service:
                    server_uri:
                      uri: auth
                      cluster: auth
                      timeout: 0.250s
              # -----------------------------------------------------------------------------
              - name: envoy.filters.http.router
    ... snip ...
    # ----- Add auth cluster for delegation of external authorization requests -----
      - name: auth
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: auth
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: auth
                    port_value: 8080
    
    # 
    docker-compose restart envoy
    curl https://localhost:8443/hello -H "host: hello.envoyproxy.io" -k -i
    Forbidden
    server: envoy
    date: Sat, 12 Apr 2025 04:09:01 GMT
    content-type: text/html; charset=utf-8
    content-length: 213
    x-envoy-upstream-service-time: 2
    
    <!doctype html>
    <html lang=en>
    <title>403 Forbidden</title>
    <h1>Forbidden</h1>
    <p>You don&#39;t have the permission to access the requested resource. It is either read-protected or not readable by the server.</p>
    
    # You'll see a similar result if you add the Authorization header but with a bad key.
    curl https://localhost:8443/hello -k -H "host: hello.envoyproxy.io" -H "Authorization: bad-key" -i
    HTTP/1.1 403 Forbidden
    server: envoy
    
    #
    curl https://localhost:8443/hello -k -H "host: hello.envoyproxy.io" -H "Authorization: workshop" -i
    HTTP/1.1 200 OK
    server: envoy
    date: Sat, 12 Apr 2025 04:09:28 GMT
    content-type: text/html; charset=utf-8
    content-length: 11
    x-envoy-upstream-service-time: 2
    
    #
    curl -s http://localhost:19000/stats | grep 'ext_authz\|listener.0.0.0.0_8443.http.ingress_http.downstream_rq\|listener.0.0.0.0_8443.downstream_cx'
    cluster.upstream.ext_authz.denied: 4
    cluster.upstream.ext_authz.ok: 1
    http.ingress_http.ext_authz.denied: 4
    http.ingress_http.ext_authz.disabled: 0
    http.ingress_http.ext_authz.error: 0
    http.ingress_http.ext_authz.failure_mode_allowed: 0
    http.ingress_http.ext_authz.ok: 1
    listener.0.0.0.0_8443.downstream_cx_active: 0
    listener.0.0.0.0_8443.downstream_cx_destroy: 5
    listener.0.0.0.0_8443.downstream_cx_overflow: 0
    listener.0.0.0.0_8443.downstream_cx_overload_reject: 0
    listener.0.0.0.0_8443.downstream_cx_total: 5
    listener.0.0.0.0_8443.downstream_cx_transport_socket_connect_timeout: 0
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_1xx: 0
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_2xx: 1
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_3xx: 0
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_4xx: 4
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_5xx: 0
    listener.0.0.0.0_8443.http.ingress_http.downstream_rq_completed: 5
    listener.0.0.0.0_8443.downstream_cx_length_ms: P0(nan,15.0) P25(nan,21.125) P50(nan,21.75) P75(nan,22.75) P90(nan,25.5) P95(nan,25.75) P99(nan,25.95) P99.5(nan,25.975) P99.9(nan,25.995) P100(nan,26.0)

     

    Observability Using Grafana and Prometheus

    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 19000
    static_resources:
      listeners:
      - name: envoy-listener-8443
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 8443
        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: ingress_http
              # ---------- Add simple access log config to HCM ----------
              access_log:
              - name: envoy.access_loggers.stdout
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog 
              # ---------------------------------------------------------        
              route_config:
                name: local_route
                virtual_hosts:
                - name: upstream
                  domains:
                  - "hello.envoyproxy.io"
                  routes:
                  - match:
                      prefix: "/hello"
                    route:
                      cluster: upstream
              http_filters:
              - name: envoy.filters.http.ext_authz
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
                  transport_api_version: V3
                  http_service:
                    server_uri:
                      uri: auth
                      cluster: auth
                      timeout: 0.250s
              - name: envoy.filters.http.router
          transport_socket:
            name: envoy.transport_sockets.tls
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
              require_client_certificate: true
              common_tls_context:
                tls_certificates:
                - certificate_chain:
                    filename: /etc/certs/tls.crt
                  private_key:
                    filename: /etc/certs/tls.key
      clusters:
      - name: upstream
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: upstream
          endpoints:
          - lb_endpoints:
            - endpoint:
                health_check_config:
                  port_value: 8080        
                address:
                  socket_address:
                    address: upstream
                    port_value: 8080        
    
      - name: auth
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: auth
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: auth
                    port_value: 8080
    #
    docker-compose restart envoy
    for i in {1..5};do curl https://localhost:8443/hello -k -H "host: hello.envoyproxy.io" -H "Authorization: workshop"; done
    Hello back!Hello back!Hello back!Hello back!Hello back!
    
    #
    docker-compose logs envoy
    envoy_1       | [2025-04-12T04:12:01.400Z] "GET /hello HTTP/1.1" 200 - 0 11 6 1 "-" "curl/7.68.0" "f98ad8a8-d1b5-47b8-9b1e-677673e0122a" "hello.envoyproxy.io" "172.18.0.2:8080"
    envoy_1       | [2025-04-12T04:12:01.430Z] "GET /hello HTTP/1.1" 200 - 0 11 4 1 "-" "curl/7.68.0" "a8df9d98-0d6a-4fe6-858a-f7069fcb4e38" "hello.envoyproxy.io" "172.18.0.2:8080"
    envoy_1       | [2025-04-12T04:12:01.457Z] "GET /hello HTTP/1.1" 200 - 0 11 4 1 "-" "curl/7.68.0" "179e906a-c7e5-4cc2-85d5-7b3c0db75ced" "hello.envoyproxy.io" "172.18.0.2:8080"
    envoy_1       | [2025-04-12T04:12:01.485Z] "GET /hello HTTP/1.1" 200 - 0 11 4 1 "-" "curl/7.68.0" "bf3f50b2-06f7-4864-a931-9d53646b1b03" "hello.envoyproxy.io" "172.18.0.2:8080"
    envoy_1       | [2025-04-12T04:12:01.513Z] "GET /hello HTTP/1.1" 200 - 0 11 4 1 "-" "curl/7.68.0" "ce0f03c0-5531-4466-9acb-edb5b9bd1b95" "hello.envoyproxy.io" "172.18.0.2:8080"