通过 Gateway API 和 mTLS 在 Kubernetes 中暴露 OTel Collector

本篇博文旨在演示如何使用 Kubernetes Gateway API双向 TLS (mTLS) 进行身份验证和加密,将运行在 Kubernetes 中的 OpenTelemetry (OTel) Collector 安全地暴露给外部世界。

随着可观测性在现代分布式系统中变得越来越重要,通过在一个或多个 Kubernetes 集群中部署的 OTel Collector 来集中遥测数据已成为一种常见做法。通常,运行在 Kubernetes 集群外部的服务或代理需要将数据发送这些 Collector。公开内部服务需要仔细考虑安全性和标准化。这就是 Kubernetes Gateway API 和 mTLS 发挥作用的地方。

通常,这种设置在您拥有集群外部的应用程序或工作负载,并且需要收集它们的遥测数据时很有用。一些例子:

  • 混合云/本地环境:运行在传统数据中心、不同云环境或 Kubernetes 集群外部的应用程序或服务器需要将其指标、跟踪或日志转发到您的集中式可观测性解决方案。
  • 多集群遥测聚合:在可能运行在多个 Kubernetes 集群的设置中,您可能指定一个集群来托管主要的 OTel Collector 部署。其他“分支”集群中的 Collector 将充当客户端,通过其外部端点将数据导出到此中央 Collector。例如,在多集群服务网格设置中,工作负载可能需要进行尾部采样。在这种情况下,配置了 尾部采样处理器并通过 Gateway 公开的中央 Collector 会聚合所有集群的 Span 以做出采样决策。
  • 边缘计算/物联网:部署在边缘的设备通常需要将其操作数据发送回中央平台。
  • 无服务器函数/PaaS:运行在无服务器平台(如 AWS Lambda、Google Cloud Functions)或集群外部的平台即服务产品中的应用程序可能需要导出 OTLP 数据。
  • 外部监控代理:需要连接到集群内共享 Collector 的第三方代理或本地运行的开发实例。
  • 客户端监控:来自浏览器和移动应用程序等外部客户端的遥测数据。虽然 mTLS 可能不用于从浏览器导出遥测数据,但最终必须提供 Collector。

先决条件

在开始之前,请确保您拥有以下内容:

  1. Kubernetes 集群:Minikube、Kind、Docker Desktop、Gardener 或云提供商的托管 Kubernetes 服务均可。
  2. kubectl已配置以与您的集群进行交互。
  3. helm已配置以安装 Helm Chart。
  4. Gateway API 实现:在本例中,我们将使用 Istio。其他实现,如 Contour、NGINX Gateway Fabric 等,也可以使用,可能只需进行少量配置调整。
  5. openssl用于生成证书的 OpenSSL CLI

什么是 Kubernetes Gateway API?

Kubernetes Gateway API 是旧版 Ingress API 的演进。它提供了一种更具表现力、面向角色且灵活的方式来管理集群的入站流量。 GAMMA 项目定义了 Gateway API 的实现。引入它是出于以下原因:

  • Ingress 的局限性:Ingress API 虽然有用,但已变得有限。它缺乏跨实现的标准化,并且路由功能也因不同实现而异。
  • 角色分离:Gateway API 分离了关注点
    • GatewayClass:定义了一种负载均衡器类型(例如,Istio、GKE LB)。由基础设施管理员管理。
    • Gateway:表示一个请求特定 GatewayClass 的负载均衡器实例。定义侦听器(端口、协议、TLS)。由集群操作员管理。它们也可以跨命名空间共享。
    • HTTPRouteGRPCRouteTCPRouteTLSRoute 等:定义应用级路由规则,并连接到 Gateway。由应用程序开发人员/所有者管理。
  • 可移植性:标准化的 API 定义旨在提高跨不同底层网关/服务网格实现的便携性。
  • 表现力:原生支持高级功能,如请求头操作、流量拆分、mTLS 配置、gRPC 路由等。

总而言之,与传统的 Ingress API 相比,Gateway API 为管理南北向流量提供了更健壮和标准化的模型。

mTLS - 简要介绍

双向 TLS (mTLS) 扩展了标准 TLS,要求客户端和服务器都必须提供和验证证书以进行相互身份验证。

标准 TLS(如网站上的 HTTPS)会向客户端验证服务器的身份。双向 TLS (mTLS) 更进一步:

  • 客户端验证服务器的身份(使用服务器的证书)。
  • 服务器验证客户端的身份(使用客户端的证书)。

双向 TLS 很重要,因为它提供了强大的身份验证、确保端到端加密,并符合零信任安全原则:

  • 确保只有受信任的客户端(拥有由受信任证书颁发机构 (CA) 签名的有效证书)才能连接到您公开的服务。

  • 经过身份验证的客户端和服务器(如 Gateway)之间的所有通信都经过加密。

  • 它通过要求双方进行验证来支持零信任模型,从不默认假定信任。

场景

以下是我们将在 Kubernetes 中公开 OTel Collector 部署所遵循的步骤。

  • 在 Kubernetes 中部署一个 OTel Collector,并配置一个简单的 OTLP/gRPC 接收器。
  • 生成一个自签名根 CA、一个服务器证书(用于 Gateway)和一个客户端证书(用于外部客户端)。
  • 配置一个 Kubernetes Gateway 资源,使其监听特定端口、终止 TLS 并要求提供客户端证书(mTLS)。
  • 配置一个 GRPCRoute,将来自 Gateway 的传入 gRPC 流量路由到内部 OTel Collector 服务。
  • 配置一个外部客户端(另一个 OTel Collector),使用客户端证书并通过 OTLP/gRPC 导出数据,并信任根 CA。
Scenario Diagram

设置

步骤 1:安装 Gateway API CRD。

默认情况下,Kubernetes 集群中不会安装 Gateway API。在撰写此博文时,最新版本是 v1.2。如果 Gateway API CRD 不存在,请安装它:

kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml

#To support GRPCRoute as of now. Would not be required once GRPCRoute CRD becomes GA.
kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.2.0" | kubectl apply -f -

步骤 2:生成自签名证书

为了在客户端和服务器之间设置 mTLS,我们需要一套证书。在此演示场景中,我们将使用自签名证书。在此演示中,我们将使用相同的 CA 来签名客户端和服务器。我们将使用 openssl 来创建证书。有关配置详情,请参阅 openssl 文档。

# Variables (adjust domain/names as needed)
export ROOT_CA_SUBJ="/CN=MyDemoRootCA"
# Use a relevant CN/SAN for the server/gateway. If clients connect via IP, include it.
# For DNS, use the hostname clients will use (e.g., otel.example.com)
export SERVER_HOSTNAME="otel-gateway.example.com"
export SERVER_SUBJ="/CN=${SERVER_HOSTNAME}"
export CLIENT_SUBJ="/CN=external-otel-client"

# 1. Create Root CA certificate and key
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj "${ROOT_CA_SUBJ}" -keyout rootCA.key -out rootCA.crt

# 2. Create Server CSR & sign with Root CA
openssl req -newkey rsa:4096 -nodes -keyout server.key -out server.csr -subj "${SERVER_SUBJ}" \
  -addext "subjectAltName = DNS:${SERVER_HOSTNAME}" # Add SAN for hostname validation

openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256 \
  -extfile <(printf "subjectAltName=DNS:${SERVER_HOSTNAME}") # Ensure SAN is in the final cert

# 3. Create Client CSR and sign with Root CA
openssl req -newkey rsa:4096 -nodes -keyout client.key -out client.csr -subj "${CLIENT_SUBJ}"

openssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out client.crt -days 365 -sha256

步骤 3:创建 otel-collector 命名空间

我们将在给定的命名空间中部署 OTel Collector 设置。之后,根据您使用的 Gateway/服务网格实现,您可能需要相应地配置命名空间。例如,使用 Istio 时,我们可以创建带有 istio-injection:enabled 的命名空间,以便 Istio 自动处理命名空间中部署的工作负载。

namespace.yaml:

# OpenTelemetry Collector Namespace
---
apiVersion: v1
kind: Namespace
metadata:
  name: otel-collector
  labels:
    istio-injection: enabled #Relavent only if you are using Istio.

应用此配置:

kubectl apply -f namespace.yaml

步骤 4:部署 OTel Collector(服务器)

让我们创建一个简单的 OTel Collector 部署和服务。在给定的配置中,OTel Collector 将打印传入的遥测数据。此配置将根据您的用例而改变。

otel-collector-server.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-conf
  namespace: otel-collector # Deploy collector in otel-collector namespace
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            # Note: No TLS config here. TLS terminates at the Gateway.
            endpoint: 0.0.0.0:4317

    processors:
      batch:

    exporters:
      # For demo purposes, log to stdout
      debug:

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [debug]
        metrics:
          receivers: [otlp]
          processors: [batch]
          exporters: [debug]
        logs:
          receivers: [otlp]
          processors: [batch]
          exporters: [debug]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector-server
  namespace: otel-collector # Deploy collector in otel-collector namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: otel-collector-server
  template:
    metadata:
      labels:
        app: otel-collector-server
    spec:
      containers:
        - name: otel-collector
          # Use a specific, recent version tag in production
          image: otel/opentelemetry-collector:latest
          ports:
            - containerPort: 4317 # OTLP gRPC
              name: otlp-grpc
          volumeMounts:
            - name: otel-collector-config-vol
              mountPath: /etc/otelcol
      volumes:
        - name: otel-collector-config-vol
          configMap:
            name: otel-collector-conf
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector-server-svc
  namespace: otel-collector
spec:
  selector:
    app: otel-collector-server
  ports:
    - name: grpc
      protocol: TCP
      port: 4317
      targetPort: 4317

应用此配置:

kubectl apply -f otel-collector-server.yaml

步骤 5:将证书存储为 Kubernetes Secrets

Gateway 需要访问服务器证书/密钥以及 CA 证书来验证客户端。

使用服务器证书和密钥创建一个 Secret。我们还将存储用于签名客户端的 CA 证书。在此演示中,为了方便起见,我们将其放在 otel-collector 命名空间中。

kubectl create -n otel-collector secret generic otel-gateway-server-cert --from-file=tls.crt=server.crt --from-file=tls.key=server.key --from-file=ca.crt=rootCA.crt

步骤 6:配置 Kubernetes Gateway API 资源

我们需要两个资源:GatewayGRPCRoute。为简单起见,在此演示中我们将资源保留在同一个 otel-collector 命名空间中。这会根据您的部署设置而改变。

otel-gateway-resources.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: otel-gateway
  namespace: otel-collector
spec:
  gatewayClassName: istio
  listeners:
    - name: otlp-grpc-mtls
      port: 4317
      protocol: HTTPS
      hostname: 'otel-gateway.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - group: '' # Core API group for Secrets
            kind: Secret
            name: otel-gateway-server-cert # The certificates that were uploaded as secrets in the provious step.
        options:
          # This structure might vary  depending on the implementation of your Gateway/Service Mesh
          # Please refer  documentation of the implementation installed.
          # For Istio, we set tls termination mode here
          gateway.istio.io/tls-terminate-mode: MUTUAL
---
# GRPCRoute is generally preferred for OTLP/gRPC
# Ensure GRPCRoute CRD (v1alpha2 or v1) is installed
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: otel-collector-grpcroute
  # Namespace where the backend service resides
  namespace: otel-collector
spec:
  # Link this route to our Gateway in the istio-system namespace
  parentRefs:
    - name: otel-gateway
      namespace: otel-collector # Namespace of the Gateway resource
      sectionName: otlp-grpc-mtls # Attach to the specific listener by name
  # Define routing rules for gRPC traffic
  rules:
    - backendRefs:
        - name: otel-collector-server-svc # Name of your internal OTel service
          namespace: otel-collector # Namespace of the backend service
          port: 4317 # Target port on the service
  • Gateway CRD 中,我们配置 gatewayclasslisteners。在这种情况下,我们配置一个 listener,包含必要的 porthostname。我们还配置了在此处终止的 tls。我们使用上传为 Secret 的证书。options 块用于配置任何 特定于实现的参数

  • GRPCRoute 中,我们选择一个 Gateway 和特定的 listener。我们还配置了路由将请求转发到的后端。在这种情况下,是 otel-collector-server-svc

应用 Gateway 配置:

kubectl apply -f otel-gateway.yaml

此时,Istio(或其他)Gateway 应配置为监听端口 4317(通常通过 LoadBalancer Service 暴露),使用指定的服务器证书和客户端 CA 要求 mTLS,并将有效的 gRPC 流量路由到 otel-collector-server-svc

要获取 Gateway 的详细信息,您可以运行以下命令:

# To get the Gateway hostname/IP
kubectl -n otel-collector get gateway otel-gateway -o jsonpath='{.status.addresses[0].value}'

# To get the port
kubectl -n otel-collector get gtw otel-gateway -o jsonpath='{.spec.listeners[?(@.name=="otlp-grpc-mtls")].port}'

您还可以查看为您的 Gateway 创建的 Kubernetes 服务。

kubectl -n otel-collector get svc

步骤 7:配置外部 OTel Collector(客户端)

为了测试该设置,请配置一个位于集群外部的 OTel Collector,使其使用 mTLS 向 Gateway 的外部端点发送数据。

在此演示中,客户端(OTel Collector)通过 Docker 在本地运行。

以下示例 otel-client-config.yaml 是一个简单的配置,用于抓取 CPU 和内存指标并将它们发送到服务器:

receivers:
  # Example: Receiver generating some data, e.g., host metrics
  hostmetrics:
    collection_interval: 10s
    scrapers:
      cpu:
      memory:
      # Add other scrapers as needed

processors:
  batch:

exporters:
  otlp/grpc:
    # IMPORTANT: Point to the Gateway's external IP/hostname and port
    # Replace <GATEWAY_EXTERNAL_IP_OR_HOSTNAME> with the actual address
    # It should match the hostname/SAN in the server certificate if using hostname
    # Use the hostname 'otel-gateway.example.com' if you have DNS configured at the gateway.
    endpoint: <GATEWAY_EXTERNAL_IP_OR_HOSTNAME>:4317

    tls:
      # We MUST enable TLS configuration for the client for mTLS
      insecure: false # Ensure server certificate is validated against the CA
      # Path to the CA certificate file to verify the server
      ca_file: /etc/cert/rootCA.crt
      # Path to the client's certificate file
      cert_file: /etc/cert/client.crt
      # Path to the client's private key file
      key_file: /etc/cert/client.key
      # Optional but recommended: Specify the server name for validation
      # Must match the CN or SAN in the server certificate (server.crt)
      # This is required if DNS is not configured at the Gateway and endpoint does not match the Gateway Hostname
      server_name_override: otel-gateway.example.com
service:
  pipelines:
    # Example sending host metrics
    metrics:
      receivers: [hostmetrics]
      processors: [batch]
      exporters: [otlp/grpc]
    # Add additional traces/logs pipelines if the client generates them

要运行客户端:

  1. <GATEWAY_EXTERNAL_IP_OR_HOSTNAME> 替换为您 Istio Gateway LoadBalancer 服务的实际外部 IP 地址或 DNS 名称。如果使用主机名(otel-gateway.example.com),请确保您的客户端机器能够将此主机名解析到正确的 IP(例如,通过 /etc/hosts 进行测试,或实际的 DNS)。

  2. 如果 endpoint 与服务器证书中的 SAN/CN 值不同,请使用 server_name_override

  3. 将生成的 rootCA.crtclient.crtclient.key 文件放在客户端 Collector 可访问的目录中。在此演示中,我们将其放在 certs 文件夹中。

  4. 运行客户端 Collector(根据需要调整路径和镜像标签):

    # Running command assuming certificates and config are in the current directory.
    
    docker run --rm -v $(pwd)/certs:/etc/cert/ \
               -v $(pwd)/otel-client-config.yaml:/etc/otelcol-contrib/config.yaml \
               otel/opentelemetry-collector-contrib:0.119.0
    

我们通过挂载 otel-client-config.yaml 和包含证书的 certs 文件夹来运行 opentelemetry-collector-contrib 的容器。

步骤 8:测试连接

  1. 检查服务器日志:查看 Kubernetes 中 otel-collector-server pod 的日志。如果配置了 debug 导出器,您应该会看到指示它正在接收数据批次的条目。

    kubectl logs -n otel-collector -l app=otel-collector-server -f
    
  2. 检查客户端日志:查看外部客户端 Collector 的日志(例如,Docker 容器输出)。您应该会看到诸如 Everything is ready. Begin running and processing data. 之类的消息。任何连接错误消息(例如,"certificate signed by unknown authority""bad certificate")或 connection refused 错误都表示存在问题。请检查:

    • Gateway IP/主机名可达性。
    • 防火墙规则。
    • 客户端使用的正确证书(ca_filecert_filekey_file)。
    • 匹配服务器证书 SAN/CN 的正确 server_name_override
    • Gateway 上的正确 mTLS 配置(包括客户端 CA 验证)。
    • Gateway 控制器日志(例如,istio-system 中的 istio-ingressgateway pod 日志),以排查 TLS 错误。
  3. 测试失败案例

    • 尝试在客户端的 otlp/grpc 导出器配置中包含 tls: 部分的情况下运行客户端。连接应被 Gateway 拒绝(可能是 TLS 握手失败或连接重置)。
    • 尝试在客户端配置中注释掉 ca_filecert_filekey_file。连接应失败。
    • 如果您有另一个由不同 CA 签名的证书,请尝试将其用作客户端证书。Gateway 应在 mTLS 握手期间拒绝它,因为它不是由受信任的 CA 签名的。

注意事项

在此演练中,某些步骤以特定方式进行,以便于运行和理解配置和场景。在生产环境中配置此设置时,必须注意这些问题:

  • 自签名证书在生产环境中不应使用。此外,用于客户端的CA 证书通常也与用于签名服务器证书的证书不同。
  • Kubernetes Gateway API 正在不断发展,越来越多的功能被添加到规范中。其中许多功能现已处于 Alpha/Beta 阶段,很快将正式可用,例如 GRPCRoute。请参阅最新的 Kubernetes Gateway API 文档。
  • Kubernetes Gateway API 旨在使配置尽可能具有可移植性和与实现无关。理想情况下,一旦规范成熟和发展,就会是这种情况。在此之前,配置的某些方面在不同实现之间会存在细微差别。例如,mTLS 在 Gateway 中的配置方式现在是这样的。
  • 在生产环境中运行时,您可能希望使用 specinfrastructure 块来配置特定于基础设施提供商的参数,例如 DNS
  • 生产环境中的设置将具有端到端加密通信。例如,在使用 Istio 时,集群中由 Istio 管理的命名空间中运行的所有组件都可以强制彼此通信。这是通过 PeerAuthentication 实现的。其他服务网格实现也会有类似的概念。
  • 在处理多个命名空间中的路由和网关时,您可能需要引用后端 services 等资源,以及其他命名空间中的配置。有关详细信息,请参阅 Gateway ReferenceGrant

其他 Gateway 实现

虽然我们使用了 Istio(gatewayClassName: istio),但 Gateway API 的核心优势在于其标准化潜力。如果您使用的是 Contour、NGINX Gateway Fabric、HAPROXY 等,GatewayGRPCRoute 资源定义在理想情况下会非常相似。主要区别可能在于:

  • gatewayClassName 的特定值。
  • 在如何配置特定于实现的特性或选项方面存在细微差别(例如,在 options 结构中指定客户端配置的确切语法)。
  • 底层 Gateway 控制器/代理的部署、管理和公开方式(例如,LoadBalancer 服务的名称和命名空间)。

请始终查阅您选择的具体 Gateway API 实现的文档,特别是关于 mTLS 配置的详细信息。

结论

Kubernetes Gateway API 比旧版 Ingress API 提供了重大改进,它提供了一种更强大、可移植、标准化的方法。它是一种更灵活、面向角色的入站流量管理方式。

通过将 Gateway API 与双向 TLS (mTLS) 相结合,您可以安全地公开 OpenTelemetry Collector 等内部服务,确保强大的客户端身份验证和加密通信。