正如我们首席技术官 Werner Vogels 博士经常喜欢说的那样:“任何事物总会出错,只是时间问题。” 没有绝对不会出错的应用程序,也没有永远一直运行的应用程序,我们必须假设我们将需要处理故障,并且通常会在您最不希望它们发生的时候出现。特别是在云原生领域,系统越来越复杂,运维人员需要在问题出现时快速发现问题,快速响应并解决问题,这就需要一个可观测的系统。
优秀的可观测性系统不仅面向运维人员,同时也面向开发人员,它能采集和分析来自应用程序和基础设施的数据,随时随地了解系统运行状态,不仅回答“系统发生了什么”,还要回答“为什么会这样”。使用者不仅可以通过该系统快速发现问题并定位到根因,还能通过定期巡检可观测性系统指标,及时发现系统的潜在问题并进行优化,从而避免真正的事故发生,做到防范于未然。
我们的解决方案基于 AWS Distro for OpenTelemetry(ADOT)提供了一个完全托管的平台,利用 Amazon Managed Grafana,Amazon Managed Service for Prometheus,和 Amazon OpenSearch Service 来存储和分析三个核心可观察性维度:指标、日志和跟踪。这些核心维度可以进行关联,以加快问题解决或回顾性分析,并且可以进一步增强解决方案,以构建应用程序堆栈的自愈能力。
一、方案架构
本文重点关注在 Amazon EKS 环境下,如何用开源组件进行全栈可观测性实践。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry1.png)
- EKS 控制平面的日志自动传输到 CloudWatch Logs,通过 Subscription 传输到 OpenSearch Service
- 应用将日志通过 Fluent Bit 传输至 OpenSearch Service(或如图例通过 DataPrepper 统一发送日志到 OpenSearch)
- EKS 控制平面和应用容器的性能指标通过 ADOT Collector 采集并存入 Amazon Managed Prometheus(AMP)进行持久化存储和分析,并使用 Amazon Managed Service of Grafana(AMG)进行可视化
- 应用产生的跟踪数据通过 ADOT Collector 发送 Data Prepper,再采集到支持 OpenTelemetry 协议的 OpenSearch
- AMP 通过 Alert Manager 设置告警,并通过 SNS 发送至 Lambda,再调用飞书/企业微信/自建运维平台等接口做告警通知
本博客也将通过一个 Springboot Restful 的 Java 应用,深入浅出引导您在不变更原代码的前提下,借力 OpenTelemetry 标准,使用 Amazon 的托管开源服务进行快速部署和设置,实现跟踪,日志,应用和 JVM Metrics 指标等全方面观测。
二、前置条件
您可以参考微服务化可观测 Workshop 的 CloudFormation 模版创建必要的环境,或者在已有的环境基础上构建,本文假设您已经创建了以下资源。
- 创建 EKS 集群,并创建安装好至少一个可工作的 Node Group
- 为 EKS 集群安装必要的 Add-on,包括 AWS Load Balancer Controller
- 一个内网可访问的 Amazon OpenSearch 集群,样例为使用 FineGain Access 精细化权限管控
- 一台拥有 Admin 权限的 Cloud9 IDE 或者 EC2,并安装好 AWS CLI, EKSCTL,KUBECTL
下载样例代码
# Download lab repository
git clone https://github.com/xzp1990/observability-blog
三、Spring Boot 的测试程序
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry2.png)
为了更易展示可观测性,我们通过 Spring Boot 创建的两个简单 Restful API 服务:
- 客户通过网页访问/greeting 服务,/greeting 服务会向后端/person 服务确认该用户是否存在
- 如果用户名存在,则返回欢迎 member 界面,如果该用户不存在,则返回提示注册的消息
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry3.png)
查看样例代码
Greeting Controller(GreetingController.java)会调用另外一个微服务 person 去检查该用户是否已经是会员,根据返回结果向用户展示不同的回复。
#file:/observability-blog/sample-apps/12-greet/rest-service/src/main/java/com/example/restservice/GreetingController.java
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
String url = String.format("http://service-person.elb.svc.cluster.local/person?name=%s", name);
logger.info(String.format("restTemplate url: %s", url));
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);
if (result.equals("true")){
logger.info(String.format("The person %s is a member.", name));
return new Greeting(counter.incrementAndGet(), String.format(template_member, name));
}else{
logger.info(String.format("The person %s is NOT a member, recommend to register.", name));
return new Greeting(counter.incrementAndGet(), String.format(template_anonymous, name));
}
}
Person Controller(PersonController.java)的功能演示做一次数据的查询,如果该用户存在则返回 true,否则返回 false。
#file:/observability-blog/sample-apps/13-person/rest-service/src/main/java/com/example/restservice/PersonController.java
private static final List<String> personlist= Arrays.asList("jason", "tracy", "olivia", "henry");
@GetMapping("/person")
public String getFullName(@RequestParam(value = "name", defaultValue = "anonymous") String name) {
logger.info(String.format("Verifing whether[ %s ] is a member", name));
if (personlist.contains(name)){
return "true";
}else{
return "false";
}
}
构建并推送组件镜像到 ECR
- 获取或设置当前的帐号和区域
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
export AWS_REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
- 构建并推送组件 greet 镜像到 ECR
-----
#build the greet jar project:
cd ~/environment/observability-blog/sample-apps/12-greet/rest-service
./mvnw clean package
#创建一个 repo greet
aws ecr create-repository --repository-name greet
cd ~/environment/observability-blog/sample-apps/12-greet/
docker build -t greet .
docker tag greet:latest ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/greet
docker push ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/greet
- 构建并推送组件 person 镜像到 ECR
------
#build the person jar project:
cd ~/environment/observability-blog/sample-apps/13-person/rest-service
./mvnw clean package
#创建一个 repo person
aws ecr create-repository --repository-name person
cd ~/environment/observability-blog/sample-apps/13-person/
docker build -t person .
docker tag person:latest ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/person
docker push ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/person
四、安装 ADOT 插件
ADOT 提供了一个安全的、适用于生产环境的 OpenTelemetry(OTel)发行版,允许进行仪表化和收集指标和跟踪数据。ADOT Operator 使用准入 webhooks 来变更和验证 Collector 自定义资源(CR)请求。在 Kubernetes 中,Webhook 需要一个 TLS 证书,API 服务器配置为信任该证书。有多种方法可以生成所需的 TLS 证书,但默认方法是手动安装最新版本的 cert-manager,所以在安装 ADOT 之前,我们需先安装 cert-Manager。
安装 Cert Manager
- 使用最新的 cer-manager yaml 文件直接在 EKS 集群中进行安装
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.yaml
- 通过以下命令确认 cer-manager 是否已经安装成功
jasonxie:~/environment $ kubectl get pod -w -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-66b646d76-bnhsf 1/1 Running 0 15d
cert-manager-cainjector-59dc9659c7-px44d 1/1 Running 0 15d
cert-manager-webhook-7d8f555998-7n66g 1/1 Running 0 15d
利用 EKS 附加组件将 ADOT Operator 部署到您的 Amazon EKS 集群
- 使用以下命令为 ADOT 在您的集群中应用所需的权限:
kubectl apply -f https://amazon-eks.s3.amazonaws.com/docs/addons-otel-permissions.yaml
- 使用以下命令将 ADOT Operator 安装到您的 Amazon EKS 集群中:
aws eks create-addon --addon-name adot --cluster-name <your_cluster_name>
状态字段的值将为”CREATING”,直到完成为止。
- 使用以下命令验证 ADOT 是否已安装并运行:
aws eks describe-addon --addon-name adot --cluster-name <your_cluster_name>
- 您也可以通过 EKS Console 的“Add-ons”中查看和管理。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry4.png)
五、Metrics 指标数据可视化
创建 Amazon Prometheus Workspace
使用命令行创建 Amazon Prometheus Workspace:
aws amp create-workspace --alias observability-blog --tags env=lab,app=springboot
部署 ADOT Collector 去自动采集 Metrics
- 创建一个 IAM Roles for Service Account(IRSA)给 Collector Pod 使用
通过 IRSA 对 pod 进行 IAM 授权是在 EKS 集群上为资源提供权限的最佳方式。下面的命令将使用 AWS CloudFormation 创建一个名为 otel-collector 的 K8s Namespace,创建一个名为 amp-iamproxy-ingest-role 的 K8s 服务账号,并创建一个新的 IAM 角色,附加了 AmazonPrometheusRemoteWriteAccess 策略。
eksctl create iamserviceaccount \
--name amp-iamproxy-ingest-role \
--namespace otel-collector \
--cluster <your_cluster_name> \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess \
--approve \
--override-existing-serviceaccounts
--region <your_region>
- 通过样例创建 OTEL Collector 去自动采集 Metrics 指标
进入存放 Prometheus Collector 的配置文件 otel-collector-prometheus.yaml 所在的目录
cd ~/environment/observability-blog/sample-apps/01-otel-collector/kubernetes
查看该 OTEL 配置指标模版,此文件对 Kubernetes 中常用的 pod,service 等日志信息都做了配置,并通过 relabel_configs 设置让您可以在发布 deployment/services 时指定 annotation 来动态更新指标监听配置(如 path,port 和 schema)。
WORKSPACE_ID=$(aws amp list-workspaces --alias observability-blog | jq .workspaces[0].workspaceId -r)
AMP_ENDPOINT_URL=$(aws amp describe-workspace --workspace-id $WORKSPACE_ID | jq .workspace.prometheusEndpoint -r)
AMP_REMOTE_WRITE_URL=${AMP_ENDPOINT_URL}api/v1/remote_write
OTEL_COLLECTOR_NAMESPACE="otel-collector"
curl -O <otel-collector-prometheus.yaml>
sed -i -e s/AWS_REGION/$AWS_REGION/g otel-collector-prometheus.yaml
sed -i -e s^AMP_WORKSPACE_URL^$AMP_REMOTE_WRITE_URL^g otel-collector-prometheus.yaml
sed -i -e s^OTEL_COLLECTOR_NAMESPACE^$OTEL_COLLECTOR_NAMESPACE^g otel-collector-prometheus.yaml
kubectl apply -f ./otel-collector-prometheus.yaml
验证 OTEL Collector 是否正常运行
kubectl get all -n otel-collector
结果样例如:
jasonxie:~/environment/observability-blog/kubernetes $ kubectl get all -n otel-collector
NAME READY STATUS RESTARTS AGE
pod/observability-collector-746dd54ffd-rx2h7 1/1 Running 0 37s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/observability-collector-monitoring ClusterIP 10.100.13.19 <none> 8888/TCP 37s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/observability-collector 1/1 1 1 37s
NAME DESIRED CURRENT READY AGE
replicaset.apps/observability-collector-746dd54ffd 1 1 1 38s
jasonxie:~/environment/observability-blog/kubernetes $
创建 Amazon Grafana Workspace
- 参考官方文档通过 console 创建 Grafana Workspace
- a.在 AWS Console 中打开
Amazon Grafana
- b.点击
Create workspace
- c.输入 Workspace 的名字,如
demo-amg
,点击下一步
- d.如果没有 SAML(如 Akta),也可以使用 AWS SSO(AWS IAM Identity Center)进行验证登陆,可参考此处创建 AWS SSO 用户,这里假设已经创建好了该用户
- e.“Data Sources”那里需要选择“Amazon Managed Service for Prometheus“作为数据来源
- f.选择创建
Create workspace
,并等待创建成功
- g.添加 Grafana 登录管理员用户。这里以添加 AWS SSO 用户为例。等待上一步执行成功后,点击进入该 workspace,在“Authentication”一栏里,点击“Configure users and user groups“,添加已经创建好的 AWS SSO 用户。之后再点击“Action“ → ”Make Admin”,把该用户设置为管理员
- 添加已经创建好的 Prometheus 的 data source
- a.在 Grafana 中间输入 aws,选择“AWS Data Source”
- b.选择 AMP 所在的区域,并点选上一步创建的 AMP observability-blog
- c.点击“Add 1 Data Source”添加
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry5.png)
- 3.导入一个公共的 Grafana 仪表板,以便可以可视化来自 Kubernetes 环境的指标
- a.入口:在 Dashboard 页面,点击右边 New → Import 导入模版
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry6.png)
- b.加载 3119 模版。在导入页面中,在“通过com导入”文本框中输入3119,然后点击加载
- c.选择数据源。在底部的下拉菜单中选择 Amazon Managed Service for Prometheus 数据源,然后点击导入
完成后,您将能够通过 Amazon Managed Service for Prometheus 数据源查看显示来自 EKS 集群的 Grafana 仪表板,如下所示
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry7.png)
六、跟踪和日志数据采集准备
因为当前 OTEL Collector 不能直接持久化数据到 Opensearch 中,所以需要用到 Data Prepper 去把 Trace 跟踪数据进行转储到 OpenSearch。考虑到配置的易用性,也把 FluentBit DaemonSet 监听应用日志的目标指向 Data Prepper,让其统一转储到 OpenSearch。
安装 Data Prepper
- 进入“02-data-prepper” 目录
cd /observability-blog/sample-apps/02-data-prepper/kubernetes
- 在 data-prepper.yaml 文件中,搜索以下管道:raw-pipeline(第 25 行),service-map-pipeline(第 37 行)和log-pipeline(第 50 行),配置已经创建好的 OpenSearch Endpoint 地址,用户名和密码,在生产环境推荐使用 IAM Role for Service Account(IRSA)去进行授权,去避免需要用到明文的密码
...
raw-pipeline #Line 25
...
sink: #Line 31
- opensearch:
hosts: [ "https://__AOSDomainEndpoint__" ]
username: "__AOSDomainPassword__"
password: "__AOSDomainUserName__"
...
service-map-pipeline #Line 36
...
sink: #Line 43
- opensearch:
hosts: [ "https://__AOSDomainEndpoint__" ]
username: "__AOSDomainPassword__"
password: "__AOSDomainUserName__"
...
log-pipeline #Line 49
...
sink: #Line 61
- opensearch:
hosts: [ "https://__AOSDomainEndpoint__" ]
username: "__AOSDomainPassword__"
password: "__AOSDomainUserName__"
index: sample_app_logs
- 创建 Data Prepper 的相关服务:
# kubectl apply -f data-prepper.yaml
namespace/data-prepper created
configmap/data-prepper-config created
service/data-prepper created
service/data-prepper-metrics created
deployment.apps/data-prepper created
安装 FluentBit DaemonSet 采集应用日志数据
- 进入“00-fluentBitr” 目录
cd /observability-blog/sample-apps/00-fluentBit/kubernetes
- 在 fluentbit.yaml 文件中已经配置好了 traceInfo 去识别日志中的 Trace ID 和 Span ID,这里无需修改
...
#Line 153
[PARSER]
Name traceInfo
Format regex
Regex trace_id=(?<traceId>[^ ]+) span_id=(?<spanId>[^ ]+) resource.service.name=(?<serviceName>[^]]+)
- 创建 FluentBit DaemonSet 服务
kubectl apply -f fluentbit.yaml
安装 OTEL trace collector 去采集 Trace 跟踪数据到 data prepper 服务
- 进入“01-otel-collector” 目录
cd /observability-blog/sample-apps/01-otel-collector/kubernetes
- 查看 Collector 的配置 ymal: otel-collector-trace-aos.yaml,可以根据生产环境压测配置相应的 CPU 数量
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: otel-collector
spec:
mode: deployment
resources:
requests:
cpu: "1"
limits:
cpu: "1"
config: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:55680
exporters:
otlp/data-prepper:
endpoint: data-prepper.data-prepper.svc.cluster.local:21890
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp/data-prepper]
- 查看 collector 的运行情况
jasonxie:~/environment/observability-blog/01-otel-collector/kubernetes $ kubectl get all -n otel-collector READY STATUS RESTARTS AGE
pod/my-collector-metrics-collector-594cc9f59-c7c4g 1/1 Running 0 13s
pod/otel-collector-75cf5b767f-j8xdx 1/1 Running 0 2m38s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/my-collector-metrics-collector-monitoring ClusterIP 10.100.31.124 <none> 8888/TCP 13s
service/otel-collector ClusterIP 10.100.86.180 <none> 55680/TCP 2m38s
service/otel-collector-headless ClusterIP None <none> 55680/TCP 2m38s
service/otel-collector-monitoring ClusterIP 10.100.120.41 <none> 8888/TCP 2m38s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/my-collector-metrics-collector 1/1 1 1 13s
deployment.apps/otel-collector 1/1 1 1 2m38s
NAME DESIRED CURRENT READY AGE
replicaset.apps/my-collector-metrics-collector-594cc9f59 1 1 1 13s
replicaset.apps/otel-collector-75cf5b767f 1 1 1 2m38s
七、使用自动注入功能采集程序跟踪数据
OpenTelemetry Operator 可以支持通过注入的方式去应用程序自动加载 instrumentation 依赖包,该功能支持热门的.NET,Java,Node.js 和 Python services 等框架。本 blog 中使用了应用广泛的 Spring Boot Java 框架作为示例,下面将演示如何通过自动注入,实现零代码改动地接入 OpenTelemetry 方案。
使用自动注入简化 Instrumentation 工作
参考 Injecting Auto-instrumentation 文档,在运行测试用例的 Namespace “elb”创建 Instrumentation, 当该 Namespace 有使用的 instrumentation.opentelemetry.io/inject-java: "true"
annotation 的 deployment 时,Instrumentation 会在该 deployment 启动时,把相应的 java 包注入其中。您也可以参考文档 Manual Instrumentation 使用手动的方式在代码中去做 Instrumentation。
下面 yaml 文件配置了一个 Instrumentation,它会让同一 namespace(这里的 namespace=elb)下的配置了 instrumentation.opentelemetry.io/inject-java: "true"
(参考 12-greet/kubernetes/greet-deployment.yaml)的 deployment 在启动时,会把自动把依赖的 java 包加载进来,免去了为每个 java 工程手动配置依赖包。关于如何为 java 工程加载依赖包,您可以参考这里。
#12-greet/kubernetes/instrumentation-java.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: demo-instrumentation
namespace: elb
spec:
exporter:
endpoint: http://otel-collector.otel-collector.svc.cluster.local:55680
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "1"
resource:
addK8sUIDAttributes: true
java:
image: public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v1.27.0
env:
- name: OTEL_METRICS_EXPORTER
value: "prometheus"
- name: OTEL_EXPORTER_PROMETHEUS_PORT
value: "9464"
Instrumentation 配置说明:
- Exporter 目标为内网的 otel collector “http://otel-collector.otel-collector.svc.cluster.local:55680”
- 使用特定版本的 java instrumentation “image: public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v1.27.0”, 也可以不指定
- 指定 OTEL_METRICS_EXPORTER 的端口为“9464”,OTEL 将以此端口来采指标 Metrics 数据
- 参考文档设定 OTEL 的指标 exporter 为“prometheus”的 pull 模式,而不是默认的 push 到 OTEL endpoint 模式。应用程序的 JVM metrics 就可以通过 prometheus Collector 直接采集,并制定(可参考 Grafa Dashboard 导入模板)
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry8.png)
八、配置应用日志 log 包括 Trace 和 Span ID
在 Java 应用程序的用户日志中配置 TraceID 和 SpanID 数据注入非常简单。 一般来说,将日志包的 OpenTelemetry-Java-Instrumentation 添加到项目依赖项中即可,生产环境推荐使用如 Log4j2 的成熟日志收集引擎。
在上一小节中配置了 instrumentation 后,应用中已经自动注入了 Trace 的 Context信息(trace_id,span_id,trace_flags),只需对日志 Pattern 做下配置就能将 Trace 信息打印出来,下面是 java 工程中的 pom.xml 文件的示例:
- 用 Log4j2 替代 spring-boot 自带的 logger 的配置
<!-- Exclude Spring Boot's Default Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Add Log4j2 Dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
- Log4j2 属性和格式配置:输出 trace_id、span_id 等字段,详见 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} resource.service.name=%X{service.name} - [%15.15t] %-40.40c{1.} : %m%n%ex
</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.example.log4j2demo" level="debug" additivity="false">
<AppenderRef ref="ConsoleAppender" />
</Logger>
<Root level="info">
<AppenderRef ref="ConsoleAppender" />
</Root>
</Loggers>
</Configuration>
部署样例程序
查看 greet-deployment.yaml 部署文件:
- 通过
opentelemetry.io/inject-java: "true"
实现自动加载 OTEL 的 Opentelemetry-javaagent
- 通过
prometheus.io/scrape|port|path
annotation 实现让 OTEL Prometheus 自动采 JVM 的 metrics
- 请替换所需 image 的$ACCOUNT_ID 和$AWS_REGION 来引入存放在您 ECR 的镜像 (person-deployment.yaml 也一样修改)
apiVersion: apps/v1
kind: Deployment
metadata:
name: greet
namespace: elb
spec:
replicas: 1
selector:
matchLabels:
app: greet
template:
metadata:
labels:
app: greet
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
prometheus.io/scrape: "true"
prometheus.io/port: "9464"
prometheus.io/path: /
spec:
containers:
- name: greet
image: ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/greet:latest
ports:
- name: tcp
containerPort: 8080
部署 greet 应用:
kubectl create namespace elb
cd cd /home/ec2-user/environment/observability-blog/sample-apps/12-greet/kubernetes
kubectl apply -n elb -f instrumentation-java.yaml
kubectl apply -n elb -f greet-deployment.yaml
kubectl apply -n elb -f greet-service.yaml
kubectl apply -n elb -f greet-ingress.yaml
kubectl get all -n elb
部署 person 应用:
cd cd /home/ec2-user/environment/observability-blog/sample-apps/13-person/kubernetes
kubectl apply -n elb -f person-deployment.yaml
kubectl apply -n elb -f person-service.yaml
kubectl get all -n elb
获取到 ingress 的地址:
jasonxie:~ $ kubectl get ingress -n elb
NAME CLASS HOSTS ADDRESS PORTS AGE
greet-alb alb * k8s-elb-greetalb-02a8f3262c-1728699503.us-east-1.elb.amazonaws.com 80 36h
jasonxie:~ $
在命令行中或者在浏览器中访问 greet 程序(请自行替换 query 参数 name):
$ curl http://k8s-elb-greetalb-02a8f3262c-1728699503.us-east-1.elb.amazonaws.com/greeting?name=jason
{"id":3,"content":"Hello, dear Member jason!"}
$ curl http://k8s-elb-greetalb-02a8f3262c-1728699503.us-east-1.elb.amazonaws.com/greeting?name=lucy
{"id":4,"content":"Hello, lucy, Please fee free to register!"}
查看日志,检查 trace 的上下文 Context 是否注入成功。
$ kubectl logs pod/greet-b77ddbf7-fzkrs -n elb | tail -1
Defaulted container "greet" out of: greet, opentelemetry-auto-instrumentation (init)
2023-07-10 01:31:57.332 INFO greet-b77ddbf7-fzkrs --- com.example.restservice.GreetingController trace_id=64ab5f8d9d519aa5afc115372451f72b span_id=a0cd4ab6a276a02f resource.service.name= - [nio-8080-exec-5] c.e.r.GreetingController : The person jason is a member.
$ kubectl logs pod/person-b6bdd68b5-gnf9n -n elb | tail -1
Defaulted container "person" out of: person, opentelemetry-auto-instrumentation (init)
2023-07-10 01:31:57.325 INFO person-b6bdd68b5-gnf9n --- com.example.restservice.PersonController trace_id=64ab5f8d9d519aa5afc115372451f72b span_id=5ffa9fcfa2d80c8b resource.service.name= - [nio-8080-exec-1] c.e.r.PersonController : Verifing whether[ jason ] is a member
可以看到上面 greet 和 person 输出日志的 trace_id 是相同的,但 span_id 不同,则说明他们处于同一个 trace 中的不同 span。该应用日志也已经通过 Fluentbit 采集到了 Opensearch, 下面我们将通过 OpenSearh 对 Trace 和 Log 进行联动查询。
九、通过 Opensearch 的可观测性插件实时检索 Trace 和日志
配署 Opensearch 日志的索引
- 连接 Opensearch:因为 Opensearch 存放在私有子网里面,我们使用在 VPC 公有子网的一台 EC2 作为 Proxy Server 去进行 Opensearch 的连接,以下样例为直接使用 ssh 建立通道在本地连接。
sudo ssh -L 443:<openserach domain>:443 -i "<your-pem-key.pem>" ec2-user@<your public ec2 ip>
- 打开 Opensearch Dashboard,访问 https://localhost/_dashboards/
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry9.png)
- 创建索引模式。要在 OpenSearch 仪表板中搜索日志,需要先创建一个索引模式(Index Pattern)。请导航到堆栈管理(Stack Management),在左侧附近选择索引模式(Index Patterns),点击”创建索引模式”按钮(Create Index pattern),输入在 data-prepper 配置的日志模式“sample_app_logs*”, 并点击下一步(Next)。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry10.png)
- 在 Time Field 中选择“time”,并点击 Create index pattern 完成创建。您就可以在 OpenSearch 的 Discover 中查看日志了。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry11.png)
创建 Opensearch 应用分析
- 在 Amazon OpenSearch Dashboards 中,打开页面左上角的汉堡菜单,选择 OpenSearch 插件下的”Observability”。在左侧面板选择”Application Analytics”菜单,然后点击”Create Application”。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry12.png)
- 在”Create Application”界面,将”Name”字段输入为”Greet Application”,在”Description”字段中提供一个描述,比如”Create a greet application”。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry13.png)
- 在”Composition”下展开”Log source”部分,将”sample_app_logs”作为”Sample Application”的日志来源。在”Base Query”字段中输入以下 PPL 查询。在输入时,您将可以选择日志来源:source = sample_app_logs; 在”Services & entities”部分展开,选择以下所有服务:greet,person,将它们包含在”Sample Application”中进行调查。将其余设置保持不变,然后点击底部的”Create”按钮,完成应用程序的组合。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry14.png)
查看 Opensearch 可观测性 Dashboard
让我们检查从后端服务生成的日志和跟踪信息。导航到”Observability”插件下的”Application Analytics”屏幕,点击”Greet application”以分析应用程序中的跟踪组。
在”Overview”选项卡中,有一个名为”Latency by trace group”的表格。该视图将跟踪按照 HTTP 方法和路径进行分组,以便您可以查看特定操作的平均延迟、错误率和趋势。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry15.png)
在”Services”选项卡中,您可以看到应用程序中服务的两个视图。第一个表格视图列出了各个服务及与每个服务相关的趋势,如平均延迟、错误率和吞吐量。第二个视图是”Application Composition Map”,是一个交互式地图,显示各个服务之间的连接关系。
确保在选项卡右上角选择了正确的时间窗口,以查看生成的跟踪和相关项目。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry16.png)
在”Traces & Spans”选项卡中,点击其中一个 Trace Id 以查看单个跟踪。 跟踪详情界面将弹出,显示每个跟踪的”Time spent by service”视图、时间线和每个跨度的列表部分。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry17.png)
关闭跟踪详情窗口,并通过选择 TraceID 后面的剪贴板图标来复制 TraceID。
转到”Log Events”选项卡,并通过在”Base Query”字段中编写以下查询来搜索刚刚复制的 TraceID。当您输入文本时,自动完成将帮助您构建查询。将 TraceID 粘贴到查询表达式中。这将显示与 TraceID 相关的日志消息。
where traceId = '在引号之间粘贴您的 TraceID'
查询表达式应类似于下面的截图。点击”Refresh”按钮以搜索日志。它应该显示与 TraceID 相关的日志消息。日志行还提供详细的应用信息,如有错误也会包含有关错误发生原因和遇到异常的代码行的信息。
![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/a-guide-to-modernizing-application-observability-with-opentelemetry18.png)
十、总结
本文介绍了如何使用 OpenTelemetry 实现现代化应用的可观测性。通过 AWS Distro for OpenTelemetry(ADOT)和 Amazon 托管的服务,可以收集和分析应用程序和基础设施的指标、日志和跟踪数据。博客重点讲解了在 Amazon EKS 环境下使用开源组件实现全栈可观测性的实践,包括日志传输、性能指标采集、应用跟踪和告警设置等。通过一个 Spring Boot 样例程序,展示了如何使用 OpenTelemetry 和 Amazon 的托管服务快速部署和设置跟踪、日志和指标等观测功能。运维工程师通过 Trace ID,可以快速的从海量日志中快速的查找到问题关联日志,减少故障的定位时间 mttk(mean time to know),而不是依靠工程师的经验。此外,ADOT 的 trace 信息里有很多的基础设施相关信息,如 POD ID 和 EC2 实例 ID 等,可以用来快速的在 Prometheus 里联动查看指标,帮助运维人员快速发现和解决问题,提高应用程序的可靠性和稳定性。
本篇作者