2주차 - Envoy, Isto Gateway(1)

728x90

Istio’s data plane: Envoy Proxy

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

 

  • proxy 역할: Client와 Server 중간에 중계자 역할 수행
    • 프록시가 서비스 인스턴스 간에 로드 밸런싱을 처리
  • 엔보이 프록시는 특히 서비스 디스커버리, 로드 밸런싱, 헬스 체크 같은 기능을 제공하기 위해 애플리케이션 요청 경로 중간에 삽입할 수 있는 애플리케이션 수준 프록시이지만, 엔보이는 그 이상의 기능을 제공(방화벽 기능)
  • 기본 제공 프로토콜 외에도 다양한 프로토콜을 이해하도록 확장 가능
  • 엔보이는 애플리케이션 수준 프로토콜을 이해할 수 있고, 애플리케이션 트래픽이 엔보이를 거쳐 흐르는 덕분에 통과하는 요청에 대한 텔레메트리를 수집할 수 있음
  • 개발자가 네트워크 문제를 고려하지 않아도 되도록 설계

 

Envoy concepts as a high level 

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

 

주요 기능 

  • 서비스 디스커버리(SERVICE DISCOVERY)
    클라이언트 측 서비스 디스커버리 client-side service discovery를 구현하기 위해 런타임별로 전용 라이브러리를 사용할 필요 없이, 엔보이는 서비스 디스커버를 자동으로 수행할 수 있음
  • 로드 밸런싱(LOAD BALANCING)
    엔보이는 애플리케이션이 활용할 수 있는 고급 로드 밸런싱 알고리즘을 여러 가지 구현
  • 트래픽 및 요청 라우팅(TRAFFIC AND REQUEST ROUTING)
    엔보이는 HTTP 1.1과 HTTP 2 같은 애플리케이션 프로토콜을 이해할 수 있으므로 정교한 라우팅 규칙을 사용해 트래픽을 특정 백엔드 클러스터로 보낼 수 있음
  • 트래픽 전환 및 섀도잉 기능(TRAFFIC SHIFTING AND SHADOWING CAPABILITIES)
    엔보이는 비율 기반(즉, 가중치 적용) 트래픽 분할 splitting / 전환 shifting 을 지원
  • 네트워크 복원력(NETWORK RESILIENCE)
    엔보이에게 특정 종류의 복원력 문제를 맡길 수는 있지만, 그 파라미터를 설정하고 잘 조정하는 것은 애플리케이션의 책임이라는 점을 유의
  • 메트릭 수집을 통한 관찰 가능성(OBSERVABILITY WITH METRICS COLLECTION)
    엔보이의 목표 중 하나는 네트워크를 이해할 수 있게 만드는 것
    • 이 목표를 위해 엔보이는 다양한 메트릭을 수집

 

Configuring Envoy  

  • 엔보이는 JSON/YAML 형식 설정 파일로 구동
  • 설정 파일은 리스너, 루트, 클러스터뿐 아니라 Admin API 활성화 여부, 액세스 로그 저장 위치, 트레이싱 엔진 설정 등 서버별 설정도 지정

 

 

  • 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

 

  • Dynamic configuration 동적 설정
    • 엔보이는 특정 API군을 사용해 다운타임이나 재시작 없이 설정을 실시간으로 업데이트할 수 있다.
    • 올바른 디스커버리 서비스 API를 가리키는 간단한 부트스트랩 설정 파일만 있으면 나머지 설정은 동적으로 구성
      • 엔보이는 동적 설정에 다음과 같은 API를 사용
        • Listener discovery service (LDS): 엔보이가 자신이 어떤 리스너를 노출해야 한느지 쿼리할 수 있게 하는 API
        • Route discovery service (RDS): 리스너 설정의 일부로, 사용할 루트를 지정한다. 정적 설정이나 동적 설정을 사용할 때 LDS의 부분집합
        • Cluster discovery service (CDS): 엔보이가 클러스터 목록과 각 클러스터용 설정을 찾을 수 있는 API
        • Endpoint discovery service (EDS): 클러스터 설정의 일부로, 특정 클러스터에 어떤 엔드포인트를 사용해야 하는지 지정한다. CDS의 부분집합
        • Secret discovery service (SDS): 인증서를 배부하는 데 사용하는 API
        • Aggregate discovery service (ADS): 나머지 API에 대한 모든 변경 사항을 직렬화된 스트림으로 제공한다. 이 API 하나로 모든 변경 사항을 순차적으로 가져올 수 있음
      • 이 API들을 통틀어 xDS 서비스라고 부름
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 }}]

 

기본 실습

  • 엔보이는 C++로 작성돼 플랫폼에 맞게 컴파일
  • 엔보이를 시작하기 가장 좋은 방법은 도커를 사용해 컨테이너를 실행하는 것
    • 도커 이미지 가져오기 : citizenstig/httpbin 는 arm CPU 미지원
# 도커 이미지 가져오기
docker pull envoyproxy/envoy:v1.19.0
docker pull curlimages/curl
docker pull mccutchen/go-httpbin
docker pull citizenstig/httpbin

# 확인
docker images

 

  • httpbin은 호출하는 엔드포인트에 따라 호출할 때 사용한 헤더를 반환하거나, HTTP 요청을 지연시키거나, 오류를 발생시키는 등의 서비스를 제공
    • http://httpbin.org/headers 로 이동
    • httpbin 서비스를 시작하면, 그 다음에는 엔보이를 시작하고 모든 트래픽이 httpbin 서비스로 가도록 프록시를 설정
    • 클라이언트 앱을 시작해 프록시를 호출할 것

 

 

  1. httpbin 서비스 실행
    /headers 엔드포인트를 호출하는데 사용한 헤더가 함께 반환
    # 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"
        ]
      }
    }
  2. 엔보이 프록시를 실행
    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
  3. 엔보이 실행 : 프록시를 실행하려고 했지만 유효한 설정 파일을 전달 X
    #
    docker run -it --rm envoyproxy/envoy:v1.19.0 envoy
    
    [2025-04-21 18:21:39.356][1][info][main] [source/server/server.cc:342]   envoy.dubbo_proxy.serializers: dubbo.hessian2
    [2025-04-21 18:21:39.356][1][info][main] [source/server/server.cc:342]   envoy.access_loggers: envoy.access_loggers.file, envoy.access_loggers.http_grpc, envoy.access_loggers.open_telemetry, envoy.access_loggers.stderr, envoy.access_loggers.stdout, envoy.access_loggers.tcp_grpc, envoy.access_loggers.wasm, envoy.file_access_log, envoy.http_grpc_access_log, envoy.open_telemetry_access_log, envoy.stderr_access_log, envoy.stdout_access_log, envoy.tcp_grpc_access_log, envoy.wasm_access_log
    [2025-04-21 18:21:39.356][1][info][main] [source/server/server.cc:342]   envoy.filters.udp_listener: envoy.filters.udp.dns_filter, envoy.filters.udp_listener.udp_proxy
    [2025-04-21 18:21:39.356][1][info][main] [source/server/server.cc:342]   envoy.retry_host_predicates: envoy.retry_host_predicates.omit_canary_hosts, envoy.retry_host_predicates.omit_host_metadata, envoy.retry_host_predicates.previous_hosts
    [2025-04-21 18:21:39.358][1][critical][main] [source/server/server.cc:112] error initializing configuration '': At least one of --config-path or --config-yaml or Options::configProto() should be non-empty
    [2025-04-21 18:21:39.358][1][info][main] [source/server/server.cc:855] exiting
    At least one of --config-path or --config-yaml or Options::configProto() should be non-empty
  4. 수정하고 앞서 봤던 예제 설정 파일을 전달
    기본적으로 15001 포트에 단일 리스너를 노출하고 모든 트래픽을 httpbin 클러스터로 라우팅
    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
  5. 엔보이 재시작
    #
    cat ch3/simple.yaml
    
    # 터미널1
    docker run --name proxy --link httpbin envoyproxy/envoy:v1.19.0 --config-yaml "$(cat ch3/simple.yaml)"
    
    # 터미널2
    docker logs proxy
    [2025-04-12 08:25:15.455][1][info][config] [source/server/listener_manager_impl.cc:834] all dependencies initialized. starting workers
    [2025-04-12 08:25:15.456][1][info][main] [source/server/server.cc:804] starting main dispatch loo

  6. curl 로 프록시를 호출
    • 프록시를 호출했는데도 트래픽이 httpbin 서비스로 정확하게 전송
    • 새로운 헤더도 추가됐다.
      • X-Envoy-Expected-Rq-Timeout-Ms
      • X-Request-Id

    # 터미널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"
        ]
      }
    }

  • 예상 요청 타임아웃1초로 설정
    • 라우팅 규칙을 업데이트
    • simple_change_timeout.yaml
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  timeout: 1s

 

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


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

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

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

 

 

delay/0.5
delay/2

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

 

  • Envoy request retries 요청 재시도
    • httpbin 요청을 일부러 실패시켜 엔보이가 어떻게 요청을 자동으로 재시작하는지 살펴보자.
    • 먼저 retry_policy 를 사용하도록 설정 파일을 업데이트
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
                  retry_policy:
                      retry_on: 5xx  # 5xx 일때 재시도
                      num_retries: 3 # 재시도 횟수
#
docker rm -f proxy

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

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

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

  • 엔보이는 업스트림 클러스터 httpbin 호출할 때 HTTP 500 응답을 받았다.
  • 엔보이는 요청을 재시도했으며, 이는 통계값에 cluster.httpbin_service.upstream_rq_retry: 3 으로 표시

 

 

728x90