CloudNet@에서 진행하는 Istio Study 2주차 3장 내용입니다.
📕 이번 Chapter에서 다루는 내용
- Istio의 Envoy 동작 이해
- Envoy 기능 알아보기
- Envoy 정적 설정
- Envoy Admin API로 분석 및 디버깅
다음은 Envoy의 2가지 중요한 원칙입니다.
애플리케이션에게 네트워크는 투명해야 한다. 네트워크 및 애플리케이션 문제가 발생할 때는 문제의 원인을 파악하기 쉬워야 한다.
The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem. - Envoy announcement
Envoy는 Proxy이므로, Proxy를 먼저 알아야 합니다.
아래 그림은 Client와 Server 사이에 위치하는 중개 구성 요소이다.

또한, 아래 그림처럼 여러 서비스에 대한 로그 밸런싱을 처리하여, 문제가 있는 백엔드 서비스를 우회하게 라우팅하거나 보호할 수 있다.

Envoy는 기본적으로 HTTP 1.1, HTTP 2, gRPC 등을 이해할 수 있고, 요청 수준 타임아웃, 재시도, 재시도별 타임아웃, 서킷 브레이커와 기타 복원력 동작을 추가할 수 있다.
기본 프로토콜 외에도 MongoDB, DynamoDB, AMQP 같은 비동기 프로토콜용 필터도 작성돼 왔다.
애플리케이션 트래픽이 Envoy를 통과하여 Telemetry도 수집이 가능하여, 특정 서비스의 처리량과 오류율도 확인할 수 있다.
Envoy는 애플리케이션의 외부에서 동작하므로, 개발자가 네트워크 문제를 고려하지 않아도 되도록 설계되었습니다.
Listeners
Routes
/catalog 에 일치하면 그 트래픽을 catalog 클러스터로 보내는 식이다.Cluster
Envoy가 L7 트래픽에 수행하는 작업을 개념적으로 설명한 것이다.

트래픽은 Downstream에서 Listener로 들어오며, Cluster 중 하나로 Routing, Cluster는 Upstream으로 보내는 역할이다.
아래는 Envoy가 제공하는 기능들이다.
설정 가능한 어댑터와 형식을 사용해 통계를 내보내는데, 아래는 지원 목록이다.
initial **x-b3*** 헤더를 만들 수도 있다.Envoy의 설정 파일을 이용해 리스너, 라우팅 규칙, 클러스터를 지정할 수 있다.
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
설정이 뜻하는 내용을 서술하자면,
Envoy는 다양한 설정을 xDS API로 동적으로 설정할 수 있다.
특정 API군을 사용해 다운타임이나 재시작 없이 설정을 실시간으로 업데이트할 수 있다.
올바른 디스커버리 서비스 API를 가리키는 간단한 부트스트랩 설정 파일만 있으면 나머지 설정은 동적으로 이뤄진다
아래는 동적 설정에 사용하는 API 목록이다.
이 API들을 통틀어 xDS 서비스라고 부른다.
한 가지 유념해야 할 점은 Envoy의 xDS API는 궁극적 일관성 eventual consistency 을 전제로 구축됐으며 궁극적으로는 올바른 구성으로 수렴한다는 것이다.
트래픽을 클러스터 foo로 라우팅하는 새 루트가 RDS로 업데이트됐는데, 이 클러스터 foo를 포함한 CDS 업데이트는 아직 수행되지 않았을 수 있다. 이 경우 CDS가 업데이트될 때까지 라우팅 오류가 발생할 수 있다.

이런 순서에 따른 경쟁 상태 race condition 를 해결하기 위해 도입한 것이 ADS 이다. Istio는 프록시 설정 변경을 위해 ADS를 구현한다.

아래 설정으로 리스너를 명시하지 않고, Envoy에게 LDS API를 통해 런타임에 올바른 리스너 설정 값을 찾으라 지시한다.
그렇지만 클러스터 하나는 명시적으로 설정하고 있는데, LDS API가 위치한 클러스터다.
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 }}]
간단한 httpbin 서비스를 통해 실습을 진행합니다.
필요한 Docker image를 다운 받습니다.
docker pull envoyproxy/envoy:v1.19.0
docker pull curlimages/curl
docker pull mccutchen/go-httpbin
실습 아키텍처

httpbin 서비스 실행
# mccutchen/go-httpbin 는 기본 8080 포트여서, 책 실습에 맞게 8000으로 변경
# docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin -p 8000:8000
docker run -d -e PORT=8000 --name httpbin mccutchen/go-httpbin
docker ps
# curl 컨테이너로 httpbin 호출 확인
docker run -it --rm --link httpbin curlimages/curl curl -X GET http://httpbin:8000/headers
# 결과
{
"headers": {
"Accept": [
"*/*"
],
"Host": [
"localhost:8000"
],
"User-Agent": [
"curl/8.7.1"
]
}
}
아래 설정은 15001포트로 리스너를 노출하고 트래픽을 클러스터 httpbin_service로 라우팅한다.
static_resources:
listeners:
- name: httpbin-demo
address:
socket_address: { address: 0.0.0.0, port_value: 15001 }
stat_prefix: ingress_http
...
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
Proxy를 호출하면, 연결된 httpbin 서비스로 전송되고 새로운 헤더도 추가된다.
# 터미널2
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/headers
{
"headers": {
"Accept": [
"*/*"
],
"Host": [
"httpbin"
],
"User-Agent": [
"curl/8.13.0"
],
"X-Envoy-Expected-Rq-Timeout-Ms": [
"15000" # 15000ms = 15초
],
"X-Forwarded-Proto": [
"http"
],
"X-Request-Id": [
"8d08bd8e-7899-42e1-bf74-7a3381a2494a"
]
}
}
두 번째 헤더인 X-Envoy-Expected-Rq-Timeout-Ms는 업스트림 서비스에 대한 힌트로, 요청이 15,000ms 후에 타임아웃될 것으로 기대한다는 의미다.
아래 정적 설정으로 15초를 1초로 변경할 수 있다.
- **match**: { prefix: "/" }
route:
auto_host_rewrite: true
cluster: httpbin_service
**timeout: 1s**
아래는 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
엔보이의 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 # 엔보이 통계(프로메테우스 레코드 형식)
httpbin 요청을 일부러 실패시켜 엔보이가 어떻게 요청을 자동으로 재시작하는지 살펴보자.
- match: { prefix: "/" }
route:
auto_host_rewrite: true
cluster: httpbin_service
retry_policy:
retry_on: 5xx # 5xx 일때 재시도
num_retries: 3 # 재시도 횟수
httpbin 호출 시에 HTTP 500 응답을 받으며, 결과를 보면 Envoy가 요청을 제시도하여 통계값이 3으로 표시된다.
(cluster.httpbin_service.upstream_rq_retry: 3)
docker run -p 15000:15000 --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple_retry.yaml)"
docker run -it --rm --link proxy curlimages/curl curl -X POST http://proxy:15000/logging?http=debug
# /stats/500 경로로 프록시를 호출 : 이 경로로 httphbin 호출하면 오류가 발생
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15001/status/500
# 호출이 끝났는데 아무런 응답도 보이지 않는다. 엔보이 Admin API에 확인
docker run -it --rm --link proxy curlimages/curl curl -X GET http://proxy:15000/stats | grep retry
...
cluster.httpbin_service.retry.upstream_rq_500: 3
cluster.httpbin_service.retry.upstream_rq_5xx: 3
cluster.httpbin_service.retry.upstream_rq_completed: 3
cluster.httpbin_service.retry_or_shadow_abandoned: 0
cluster.httpbin_service.upstream_rq_retry: 3
...
실습 종료
docker rm -f proxy && docker rm -f httpbin
보조 구성 요소의 필요성
아래 그림은 Istiod가 Kubernetes API를 사용해 VirtualService 등의 Istio 설정을 읽고 Proxy를 동적으로 설정하는 모습이다.



We'll call Envoy an Istio proxy