18

我有一个从 Apache2 提供的 Django Web 应用程序,其中 mod_wsgi 在 docker 容器中运行,该容器在 Google Cloud Platform 的 Kubernetes 集群上运行,受 Identity-Aware Proxy 保护。一切都很好,但我想为所有请求发送 GCP Stackdriver 跟踪,而不是为我的项目中的每个视图编写一个。我使用 Opencensus 找到了处理这个问题的中间件。我浏览了这个文档StackdriverExporter,并且能够通过指定并将project_id参数作为我的项目的谷歌云平台传递来手动生成导出到我的项目中的 Stackdriver Trace 的跟踪Project Number

现在为了让所有请求都自动完成,我按照说明设置了中间件。在 settings.py 中,我将模块添加到INSTALLED_APPS, MIDDLEWARE,并设置了OPENCENSUS_TRACE选项字典。我还添加了OPENCENSUS_TRACE_PARAMS. 这适用于默认导出器“opencensus.trace.exporters.print_exporter.PrintExporter”,因为我可以在我的 Apache2 Web 服务器日志中看到 Trace 和 Span 信息,包括 Trace ID 和所有详细信息。但是,我想将这些发送到我的 Stackdriver Trace 处理器进行分析。

我尝试将EXPORTER参数设置为opencensus.trace.exporters.stackdriver_exporter.StackdriverExporter,只要您提供项目编号,该参数在从 shell 手动运行时有效。

设置成使用StackdriverExporter时,网页不会响应加载,健康检查开始失败,最终网页返回502错误,说我应该在30秒后重试(我相信Identity-Aware一旦检测到健康检查失败,代理就会产生这个错误),但服务器没有产生错误,并且没有访问日志或 Apache2 错误。

在 settings.py 中有另一个名为 的字典OPENCENSUS_TRACE_PARAMS,我认为需要它来确定导出器应该使用哪个项目编号。该示例已GCP_EXPORTER_PROJECT设置为None,并SERVICE_NAME设置为'my_service'

我需要设置哪些选项才能让导出器发送回 Stackdriver 而不是打印到日志?你知道我该如何设置吗?

设置.py

MIDDLEWARE = (
    ...
    'opencensus.trace.ext.django.middleware.OpencensusMiddleware',
)
INSTALLED_APPS = (
    ...
    'opencensus.trace.ext.django',
)

OPENCENSUS_TRACE = {
    'SAMPLER': 'opencensus.trace.samplers.probability.ProbabilitySampler',
    'EXPORTER': 'opencensus.trace.exporters.stackdriver_exporter.StackdriverExporter',  # This one just makes the server hang with no response or error and kills the health check.
    'PROPAGATOR': 'opencensus.trace.propagation.google_cloud_format.GoogleCloudFormatPropagator',
    # 'EXPORTER': 'opencensus.trace.exporters.print_exporter.PrintExporter',  # This one works to print the Trace and Span with IDs and details in the logs.
}

OPENCENSUS_TRACE_PARAMS = {
    'BLACKLIST_PATHS': ['/health'],
    'GCP_EXPORTER_PROJECT': 'my_project_number',  # Should this be None like the example, or Project ID, or Project Number?
    'SAMPLING_RATE': 0.5,
    'SERVICE_NAME': 'my_service',  # Not sure if this is my app name or some other service name.
    'ZIPKIN_EXPORTER_HOST_NAME': 'localhost',  # Are the following even necessary, or are they causing a failure that is not detected by Apache2?
    'ZIPKIN_EXPORTER_PORT': 9411,
    'ZIPKIN_EXPORTER_PROTOCOL': 'http',
    'JAEGER_EXPORTER_HOST_NAME': None,
    'JAEGER_EXPORTER_PORT': None,
    'JAEGER_EXPORTER_AGENT_HOST_NAME': 'localhost',
    'JAEGER_EXPORTER_AGENT_PORT': 6831
}

这是 Apache2 日志设置为使用时的示例(我美化了格式以提高可读性)PrintExporter

[Fri Feb 08 09:00:32.427575 2019]
[wsgi:error]
[pid 1097:tid 139801302882048]
[client 10.48.0.1:43988]
[SpanData(
  name='services.views.my_view', 
  context=SpanContext(
    trace_id=e882f23e49e34fc09df621867d753532,
    span_id=None,
    trace_options=TraceOptions(enabled=True),
    tracestate=None
  ),
  span_id='bcbe7b96906a482a',
  parent_span_id=None,
  attributes={
    'http.status_code': '200',
    'http.method': 'GET',
    'http.url': '/',
    'django.user.name': ''
  },
  start_time='2019-02-08T17:00:29.845733Z',
  end_time='2019-02-08T17:00:32.427455Z',
  child_span_count=0,
  stack_trace=None,
  time_events=[],
  links=[],
  status=None,
  same_process_as_parent_span=None,
  span_kind=1
)]

提前感谢您提供任何提示、帮助或故障排除建议!

编辑 2019-02-08 下午 6:56 UTC:

我在中间件中找到了这个:

# Initialize the exporter
transport = convert_to_import(settings.params.get(TRANSPORT))

if self._exporter.__name__ == 'GoogleCloudExporter':
    _project_id = settings.params.get(GCP_EXPORTER_PROJECT, None)
    self.exporter = self._exporter(
        project_id=_project_id,
        transport=transport)
elif self._exporter.__name__ == 'ZipkinExporter':
    _service_name = self._get_service_name(settings.params)
    _zipkin_host_name = settings.params.get(
        ZIPKIN_EXPORTER_HOST_NAME, 'localhost')
    _zipkin_port = settings.params.get(
        ZIPKIN_EXPORTER_PORT, 9411)
    _zipkin_protocol = settings.params.get(
        ZIPKIN_EXPORTER_PROTOCOL, 'http')
    self.exporter = self._exporter(
        service_name=_service_name,
        host_name=_zipkin_host_name,
        port=_zipkin_port,
        protocol=_zipkin_protocol,
        transport=transport)
elif self._exporter.__name__ == 'TraceExporter':
    _service_name = self._get_service_name(settings.params)
    _endpoint = settings.params.get(
        OCAGENT_TRACE_EXPORTER_ENDPOINT, None)
    self.exporter = self._exporter(
        service_name=_service_name,
        endpoint=_endpoint,
        transport=transport)
elif self._exporter.__name__ == 'JaegerExporter':
    _service_name = self._get_service_name(settings.params)
    self.exporter = self._exporter(
        service_name=_service_name,
        transport=transport)
else:
    self.exporter = self._exporter(transport=transport)

导出器现在命名为StackdriverExporter,而不是GoogleCloudExporter。我在我的应用程序中设置了一个名为GoogleCloudExporterinherits的类StackdriverExporter,并更新了我的 settings.py 以使用GoogleCloudExporter,但它似乎不起作用,我想知道是否还有其他代码引用这些旧命名方案,可能用于传输。我正在搜索源代码中的线索......这至少告诉我我可以摆脱 ZIPKIN 和 JAEGER 参数选项,因为这是在EXPORTER参数上确定的。

编辑 2019-02-08 晚上 11:58 UTC:

我放弃了 Apache2 来隔离问题,只是将我的 docker 映像设置为使用 Django 的内置网络服务器CMD ["python", "/path/to/manage.py", "runserver", "0.0.0.0:80"],它就可以工作了!当我访问该站点时,它会为每个请求将跟踪写入 Stackdriver Trace,Span 名称是正在执行的模块和方法。

不知何故 Apache2 不允许发送这些,但我可以在以 root 身份运行时从 shell 发送这些。我在问题中添加了 Apache2 和 mod-wsgi 标签,因为我有一种有趣的感觉,这与 Apache2 和 mod-WSGI 中的子进程分叉有关。由于 apache2 的子进程是沙盒的,因此无法创建子进程,或者这可能是权限问题?这似乎很奇怪,因为我知道它只是调用 python 模块,没有外部系统操作系统二进制文件。任何其他想法将不胜感激!

4

1 回答 1

0

我在使用带有 gevent 作为工人阶级的 gunicorn 时遇到了这个问题。要解决并使云跟踪工作,解决方案是像这样猴子补丁 grpc

from gevent import monkey
monkey.patch_all()

import grpc.experimental.gevent as grpc_gevent
grpc_gevent.init_gevent()

https://github.com/grpc/grpc/issues/4629#issuecomment-376962677

于 2020-06-01T20:33:01.373 回答