-
[IHOS 1기] 2주차 - Envoy, IstioGatewayKubernetes 2025. 4. 19. 13:00
- Istio’s Data Plane: Envoy Proxy
3.1 What is the Envoy proxy?‣3.1.1. 엔보이 소개‣- 엔보이는 분산 시스템을 구축할 때 발생하는 어려운 애플리케이션 네트워킹 문제를 해결하고자 리프트가 개발했다.
- 엔보이는 2016년 9월 오픈소스 프로젝트로 공개됐으며, 1년 후인 2017년 9월에는 CNCF에 합류했다.
- 엔보이는 C++로 작성됐는데, 그 목표는 성능을 늘리는 것, 특히 높은 부하에서도 더 안정적이고 결정론적일 수 있도록 만드는 것이었다.
- 엔보이는 다음 2가지 중요 원칙에 따라 만들어졌다.
- 엔보이는 프록시이므로, 더 나아가기 전에 프록시란 무엇인지 명확히 해야 한다. 앞서 프록시란 클라이언트와 서버 간 통신의 중간에 위치한, 네트워크 아키텍터의 중개 구성 요소라고 언급한 바 있다. 프록시는 중간에 위치함으로써 보안, 프라이버시, 정책 같은 기능을 추가할 수 있다.

- 프록시는 클라이언트가 서비스와 통신할 때 알아야 할 사항을 단순화할 수 있다.
- 예를 들어 서비스가 동일한 인스턴스 집합(클러스터)으로 구현됐고, 각 인스턴스는 일정량의 부하를 처리할 수 있다고 가정해보자.
- 클라이언트가 이 서비스에 요청할 때 어느 인스턴스 혹은 IP 주소인지 어떻게 알아야 하는가?
- 프록시가 단일 식별자 혹은 IP주소를 갖고 중간에 위치할 수 있고, 클라이언트는 서비스 인스턴스들과 통신하는 데 프록시를 사용할 수 있다.
- 아래 그림은 클라이언트가 실제 서비스 인스턴스 배포 방식을 자세히 알지 못하는 상태에서 프록시가 서비스 인스턴스 간에 로드 밸런싱을 처리하는 것을 보여준다.
- 이런 리버스 프록시 유형의 또 다른 기능은 클러스터의 인스턴스 상태를 검사하고 실패하거나 오동작하는 백엔드 인스턴스를 우회하도록 라우팅하는 것이다. 이런 방식으로, 프록시는 클라이언트가 어느 백엔드가 과부화돼 있거나 장애가 발생했는지 파악할 필요가 없도록 보호 할 수 있다.

- 엔보이 프록시는 특히 서비스 디스커버리, 로드 밸런싱, 헬스 체크 같은 기능을 제공하기 위해 애플리케이션 요청 경로 중간에 삽입할 수 있는 애플리케이션 수준 프록시이지만, 엔보이는 그 이상을 할 수 있다.
- 이전 장에서 이런 강화된 기능 중 일부를 언급했는데, 이번 장에서 더 자세히 다룰 것이다.
- 엔보이는 애플리케이션이 다른 서비스와 통신할 때 사용하는 7계층 프로토콜을 이해할 수 있다.
- 예를 들어, 엔보이는 기본적으로 HTTP 1.1, HTTP 2, gRPC 등을 이해할 수 있고, 요청 수준 타임아웃, 재시도, 재시도별 타임아웃, 서킷 브레이커와 기타 복원력 기능 같은 동작을 추가할 수 있다.
- 커넥션만을 이해하는 기본적인 커넥션 수준(L3/L4) 프록시로는 이와 같은 작업을 수행할 수 없다.
- 엔보이는 기본 제공 프로토콜 외에도 다양한 프로토콜을 이해하도록 확장할 수 있다.
- MongoDB, DynamoDB 같은 데이터베이스용, 심지어는 AMQP 같은 비동기 프로토콜용 필터도 작성돼 왔다.
- 애플리케이션에 투명하게 보이고 신뢰성을 갖추는 것도 중요하지만, 분산 아키텍처에서 일어나고 있는 일을 빠르게 이해할 수 있는 능력도 그만큼 중요하다. 특히 예상대로 작동하지 않을 때는 더욱 그렇다.
- 엔보이는 애플리케이션 수준 프로토콜을 이해할 수 있고, 애플리케이션 트래픽이 엔보이를 거쳐 흐르는 덕분에 통과하는 요청에 대한 텔레메트리를 수집할 수 있다.
- 예를 들어 요청 처리에 얼마나 시간이 소요되는지, 특정 서비스가 얼마나 많은 요청을 처리하고 있는지(처리량 throughput), 서비스의 오류율은 어느 정도인지 등이다.
- 프록시로서, 엔보이는 애플리케이션 외부에서 동작함으로써 개발자가 네트워크 문제를 고려하지 않아도 되도록 설계됐다.
- 즉, 어느 프로그래밍 언어로 작성됐든, 어느 프레임워크를 사용하든 모든 애플리케이션이 이 기능들을 사용할 수 있다.
- 게다가 서비스 아키텍처(SOA)가 표준 아키텍처이기는 하지만, 마이크로서비스든, 어떤 언어로 작성된 모놀리스 및 레거시 애플리케이션이든 엔보이는 상관없다.
- 엔보이가 이해할 수 있는 프로토콜(HTTP 등)을 사용하는 한, 엔보이의 이점을 누릴 수 있다.
- 엔보이는 매우 다재다응한 프록시로 다양한 역할을 할 수 있다. 클러스터 에지 프록시(진입 지점으로), 서비스의 공유 프록시로, 심지어는 이스티오처럼 서비스별 프록시로도 사용할 수 있다.
- 이스티오에서는 엔보이 프록시를 서비스 인스턴스당 하나씩 배포해 유연성, 성능, 제어 능력을 높인다.
- 사이드가 프록시와 에지 프록시를 구현하면 인프라를 운영하고 이해하기가 더 쉬워진다.
- 엔보이는 서비스 메시의 진입점으로 사용돼 클러스터에 들어오는 트래픽을 완벽히 제어하고 관찰할 수 있다.
3.1.2 Envoy’s core features* 엔보이의 핵심 기능‣Envoy concepts as a high level
- 리스너 Listeners
- 애플리케이션이 연결할 수 있는 외부 세계로 포트를 노출한다.
- 예를 들어 포트 80에 대한 리스터는 트래픽을 받고, 설정된 동작을 해당 트래픽에 적용한다.
- 루트(라우트) Routes
- 리스너로 들어오는 트래픽을 처리하는 라우팅 규칙,
- 예를 들어 요청이 들어오고
/catalog에 일치하면 그 트래픽을 catalog 클러스터로 보내는 식이다. - 클러스터 Cluster
- 엔보이가 트래픽을 라우팅할 수 있는 특정 업스트림 서비스, 예를 들어 catalog-v1 과 catalog-v2 는 별도 클러스터일 수 있고,
- 루트는 catalog 서비스의 v1이나 v2로 트래픽을 보내는 방법에 대한 규칙을 지정할 수 있다.
→ 이는 엔보이가 L7 트래픽에 수행하는 작업을 개념적으로 설명한 것이다.
- 엔보이는 트래픽 방향성을 나타낼 때 다른 프록시와 비슷한 용어를 사용한다.
- 예를 들어 트래픽은 다운스트림 시스템에서 리스너로 들어온다.
- 이 트래픽은 엔보이의 클러스터 중 하나로 라우팅되며, 클러스터는 트래픽을 업스트림 시스템으로 보내는 역할을 한다.
- 트래픽은 엔보이를 거쳐 다운스트림에서 업스트림으로 흐른다.

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

로드 밸런싱 LOAD BALANCING
- 엔보이는 애플리케이션이 활용할 수 있는 고급 로드 밸런싱 알고리듬을 여러 가지 구현하고 있다.
- 엔보이의 로드 밸런싱 알고리듬에서 흥미로운 기능 중 하나는 지역 인식 locality-aware 로드 밸런싱이다.
- 엔보이는 특정 기준을 충족하지 않으면 트래픽이 지역 경계를 넘지 않게 해 트래픽을 더 잘 분산시킬 수 있다.
- 예를 들어 엔보이는 장애 상황으로 이어지는 것이 아닌 이상, 서비스 간 트래픽을 동일한 지역의 인스턴스로 라우팅되도록 한다.
- 엔보이는 다음 로드 밸런싱 알고리듬들을 기본적으로 제공한다. - Docs
- 랜덤 Random
- (가중치) 라운드 로빈 (Weighted) Round robin
- 가중치를 적용한 최소 요청 Weighted, least request
- 일관된 해싱 Consistent hashing (sticky) : Ring hash, Maglev
트래픽 및 요청 라우팅 TRAFFIC AND REQUEST ROUTING
- 엔보이는 HTTP 1.1과 HTTP 2 같은 애플리케이션 프로토콜을 이해할 수 있으므로 정교한 라우팅 규칙을 사용해 트래픽을 특정 백엔드 클러스터로 보낼 수 있다. - Docs
- 이를 통해 가상 호스트 매핑과 콘텍스트 경로 context-path 라우팅 같은 기본적인 리버스 프록시 라우팅을 처리할 수 있고, 또한 헤더 및 우선순위 기반 라우팅, 라우팅 재시도 및 타임아웃, 오류 주입까지도 수행할 수 있다.
트래픽 전환 및 섀도잉 기능 TRAFFIC SHIFTING AND SHADOWING CAPABILITIES
- 엔보이는 비율 기반(즉, 가중치 적용) 트래픽 분할 splitting / 전환 shifting 을 지원한다.
- 이 기능을 활용해 애자일 팀은 카나리 릴리스와 같이 위험을 완화하는 CD 기술을 사용할 수 있다.
- 위험성을 더 작은 유저 풀로 완화하기는 하지만, 카나리 릴리스는 여전히 라이브 사용자 트래픽을 다룬다.
- 엔보이는 트래픽의 사본을 만들어 ‘보내고 망각하는 fire and forget’ 방식으로 트래픽을 엔보이 클러스터에 섀도잉 shadowing 할 수 있다.
- 이 섀도잉 기능을 트래픽 분할과 같은 것으로 생각할 수 있지만, 업스트림 클러스터가 보는 요청은 라이브 트래픽의 복사본이다.
- 따라서 라이브 운영 환경 트래픽에 영향을 주지 않고 섀도잉한 트래픽을 서비스의 새 버전으로 라우팅할 수 있다.
- 이는 고객에게 영향을 주지 않고 운영 환경 트래픽으로 서비스 변경 사항을 테스트 할 수 있는 아주 강력한 기능이다.
네트워크 복원력 NETWORK RESILIENCE
- 엔보이에게 특정 종류의 복원력 문제를 맡길 수는 있지만, 그 파라미터를 설정하고 잘 조정하는 것은 애플리케이션의 책임이라는 점을 유의해야 한다.
- 엔보이는 요청 타임아웃과 요청 수준 재시도(재시도별 타임아웃 포함)을 자동으로 수행할 수 있다.
- 이런 재시도 동작은 네트워크 불안정이 간간히 요청에 영향을 줄 때 매우 유용한다.
- 반면에 재시도 증폭은 연쇄 장애로 이어질 수 있어 엔보이에서는 재시도 동작을 제한할 수 있다.
- 또한 애플리케이션 수준 재시도는 여전히 필요할 수 있으며 엔보이가 완전히 대체할 수 없다.
- 게다가 엔보이가 업스트림 클러스터를 호출할 때 진행 중인 커넥션 혹은 요청의 개수를 제한하고, 그 임계값을 넘어서는 것은 빠르게 실패시키도록 설정 할 수 있다. (임계값에는 지터 jitter 가 있을 수 있다).
- 마지막으로 엔보이는 서킷 브레이커처럼 동작하는, 이상값 감지 outlier detection 를 수행해 오동작하는 엔드포인트를 로드 밸런싱 풀에서 제거할 수 있다.
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
- 이는 강력하지만 제대로 구현하기 어려운 기능이며 다른 서비스 프록시와 엔보이를 차별화한다.
메트릭 수집을 통한 관찰 가능성 OBSERVABILITY WITH METRICS COLLECTION
- 엔보이의 목표 중 하나는 네트워크를 이해할 수 있게 만드는 것이다. 이 목표를 위해 엔보이는 다양한 메트릭을 수집한다.
- 엔보이는 서버에 호출하는 다운스트림 시스템, 서버 그 자체, 서버가 요청을 보내는 업스트림 클러스터에 대한 여러 측면(디멘션 dimentsion) 을 추척한다.
- 엔보이의 통계는 카운터, 게이지, 히스토그램으로 추적된다.
- 아래 표는 업스트림 클러스터에 대해 추적하는 통계 유형의 몇 가지 예시를 나열한다.
통계설명downstream_cx_total총 커넥션 개수 Total connectionsdownstream_cx_http1_active총 활성 HTTP/1.1 커넥션 개수 Total active HTTP/1.1 connectionsdownstream_rq_http2_total총 HTTP/2 요청 개수 Total HTTP/2 requestscluster.<name>.upstream_cx_overflow클러스터의 커넥션 서킷 브레이커가 임계값을 넘겨 발동한 횟수 Total number of times that the cluster’s connection circuit breaker overflowedcluster.<name>.upstream_rq_retry총 요청 재시도 횟수 Total number of request retriescluster.<name>.ejections_detected_consecutive_5xx5xx 오류가 계속돼 퇴출된 횟수 (시행되지 않은 경우도 포함) Number of detected consecutive 5xx ejections (even if unenforced)- 엔보이는 설정 가능한 어댑터와 형식을 사용해 통계를 내보낼 수 있다. 기본적으로 지원하는 목록은 아래와 같다.
- StatsD
- Datadog; DogStatsD
- Hystrix formatting
- Generic metrics service
분산 트레이싱을 통한 관찰 가능성 OBSERVABILITY WITH DISTRIBUTED TRACING
- 엔보이는 트레이스 스팬을 오픈트레이싱 OpenTracing 엔진에 보고해 호출 그래프 내 트래픽 흐름, 홉, 지연 시간을 시각화할 수 있다.
- 즉, 특별한 오픈트레이싱 라이브러리를 설치할 필요가 없다.
- 한편 필요한 집킨 헤더를 전파하는 것은 애플리케이션의 역할이며, 이는 가벼운 래퍼 wrapper 라이브러리로 수행할 수 있다.
- 엔보이는 서비스 간 호출을 연관시킬 목적으로 x-request-id 헤더를 생성하며, 트레이싱이 시작될 때
initialx-b3*헤더를 만들 수도 있다. - 애플리케이션이 전파해야 하는 헤더는 다음과 같다.
- x-b3-traceid
- x-b3-spanid
- x-b3-parentspanid
- x-b3-sampled
- x-b3-flags
자동 TLS 종료 및 시작 AUTOMATIC TLS TERMINATION AND ORIGINATION
- 엔보이는 특정 서비스로 향하는 TLS 트래픽을 종료 terminate 시킬 수 있다. 클러스터의 에지와 서비스 메시 내부의 프록시 모두에서 가능하다.
- 더 흥미로운 기능은 애플리케이션 대신 엔보이가 업스트림 클러스터로 TLS 트래픽을 시작할 수도 있다는 것이다.
- 즉, 엔터프라이즈 개발자와 운영자가 언어별 설정과 키스토어 또는 트러스터 스토어를 만지작거리지 않아도 된다.
- 요청 경로에 엔보이가 있으면 TLS, 심지어 mTLS 까지도 자동으로 얻을 수 있다.
속도 제한 RATE LIMITING
- 복원력이 중요한 측면은 보호받는 리소스로의 접근을 차단하거나 제한할 수 있는 기능이다.
- 데이터베이스나 캐시, 공유 서비스 같은 리소스들은 다음과 같은 여러 이유로 보호받을 수 있다.
- 호출 call 비용이 비쌈 (실행 invocation 당 비용)
- 지연 시간이 길거나 예측 불가능
- 기아 starvation 를 방지하기 위해 공정성 알고리듬이 필요
- 특히 서비스가 재시도하도록 설정한 경우에 시스템 내에서 특정 장애의 영향이 과도하게 확대되는 것을 원하지 않는다.
- 이런 시나리오에서 요청을 제한하는 데 전역 속도 제한 서비스를 사용할 수 있다.
- 엔보이는 네트워크(커넥션별)와 HTTP(요청별) 수준 모두에서 속도 제한 서비스와 통합할 수 있다.
엔보이 확장하기 EXTENDING ENVOY
- 엔보이의 핵심은 프로토콜(7계층) 코덱(필터 filter 라고 부름)을 구축할 수 있는 바이트 처리 엔진이다.
- 엔보이에서는 추가 필터를 구축하는 것을 주요 사용 사례로 삼고 있으며, 이는 필요에 맞게 엔보이를 확장할 수 있는 흥미로운 방법이다.
- 엔보이 필터는 C++로 작성돼 엔보이의 바이너리로 컴파일된다.
- 또한 엔보이는 루아 Lua 스크립트와 웹어셈블리 Wasm, WebAssembly를 지원하므로 덜 침습적인 invasive 방법으로도 엔보이 기능을 확장할 수 있다.
3.1.3 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:
- Envoy documentation and comparison: http://bit.ly/2U2g7zb
- Turbine Labs’ switch from Nginx to Envoy: http://bit.ly/2nn4tPr
- Cindy Sridharan’s initial take on Envoy: http://bit.ly/2OqbMkR
- Why Ambassador chose Envoy over HAProxy and Nginx: http://bit.ly/2OVbsvz
3.2 Configuring Envoy‣3.2.1. 들어가며‣- 엔보이는 JSON/YAML 형식 설정 파일로 구동된다.
- 설정 파일은 리스너, 루트, 클러스터뿐 아니라 Admin API 활성화 여부, 액세스 로그 저장 위치, 트레이싱 엔진 설정 등 서버별 설정도 지정한다.
- 엔보이나 엔보이 설정에 이미 친숙하다면 엔보이 설정에는 여러 버전이 있다는 사실도 알 것이다.
- 초기 버전인 v1 과 v2 는 v3 로 대체돼 더 이상 사용하지 않는다.
- 이 책에서는 v3 설정만을 살펴볼 것인데, 최신 버전이자 이스티오가 사용하는 버전이기 때문이다.
- 엔보이의 v3 설정 API는 gRPC를 사용한다.
- 엔보이 및 v3 API 구현자들은 API 호출 시 스트리밍 기능을 사용해 엔보이 프록시가 올바른 설정으로 수렴하는 데 걸리는 시간을 줄 일 수 있다.
- 실제로 이렇게 하면, 프록시가 주기적으로 폴링하는 대신 서버가 업데이트를 엔보이에 푸시할 수 있어 API를 폴링할 필요가 없어진다.
3.2.2 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.3. Dynamic configuration 동적 설정‣- 엔보이는 특정 API군을 사용해 다운타임이나 재시작 없이 설정을 실시간으로 업데이트할 수 있다.
- 올바른 디스커버리 서비스 API를 가리키는 간단한 부트스트랩 설정 파일만 있으면 나머지 설정은 동적으로 이뤄진다.
- 엔보이는 동적 설정에 다음과 같은 API를 사용한다(아래 API들을 xDS라고 부름). - Blog
- 엔보이가 자신이 어떤 리스너를 노출해야 한느지 쿼리할 수 있게 하는 API
- 리스너 설정의 일부로, 사용할 루트를 지정한다. 정적 설정이나 동적 설정을 사용할 때 LDS의 부분집합이다.
- 엔보이가 클러스터 목록과 각 클러스터용 설정을 찾을 수 있는 API
- 클러스터 설정의 일부로, 특정 클러스터에 어떤 엔드포인트를 사용해야 하는지 지정한다. CDS의 부분집합니다.
- 인증서를 배부하는 데 사용하는 API
- 나머지 API에 대한 모든 변경 사항을 직렬화된 스트림으로 제공한다. 이 API 하나로 모든 변경 사항을 순차적으로 가져올 수 있다.
- Listener discovery service (LDS)
- Route discovery service (RDS)
- Cluster discovery service (CDS)
- Endpoint discovery service (EDS)
- Secret discovery service (SDS)
- Aggregate discovery service (ADS)
- 이 API들을 통틀어 xDS 서비스라고 부른다.
- 이들 중 하나 이상을 조합해 설정할 수 있으며, 전부 사용해야만 하는 것은 아니다.
- 한 가지 유념해야 할 점은 엔보이의 xDS API는 궁극적 일관성 eventual consistency 을 전제로 구축됐으며 궁극적으로는 올바른 구성으로 수렴한다는 것이다.
- 예를 들어 엔보이가 트래픽을 클러스터 foo로 라우팅하는 새 루트가 RDS로 업데이트됐는데, 이 클러스터 foo를 포함한 CDS 업데이트는 아직 수행되지 않았을 수 있다.
- 이 경우 CDS가 업데이트될 때까지 RDS로 설정된 새 route에 대해서 보낼곳이 없으므로 라우팅 오류가 발생할 수 있다. → ADS의 필요성이 발생!!

- 이런 순서에 따른 경쟁 상태 race condition 를 해결하기 위해 도입한 것이 ADS 이다.
- 이스티오는 프록시 설정 변경을 위해 ADS를 구현한다. → Aggregate Discover Service
- 간단히 말해서 xDS관련된 API를 한번에 Aggregate하여 요청한다는 개념!


- 예를 들어 엔보이 프록시의 리스너를 동적으로 찾으려면 다음과 같은 설정을 사용할 수 있다.
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 (실습)‣3.1.1. 기본 실습‣- 엔보이는 C++로 작성돼 플랫폼에 맞게 컴파일 된다. 엔보이를 시작하기 가장 좋은 방법은 도커를 사용해 컨테이너를 실행하는 것이다.
- 도커 이미지 가져오기 : citizenstig/httpbin 는 arm CPU 미지원 - Link
# 도커 이미지 가져오기 docker pull envoyproxy/envoy:v1.19.0 docker pull curlimages/curl docker pull mccutchen/go-httpbin docker pull citizenstig/httpbin # 확인 docker images- 간단한 httpbin 서비스 만들어보자. 기본적으로 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": [ "httpbin:8000" ], "User-Agent": [ "curl/8.13.0" ] } }- /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 [2025-04-18 15:52:44.495][1][info][main] [source/server/server.cc:338] initializing epoch 0 (base id=0, hot restart version=11.120) [2025-04-18 15:52:44.495][1][info][main] [source/server/server.cc:340] statically linked extensions: [2025-04-18 15:52:44.495][1][info][main] [source/server/server.cc:342] envoy.matching.common_inputs: envoy.matching.common_inputs.environment_variable [2025-04-18 15:52:44.495][1][info][main] [source/server/server.cc:342] envoy.thrift_proxy.protocols: auto, binary, binary/non-strict, compact, twitter [2025-04-18 15:52:44.495][1][info][main] [source/server/server.cc:342] envoy.guarddog_actions: envoy.watchdog.abort_action, envoy.watchdog.profile_action ...- 이를 수정하고 앞서 봤던 예제 설정 파일을 전달해보자.
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-18 15:54:39.384][1][info][main] [source/server/server.cc:785] all clusters initialized. initializing init manager [2025-04-18 15:54:39.384][1][info][config] [source/server/listener_manager_impl.cc:834] all dependencies initialized. starting workers [2025-04-18 15:54:39.402][1][info][main] [source/server/server.cc:804] starting main dispatch loop- 프록시가 성공적으로 시작해 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" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "d6d43a6c-9aa5-495d-a8cf-343e28f97775" ] } }- 프록시를 호출했는데도 트래픽이 httpbin 서비스로 정확하게 전송됐다. 또 다음과 같은 새로운 헤더도 추가됐다.
- X-Envoy-Expected-Rq-Timeout-Ms
- X-Request-Id
- 사소하게 보일 수 있지만, 이미 엔보이는 우리를 위해 많은 일을 하고 있다.
- 엔보이는 새 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" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "e4ba3eb8-73d4-4d51-97bb-ad7ac467ab58" ] } } # 추가 테스트 : 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다음 실습을 위해 Envoy 종료docker rm -f proxy
3.3.2. 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.3. 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.circuit_breakers.default.rq_retry_open: 0 cluster.httpbin_service.circuit_breakers.high.rq_retry_open: 0 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 cluster.httpbin_service.upstream_rq_retry_backoff_exponential: 3 cluster.httpbin_service.upstream_rq_retry_backoff_ratelimited: 0 cluster.httpbin_service.upstream_rq_retry_limit_exceeded: 1 cluster.httpbin_service.upstream_rq_retry_overflow: 0 ...- 엔보이는 업스트림 클러스터 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를 통해 살펴볼 것이다.
- 그렇지만 실제로는 많은 것이 엔보이가 제공하고 구현하는 것임을 이해하자.
3.5. Summary‣- Envoy는 애플리케이션이 애플리케이션 수준의 동작에 사용할 수 있는 프록시입니다.
- Envoy는 이스티오의 데이터 플레인입니다.
- Envoy는 클라우드 신뢰성 문제(네트워크 장애, 토폴로지 변경, 탄력성)를 일관되고 정확하게 해결하는 데 도움을 줄 수 있습니다.
- Envoy는 런타임 제어를 위해 동적 API를 사용합니다(Istio는 이를 사용합니다).
- Envoy는 애플리케이션 사용 및 프록시 내부에 대한 강력한 지표와 정보를 많이 노출합니다.
3.6. 참고 자료‣- Istio 구성요소와 envoy : 컨트롤 플레인(istiod) - ADS 를 이용한 Configuration 동기화 - 데이터 플레인(istio-proxy → envoy)

- Cluster : envoy 가 트래픽을 포워드할 수 있는 논리적인 서비스 (엔드포인트 세트), 실제 요청이 처리되는 IP 또는 엔드포인트의 묶음을 의미.
- Endpoint : IP 주소, 네트워크 노드로 클러스터로 그룹핑됨, 실제 접근이 가능한 엔드포인트를 의미. 엔드포인트가 모여서 하나의 Cluster 가 된다.
- Listener : 무엇을 받을지 그리고 어떻게 처리할지 IP/Port 를 바인딩하고, 요청 처리 측면에서 다운스트림을 조정하는 역할.
- Route : Listener 로 들어온 요청을 어디로 라우팅할 것인지를 정의. 라우팅 대상은 일반적으로 Cluster 라는 것에 대해 이뤄지게 된다.
- Filter : Listener 로부터 서비스에 트래픽을 전달하기까지 요청 처리 파이프라인
- UpStream : envoy 요청을 포워딩해서 연결하는 백엔드 네트워크 노드 - 사이드카일때 application app, 아닐때 원격 백엔드
- DownStream : An entity connecting to envoy, In non-sidecar models this is a remote client
Envoy 용어 정리 : 정환열님이 작성해서 공유해주신 Istio 사내 교육용 자료 내용이 좋아서 가져왔습니다.


Envoy는 구성을 동적으로 관리하기 위한 강력한 API를 제공이 중요합니다.

- Service Mesh 솔루션이나, Gateway API 구현체들을 Enovy를 내부적으로 사용하고 있으며, Envoy가 제공하는 동적 구성을 위한 API (xDS Sync API)를 이용하여 다양한 네트워크 정책을 구성하게 됩니다.
- Envoy의 xDS Sync API는 아래와 같은 레이어에서 동작하게 됩니다.
- LDS - Listener Discovery Service
- RDS - Route Discovery Service
- CDS - Cluseter Discovery Service
- EDS - Endpoint Discovery Service


- Istio gateways: Getting traffic into a cluster
4.0. 실습 환경 준비‣4.0.1. k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)‣# 자신의 실습 root dir로 이동 KUBECONFIG=$PWD/kubeconfig # 아래 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: # linux or mac의 경우 해당 부분 생략 가능 - 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 git clone https://github.com/AcornPublishing/istio-in-action cd istio-in-action/book-source-code-master pwd # 각자 자신의 pwd 경로 code .# 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 # kiali, jaeger, prometheus, grafana 설치 확인 kubectl get pod -n istio-system NAME READY STATUS RESTARTS AGE grafana-b854c6c8-dx2q5 0/1 ContainerCreating 0 14s istio-ingressgateway-996bc6bb6-5q245 1/1 Running 0 38s istiod-7df6ffc78d-2x2lh 1/1 Running 0 54s jaeger-5556cd8fcf-4ppzc 0/1 ContainerCreating 0 14s kiali-648847c8c4-vz99q 0/1 ContainerCreating 0 14s prometheus-7b8b9dd44c-2rspb 0/2 ContainerCreating 0 14s # 빠져나오기 exit ----------------------------------- # 실습을 위한 네임스페이스 설정 kubectl create ns istioinaction # envoy proxy sidecar container injection을 위한 설정 kubectl label namespace istioinaction istio-injection=enabled kubectl get ns --show-labels AME STATUS AGE LABELS default Active 5m23s kubernetes.io/metadata.name=default istio-system Active 97s kubernetes.io/metadata.name=istio-system istioinaction Active 0s istio-injection=enabled,kubernetes.io/metadata.name=istioinaction kube-node-lease Active 5m24s kubernetes.io/metadata.name=kube-node-lease kube-public Active 5m24s kubernetes.io/metadata.name=kube-public kube-system Active 5m24s kubernetes.io/metadata.name=kube-system local-path-storage Active 5m21s kubernetes.io/metadata.name=local-path-storage # 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 EOF4.0.3. 참고자료‣Istio1.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.io와api.istioinaction.io가 모두 동일한 가상 IP로 해석되게 할 수 있다. - 즉, 호스트네임에 대한 요청 모두 동일한 가상 IP로 흐르게 되고, 그에 따라 동일한 인그레스 리버스 프록시가 그 요청을 라우팅하게 된다.
- 리버스 프록시가 충분히 똑똑하다면, Host HTTP 헤더를 사용해 어느 요청이 어느 서비스 그룹으로 가야 하는지 기술할 수 있다.

- 서로 다른 서비스 여러 개를 진입점 하나로 호스팅하는 것을 가상 호스팅 virtual hosting 이라고 한다.
- 특정 요청이 향하는 가상 호스트 그룹을 결정할 방법이 필요한데, HTTP/1.1 에서는
Host헤더를 사용할 수 있고 HTTP/2에서는:authority헤더를 사용할 수 있다. - Blog1, Blog2, Blog3 - 또한 TCP 커넥션에서는 TLS의 SNI server name indication 에 의존할 수 있다. SNI는 이 장의 뒷부분에서 자세히 살펴볼 것이다.
- 여기서 중요한 사실은 이스티오에서 보이는 에지 인그레스 기능이 서비스 트래픽을 클러스터 안으로 라우팅하는 데 가상 IP 라우팅과 가상 호스팅을 사용한다는 것이다.
4.2 Istio ingress gateways 이스티오 인그레스 게이트웨이‣소개 (실습) Gateway(L4/L5) , VirtualService(L7)‣- 이스티오에는 네트워크 인그레스 포인트 역할을 하는 인그레스 게이트웨이라는 개념이 있는데, 이것은 클러스터 외부에서 시작한 트래픽이 클러스터에 접근하는 것을 방어하고 제어한다. 로그 밸런싱과 가상 호스트 라우팅도 처리한다.
- 아래 그림은 이스티오의 인그레스 게이트웨이가 클러스터로 트래픽을 허용하면서 리버스 프록시 기능도 수행하는 것을 보여준다.
- 이스티오는 인그레스 게이트웨이로 단일 엔보이 프록시를 사용한다.
- 3장에서 엔보이는 서비스 간 프록시이지만 서비스 메시 외부에서 내부의 서비스로 향하는 트래픽을 로드 밸런싱하고 라우팅하는 데 사용할 수도 있음을 확인했다.
- 이전 장에서 이야기했던 엔보이의 모든 기능은 인그레스 게이트웨이에서도 사용할 수 있다.

- 이스티오가 어떻게 엔보이를 사용해 인그레스 게이트웨이를 구현하는지 자세히 살펴보자.
- 아래 그림은 컨트롤 플레인을 구성하는 요소와 컨트롤 플레인을 보조하는 추가 구성 요소의 목록을 보여준다.

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

# 파드에 컨테이너 1개 기동 : 별도의 애플리케이션 컨테이너가 불필요. kubectl get pod -n istio-system -l app=istio-ingressgateway NAME READY STATUS RESTARTS AGE istio-ingressgateway-996bc6bb6-5q245 1/1 Running 0 8m51s # proxy 상태 확인 docker exec -it myk8s-control-plane istioctl proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION istio-ingressgateway-996bc6bb6-5q245.istio-system Kubernetes SYNCED SYNCED SYNCED NOT SENT NOT SENT istiod-7df6ffc78d-2x2lh 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 RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE default Cert Chain ACTIVE true 12401901870532937100071839722145404108 2025-04-17T14:44:27Z 2025-04-16T14:42:27Z ROOTCA CA ACTIVE true 213226523371779796801291257182983036466 2035-04-14T14:44:14Z 2025-04-16T14:44:14Z # 설정 참고 kubectl get istiooperators -n istio-system -o yaml # pilot-agent(1번 프로세스) 프로세스가 envoy 를 부트스트랩 kubectl exec -n istio-system deploy/istio-ingressgateway -- ps PID TTY TIME CMD 1 ? 00:00:00 pilot-agent 26 ? 00:00:06 envoy 54 ? 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.0 0.4 754344 55436 ? Ssl 14:44 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+ 26 0.7 0.4 311296 58672 ? Sl 14:44 0:07 /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+ 60 0.0 0.0 6412 2496 ? Rs 14:59 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 4s # docker exec -it myk8s-control-plane istioctl proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION istio-ingressgateway-996bc6bb6-5q245.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-2x2lh 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 ... # 현재 gateway라는 CR만 생성하고 virtualservice CR은 생성하지 않았으므로, VirtualService에 404(not found)가 뜬다! docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system NAME DOMAINS MATCH VIRTUAL SERVICE http.8080 * /* 404 ... # coolstore-gateway gw가 생성되면서 istio-system ns의 istio-ingressgateway에 아래와 같은 설정들이 주입됨 kubectl stern -n istio-system -l app=istiod istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:02:11.139066Z info ads Push debounce stable[18] 1 for config Gateway/istioinaction/coolstore-gateway: 101.717875ms since last change, 101.717416ms since last push, full=true istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:02:11.139370Z info ads XDS: Pushing:2025-04-16T15:02:11Z/14 Services:11 ConnectedEndpoints:1 Version:2025-04-16T15:02:11Z/14 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:02:11.142270Z info ads LDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:2.2kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:02:11.147553Z warn constructed http route config for route http.8080 on port 80 with no vhosts; Setting up a default 404 vhost istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:02:11.147821Z info ads RDS: PUSH request for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:34B cached:0/0 # 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, # 위에서 설정한 istio-ingressgateway svc의 nodeport중 하나 node의 30000로 받아서 istio-ingressgateway의 8080으로 보냄 "port": 80, "protocol": "TCP", "targetPort": 8080 }, { "name": "https", "nodePort": 30005, # 위에서 설정한 istio-ingressgateway svc의 nodeport중 하나 node의 30005로 받아서 istio-ingressgateway의 8443으로 보냄 "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 # 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 10m NAME GATEWAYS HOSTS AGE virtualservice.networking.istio.io/webapp-vs-from-gw ["coolstore-gateway"] ["webapp.istioinaction.io"] 2m32s # istiod 로그 확인 # virtualservice가 생성되면서 CDS, LDS, RDS가 설정된다. kubectl stern -n istio-system -l app=istiod istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:10:00.269714Z info ads Push debounce stable[19] 1 for config VirtualService/istioinaction/webapp-vs-from-gw: 100.271208ms since last change, 100.270958ms since last push, full=true istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:10:00.270216Z info ads XDS: Pushing:2025-04-16T15:10:00Z/15 Services:11 ConnectedEndpoints:1 Version:2025-04-16T15:10:00Z/15 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:10:00.270780Z info ads CDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:22 size:21.8kB cached:21/21 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:10:00.271381Z info ads LDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:2.2kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:10:00.272265Z info ads RDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:538B cached:0/0 # docker exec -it myk8s-control-plane istioctl proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION istio-ingressgateway-996bc6bb6-5q245.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-2x2lh 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 # istioinaction ns에 webapp, catalog pod가 생성되면서 envoy proxy sidecar container가 자동 주입된다. # ADS에 의해 aggregate된 xDS들이 설정된다. 여기서 CDS, EDS가 생성되면서 실제 upstream(target pod)에 대한 정보가 등록된다. kubectl stern -n istio-system -l app=istiod istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:34.976352Z info ads Incremental push, service catalog.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:34.987320Z info Sidecar injection request for istioinaction/catalog-6cf4b97d-***** (actual name not yet known) istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:34.999025Z info ads Incremental push, service catalog.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.053242Z info ads Incremental push, service webapp.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.058363Z info Sidecar injection request for istioinaction/webapp-7685bcb84-***** (actual name not yet known) istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.070483Z info ads Incremental push, service webapp.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.171122Z info ads Push debounce stable[20] 6 for config ServiceEntry/istioinaction/catalog.istioinaction.svc.cluster.local and 1 more configs: 100.599791ms since last change, 197.380333ms since last push, full=true istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.171457Z info ads XDS: Pushing:2025-04-16T15:13:35Z/16 Services:13 ConnectedEndpoints:1 Version:2025-04-16T15:13:35Z/16 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.171746Z info ads CDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:24 size:23.7kB cached:21/23 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.172359Z info ads LDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:2.2kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.172421Z info ads RDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:538B cached:0/0 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.175413Z info ads EDS: PUSH request for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:2 size:107B empty:2 cached:0/2 filtered:21 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.601236Z info ads Incremental push, service catalog.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.701621Z info ads Push debounce stable[21] 1 for config ServiceEntry/istioinaction/catalog.istioinaction.svc.cluster.local: 100.348459ms since last change, 100.348209ms since last push, full=false istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.701700Z info ads XDS: Incremental Pushing:2025-04-16T15:13:35Z/16 ConnectedEndpoints:1 Version:2025-04-16T15:13:35Z/16 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.815287Z info ads Incremental push, service webapp.istioinaction.svc.cluster.local at shard Kubernetes/Kubernetes has no endpoints istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.917336Z info ads Push debounce stable[22] 1 for config ServiceEntry/istioinaction/webapp.istioinaction.svc.cluster.local: 101.964667ms since last change, 101.964333ms since last push, full=false istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:35.917476Z info ads XDS: Incremental Pushing:2025-04-16T15:13:35Z/16 ConnectedEndpoints:1 Version:2025-04-16T15:13:35Z/16 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.693106Z info ads ADS: new connection for node:catalog-6cf4b97d-b99jw.istioinaction-2 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.693413Z info ads CDS: PUSH request for node:catalog-6cf4b97d-b99jw.istioinaction resources:27 size:25.3kB cached:0/23 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.693667Z info serverca generating a certificate for [spiffe://cluster.local/ns/istioinaction/sa/catalog], signer: , ttl: 86400 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.697327Z info ads EDS: PUSH request for node:catalog-6cf4b97d-b99jw.istioinaction resources:23 size:3.7kB empty:0 cached:23/23 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.702268Z info ads LDS: PUSH request for node:catalog-6cf4b97d-b99jw.istioinaction resources:20 size:47.5kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:46.709294Z info ads RDS: PUSH request for node:catalog-6cf4b97d-b99jw.istioinaction resources:12 size:9.3kB cached:0/12 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:47.327114Z info ads CDS: PUSH for node:catalog-6cf4b97d-b99jw.istioinaction resources:27 size:25.3kB cached:0/23 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:47.327215Z info ads EDS: PUSH for node:catalog-6cf4b97d-b99jw.istioinaction resources:23 size:3.7kB empty:0 cached:23/23 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:47.328501Z info ads LDS: PUSH for node:catalog-6cf4b97d-b99jw.istioinaction resources:20 size:47.5kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:13:47.328874Z info ads RDS: PUSH for node:catalog-6cf4b97d-b99jw.istioinaction resources:12 size:9.3kB cached:0/12 ... # 배포 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-b99jw | catalog | istioinaction/catalog:latest | + +-------------------+--------------------------------+ | | istio-proxy | docker.io/istio/proxyv2:1.17.8 | + +-------------------+ + | | (init) istio-init | | +------------------------+-------------------+--------------------------------+ | webapp-7685bcb84-xrtt7 | 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 catalog-6cf4b97d-b99jw.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-2x2lh 1.17.8 istio-ingressgateway-996bc6bb6-5q245.istio-system Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-2x2lh 1.17.8 webapp-7685bcb84-xrtt7.istioinaction Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-7df6ffc78d-2x2lh 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 를 사용하지 않아서, 이 부분에 대한 통신 라우팅 효율이 있다. ## 아래 보이는 10.10.0.13, 10.10.0.14가 각각 catalog, webapp의 pod ip 이다. 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.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 kubectl get po -n istioinaction -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES catalog-6cf4b97d-b99jw 2/2 Running 0 6m51s 10.10.0.13 myk8s-control-plane <none> <none> webapp-7685bcb84-xrtt7 2/2 Running 0 6m50s 10.10.0.14 myk8s-control-plane <none> <none> # 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.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 # 현재 모든 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.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 # 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 들에게 전파가 될 것인데, 이를 최적화 하는 방법은 무엇이 있을까?
- 이제 외부(?)에서 호출 해보자!
# 터미널 : 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: Wed, 16 Apr 2025 15:26:07 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-ingressgateway → webapp(webapp → catalog 도 동일) 구간에 mTLS 미사용으로 오른쪽 Principals 식별 ID 없음# 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.
k8s ingress flow 도식화 부분이지만, istio-ingress 도 유사하게 동작함.- 자신의 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 변환
- 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.6wireshark 패킷으로 확인

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- 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- 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 파일 다운로드해서 확인하셔도 됩니다.
istio.pcap9.3KB
[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계층(애플리케이션) 라우팅 문제로부터 명확하게 분리해냈다.
- 이스티오 Gateway는 L4/L5 문제를 처리하고, VirtualService는 L7 문제를 처리한다.
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가 서명한 것인지 검증할 수 있다.
- 그런 다음, 클라이언트는 인증서 내 공개 키를 사용해 서버로 보내는 트래픽을 암호화 한다.
- 서버는 비밀 키로 트래픽을 복호화 할 수 있다.

- 기본 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 인증서가 들어 있는 쿠버네티스 시크릿 이름(위에서 만든 tls secret) 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 # 방금 생성한 gw의 tls secret에 의해 등록된 cert chain이 등록됨 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 12401901870532937100071839722145404108 2025-04-17T14:44:27Z 2025-04-16T14:42:27Z kubernetes://webapp-credential Cert Chain ACTIVE true 1049106 2041-06-29T12:49:32Z 2021-07-04T12:49:32Z ROOTCA CA ACTIVE true 213226523371779796801291257182983036466 2035-04-14T14:44:14Z 2025-04-16T14:44:14Z # SDS에 의해서 ingressgateway에 secrets이 등록된것을 확인 kubectl stern -n istio-system -l app=istiod istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.015808Z info ads Push debounce stable[32] 1 for config Gateway/istioinaction/coolstore-gateway: 101.667583ms since last change, 101.667208ms since last push, full=true istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.016109Z info ads XDS: Pushing:2025-04-16T15:35:37Z/19 Services:13 ConnectedEndpoints:3 Version:2025-04-16T15:35:37Z/19 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.017275Z info ads LDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:2 size:4.8kB istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.017731Z info ads RDS: PUSH for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:538B cached:0/0 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.029120Z info ads SDS: PUSH request for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:3.6kB cached:0/1 istiod-7df6ffc78d-2x2lh discovery 2025-04-16T15:35:37.029387Z info ads RDS: PUSH request for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:1 size:577B cached:0/0 filtered:1 # 호출 테스트 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 # webapp.istioinaction.io로 certfication과 함께 호출하니 성공! curl -v https://webapp.istioinaction.io:30005/api/catalog \ --cacert ch4/certs/2_intermediate/certs/ca-chain.cert.pem * Host webapp.istioinaction.io:30005 was resolved. * IPv6: (none) * IPv4: 127.0.0.1 * Trying 127.0.0.1:30005... * Connected to webapp.istioinaction.io (127.0.0.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 * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: C=US; ST=Denial; L=Springfield; O=Dis; CN=webapp.istioinaction.io * start date: Jul 4 12:49:32 2021 GMT * expire date: Jun 29 12:49:32 2041 GMT * common name: webapp.istioinaction.io (matched) * issuer: C=US; ST=Denial; O=Dis; CN=webapp.istioinaction.io * SSL certificate verify ok. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://webapp.istioinaction.io:30005/api/catalog * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: webapp.istioinaction.io:30005] * [HTTP/2] [1] [:path: /api/catalog] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: */*] > GET /api/catalog HTTP/2 > Host: webapp.istioinaction.io:30005 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/2 200 < content-length: 357 < content-type: application/json; charset=utf-8 < date: Wed, 16 Apr 2025 15:39:56 GMT < x-envoy-upstream-service-time: 6 < server: istio-envoy < * Connection #0 to host webapp.istioinaction.io left intact [{"id":1,"color":"amber","department":"Eyewear","name":"Elinor Glasses","price":"282.00"},{"id":2,"color":"cyan","department":"Clothing","name":"Atlas Shirt","price":"127.00"},{"id":3,"color":"teal","department":"Clothing","name":"Small Metal Shoes","price":"232.00"},{"id":4,"color":"red","department":"Watches","name":"Red Dragon Watch","price":"232.00"} 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를 사용해 암호화한다.
4.3.2 HTTP redirect to HTTPS (실습)‣- 모든 트래픽 항상 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"- 실행
# 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: Wed, 16 Apr 2025 15:42:14 GMT < server: istio-envoy < content-length: 0 < * Connection #0 to host webapp.istioinaction.io left intac- 이 리다이렉트는 클라이언트에게 API의 HTTPS 버전을 호출하라고 지시한다.
4.3.3 HTTP traffic with mutual TLS (실습)‣- 앞 절에서는 표준 TLS를 사용해 서버가 자신의 정체를 클라이언트에게 증명하게 했다.
- 그런데 만약 클러스터 외부의 트래픽을 받아들이기 전에 클라이언트가 누군지 검증하고 싶다면 어떻게 해야 할까?
- 단순 TLS 시나리오에서는 서버가 자신의 공개 인증서를 클라이언트에게 보냈고, 클라이언트는 서버의 인증서에 서명한 CA를 신뢰할 수 있음을 검증했다.
- 우리는 클라이언트가 자신의 공개 인증서를 보내게 하고 서버가 인증서를 신뢰하는지 검증하게 하고 싶다.
- 아래 그림은 mTLS 프로토콜에서 클라이언트와 서버 모두가 어떻게 서로의 인증서를 검증하는지, 즉 어떻게 서로 인증하는지 보여준다.
- 그리고 인증서는 트래픽을 암호화하는 데 사용한다.


- 기본 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 # Setigo와 같은 intermediate CA 이다! openssl x509 -in ch4/certs/2_intermediate/certs/ca-chain.cert.pem -noout -text # Secret 생성 : (적절한 CA 인증서 체인) 클라이언트 인증서 # application용(server용) privatekey와 certification 뿐만아니라, intermediate CA chain을 통해서 CA 인증서 체인을 만들어야 client에서 제공하는 key를 검증 할 수 있다!! 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-2x2lh discovery 2025-04-16T15:51:25.325519Z info ads SDS: PUSH request for node:istio-ingressgateway-996bc6bb6-5q245.istio-system resources:2 size:7.7kB cached:0/2 filtered:1... # mtls용 CA와 Cert Chain이 생성된것을 확인! 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 12401901870532937100071839722145404108 2025-04-17T14:44:27Z 2025-04-16T14:42:27Z 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 ROOTCA CA ACTIVE true 213226523371779796801291257182983036466 2035-04-14T14:44:14Z 2025-04-16T14:44:14Z 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 * Host webapp.istioinaction.io:30005 was resolved. * IPv6: (none) * IPv4: 127.0.0.1 * Trying 127.0.0.1:30005... * Connected to webapp.istioinaction.io (127.0.0.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 * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Request CERT (13): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Certificate (11): * (304) (OUT), TLS handshake, CERT verify (15): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: C=US; ST=Denial; L=Springfield; O=Dis; CN=webapp.istioinaction.io * start date: Jul 4 12:49:32 2021 GMT * expire date: Jun 29 12:49:32 2041 GMT * common name: webapp.istioinaction.io (matched) * issuer: C=US; ST=Denial; O=Dis; CN=webapp.istioinaction.io * SSL certificate verify ok. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://webapp.istioinaction.io:30005/api/catalog * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: webapp.istioinaction.io:30005] * [HTTP/2] [1] [:path: /api/catalog] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: */*] > GET /api/catalog HTTP/2 > Host: webapp.istioinaction.io:30005 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/2 200 < content-length: 357 < content-type: application/json; charset=utf-8 < date: Wed, 16 Apr 2025 15:53:07 GMT < x-envoy-upstream-service-time: 11 < server: istio-envoy < * Connection #0 to host webapp.istioinaction.io left intact [{"id":1,"color":"amber","department":"Eyewear","name":"Elinor Glasses","price":"282.00"},{"id":2,"color":"cyan","department":"Clothing","name":"Atlas Shirt","price":"127.00"},{"id":3,"color":"teal","department":"Clothing","name":"Small Metal Shoes","price":"232.00"},{"id":4,"color":"red","department":"Watches","name":"Red Dragon Watch","price":"232.00"}]- 이스티오 게이트웨이는 istio-proxy 를 시작하는 데 사용하는 istio-agent 프로세스에 내장된 SDS에서 인증서를 가져온다.
- SDS는 업데이트를 자동으로 전파해야 하는 동적 API이다. 서비스 프록시도 마찬가지다.
4.3.4 Serving multiple virtual hosts with TLS (실습)‣- 이스티오의 인그레스 게이트웨이는 동일한 HTTPS 포트(443)에서 자체 인증서와 비밀 키가 있는 여러 가상 호스트를 서빙할 수 있다.
- 이를 위해 동일 포트 및 프로토콜에 여러 항목을 추가한다.
- 예를 들어 자체 인증서와 키 쌍이 있는
webapp.istioinaction.io와catalog.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 kubectl get gw -n istioinaction coolstore-gateway -o yaml apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: annotations: ... generation: 4 name: coolstore-gateway namespace: istioinaction spec: selector: istio: ingressgateway servers: - hosts: - webapp.istioinaction.io port: name: https-webapp number: 443 protocol: HTTPS tls: credentialName: webapp-credential mode: SIMPLE - hosts: - catalog.istioinaction.io port: name: https-catalog number: 443 protocol: HTTPS tls: credentialName: catalog-credential mode: SIMPLE # 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 # catalog pod의 service port: number: 80 # catalog svc의 port(targetPort 아님) # 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 * Host catalog.istioinaction.io:30005 was resolved. * IPv6: (none) * IPv4: 127.0.0.1 * Trying 127.0.0.1:30005... * Connected to catalog.istioinaction.io (127.0.0.1) port 30005 * ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * CAfile: ch4/certs2/2_intermediate/certs/ca-chain.cert.pem * CApath: none * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): ... < [ { "id": 1, "color": "amber", "department": "Eyewear", "name": "Elinor Glasses", "price": "282.00" }, { "id": 2, "color": "cyan", "department": "Clothing", "name": "Atlas Shirt", "price": "127.00" }, { "id": 3, "color": "teal", "department": "Clothing", "name": "Small Metal Shoes", "price": "232.00" }, { "id": 4, "color": "red", "department": "Watches", "name": "Red Dragon Watch", "price": "232.00" } * Connection #0 to host catalog.istioinaction.io left intact ]%- 두 호출 모두 동일한 응답으로 성공했다. 그런데 발신자마다 어떤 인증서를 제시해야 하는지를 이스티오 인그레스가 어떻게 아는지가 궁금할 수 있다.
- 이 커넥션들이 열린 포트는 하나인데, 게이트웨이는 클라이언트가 접근하려는 서비스가 어느것이고 그 서비스에 해당하는 인증서가 어느 것인지를 알 수 있을까?
- 해답은 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 의 주소로 통제- 위 그림에서 보는 것처럼 TLS Packet의 Extension header를 보면 SNI가 있고, 해당 SNI의 Server Name을 통해서 어떤 server로의 요청인지 istio-ingressgateway(정확히는 envoy)가 알 수 있고 해당 요청을 gw, vs를 통해 upstream으로 전송 할 수 있게된다.
- https 트래픽 패킷 확인 : TLSv1에 Handshake Type(Client Hello) 에 Extension 에 SNI 부분에 도메인 확인
# NodePort 30005는 istio-ingressgateway 에 8443(https)로 전달 docker exec -it myk8s-control-plane tcpdump -i eth0 tcp port 30005 -nn -w /root/test3.pcap # 호출테스트 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 # docker cp myk8s-control-plane:/root/test3.pcap ./ wireshark
- 이 절에서는 인그레스 게이트웨이에서 서로 다른 가상 호스트들을 노출했고, 동일한 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 클라이언트로 접속해 명령어를 입력하면 그대로 되돌려준다.
- 배포 실행 - 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 catalog-6cf4b97d-b99jw 2/2 Running 0 43h tcp-echo-deployment-584f6d6d6b-w9wsr 2/2 Running 0 9s- 실습
# 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 protocol: TCP hosts: - "*" kubectl apply -f ch4/gateway-tcp.yaml -n istioinaction kubectl get gw -n istioinaction NAME AGE coolstore-gateway 44h echo-tcp-gateway 87s # 에코 서비스로 라우팅하기 위해 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"] 43h tcp-echo-vs-from-gw ["echo-tcp-gateway"] ["*"] 2s webapp-vs-from-gw ["coolstore-gateway"] ["webapp.istioinaction.io"] 43h # 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-w9wsr. In namespace istioinaction. With IP address 10.10.0.17. Service default. hello gylee # <- echo request hello gylee # <- echo response # telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit4.4.2 Traffic routing with SNI passthrough (실습)‣- 이번 절에서는 두 기능의 조합. 즉 이스티오 인그레스 게이트웨이에서 트래픽을 종료하지 않고 SNI 호스트네임에 따라 TCP 트래픽을 라우팅하는 방법을 다룬다.
- 게이트웨이가 하는 일은 SNI 헤더를 살펴보고 트래픽을 특정 백엔드로 라우팅하는 것 뿐이다. TLS 커넥션 종료는 그 후 백엔드에서 처리한다.
- 커넥션은 게이트웨이를 ‘통과 passthrough’ 하고, 처리는 게이트웨이가 아닌 실제 서비스가 담당하게 된다.
- 이런 방식은 서비스 메시에 참여할 수 있는 애플리케이션의 범위를 휠씬 넓혀준다.
- 데이터베이스, 메시지 큐, 캐시 등과 같은 TLS 상의 TCP 서비스는 물론, HTTPS/TLS 트래픽을 직접 처리하고 종료할 것이라고 예상되는 레거시 애플리케이션까지도 포함될 수 있다.
- Gateway 살펴보자
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를 종료하도록 설정한다. 즉, 인그레스 게이트웨이는 커넥션에 아무것도 하지 않아도 된다.
- 앞 절에서 했던 것처럼 게이트웨이에 인증서를 설정할 필요가 없다.
- 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- 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 에 도달한다.
- 라우팅을 더 명확하게 하기 위해 인증서가 다르고 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 # host를 기준으로 routing 한다 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: sni-passthrough-gateway spec: selector: istio: ingressgateway servers: - port: number: 31400 name: tcp-sni-1 protocol: TLS hosts: - "simple-sni-1.istioinaction.io" tls: mode: PASSTHROUGH - port: number: 31400 name: tcp-sni-2 protocol: TLS hosts: - "simple-sni-2.istioinaction.io" tls: mode: PASSTHROUGH kubectl apply -f ch4/sni/passthrough-sni-gateway-both.yaml -n istioinaction # VirtualService 설정 cat ch4/sni/passthrough-sni-vs-2.yaml # TLS Extension의 SNI가 simple-sni-2.istioinaction.io인것을 확인하고 simple-tls-service-2 svc로 routing apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: simple-sni-2-vs spec: hosts: - "simple-sni-2.istioinaction.io" gateways: - sni-passthrough-gateway tls: - match: - port: 31400 sniHosts: - simple-sni-2.istioinaction.io route: - destination: host: simple-tls-service-2 port: number: 80 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 { "name": "simple-tls-service-2", "uri": "/", "type": "HTTP", "ip_addresses": [ "10.10.0.16" ], "start_time": "2025-04-18T12:19:04.851719", "end_time": "2025-04-18T12:19:04.858520", "duration": "6.8ms", "body": "Hello from simple-tls-service-2!!!", # SNI에 맞게 simple-tls-service-2에서 response가 온것을 볼 수 있다. "code": 200 } # simple-tls-service-2 pod의 ip 확인 kubectl get po -n istioinaction -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES simple-tls-service-1-ffcc5bfd-8t6kz 2/2 Running 0 6m13s 10.10.0.15 myk8s-control-plane <none> <none> simple-tls-service-2-b84548475-cp75x 2/2 Running 0 3m53s 10.10.0.16 myk8s-control-plane <none> <none>- 이 요청을 simple-tls-sni-2 서비스가 서빙했음을 body 필드의 응답이 어떻게 알려주는지 주목하자.
4.5 Operational tips 운영 팁‣실습 참고https://netpple.github.io/docs/istio-in-action/Istio-ch4-4-tips4.5.1 Split gateway responsibilities 게이트웨이 책임 나누기 (실습)‣- 인그레스 게이트웨이를 여럿(다른 진입점) 둘 수 있다.
- 다른 진입점을 배포해 트래픽을 나누고 다양한 서비스 간 트래픽 경로를 격리하고 싶을 수도 있다.
- 어떤 서비스는 성능에 더 민감하거나, 고가용성이 더 필요하거나, 컴플라이언스를 이유로 격리돼야 할 수도 있다.
- 어떤 떄는 다른 팀에 영향을 주지 않도록 각 팀이 각자의 게이트웨이 및 설정을 소유하게 하고 싶을 수도 있다.


- 새 커스텀 게이트웨이 정의 예시
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 확인 # 새롭게 istioOpertor가 설치 되었다. -> 즉, 새로운 ingressgateway가 하나 생성되었다.(service, pod) kubectl get IstioOperator -A NAMESPACE NAME REVISION STATUS AGE istio-system installed-state 29m istioinaction installed-state-my-user-gateway-install 10s # istio-system에 있는 default ingressgateway가 아닌 새로운 ingressgateway가 생성됨! kubectl get deploy my-user-gateway -n istioinaction NAME READY UP-TO-DATE AVAILABLE AGE my-user-gateway 1/1 1 1 58s # 포트 확인 # 기존 istio-system ns에 있는 default ingressgatway svc nodeport로 30000(http), 30005(https), 30006(tcp)사용 중이었다. # 새로운 ingressgateway svc는 아래와 같이 30007 Nodeport만 사용 kubectl get svc my-user-gateway -n istioinaction -o yaml ... - name: tcp nodePort: 30007 port: 31400 protocol: TCP targetPort: 31400 ...- 실습 : my-user-gateway를 경유하여 TCP 통신을 해봅시다
# 이제 default gateway가 아닌 새로운 insgressgateway를 사용하는 gateway를 만들어보자! # 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 명세 # TCP 통신용 vs(위에서 echo server test한 vs임) 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로 접속 테스트 Trying ::1... Connected to localhost. Escape character is '^]'. Welcome, you are connected to node myk8s-control-plane. Running on Pod tcp-echo-deployment-584f6d6d6b-jlmhz. In namespace istioinaction. With IP address 10.10.0.18. Service default. gylee test # <-- echo request gylee test. # <-- echo response # telnet 종료하기 : 세션종료 Ctrl + ] > 텔넷 종료 quit4.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 # 새롭게 istiod에 의해 injected된 ingressgateway deployment NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-user-gateway-injected 1/1 1 1 3s # istiod에 의해 injected된 ingressgateway의 LB typ service. # 32577(http), 31258(https) nodePort를 노출 중이다. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-user-gateway-injected LoadBalancer 10.200.1.5 <pending> 80:32577/TCP,443:31258/TCP 3s NAME ENDPOINTS AGE endpoints/my-user-gateway-injected 10.10.0.19:443,10.10.0.19:80 3s4.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-5r5fz webapp 2025/04/18 12:48:54.122 [M] [router.go:1014] 192.168.65.1 - - [18/Apr/2025 12:48:54] "GET /api/catalog HTTP/1.1 200 0" 0.005034 curl/8.7.1 webapp-7685bcb84-5r5fz webapp ... # 초기 부팅 로그 출력 및 xDS 설정 관련 log 출력 kubectl stern -n istioinaction -l app=webapp -c istio-proxy + webapp-7685bcb84-5r5fz › istio-proxy webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174512Z info FLAG: --concurrency="2" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174524Z info FLAG: --domain="istioinaction.svc.cluster.local" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174526Z info FLAG: --help="false" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174527Z info FLAG: --log_as_json="false" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174528Z info FLAG: --log_caller="" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174529Z info FLAG: --log_output_level="default:info" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174529Z info FLAG: --log_rotate="" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174530Z info FLAG: --log_rotate_max_age="30" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174531Z info FLAG: --log_rotate_max_backups="1000" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174531Z info FLAG: --log_rotate_max_size="104857600" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174532Z info FLAG: --log_stacktrace_level="default:none" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174534Z info FLAG: --log_target="[stdout]" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174535Z info FLAG: --meshConfig="./etc/istio/config/mesh" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174535Z info FLAG: --outlierLogPath="" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174536Z info FLAG: --proxyComponentLogLevel="misc:error" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174537Z info FLAG: --proxyLogLevel="warning" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174537Z info FLAG: --serviceCluster="istio-proxy" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174538Z info FLAG: --stsPort="0" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174539Z info FLAG: --templateFile="" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174540Z info FLAG: --tokenManagerPlugin="GoogleTokenExchange" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174541Z info FLAG: --vklog="0" webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.174542Z info Version 1.17.8-a781f9ee6c511d8f22140d8990c31e577b2a9676-Clean ... webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.184219Z info Envoy command: [-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 --concurrency 2] webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.209704Z info xdsproxy connected to upstream XDS server: istiod.istio-system.svc:15012 webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.216181Z info ads ADS: new connection for node:webapp-7685bcb84-5r5fz.istioinaction-1 webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.216277Z info ads ADS: new connection for node:webapp-7685bcb84-5r5fz.istioinaction-2 webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.220656Z info cache generated new workload certificate latency=37.15875ms ttl=23h59m59.779348578s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.220670Z info cache Root cert has changed, start rotating root cert webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.220677Z info ads XDS: Incremental Pushing:0 ConnectedEndpoints:2 Version: webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.220690Z info cache returned workload trust anchor from cache ttl=23h59m59.779310328s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.220702Z info cache returned workload certificate from cache ttl=23h59m59.779298078s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.221062Z info cache returned workload trust anchor from cache ttl=23h59m59.778940578s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.221357Z info ads SDS: PUSH request for node:webapp-7685bcb84-5r5fz.istioinaction resources:1 size:4.0kB resource:default webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.221720Z info ads SDS: PUSH request for node:webapp-7685bcb84-5r5fz.istioinaction resources:1 size:1.1kB resource:ROOTCA webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:37.221741Z info cache returned workload trust anchor from cache ttl=23h59m59.778260328s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:38.697239Z info Readiness succeeded in 1.526902959s webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:00:38.697686Z info Envoy proxy is ready webapp-7685bcb84-5r5fz istio-proxy 2025-04-18T12:31:05.611163Z info xdsproxy connected to upstream XDS server: istiod.istio-system.svc:15012 ... #- 그러나 표준 출력 스트림으로 출력하도록 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 부분 추가됨 # application에서 /dev/stdout에 찍히는 log를 istio-proxy container에서 확인 할 수 있도록 설정!! # 예를들어 nginx app의 경우 보통 access log가 /dev/stdout으로 찍히도록 설정되어 console output으로 log가 찍혀 kubectl logs 혹은 kubectl stern으로 확인 할 수 있는것임! kubectl get cm -n istio-system istio -o yaml ... mesh: |- accessLogFile: /dev/stdout ... # istio-proxy container에서 application의 /dev/stdout로그를 확인 할 수 있음 # 즉, # 애플리케이션 호출에 대한 로그도 출력됨! kubectl stern -n istioinaction -l app=webapp -c istio-proxy webapp-7685bcb84-5r5fz istio-proxy [2025-04-18T12:53:07.365Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 3 2 "192.168.65.1" "beegoServer" "dbe3a986-65ba-486a-9f4e-ed84c247478b" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:45138 10.200.1.98:80 192.168.65.1:0 - default webapp-7685bcb84-5r5fz istio-proxy [2025-04-18T12:53:07.363Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 5 5 "192.168.65.1" "curl/8.7.1" "dbe3a986-65ba-486a-9f4e-ed84c247478b" "webapp.istioinaction.io:30005" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:54943 10.10.0.14:8080 192.168.65.1:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local 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

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

- 그러나 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 # IstioOperator 설정 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: pilot: enabled: false k8s: env: - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG value: "true" 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- 이 설정의 핵심은 PILOT_FILTER_GATEWAY_CLUSTER_CONFIG 기능 플래그이다. - Docs
- If enabled, Pilot will send only clusters that referenced in gateway virtual services attached to gateway
- pilot은 istioComponentSetSpec의 설정이다 → https://istio.io/latest/docs/reference/config/istio.operator.v1alpha1/#IstioComponentSetSpec
- 이 플래그는 게이트웨이 프록시 설정 내에 있는 클러스터를 해당 게이트웨이에 적용한 VirtualService 에서 실제로 참조하는 클러스터로만 좁힌다.
4.5.4. 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.
'Kubernetes' 카테고리의 다른 글
[IHOS 1기] 6주차 - 운영, 튜닝 (1) 2025.05.18 [IHOS 1기] 5주차 - 마이크로서비스 통신 보안 (1) 2025.05.11 [IHOS 1기] 4주차 - Observability (1) 2025.05.03 [IHOS 1기] 3주차 - Traffic control, Resilience (0) 2025.04.27 [IHOS 1기] 1주차 - Istio소개, 첫걸음 (0) 2025.04.12
