在不同的 Kubernetes 集群之间实现暴露 Collector 进行跨集群通信

在不同的 Kubernetes 集群之间实现暴露 Collector 进行跨集群通信

本文将演示如何在不同的 Kubernetes 集群中建立安全通信以保障用户数据的传输。目前,在OpenTelemetry Collector中,暴露 Collector 需要进行一系列的配置步骤。通过本文章,我们将展示使用基本身份验证(Basic Authentication)的方法以简化安全设置,无需使用密钥管理或其他第三方服务。

注:本文不涵盖CRD和依赖安装的详细信息。

概述

在将 Collector 公开访问时,首要的问题是通过 TLS 进行用户数据的安全传输。然而,对服务器进行身份验证同样至关重要,以防止未经授权的服务发送数据。

OpenTelemetry Collector 支持多种身份验证方法,目前最常用的有:

  1. TLS 身份验证
  2. OpenID Connect(OIDC-Authentication)
  3. 基本身份验证(HTTP Basic Authentication)

本文将重点介绍基本身份验证(HTTP Basic Authentication),以简化定义安全设置的方法,无需密钥管理或其他第三方服务。对于有关 TLS 配置的更多信息,请参考以下文章:

如果您有兴趣使用外部身份验证提供者,请参阅以下文章:

基本身份验证

HTTP 基本身份验证机制非常简单。HTTP 用户代理(如 Web 浏览器)在每个请求中提供用户名和密码的组合。当建立连接时,通过Authorization键在 HTTP 标头中包含传输的凭据。身份验证方法的值首先是basic,然后是编码后的凭据。注意,凭据的形式是“用户名:密码”。

下面的示例中,dXNlci0xOjEyMzQK用户名=user-1密码=1234的组合的编码值。要编码或解码 base64 值,可以使用以下命令:

# HTTP 标头键:值对
Authorization: Basic <credentials-base64-encoded>

# 例如: 用户: user-1, 密码: 1234
Authorization: Basic dXNlci0xOjEyMzQK

可以使用 base64 cli 工具 来创建自己的用户名和密码组合。

# 编码
$ echo "user-1:1234" | base64
dXNlci0xOjEyMzQK

# 解码
$ echo "dXNlci0xOjEyMzQK" | base64 -d
user-1:1234

数据流

下图展示了目标拓扑图。目标是通过专用的 Collector 将测试应用(test application)生成的跟踪传输到一个公开的集群。接收的 Collector 使用传输的基本身份验证凭据来检查发送者是否有权存储数据。最后,已传输的跟踪将存储在 Jaeger in-memory 中。

概述图

前提条件

界面和行为将来可能会发生变化。因此,在本设置中使用的版本如下所示:

远程集群配置

由于除 Jaeger 后端以外的所有组件都依赖于后续组件,我们首先部署后端。请参考以下 YAML 配置:

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: my-in-memory

接下来,我们使用 OpenTelemetryCollector CRD 创建一个 OpenTelemetry Collector。其中最重要的条目是 modeimage 和已配置的 basicauth 扩展。在下面的配置中,选择了 deployment 模式,以确保至少有一个 Collector Pod 可用来处理传入的信息。此外,还使用了默认的 Collector 镜像,但后面被contrib版本覆盖。这是因为核心版不包含 basicauth extension。此扩展配置了名称为basicauth/server的 basicauth,并在 otlp/basicauth 中进行了注册。作为 otlp exporter 端点,配置了 Jaeger in-memory 服务。

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: otel-collector-app
spec:
  mode: deployment
  image: otel/opentelemetry-collector-contrib:0.58.0
  config: |
    extensions:
      basicauth/server:
        htpasswd:
          inline: |
            <REPLACE: your backend credentials, e.g.: "user-1:1234">

    receivers:
      otlp/basicauth:
        protocols:
          grpc:
            auth:
              authenticator: basicauth/server

    exporters:
      otlp/jaeger:
        endpoint: my-in-memory-collector:4317
        tls:
          insecure: true
          insecure_skip_verify: true

    service:
      extensions: [basicauth/server]
      pipelines:
        traces:
          receivers: [otlp/basicauth]
          exporters: [otlp/jaeger]    

安装成功后,所选命名空间中应该有一个 Jaeger 后端和一个 OpenTelemetry Collector 的 Pod。

NAME                                            READY   STATUS    RESTARTS   AGE
my-in-memory-6c5f5f87c5-rnp99                   1/1     Running   0          4m
otel-collector-app-collector-55cccf4b7d-llczt   1/1     Running   0          3m

此外,还应该存在以下服务:

NAME                                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                    AGE
my-in-memory-agent                        ClusterIP   None            <none>        5775/UDP,5778/TCP,6831/UDP,6832/UDP                         7m
my-in-memory-collector                    ClusterIP   10.245.43.185   <none>        9411/TCP,14250/TCP,14267/TCP,14268/TCP,4317/TCP,4318/TCP    7m
my-in-memory-collector-headless           ClusterIP   None            <none>        9411/TCP,14250/TCP,14267/TCP,14268/TCP,4317/TCP,4318/TCP    7m
my-in-memory-query                        ClusterIP   10.245.91.239   <none>        16686/TCP,16685/TCP                                         7m
otel-collector-app-collector              ClusterIP   10.245.5.134    <none>        4317/TCP                                                    5m
otel-collector-app-collector-headless     ClusterIP   None            <none>        4317/TCP                                                    5m
otel-collector-app-collector-monitoring   ClusterIP   10.245.116.38   <none>        8888/TCP                                                    5m

最后,配置 cert-manager 以自动从 Let’s Encrypt 请求 TLS 证书,并将其提供给 Ingress 的 TLS 配置。以下 ClusterIssuerIngress 条目公开了 otel-collector-app-collector 服务。请注意,您需要替换 emailhost 字段的值。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
  namespace: cert-manager
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email-address-here@example.com # REPLACE
    privateKeySecretRef:
      name: letsencrypt
    solvers:
      - http01:
          ingress:
            class: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-otel
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: GRPC
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts:
        - your-host # REPLACE your domain endpoint, e.g., traces@example.com
      secretName: letsencrypt
  rules:
    - host: your-host # REPLACE your domain endpoint, e.g., traces@example.com
      http:
        paths:
          - pathType: Prefix
            path: '/'
            backend:
              service:
                name: otel-collector-app-collector
                port:
                  number: 4317

边缘集群配置

为了能够确定传输的跟踪的不同主机,跨集群传输中的跟踪将通过 k8sattributes processor 将识别元数据添加到 span 标签中。该 processor 可在 OpenTelemetry Collector contrib 版本中使用。首先,使用需要的权限创建一个 service account。如果您想了解有关 Kubernetes 元数据的更多信息,可以阅读本文 “Improved troubleshooting using K8s metadata"。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: attributes-role
rules:
  - apiGroups:
      - ''
    resources:
      - pods
    verbs:
      - get
      - list
      - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: attributes-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: attributes-role
subjects:
  - kind: ServiceAccount
    name: attributes-account
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: attributes-account

以下是边缘 Collector 的一些重要设置。使用 daemonset 作为部署模式,以确保每个节点上都有一个 Collector 实例。basicauth 扩展中包含了用于标识自身以连接到远程 Collector 的 usernamepassword。使用 k8sattributes processor 利用 Kubernetes 的 Kubernetes downward-api 提供更多的容器和节点的具体信息。未涵盖的信息包括集群的可用区和集群名称。为了能够在以后区分报告的跟踪,这些信息将在 resource processor 的帮助下手动插入。此外,OTLP exporter 的端点也已给出了占位符值,您需要将其替换为远程集群的域名。

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: otel-collector-app
spec:
  mode: daemonset
  image: otel/opentelemetry-collector-contrib:0.58.0
  serviceAccount: attributes-account
  env:
    - name: KUBE_NODE_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: spec.nodeName

  config: |
    extensions:
      basicauth/client:
        client_auth: # credentials must be consistent with those of the receiving collector.
          username: <REPLACE: your basicauth username, e.g.: "user-1">
          password: <REPLACE: your basicauth password, e.g.: "1234">

    receivers:
      otlp:
        protocols:
          grpc:

    processors:
      resource:
        attributes:
        - key: cloud.availability_zone
          value: <REPLACE: your availability zone, e.g.: "eu-west-1">
          action: insert
        - key: k8s.cluster.name
          value: <REPLACE: your cluster name, e.g.: "edge-cluster-1">
          action: insert
      k8sattributes:
        filter:
          node_from_env_var: KUBE_NODE_NAME

    exporters:
      otlp:
        endpoint: "<REPLACE: your domain endpoint, e.g.: "traces.example.com:443">"
        auth:
          authenticator: basicauth/client
      logging:

    service:
      extensions: [basicauth/client]
      pipelines:
        traces:
          receivers: [otlp]
          processors: [k8sattributes]
          exporters: [otlp,logging]    

安装成功后,应在边缘集群中创建一个名为 otel-collector-app-collectordaemonset,以确保每个集群节点上都有一个本地 Collector 实例在运行。

部署跟踪生成器以生成测试数据

apiVersion: apps/v1
kind: Deployment
metadata:
  name: trace-gen
spec:
  selector:
    matchLabels:
      app: trace-gen
  template:
    metadata:
      labels:
        app: trace-gen
    spec:
      containers:
        - name: trace-gen
          image: ghcr.io/frzifus/jaeger-otel-test:latest
          args:
            [
              '-otel.agent.host=otel-collector-app-collector',
              '-otel.agent.port=4317',
            ]
          env:
            - name: OTEL_SERVICE_NAME
              value: 'local-test-service'

测试

现在,在边缘集群中生成的跟踪应该已扩展为包含源数据。然后将它们传输到远程集群并存储在 Jaeger 后端中。Jaeger 提供了用于检查传输数据的用户界面。

一种简单的方法是通过端口转发到本地系统来访问 UI。

$ kubectl port-forward deployments/my-in-memory 16686
Forwarding from 127.0.0.1:16686 -> 16686
远程集群上的 Jaeger UI

结论

手动配置 IngressClusterIssuer 和客户端/服务器端的 OpenTelemetryCollector 等设置项。根据安装的 Kubernetes 组件,配置可能会有很大差异。总体来说,配置非常容易出错。在未来,我们希望通过 OpenTelemetry operator 简化 Collector 的暴漏。如果您对此开发感兴趣,可以关注 GitHub issue #902 以获取最新消息。

参考资料