亚马逊AWS官方博客

AWS Distro for OpenTelemetry 的新功能 – 现已全面推出追踪支持

去年,在 re:Invent 之前,我们推出了 AWS Distro for OpenTelemetry 的公开预览版,这是由 AWS 支持的 OpenTelemetry 项目的安全分发。OpenTelemetry 提供工具、API 和 SDK 来检测、生成、收集和导出遥测数据,以更好地了解应用程序的行为和性能。昨天,上游 OpenTelemetry 宣布了其组件的追踪稳定性里程碑。今天,我很高兴与大家分享的是,AWS Distro for OpenTelemetry 现已全面推出追踪支持

使用 OpenTelemetry,您只需对应用程序进行一次检测,然后将追踪发送到多个监控解决方案。

您可以使用 AWS Distro for OpenTelemetry 来检测在 Amazon Elastic Compute Cloud (Amazon EC2)Amazon Elastic Container Service (Amazon ECS)Amazon Elastic Kubernetes Service (EKS)AWS Lambda 以及内部部署上运行的应用程序。该工具还支持在 AWS Fargate 上运行并通过 ECS 或 EKS 进行编排的容器。

您可以将 AWS Distro for OpenTelemetry 收集的追踪数据发送到 AWS X-Ray 以及合作伙伴目的地,例如:

您可以使用自动检测代理来收集追踪数据,而无需更改代码。如今,Java 和 Python 应用程序可以使用自动检测功能。Python 的自动检测支持目前仅涵盖 AWS 开发工具包。您可以结合使用 OpenTelemetry 开发工具包和其他编程语言 (例如 Go、Node.js 和 .NET) 对应用程序进行测试。

让我们看看针对 Java 应用程序的实际操作。

使用自动检测可视化 Java 应用程序的追踪
我创建了一个简单的 Java 应用程序,其中显示了我的 Amazon Simple Storage Service (Amazon S3) 存储桶列表和我的 Amazon DynamoDB 表:

package com.example.myapp;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

import java.util.List;

/**
 * Hello world!
 *
 */
public class App {

    public static void listAllTables(DynamoDbClient ddb) {

        System.out.println("DynamoDB Tables:");

        boolean moreTables = true;
        String lastName = null;

        while (moreTables) {
            try {
                ListTablesResponse response = null;
                if (lastName == null) {
                    ListTablesRequest request = ListTablesRequest.builder().build();
                    response = ddb.listTables(request);
                } else {
                    ListTablesRequest request = ListTablesRequest.builder().exclusiveStartTableName(lastName).build();
                    response = ddb.listTables(request);
                }

                List<String> tableNames = response.tableNames();

                if (tableNames.size() > 0) {
                    for (String curName : tableNames) {
                        System.out.format("* %s\n", curName);
                    }
                } else {
                    System.out.println("No tables found!");
                    System.exit(0);
                }

                lastName = response.lastEvaluatedTableName();
                if (lastName == null) {
                    moreTables = false;
                }
            } catch (DynamoDbException e) {
                System.err.println(e.getMessage());
                System.exit(1);
            }
        }

        System.out.println("Done!\n");
    }

    public static void listAllBuckets(S3Client s3) {

        System.out.println("S3 Buckets:");

        ListBucketsRequest listBucketsRequest = ListBucketsRequest.builder().build();
        ListBucketsResponse listBucketsResponse = s3.listBuckets(listBucketsRequest);
        listBucketsResponse.buckets().stream().forEach(x -> System.out.format("* %s\n", x.name()));

        System.out.println("Done!\n");
    }

    public static void listAllBucketsAndTables(S3Client s3, DynamoDbClient ddb) {
        listAllBuckets(s3);
        listAllTables(ddb);
    }

    public static void main(String[] args) {

        Region region = Region.EU_WEST_1;

        S3Client s3 = S3Client.builder().region(region).build();
        DynamoDbClient ddb = DynamoDbClient.builder().region(region).build();

        listAllBucketsAndTables(s3, ddb);

        s3.close();
        ddb.close();
    }
}

我使用 Apache Maven 打包应用程序。以下是管理依赖项的项目对象模型 (POM) 文件,例如我用来与 S3 和 DynamoDB 交互的 AWS SDK for Java 2.x:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <groupId>com.example.myapp</groupId>
  <artifactId>myapp</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myapp</name>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>软件 .amazon.awssdk</groupId>
        <artifactId>Bom</artifactId>
        <version>2.17.38</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>软件 .amazon.awssdk</groupId>
      <artifactId>s3</artifactId>
    </dependency>
    <dependency>
      <groupId>软件 .amazon.awssdk</groupId>
      <artifactId>dynamodb</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>com.example.myapp.App</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

我使用 Maven 创建一个包含所有依赖项的可执行 Java 归档 (JAR) 文件:

$ mvn clean compile assembly:single

要运行应用程序并获取追踪数据,我需要两个组件:

在一个终端中,我使用 Docker 运行 AWS Distro for OpenTelemetry Collector:

$ docker run --rm -p 4317:4317 -p 55680:55680 -p 8889:8888 \
         -e AWS_REGION=eu-west-1 \
         -e AWS_PROFILE=default \
         -v ~/.aws:/root/.aws \
         --name awscollector public.ecr.aws/aws-observability/aws-otel-collector:latest

收集器现在已准备好接收追踪数据并将其转发到监控平台。默认情况下,AWS Distro for OpenTelemetry Collector 会向 AWS X-Ray 发送追踪数据。我可以通过编辑收集器配置来更改导出程序或添加更多导出程序。例如,我可以按照文档将 OLTP 导出程序配置为使用 OLTP 协议发送遥测数据。在文档中,我还发现如何配置其他合作伙伴目的地。[[如果我们有合作伙伴部分的链接,那就太好了,我只能找到指向特定合作伙伴的链接]]

下载了 AWS Distro for OpenTelemetry 自动检测 Java 代理的最新版本。现在,我运行应用程序并使用代理来捕获遥测数据,而无需在代码中添加任何特定的工具。在 OTEL_RESOURCE_ATTRIBUTES 环境变量中,我为服务设置了名称和命名空间:[[X-Ray 是否正在使用 service.name 和 service.namespace? 我在服务地图中找不到它们]]

$ OTEL_RESOURCE_ATTRIBUTES=service.name=MyApp,service.namespace=MyTeam \
  java -javaagent:otel/aws-opentelemetry-agent.jar \
       -jar myapp/target/myapp-1.0-SNAPSHOT-jar-with-dependencies.jar

正如预期的那样,我获得了全局 S3 存储桶列表和区域中的 DynamoDB 表。

为了生成更多的追踪数据,我多次运行上一条命令。每次运行应用程序时,代理都会收集遥测数据并将其发送到收集器。收集器会缓冲数据,然后将其发送到配置的导出程序。默认情况下,它会向 X-Ray 发送追踪数据。

现在,我查看 AWS X-Ray 控制台中的服务地图,了解我的应用程序与其他服务的交互情况:

控制台屏幕截图。

它们在那里! 如果代码没有任何更改,我会看到应用程序对 S3 和 DynamoDB API 的调用。没有出现错误,所有的圆圈都为绿色。在圆圈中,我发现调用的平均延迟和每分钟的交易次数。

将跨度添加到 Java 应用程序
通过在追踪中提供更多信息,可以改善自动收集的信息。例如,我可能会在应用程序的不同部分与同一服务进行交互,在服务地图中分离这些交互将非常有用。这样,如果出现错误或高延迟,我就知道应用程序的哪一部分受到影响。

一种方法是使用跨度或区段。跨度表示一组逻辑上相关的活动。例如,listAllBucketsAndTables方法正在执行两个操作,一个使用 S3,另一个使用 DynamoDB。我想将它们组合到一个跨度中。使用 OpenTelemetry 的最快速途经是在方法中添加 @WithSpan 注解。因为方法的结果通常取决于它的参数,所以我还使用 @SpanAttribute 注释来描述方法调用中的哪些参数应该作为属性自动添加到跨度中。

@WithSpan
    public static void listAllBucketsAndTables(@SpanAttribute("title") String title, S3Client s3, DynamoDbClient ddb) {

        System.out.println(title);

        listAllBuckets(s3);
        listAllTables(ddb);
    }

为了能够使用 @WithSpan @SpanAttribute 注释,我需要将它们导入代码中,然后将必要的 OpenTelemetry 依赖项添加到 POM 中。所有这些更改都基于 OpenTelemetry 规范,不取决于我正在使用的实际实现,也不取决于将用来可视化或分析遥测数据的工具。我只需进行一次这些更改即可对应用程序进行检测。这不是很棒吗?

为了更好地了解跨度的工作原理,我创建了另一种以相反顺序运行相同操作的方法,首先列出 DynamoDB 表,然后列出 S3 存储桶:

@WithSpan
    public static void listTablesFirstAndThenBuckets(@SpanAttribute("title") String title, S3Client s3, DynamoDbClient ddb) {

        System.out.println(title);

        listAllTables(ddb);
        listAllBuckets(s3);
    }

应用程序现在正依次地运行两个方法 (listAllBucketsAndTableslistTablesFirstAndThenBuckets)。为简单起见,以下是插入工具的应用程序的完整代码:

package com.example.myapp;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

import java.util.List;

import io.opentelemetry.extension.annotations.SpanAttribute;
import io.opentelemetry.extension.annotations.WithSpan;

/**
 * Hello world!
 *
 */
public class App {

    public static void listAllTables(DynamoDbClient ddb) {

        System.out.println("DynamoDB Tables:");

        boolean moreTables = true;
        String lastName = null;

        while (moreTables) {
            try {
                ListTablesResponse response = null;
                if (lastName == null) {
                    ListTablesRequest request = ListTablesRequest.builder().build();
                    response = ddb.listTables(request);
                } else {
                    ListTablesRequest request = ListTablesRequest.builder().exclusiveStartTableName(lastName).build();
                    response = ddb.listTables(request);
                }

                List<String> tableNames = response.tableNames();

                if (tableNames.size() > 0) {
                    for (String curName : tableNames) {
                        System.out.format("* %s\n", curName);
                    }
                } else {
                    System.out.println("No tables found!");
                    System.exit(0);
                }

                lastName = response.lastEvaluatedTableName();
                if (lastName == null) {
                    moreTables = false;
                }
            } catch (DynamoDbException e) {
                System.err.println(e.getMessage());
                System.exit(1);
            }
        }

        System.out.println("Done!\n");
    }

    public static void listAllBuckets(S3Client s3) {

        System.out.println("S3 Buckets:");

        ListBucketsRequest listBucketsRequest = ListBucketsRequest.builder().build();
        ListBucketsResponse listBucketsResponse = s3.listBuckets(listBucketsRequest);
        listBucketsResponse.buckets().stream().forEach(x -> System.out.format("* %s\n", x.name()));

        System.out.println("Done!\n");
    }

    @WithSpan
    public static void listAllBucketsAndTables(@SpanAttribute("title") String title, S3Client s3, DynamoDbClient ddb) {

        System.out.println(title);

        listAllBuckets(s3);
        listAllTables(ddb);

    }

    @WithSpan
    public static void listTablesFirstAndThenBuckets(@SpanAttribute("title") String title, S3Client s3, DynamoDbClient ddb) {

        System.out.println(title);

        listAllTables(ddb);
        listAllBuckets(s3);

    }

    public static void main(String[] args) {

        Region region = Region.EU_WEST_1;

        S3Client s3 = S3Client.builder().region(region).build();
        DynamoDbClient ddb = DynamoDbClient.builder().region(region).build();

        listAllBucketsAndTables("My S3 buckets and DynamoDB tables", s3, ddb);
        listTablesFirstAndThenBuckets("My DynamoDB tables first and then S3 bucket", s3, ddb);

        s3.close();
        ddb.close();
    }
}

以下是更新后的 POM,其中包括额外的 OpenTelemetry 依赖项:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <groupId>com.example.myapp</groupId>
  <artifactId>myapp</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myapp</name>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>软件 .amazon.awssdk</groupId>
        <artifactId>Bom</artifactId>
        <version>2.16.60</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>软件 .amazon.awssdk</groupId>
      <artifactId>s3</artifactId>
    </dependency>
    <dependency>
      <groupId>软件 .amazon.awssdk</groupId>
      <artifactId>dynamodb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-extension-annotations</artifactId>
      <version>1.5.0</version>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-api</artifactId>
      <version>1.5.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>com.example.myapp.App</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

我使用这些更改编译我的应用程序,然后再运行几次:

$ mvn clean compile assembly:single

$ OTEL_RESOURCE_ATTRIBUTES=service.name=MyApp,service.namespace=MyTeam \
  java -javaagent:otel/aws-opentelemetry-agent.jar \
       -jar myapp/target/myapp-1.0-SNAPSHOT-jar-with-dependencies.jar

现在,让我们看一下使用这些注释提供的其他信息计算的 X-Ray 服务地图。

控制台屏幕截图。

现在我看到了这两种方法以及它们调用的其他服务。如果有错误或高延迟,我可以很容易地理解这两种方法是如何受到影响的。

在 X-Ray 控制台的追踪部分,我查看了一些追踪的原始数据。由于 title 参数带有 @SpanAttribute 注释,因此每条追踪在元数据部分中都有该参数的值。

控制台屏幕截图。

从 Lambda 函数收集追踪
前面的步骤适用于内部部署、EC2 上以及在容器中运行的应用程序。要收集追踪并将自动检测与 Lambda 函数结合使用,您可以使用 AWS 托管的 OpenTelemetry Lambda Layers (存储库中包含一些示例)。

将 Lambda 层添加到函数后,您可以使用环境变量 OPENTELEMETRY_COLLECTOR_CONFIG_FILE 将自己的配置传递给收集器。有关将 AWS Distro for OpenTelemetry 与 AWS Lambda 结合使用的更多信息,请参阅文档

可用性和定价
您可以使用 AWS Distro for OpenTelemetry 从在本地和 AWS 上运行的应用程序获取遥测数据。使用 AWS Distro for OpenTelemetry 不会产生额外费用。根据您的配置,您可能需要为作为 OpenTelemetry 数据目的地的 AWS 服务付费,例如 AWS X-RayAmazon CloudWatchAmazon Managed Service for Prometheus (AMP)

欲了解更多信息,您可以参加将于 10 月 7 日星期四 10:00 am PT / 1:00 pm EDT / 7:00 pm CEST 举行的网络研讨会。

立即使用 AWS Distro for OpenTelemetry 简化应用程序的检测并提高其可观察性。

Danilo