开始使用OpenTelemetry的详细指南

4,634 阅读10分钟

现在,有了更多关于OpenTelemetry的主要概念、架构和组件的背景,我们就可以开始追踪了。我们将使用两个API;一个用.NET构建,另一个用Python。它们将被设计成具有相同的端点和相同的目的。它将随机或按语言返回只存在于一种语言中的无法翻译的词。在这张图中,你可以遵循每个API的简单流程。

图8:不可翻译的API流程图

不同的编程语言范式给我们带来了不同的挑战,所以我选择了Python和.NET两种语言的例子--不是为了强调挑战,而是为了证明OpenTelemetry在不同堆栈中的一致性。请注意,所有的.NET例子都是针对ASP.NET Core的,对于.NET框架的配置可能有所不同。

配置

首先,想象一下,我们已经为我们将使用的任何语言创建了一个项目,其基本结构。你将通过运行以下命令来安装(Python)或添加必要的库(.NET)到你的项目。对于Python,如果使用setuptools,你可以把这个库作为一个安装要求加入。

Python:

$ pip install opentelemetry-distro

.NET。

$ dotnet add package OpenTelemetry --prerelease

$ dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease

$ dotnet add package OpenTelemetry.Extensions.Hosting --prerelease

这些命令也将添加OpenTelemetry的SDK和API作为依赖。

使用 OpenTelemetry 采集跟踪

在OTel中,我们可以在Tracer上执行跟踪操作。我们可以通过在全局Tracer Provider中使用GetTracer() ,返回一个可以用于追踪操作的对象来获得它。然而,当使用自动仪表并取决于语言时,这可能是不必要的。

用自动仪表添加一个简单的跟踪

并非所有的框架都提供自动仪表,但OpenTelemetry建议对那些提供自动仪表的框架使用它。它不仅可以节省代码行数,而且还可以为遥测提供一个基线,而且工作量很小。它的工作原理是将一个代理附加到正在运行的应用程序,并提取跟踪数据。在考虑自动仪表时,请记住它不像手动仪表那样灵活,而且只能捕获基本信号。

让我们来看看代码实现。下面,我们为自动仪表化的API做了基本设置。

Python

# app.py
from flask import Flask, Response

app = Flask(__name__)

@app.route("/")
@app.route("/home")
@app.route("/index")
def index():
   return Response("Welcome to Untranslatable!", status=200)
# Add more actions here

if __name__ == "__main__":
   app.run(debug=True, use_reloader=False)

.NET。

// Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var serviceName = "untranslatable-dotnet";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);
var resource = ResourceBuilder.CreateDefault().AddService(serviceName);

builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
   tracerProviderBuilder
   .SetResourceBuilder(resource)
   .AddSource(serviceName)
   .SetResourceBuilder(
       ResourceBuilder.CreateDefault()
           .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
   .AddAspNetCoreInstrumentation()
   .AddConsoleExporter()
).AddSingleton(TracerProvider.Default.GetTracer(serviceName));

var app = builder.Build();

//… Rest of the setup and actions here

在Python中,我们不需要在代码中添加任何东西来提取基本指标,但我建议使用FlaskInstrumentor,它增加了对flask特定功能的支持。你可以在实例化Flask后添加FlaskInstrumentor().instrument_app(app) ,并根据需要添加额外的配置。

在.NET中,我们需要配置必要的OpenTelemetry设置,如输出器、仪器库和常量。像在Python中一样,添加 OpenTelemetry.Instrumentation.AspNetCore包将提供特定于该框架的额外功能,添加到基本的仪表库中。ASP.NET Core的仪器库将自动从入站的HTTP请求中创建跨度和跟踪。

要用自动仪表运行我们的应用程序,并开始收集和导出遥测数据,请运行下面的命令。

Python

$ python3 -m venv .
$ source ./bin/activate
$ pip install .
$ opentelemetry-bootstrap -a install
$ opentelemetry-instrument \
   --traces_exporter console \
   --metrics_exporter console \
   flask run

.NET

$ dotnet run Untranslatable.Api.csproj

这些命令将启动仪器代理并设置特定的仪器库。现在,每当我们发送一个请求时,就会有一个跟踪信息打印到控制台。我们可以看到下面的输出例子。

Python:

{
 "name": "/words",
 "context": {
   "trace_id": "0x55072f6cc00531a489613e782942f75a",
   "span_id": "0x28135f1ccf37d85f",
   "trace_state": "[]"
 },
 "kind": "SpanKind.SERVER",
 "parent_id": null,
 "start_time": "2022-07-28T10:14:38.951442Z",
 "end_time": "2022-07-28T10:14:38.952775Z",
 "status": {
   "status_code": "UNSET"
 },
 "attributes": {
   "http.method": "GET",
   "http.server_name": "127.0.0.1",
   "http.scheme": "http",
   "net.host.port": 8000,
   "http.host": "127.0.0.1:8000",
   "http.target": "/words?language='es'",
   "net.peer.ip": "127.0.0.1",
   "http.user_agent": "python-requests/2.28.1",
   "net.peer.port": 58618,
   "http.flavor": "1.1",
   "http.route": "/words",
   "http.status_code": 200
 },
 "events": [],
 "links": [],
 "resource": {
   "telemetry.sdk.language": "python",
   "telemetry.sdk.name": "opentelemetry",
   "telemetry.sdk.version": "1.12.0rc2",
   "telemetry.auto.version": "0.32b0",
   "service.name": "unknown_service"
 }
}

.NET

Activity.TraceId:      	e5e958c3cf3cfb4819605c102cdcfeba
Activity.SpanId:       	b75dd2c4abb36412
Activity.TraceFlags:       	Recorded
Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore
Activity.DisplayName: words
Activity.Kind:    	Server
Activity.StartTime:   2022-07-28T10:10:42.9292690Z
Activity.Duration:	00:00:00.0004600
Activity.Tags:
	http.host: localhost:7104
	http.method: GET
	http.target: /words
	http.url: http://localhost:7104/words?language=pt
	http.user_agent: python-requests/2.28.1
	http.route: words
	http.status_code: 200
   StatusCode : UNSET
Resource associated with Activity:
	service.name: untranslatable-dotnet
	service.instance.id: d715f73f-3147-4708-aec6-98bd75a3ad77

注意,在Python中,由于没有添加任何配置,traces有很多空的未知值。

添加手动仪表

手动仪表是指在应用程序中添加额外的代码来开始和结束跨度,定义有效载荷,并添加计数器或事件。你可以使用客户端库和SDK,可用于许多编程语言。手动仪表和自动仪表应该携手并进,因为它们相互补充。用意的仪器化你的应用程序将增强自动仪器化,并提供更好、更深入的观察能力。下面的实现将为API的方法添加跟踪。

Python

# app.py
# the libraries you already had
from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor

resource = Resource(attributes={SERVICE_NAME: "untranslatable-python"})

tracer_provider = TracerProvider(resource=resource)
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)
trace.get_tracer_provider().add_span_processor(
   BatchSpanProcessor(ConsoleSpanExporter())
)

app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)

@tracer.start_as_current_span("welcome-message")
@app.route("/")
def index():
   return Response("Welcome to Untranslatable!", status=200)

@app.route("/words/random", methods=["GET"])
def word_random():
   with tracer.start_as_current_span("random-word"):
       data = read_json_from_file()
       words = json.dumps(data)
       random_word = random.choice(words)

   return Response(random_word, mimetype="application/json", status=200)

if __name__ == "__main__":
   app.run()

.NET与其他支持OpenTelemetry的语言不同。System.Diagnostics API实现了跟踪,重用了现有的对象,如ActivitySource Activity,以符合OpenTelemetry的要求。为了保持一致性,我使用了OpenTelemetry追踪 Shim,这样你就可以学习使用OpenTelemetry的概念。如果你想看一个使用Activities 的实现,你可以查看这个repo

.NET

// UntranslatableController.cs
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Mvc;
using OpenTelemetry.Trace;
using Untranslatable.Api.Controllers.Extensions;
using Untranslatable.Api.Models;
using Untranslatable.Data;
using Untranslatable.Shared.Monitoring;

namespace Untranslatable.Api.Controllers
{
   [ApiController]
   [Route("words")]
   [Produces("application/json")]
   public class WordsController : ControllerBase
   {
       private readonly IWordsRepository wordsRepository;
       private readonly Tracer tracer;

       public WordsController(Tracer tracer, IWordsRepository wordsRepository)
       {
           this.wordsRepository = wordsRepository;
           this.tracer = tracer;
       }

       [HttpGet]
       public ActionResult<UntranslatableWordDto> Get([FromQuery] string language = null, CancellationToken cancellationToken = default)
       {
           using var span = this.tracer?.StartActiveSpan("GetWordByLanguage");

           Metrics.Endpoints.WordsCounter.Add(1);
           using (Metrics.Endpoints.WordsTime.StartTimer())
           {
               var allWords = Enumerable.Empty<UntranslatableWord>();
               using (var childSpan1 = tracer.StartActiveSpan("GetByLanguageFromRepository"))
               {
                   childSpan1.AddEvent("Started loading words from file...");
                   allWords = wordsRepository.GetByLanguage(language, cancellationToken);
                   childSpan1.AddEvent("Finished loading words from file...");
               }
               using (tracer.StartActiveSpan("WordsToArray"))
               {
                   var result = allWords.Select(w => w.ToDto()).ToArray();
                   return Ok(result);
               }
           }
       }

       [HttpGet]
       [Route("random")]
       public ActionResult<UntranslatableWordDto> GetRandom(CancellationToken cancellationToken = default)
       {
           using var span = this.tracer?.StartActiveSpan("GetRandomWord");

           Metrics.Endpoints.WordRandom.Add(1);
           using (Metrics.Endpoints.WordRandomTime.StartTimer())
           {
               span.AddEvent("GetRandomWord");
               var word = wordsRepository.GetRandom(cancellationToken);
               span.AddEvent("Done select Random Word");

               return Ok(word.ToDto());
           }
       }
   }
}

存储和可视化数据

Jaeger是一个流行的开源分布式跟踪工具,最初由Uber的团队建立,然后在成为CNCF家族的一部分后开源。它是一个用于跟踪的后端应用程序,允许开发人员查看、搜索和分析跟踪。它最强大的功能之一是通过系统域中的服务可视化请求跟踪,使工程师能够快速确定复杂架构中的故障。

Jaeger提供建立在OpenTracing标准上的仪器库。使用Jaeger的特定导出器可以为观察你的应用程序提供一个快速的胜利。这里我们将使用 OTel 输出器和 OpenTelemetry 的 Jaeger 输出器来发送 OTel 追踪到 Jaeger 后端服务。我们已经看到 OTel 采集器是如何工作和设置的;下图显示了使用 Jaeger 的特定输出器管道的情况。

图9:OTel采集器管道

要开始可视化数据,你需要先设置Jaeger。你可以选择其他的设置,但我将使用一体化的图像,在一个容器中安装收集器、查询和Jaeger UI,使用内存作为默认存储(不用于生产环境)。这个docker-compose 文件设置了所有组件、网络、所需的端口和 OTel 采集器。这个例子中使用的端口是每个服务的默认端口。运行docker-compose 来启动容器。

version: "3.5"
services:
 jaeger:
   networks:
     - backend
   image: jaegertracing/all-in-one:latest
   ports:
     - "16686:16686"
     - "14268"
     - "14250"
 otel_collector:
   networks:
     - backend
   image: otel/opentelemetry-collector:latest
   volumes:
     - "/YOUR/FOLDER/otel-collector-config.yml:/etc/otelcol/otel-collector-config.yml"
   command: --config /etc/otelcol/otel-collector-config.yml
   environment:
     - OTEL_EXPORTER_JAEGER_GRPC_INSECURE:true
   ports:
     - "1888:1888"
     - "13133:13133"
     - "4317:4317"
     - "4318:4318"
     - "55670:55679"
   depends_on:
     - jaeger
networks:
 backend:

现在你应该有两个容器在运行,一个是 Jaeger,另一个是 OTel 收集器。

NAMES                 STATUS
otel_collector-1      Up 23 minutes
jaeger-1              Up 23 minutes

如果你导航到http://localhost:16686 ,你应该看到 Jaeger 的用户界面。在这里,你将能够探索由你的仪器产生的痕迹。

图10:Jaeger的用户界面

在左上方的下拉菜单中是我们创建的服务。当我们把traces导出到Jaeger时,服务会被添加到这个列表中。正如我所提到的,有两种方法可以将遥测数据导出到Jaeger,使用OTLP或者使用支持的协议之一直接导出到Jaeger。我们已经看到了如何配置OTLP收集器,所以现在我们所要配置的就是收集器来导出到Jaeger。

receivers:
 otlp:
   protocols:
     http:
       endpoint: 0.0.0.0:4318
     grpc:
       endpoint: 0.0.0.0:4317
processors:
 batch:
   timeout: 1s
exporters:
 logging:
   loglevel: info
 jaeger:
   endpoint: jaeger:14250
   tls:
     insecure: true
extensions:
 health_check:
 pprof:
   endpoint: :1888
 zpages:
   endpoint: :55679
service:
 extensions: [pprof, zpages, health_check]
 pipelines:
   traces:
     receivers: [otlp]
     processors: [batch]
     exporters: [logging, jaeger]

然而,如果设置收集器似乎令人生畏,或者如果你想使用OpenTelemetry从小处开始,直接向后端发送数据可以在没有收集器的情况下提供合理的快速结果。让我们从安装OpenTelemetry的Jaeger输出器开始。

Python

$ pip install opentelemetry-exporter-jaeger

.NET

$ dotnet add package OpenTelemetry.Exporter.Jaeger

同样,在Python中,我们在主机或虚拟环境中安装软件包,而对于.NET,我们直接将其作为项目的依赖项添加。对于Python来说,该软件包同时带有gRPC和Thrift协议。

Python

# app.cs
# ... other imports
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import SERVICE_NAME, Resource

resource = Resource(attributes={SERVICE_NAME: "untranslatable-python"})

jaeger_exporter = JaegerExporter(
   agent_host_name="localhost",
   agent_port=6831,
   collector_endpoint="http://localhost:14268/api/traces?format=jaeger.thrift",
)

tracer_provider = TracerProvider(resource=resource)
jaeger_processor = BatchSpanProcessor(jaeger_exporter)
tracer_provider.add_span_processor(jaeger_processor)

trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)
#... rest of initializations and actions

在安装完软件包后,你可以在TracerProvider 中设置出口器,这将在追踪开始时被配置。现在我们将对.NET做同样的事情。将NuGet包添加到项目中后,我们将配置输出器。在这里,我们将使用一个扩展方法--AddAspNetCoreInstrumentation --在IServiceCollection ,并绑定Jaeger导出器来启用仪器。

.NET

// Program.cs
// ... other imports and initializations
var serviceName = "untranslatable-dotnet";
var serviceVersion = "1.0.0";

var resource = ResourceBuilder.CreateDefault().AddService(serviceName);
builder.Services.AddOpenTelemetryTracing(b => b
   // ...  rest of setup code
   .AddJaegerExporter(o =>
   {
              o.AgentHost = "localhost";
       o.AgentPort = 6831;
       o.Endpoint = new Uri("http://localhost:14268/api/traces?format=jaeger.thrift");
   })
).AddSingleton(TracerProvider.Default.GetTracer(serviceName));
// ...  rest of initializations and actions

现在运行你的API并提出一些请求。进入Jaeger的用户界面,通过选择你指定的服务名称和你追踪的操作,你应该能够看到这些请求所产生的痕迹。下面,你可以看到在一个时间窗口内捕获的所有跟踪,以及一个跟踪的细节和相关跨度。

图11:所有追踪

**图12:**追踪细节

对于复杂的系统和架构,分布式跟踪是非常有价值的。你可以迅速开始直接导出到Jaeger的后端,并添加OpenTelemetry自动仪表以获得遥测数据。有了Jaeger,比起通过日志,更容易找到问题发生的地方,允许你监控交易,执行根本原因分析,优化性能和延迟,以及可视化服务依赖性。

将传统应用程序迁移到OpenTelemetry的常见陷阱

你的服务可能已经发出了与一些可观察性后端绑定的遥测数据。改变你的可观察性架构可能是非常痛苦的:

  • 重新测量是非常耗时的
  • 数据会发生变化
  • 遥测数据必须继续流动,不允许在系统中出现盲点
  • 追踪必须保持联系

为了按顺序并尽可能地无缝迁移,你可以使用OpenTelemetry Collector作为你的服务和你使用的后端之间的代理。采集器可以取代大多数遥测服务,消除处理和传输信号的单独服务的需要,使其成为多余的。它的遥测管道的灵活性让你可以配置任何兼容的后端或服务,同时保持你的代码不可知。

假设你想慢慢开始迁移你的所有应用程序。在这种情况下,采集器可以将任何输入转化为你需要的输出;你可以将一个应用程序转移到OpenTelemetry,并将数据发送到同一后端。注意,当改变仪表库时,产生的输出也会改变,所以你可能要调整你的仪表盘和警报系统。