NGINX.COM
Web Server Load Balancing with NGINX Plus

近年来,Kubernetes 的关注度稳健增长日益获得业内青睐,许多企业纷纷在生产环境中采用该平台与微服务架构。然而,采用分布式服务也带来了新的挑战。充分理解和调试微服务应用程序的运行并非易事,尤其在服务数量多的情况下。想要识别故障或性能问题,我们需要分布式的追踪工具 —— 当数据在构成应用的微服务之间传输时,这个工具能够端到端地追踪请求。

OpenTracing 是一个用于分布式跟踪的规范和api集。在此前发表的一篇博文中,我们展示了如何使用OpenTracing社区创建的开源模块(nginx-opentracing 为 NGINX Open Source 和 NGINX Plus 代理和负载平衡的应用程序启用分布式跟踪。在撰写本文时,OpenTracing 提供 9 种编程语言的类库。

现在,为了在使用 NGINX 和 NGINX Plus Ingress Controllers for Kubernetes 时对集群中的流量执行负载均衡,我们还在 Kubernetes 集群中添加了对 HTTP 和 gRPC 请求 OpenTracing 的原生支持。

使用 OpenTracing 现有多种用例,此处我们将重点讨论如何使用请求上下文传播跟踪服务器端点。在分布式环境中,集群内的每个应用均被视为不同的服务器。设想一下,两个应用或服务都参与处理来自客户端的请求。例如,在下图所示拓扑结构中,应用 1 是一台 既可处理 HTTP 请求并将其重定向至应用 2 的 Web 服务器。这两个应用均在 NGINX Ingress Controller 所执行负载均衡的 Kubernetes 集群中运行,并启用了 OpenTracing。因此我们可在来自 Ingress Controller 的请求通过应用 1 并到达应用 2 时对其进行跟踪。

有关系统组件执行的任何操作(例如服务)的信息将会被 OpenTracing 所谓的 span 捕获。将这些 span 链接在一起,我们即可在信息穿过集群微服务的整个过程中对其进行识别和跟踪。

注:在撰写本文时,OpenTracing 仅在 NGINX Ingress Controller 的 edge 版本中可用。

将 OpenTracing 构建到 NGINX Ingress Controller 镜像中

搭配使用 OpenTracing 和我们的 Ingress Controller 需要将 OpenTracing 模块结合到 NGINX 或 NGINX Plus Ingress Controller 的 Docker 镜像中,并指定所用跟踪器。

在 Ingress Controller 的 GitHub 存储库中,我们为 NGINX 和 NGINX Plus 提供了单独的 Dockerfile。虽然二者均将开源 OpenTracing 模块整合到 Docker 镜像中,但处理的方式不同:

执行以下步骤:

  1. (可选)指定一个 Jaeger 默认以外的跟踪器。插件也适用于 Datadog、LightStep 和 Zipkin。使用不同的跟踪器需根据 OpenTracing 启用说明前提条件部分修改 Dockerfile。
  2. 遵照 Ingress Controller 存储库中的说明构建 Docker 镜像。在第 3 步中,需指定相应的 Dockerfile。

    NGINX:

    $ make clean
    $ make DOCKERFILE=DockerfileWithOpentracing PREFIX=YOUR-PRIVATE-REGISTRY/nginx-ingress

    NGINX Plus:

    $ make clean
    $ make DOCKERFILE=DockerfileWithOpentracingForPlus PREFIX=YOUR-PRIVATE-REGISTRY/nginx-plus-ingress
  3. 按照 Ingress Controller 存储库中的说明安装镜像。

    在运行说明第 3 节中的 kubectl apply 命令前,切记更新 YAML 文件,以指定已引入 OpenTracing 的新建镜像:

    containers:
       - image: IMAGE_WITH_OPENTRACING
  4. 部署 Jaeger 跟踪器

    本博文所使用的是 Jaeger 默认跟踪器。为简单起见,我们将其部署在集群内部,但也您也可以将其部署在集群外部。Ingress Controller Pod 和任何使用 OpenTracing 的 Pod 必须有访问跟踪器的权限。

    同样为简单起见,我们将使用 Jaeger 提供的一体化模版在集群中设立非生产 Jaeger 实例。最终适合开发环境的部署将创建一个 Jaeger Pod 及访问该 Pod 所需的服务,并在默认命名空间中设立最新 Jaeger 版本,其具有内存存储容量和有限功能。

    $ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml

    在生产环境中,我们推荐使用 Jaeger Operator 进行安装。获取更多有关 Jaeger 的信息,请访问 Jaeger 网站

    启用 OpenTracing

    以下针对 NGINX Ingress Controller 的 ConfigMap (nginx-config.yaml) 全局启用 OpenTracing,并在 data 部分中添加了三个新的 ConfigMap 键名:

    • opentracing 键为所有在集群中创建的 Ingress 资源启用 OpenTracing。
    • opentracing-tracer 键指定前往跟踪器库路径,并在您构建它时将跟踪器库下载并复制到 Ingress 控制器镜像中。
    • opentracing-tracer-config 键嵌入了跟踪器配置。service_name 字段定义与实际 span 相关的服务;reporter 字段指定跟踪器地址和端口(在此示例中是上一部分 Jaeger 服务一体式模板部署的的地址和端口)。sampler 字段指定客户端采样配置;为简单起见,我们将配置可供所有 trace 进行采样的“常量”采样器(常项)。
    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: nginx-config
      namespace: nginx-ingress
    data:
      opentracing: "True"
      opentracing-tracer: "/usr/local/lib/libjaegertracing_plugin.so"
      opentracing-tracer-config: |
        {
          "service_name": "nginx-ingress",
          "sampler": {
            "type": "const",
            "param": 1
          },
          "reporter": {
            "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"
          }
        }

    应用,只需运行:

    $ kubectl apply –f nginx-config.yaml

    部署示例应用

    为简单起见,我们将使用 NGINX 实例作为我们的后端应用,其中有两个不同应用——应用 1 配置将所有 HTTP 流量重定向到应用 2。

    两个应用均使用 ConfigMaps 和数据卷配置在 Pod 内运行的 NGINX 实例。 其中一个 ConfigMap 采用 NGINX 配置,另一个则用于为每个 NGINX 实例指定跟踪器配置(我们在每个应用中采用该配置,因为如前所述,应用 Pod 发送有关请求的信息需访问跟踪器)。

    部署应用 1

    以下为第一个应用的 YAML 清单 (app1.yaml):

    apiVersion: v1 kind: ConfigMap metadata: name: app1-config data: nginx.conf: |- user nginx; worker_processes 1; load_module modules/ngx_http_opentracing_module.so; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so /etc/jaeger-config.json; opentracing on; server { listen 80; server_name example.com; location / { opentracing_propagate_context; proxy_set_header Host $host; proxy_pass http://app2-svc:80; } } } --- apiVersion: v1 kind: ConfigMap metadata: name: jaeger-config-app1 data: jaeger-config.json: |- { "service_name": "app1", "sampler": { "type": "const", "param": 1 }, "reporter": { "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831" } } --- apiVersion: apps/v1 kind: Deployment metadata: name: app1 spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - name: app1 image: opentracing/nginx-opentracing ports: - containerPort: 80 volumeMounts: - name: config-app1 mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: true - name: config-jaeger mountPath: /etc/jaeger-config.json subPath: jaeger-config.json readOnly: true volumes: - name: config-app1 configMap: name: app1-config - name: config-jaeger configMap: name: jaeger-config-app1 --- apiVersion: v1 kind: Service metadata: name: app1-svc spec: ports: - port: 80 targetPort: 80 selector: app: app1

    运行以下命令部署第一个应用:

    $ kubectl apply –f app1.yaml

    部署应用 2

    以下为第二个应用的 YAML 清单 (app2.yaml):

    apiVersion: v1 kind: ConfigMap metadata: name: app2-config data: nginx.conf: |- user nginx; worker_processes 1; load_module modules/ngx_http_opentracing_module.so; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so /etc/jaeger-config.json; opentracing on; server { listen 80; server_name example.com; location / { opentracing_propagate_context; opentracing_tag app app2; return 200 "Success!\n"; } } } --- apiVersion: v1 kind: ConfigMap metadata: name: jaeger-config-app2 data: jaeger-config.json: |- { "service_name": "app2", "sampler": { "type": "const", "param": 1 }, "reporter": { "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831" } } --- apiVersion: apps/v1 kind: Deployment metadata: name: app2 spec: replicas: 1 selector: matchLabels: app: app2 template: metadata: labels: app: app2 spec: containers: - name: app2 image: opentracing/nginx-opentracing ports: - containerPort: 80 volumeMounts: - name: config-app2 mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: true - name: config-jaeger mountPath: /etc/jaeger-config.json subPath: jaeger-config.json readOnly: true volumes: - name: config-app2 configMap: name: app2-config - name: config-jaeger configMap: name: jaeger-config-app2 --- apiVersion: v1 kind: Service metadata: name: app2-svc spec: ports: - port: 80 targetPort: 80 selector: app: app2

    运行以下命令部署第二个应用:

    $ kubectl apply -f app2.yaml

    部署 Ingress 资源

    为了支持集群外部对应用 1 的访问,我们将创建以下 Ingress 资源 (opentracing-ingress.yaml):

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: opentracing-ingress
      annotations:
        nginx.org/location-snippets: |
          opentracing_propagate_context;
    spec:
      rules:
      - host: example.com
        http:
          paths:
          - path: /
            backend:
              serviceName: app1-svc
              servicePort: 80

    在这里,我们在 Ingress Controller 使用了 nginx.org/location-snippets 注释启用跟踪上下文传播代码段注释是将自定义代码添加到 Ingress Controller 中最终 NGINX 配置的一种有效方法。

    最后,我们通过运行以下命令来应用 Ingress 资源:

    $ kubectl apply -f opentracing-ingress.yaml

    验证应用 1 和应用 2 是否均正常运行:

    $ kubectl get pods
    NAME                      READY   STATUS    RESTARTS   AGE
    app1-68fd9db45c-szqpr     1/1     Running   0          53m
    app2-67c7788789-lvbgw     1/1     Running   0          53m

    跟踪请求

    现在,我们只需通过 Ingress Controller 向应用 1 发出请求,其中:

    • IC_HTTP_PORT 是 Ingress 控制器 Pod 中的 HTTP 端口(默认为 80)
    • IC_IP_ADDRESS 是 Ingress 控制器 Pod 的 IP 地址。如果使用 minikube,则采用 minikube IP 地址。
    $ curl --resolve example.com:IC_HTTP_PORT:IC_IP_ADDRESS http://example.com:IC_HTTP_PORT/ --insecure
    Success!

    查看 trace

    我们运行以下命令来访问 Jaeger 用户界面,其中 JAEGER_POD 是我们通过部署 Jaeger 跟踪器一体式模板所创建的 Pod 中的“类型/名称”值:

    $ kubectl port-forward JAEGER_POD 16686:16686

    每个请求都会创建一个新的 trace。您可在浏览器中在 http://localhost:16686 打开 Jaeger 用户界面去查看刚刚发出请求的 trace,在左栏 Service 字段中键入 nginx-egress,然后点击栏底部的 Find races 按钮。

    点击搜索窗口右栏中的 nginx-ingress,选择 trace。在打开的窗口(下图)中,我们看到我们请求的三种 span:棕色代表 Ingress Controller,蓝色代表应用 1,黄色代表应用 2。

    获取更多相关各个 span 的详细信息,包括 HTTP 状态代码、主机名、IP 地址和 Jaeger 版本, 请点击其 span 的按钮。

    结语

    在 Kubernetes 中为您的服务启用 OpenTracing 可支持您和您的团队及时了解应用运行的情况并快速排除故障。

    此外,从 Ingress Controller Pod 开始对请求的跟踪有助于您全面地了解来自集群外部请求在通过每个服务时的情况。

    了解 NGINX Plus 中的增强功能如何促进您的 Kubernetes 部署 —— 立即开始您的 30 天免费试用联系我们讨论您的用例

Hero image
Kubernetes:
从测试到生产

通过多种流量管理工具提升弹性、可视性和安全性

关于作者

Raúl Marrero Rodríguez

Software Engineer

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。