实验 13:使用 gRPC 访问日志服务(ALS)记录日志

本实验涵盖了如何配置 Envoy 使用独立的 gRPC 访问日志服务(ALS)。我们将使用一个基本的 gRPC 服务器,它实现了 StreamAccessLogs 函数,并将收到的日志从 Envoy 输出到标准输出。

让我们先把 ALS 服务器作为一个 Docker 容器来运行。

docker run -dit -p 5000:5000 gcr.io/tetratelabs/envoy-als:0.1.0

ALS 服务器默认监听端口为 5000,所以如果我们看一下 Docker 容器的日志,它们应该类似于下面的内容。

$ docker logs [container-id]
Creating new ALS server
2021/11/05 20:24:03 Listening on :5000

输出告诉我们,ALS 正在监听 5000 端口。

在 Envoy 配置中,有两个要求需要配置。首先是使用 access_log 字段的访问日志和一个名为 HttpGrpcAccessLogConfig 的日志类型。其次,在访问日志配置中,我们必须引用 gRPC 服务器。为此定义一个 Envoy 集群。

下面是配置记录器并指向名为 grpc_als_cluster的 Envoy 集群的片段。

...
access_log:
- name: envoy.access_loggers.http_grpc
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig
    common_config:
      log_name: "mygrpclog"
      transport_api_version: V3
      grpc_service: 
        envoy_grpc:
          cluster_name: grpc_als_cluster
...

下一个片段是集群配置,在这一点上我们应该已经很熟悉了。在我们的例子中,我们在同一台机器上运行 gRPC 服务器,端口为 5000。

...
  clusters:
    - name: grpc_als_cluster
      connect_timeout: 5s
      type: STRICT_DNS
      http2_protocol_options: {}
      load_assignment:
        cluster_name: grpc_als_cluster
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: 127.0.0.1
                  port_value: 5000
...

让我们把这两块放在一起,得出一个使用 gRPC 访问日志服务的 Envoy 配置示例。

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    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
          access_log:
          - name: envoy.access_loggers.http_grpc
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig
              common_config:
                log_name: "mygrpclog"
                transport_api_version: V3
                grpc_service: 
                  envoy_grpc:
                    cluster_name: grpc_als_cluster
          http_filters:
          - name: envoy.filters.http.router
          route_config:
            name: my_first_route
            virtual_hosts:
            - name: direct_response_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/404"
                direct_response:
                  status: 404
                  body:
                    inline_string: "404"
              - match:
                  prefix: "/"
                direct_response:
                  status: 200
                  body:
                    inline_string: "200"
  clusters:
    - name: grpc_als_cluster
      connect_timeout: 5s
      type: STRICT_DNS
      http2_protocol_options: {}
      load_assignment:
        cluster_name: grpc_als_cluster
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: 127.0.0.1
                  port_value: 5000

将上述 YAML 保存为 6-lab-2-grpc-als.yaml,并使用 func-e 启动 Envoy 代理。

func-e run -c 6-lab-2-grpc-als.yaml &

我们正在后台运行 Docker 容器和 Envoy,所以我们现在可以用 curl 向 Envoy 代理发送几个请求。

$ curl localhost:10000
200

代理的回应是 200,因为那是我们在配置中所定义的。你会注意到没有任何日志输出到标准输出,这是预期的。

要看到这些日志,我们必须看一下 Docker 容器的日志。你可以使用 docker ps 来获取容器 ID,然后运行 logs 命令。

$ docker logs 96f
Creating new ALS server
2021/11/05 20:24:03 Listening on :5000
2021/11/05 20:33:52 Received value
2021/11/05 20:33:52 {"identifier":{"node":{"userAgentName":"envoy","userAgentBuildVersion":{"version":{"majorNumber":1,"minorNumber":20},"metadata":{"fields":{"build.type":{"stringValue":"RELEASE"},"revision.sha":{"stringValue":"96701cb24611b0f3aac1cc0dd8bf8589fbdf8e9e"},"revision.status":{"stringValue":"Clean"},"ssl.version":{"stringValue":"BoringSSL"}}}},"extensions":[{"name":"envoy.matching.common_inputs.environment_variable","category":"envoy.matching.common_inputs"},{"name":"envoy.access_loggers.file","category":"envoy.access_loggers"},{"name":"envoy.access_loggers.http_grpc","category":"envoy.access_loggers"},{"name":"envoy.access_loggers.open_telemetry","category":"envoy.access_loggers"},{"name":"envoy.access_loggers.stderr","category":"envoy.access_loggers"},{"name":"envoy.access_loggers.stdout","category":"envoy.access_loggers"},{"name":"envoy.acc...
...

然后我们会注意到从 Envoy 代理发送到我们 gRPC 服务器的日志条目。gRPC 服务器中的代码很简单,只是将收到的值转换为一个字符串并输出。

下面是完整的 StreamAccessLogs 函数的样子。

func (s *server) StreamAccessLogs(stream v3.AccessLogService_StreamAccessLogsServer) error {
  for {
    in, err := stream.Recv()
    log.Println("Received value")
    if err == io.EOF {
      return nil
    }
    if err != nil {
      return err
    }
    str, _ := s.marshaler.MarshalToString(in)
    log.Println(str)
  }
}

在这一点上,我们可以从收到的数据流中解析具体的数值,并决定如何格式化它们以及将它们发送到哪里。