# Part.1 Amazon Distro for OpenTelemetry 简介
## 1.1 可观测性介绍
随着微服务技术的普及,微服务(MicroServices)的概念早已深入人心,越来越多的公司开始使⽤微服务架构来解耦应用,提高业务的迭代发布速度,从而快速交付最终用户的需求,实现业务快速创新。然而微服务架构不是“银弹”,如果微服务治理不当,反而有可能适得其反,不仅无法享受到微服务架构带来的优势,反而可能由于微服务架构的系统复杂性,造成开发、运维部署的复杂度增加,进而影响开发迭代的速度,甚至影响系统的整体稳定性。因此容器编排、服务网格、应用可观测等技术被越来越多的提及,用于解决微服务架构中碰到的各种挑战。
对于微服务架构,由于其远超传统软件的系统复杂性,系统运维的难度大大增加,且会随着分布式节点的增加而指数级增长。为了实现卓越运营和业务创新目标,客户需要为系统提供“可观测性”,让开发运维人员了解系统的内部运行情况。简单来说,可观测性就是为复杂 IT 系统寻求应用的白盒监控能力,通过“某种手段”让开发运维人员,便捷的观测应用在“各个时间点”的行为,获取对应用系统的洞察,不断改进支持流程和程序以实现业务价值,达到卓越运营的目标。在正常运行时,观测系统能对系统负载进行评估,对运维操作提供建议。在发生故障时,可协助快速定位和修复问题。
## 1.2 部署“可观测”系统时遇到的挑战
在为现代化应用引入“可观测性”时,“三大支柱”中 logs 系统提供事件细节、metrics 系统负责统计和聚合、traces 系统则专注请求延迟。然而“三大支柱”各司其职,往往是独立的系统,例如 CNCF 社区的开源方案体系中 Prometheus 负责指标、Jaeger 负责跟踪、EFK 负责日志。当出现问题时,往往只能通过人去寻找各种信息的关联性,再根据经验判断和优化,显然是不可行的,耗时耗力还无法找到问题根因。
需要重点指出的是,traces 系统往往需要在调用的函数或服务的边界进行跟踪,需要一些代码的侵入,各 traces 方案有单独的 DSK/API,互不兼容,更换 traces 方案需要花费大量的成本进行代码改造,也直接影响了用户对 traces 技术采用。
## 1.3 OpenTelemetry 和 ADOT
那么如何解决这些问题呢?答案结构化和标准化。OpenTelemetry (简称:OTel) 从 OpenTracing 和 OpenCensus 合并而来,结合 w3c trace context,为可观测体系提供了数据采集和标准规范的统一,将 traces、metrics、logs 这些孤立的信息,作为一个单一、高度“结构化”的数据流连接起来。
![fccf0ca340e743e6e9ea92b86fb07933.png](https://dev-media.amazoncloud.cn/5170007ed5ea405c998b81158f5d2a31_fccf0ca340e743e6e9ea92b86fb07933.png "fccf0ca340e743e6e9ea92b86fb07933.png")
OTel 由以下几部分组成:
▌跨语言的标准规范 **Specification**:定义了数据标准、语义规范、OTLP 协议、API、SDK 等;
Specification:
https://opentelemetry.io/docs/reference/specification/?trk=cndc-detail
● **API**:定义用于生成和关联 traces、metrics 和 logs 数据的数据类型和操作;
● **SDK**:基于 OTel API 标准实现的各种语言的 SDK,还定义了配置、数据处理和导出概念,方便用户使用这些 SDK 开发生成导出观测数据;
● **Data**:定义观测系统后端可以提供支持的 OpenTelemetry 协议(OTLP)和语义约定 **Semantic Conventions**;
Semantic Conventions:
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/?trk=cndc-detail
▌接收、处理、输出观测数据的(Collector):用于接收 OTel 观测数据的工具,允许用户通过配置 Pipeline 定义观测数据如何接收、如何处理、如何导出到后端;
▌Automatic Instrumentation:一些开箱即用的观测数据采集器。
**Amazon Distro for OpenTelemetry** (简称:ADOT) 是 Amazon 支持的 OpenTelemetry 项目发行版,安全且可直接用于生产。使用 ADOT 可将应用生成的相关的指标和跟踪数据发送至多个 Amazon 和合作伙伴监控解决方案。ADOT 还可以从 Amazon 资源和托管服务中收集元数据,以便您可以将应用程序性能数据与底层基础设施数据关联,从而减少解决问题的平均时间。
Amazon Distro for OpenTelemetry:
https://aws-otel.github.io/?trk=cndc-detail
2022年4月21日,[Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 发布了 ADOT EKS Addon,方便 EKS 用户安装和管理 ADOT Operator。简化了在 EKS 上运行的应用程序的观测体验,可将指标和跟踪发送到多个监控服务,包括 Amazon X-Ray、[Amazon Managed Service for Prometheus](https://aws.amazon.com/cn/prometheus/?trk=cndc-detail) 和 [Amazon CloudWatch](https://aws.amazon.com/cn/cloudwatch/?trk=cndc-detail) 或合作伙伴监控解决方案。
本文主要围绕 EKS 的 ADOT Operator 演示如何构建容器化应用的可观测。
# Part.2 安装 ADOT Operator
Kubernetes Operator 是一种打包、部署和管理 Kubernetes 原生应用程序的方法,该应用程序既部署在 Kubernetes 上,又使用 Kubernetes API 和 kubectl 工具进行管理。Kubernetes Operator 是一个自定义 Kubernetes 控制器,ADOT Operator 通过自定义资源定义(CRD)引入新的对象类型,来简化 Collector 管理。通过 ADOT Operator,用户可以以声明 API 的方式来管理如何采集、处理、导出观测数据。
## 2.1 安装工具和集群
在安装 ADOT Operator 前,你需要一台能够连接到 Amazon 的机器,安装配置好 awscli、eksctl、kubectl、Helm 工具。这里不展开说明,可参考:**客户端工具安装**
https://github.com/xufanglin/eks-quickstart/blob/main/01-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%B7%A5%E5%85%B7.md?trk=cndc-detail
除了这些工具,你还需要一个 EKS 集群来运行 ADOT Operator,这里可以使用 eksctl 快速拉起一个集群或使用现有集群,注意集群得启用 **IAM OIDC Provider**
https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html?trk=cndc-detail
```js
cat <<EOF | eksctl create cluster -f -
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: "otel" # 集群名字
region: "ap-northeast-2" # 集群所在的region
version: "1.23" # eks版本
iam:
withOIDC: true # OIDC配置,很重要,AWS Loadbalancer Controller等addon都需要
managedNodeGroups:
- name: Private-01
instanceType: m6i.large # worker节点使用的机器类型
desiredCapacity: 3 # Autoscaling group的初始大小
minSize: 3 # Autoscaling group的最小规模
maxSize: 3 # Autoscaling group的最大规模
volumeSize: 30 # 节点EBS大小
volumeType: gp3 # 节点存储类型
EOF
```
## 2.2 配置 IAM
Operator 需要的 Kubernetes 的 API 权限,通过以下命令配置 Kubernetes RBAC:
```js
kubectl apply -f https://amazon-eks.s3.amazonaws.com/docs/addons-otel-permissions.yaml
```
ADOT Addon 依赖 cert-manager,需要提前安装:
```js
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.9.1/cert-manager.yaml
```
定义环境变量,通过以下命令获取 Amazon 账户 ID、EKS 集群名、OIDC Provider 等信息,用于配置 IRSA:
```js
export ACCOUNT_ID=\$(aws sts get-caller-identity --query "Account" --output text)
export CLUSTER_NAME=\$(aws eks list-clusters --query "clusters[]" --output text)
export OIDC_PROVIDER=\$(aws eks describe-cluster --name \$CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\\\\/\\\\///")
```
配置 IAM Role for ServiceAccounts (简称:IRSA),与给 EKS 的 worker 节点 role 角色不同,IRSA 将 IAM Role 与 Kuberntes 原生的 ServiceAccount 关联,使用该 ServiceAccount 运行的 Pod,将获得 IAM Role 的权限,而不是节点,实现了更细的访问控制。
首先创建 IAM Role 的 Trust Relationships 配置文件:
```js
read -r -d '' TRUST_RELATIONSHIP <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::\${ACCOUNT_ID}:oidc-provider/\${OIDC_PROVIDER}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"\${OIDC_PROVIDER}:aud": "sts.amazonaws.com",
"\${OIDC_PROVIDER}:sub": "system:serviceaccount:aws-otel-eks:aws-otel-collector"
}
}
}
]
}
EOF
echo "\${TRUST_RELATIONSHIP}" > trust.json
```
创建 ADOT Collector 运行需要的 IAM Role ADOTCollectorExecutionRole-${CLUSTER_NAME},
```js
aws iam create-role --role-name AmazonADOTCollectorExecutionRole-\${CLUSTER_NAME} --assume-role-policy-document file://trust.json --description "ADOT Collector Execution Role for EKS Cluster \${CLUSTER_NAME}"
```
根据需要为 ADOTCollectorExecutionRole-${CLUSTER_NAME} 附加 AmazonPrometheusRemoteWriteAccess、AmazonXrayWriteOnlyAccess、CloudWatchAgentServerPolicy 需要的托管 policy,使这个 role 有权限访问这些服务。
```js
aws iam attach-role-policy --role-name AmazonADOTCollectorExecutionRole-\${CLUSTER_NAME} --policy-arn="arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess"
aws iam attach-role-policy --role-name AmazonADOTCollectorExecutionRole-\${CLUSTER_NAME} --policy-arn="arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess"
aws iam attach-role-policy --role-name AmazonADOTCollectorExecutionRole-\${CLUSTER_NAME} --policy-arn="arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
```
创建 IRSA 对应的 ServiceAccount,并通过 annotations 映射到 IAM Role:
创建 Kubernetes 的 namespace。
```js
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Namespace
metadata:
name: aws-otel-eks
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: aws-otel-collector
namespace: aws-otel-eks
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::\${ACCOUNT_ID}:role/AmazonADOTCollectorExecutionRole-\${CLUSTER_NAME}
EOF
```
## 2.3 部署 ADOT Addon
创建完 IRSA 后,可以使用以下的命令创建 EKS 的 ADOT add-on:
```js
aws eks create-addon --addon-name adot --cluster-name \${CLUSTER_NAME}
```
使用以下命令查看 add-on 的状态,确保处于 `ACTIVE` 状态。
```js
aws eks describe-addon --addon-name adot --cluster-name \${CLUSTER_NAME}
{
"addon": {
"addonName": "adot",
"clusterName": "otel",
"status": "ACTIVE",
"addonVersion": "v0.58.0-eksbuild.1",
"health": {
"issues": []
},
"addonArn": "arn:aws:eks:ap-northeast-2:<AWS ACCOUNT ID>:addon/otel/adot/18c1bee4-3665-9fc2-1fe2-303bf9938657",
"createdAt": "2022-09-27T07:10:30.740000+00:00",
"modifiedAt": "2022-09-27T07:11:42.641000+00:00",
"tags": {}
}
}
```
至此,ADOT 的 K8S Operator 安装完成,下面展示如何使用 Operator 来管理 Collector。
**参考资料**:
https://github.com/aws-observability/aws-otel-collector?trk=cndc-detail
## Part.3 使用 Operator 创建管理 Collector
## 3.1 ADOT Collector 的4个部署模式
ADOT Collector 支持多种部署模式来匹配不同的场景:
**▌Deployment**:以最简单的方式创建 Collector 收集 metrics 和 traces,需要注意的是,如果使用 Prometheus 的服务发现,由于每个 Collector 都拥有完整的发现列表,将导致 Collector 重复收集指标信息;
**▌Sidecar**:作为 Sidecar container 与应用 container 一同运行在 Pod 中,可以设置 Pod annotation 自动注入;
**▌DaemonSet**:每个 Kubernetes 节点部署一个 Collector 作为代理运行;
**▌StatefulSet**:使用此模式可以保证运行实例固定命名,支持 Prometheus 服务发现 target 重新调度和分配。
![b1767d8470c3cbcb98bf5471af1d6216.png](https://dev-media.amazoncloud.cn/88e6c5611c2c40b59b03731895969543_b1767d8470c3cbcb98bf5471af1d6216.png "b1767d8470c3cbcb98bf5471af1d6216.png")
详细的区别可参考:
https://aws-otel.github.io/docs/getting-started/operator?trk=cndc-detail
## 3.2 OTel Collector Pipeline 介绍
OTel Collector 支持丰富的 Pipeline 来帮助客户收集、处理、导出观测数据,并且能在多种不同的观测系统之间进行数据的灵活转换导出。在配置之前,我们得了解 OTel Collector Pipeline 的一些概念:
**▌receivers**:定义以什么样的格式接收观测信号,如: Prometheus、X-Ray、statsD、Jaeger、Zipkin、OpenTelemetry;
**▌processors**:定义如何处理观测数据,例如标签转换、批量发送,甚至设置 Collector 内存分配等;
**▌exporters**:定义如何将观测数据发送到后端系统,如:发送到 X-Ray、Prometheus、文件、三方合作伙伴的 APM 等;
**▌extensions**:用于扩展增强 Collector 功能,如:使用 sigv4auth 来扩展 Prometheus 的身份验证。
ADOT Collector Pipeline 支持列表,可参考:
https://github.com/aws-observability/aws-otel-collector?trk=cndc-detail
## 3.3 使用 Kubernetes Operator 配置 Collector
范例1:将 OTLP 协议的 traces 通过 Collector 转换后输出到 Amazon X-Ray。
```js
---
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-trace
namespace: aws-otel-eks
spec:
mode: deployment
serviceAccount: aws-otel-collector
resources:
limits:
cpu: 500m
memory: 1024Mi
requests:
cpu: 250m
memory: 512Mi
config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
timeout: 30s
send_batch_size: 8192
exporters:
awsxray:
region: "ap-northeast-2"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [awsxray]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-collector-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: otel-collector-role
subjects:
- kind: ServiceAccount
name: aws-otel-collector
namespace: aws-otel-eks
```
范例2:使用 Collector 替代 Prometheus Server,将 metrics 信息同时输出到 CloudWatch 和 Prometheus。
```js
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-prom
namespace: aws-otel-eks
spec:
mode: deployment
serviceAccount: aws-otel-collector
config: |
extensions:
sigv4auth:
service: "aps"
region: "ap-northeast-2"
receivers:
prometheus:
config:
scrape_configs:
- job_name: 'kubernetes-apiservers'
sample_limit: 8192
scheme: https
kubernetes_sd_configs:
- role: endpoints
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: kubernetes;https
processors:
batch:
timeout: 30s
send_batch_size: 8192
exporters:
prometheusremotewrite:
endpoint: "https://<aws-managed-prometheus-endpoint>/v1/api/remote_write"
auth:
authenticator: sigv4auth
awsemf:
region: "ap-northeast-2"
log_group_name: "/metrics/my-adot-collector"
log_stream_name: "adot-stream"
service:
extensions: [sigv4auth]
pipelines:
metrics:
receivers: [prometheus]
processors: []
exporters: [prometheusremotewrite,awsemf]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: otel-collector-role
rules:
- apiGroups:
- ""
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- nonResourceURLs:
- /metrics
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-collector-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: otel-collector-role
subjects:
- kind: ServiceAccount
name: aws-otel-collector
namespace: aws-otel-eks
```
范例3:使用 Collector 收集 OTLP 协议的 metric 和 trace,输出到 Prometheus 的 X-Ray。
```js
---
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-allinone
namespace: aws-otel-eks
spec:
mode: deployment
serviceAccount: aws-otel-collector
resources:
limits:
cpu: 500m
memory: 1024Mi
requests:
cpu: 250m
memory: 512Mi
config: |
extensions:
sigv4auth:
service: "aps"
region: "ap-northeast-2"
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
timeout: 30s
send_batch_size: 8192
exporters:
prometheusremotewrite:
endpoint: "https://<aws-managed-prometheus-endpoint>/v1/api/remote_write"
auth:
authenticator: sigv4auth
awsxray:
region: "ap-northeast-2"
service:
extensions: [sigv4auth]
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheusremotewrite]
traces:
receivers: [otlp]
processors: [batch]
exporters: [awsxray]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: otel-collector-role
rules:
- apiGroups:
- ""
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- nonResourceURLs:
- /metrics
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-collector-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: otel-collector-role
subjects:
- kind: ServiceAccount
name: aws-otel-collector
namespace: aws-otel-eks
```
## 3.4 部署 ADOT Collector
为方便入门,我们选择范例1的的配置进行部署,将范例1的内容保存为 Collector-trace.yaml,使用以下命令创建资源:
```js
kubectl create -f collector-trace.yaml
```
Operator 将在 EKS 中创建 deployment 和 service 资源。
```js
kubectl get all -n aws-otel-eks
NAME READY STATUS RESTARTS AGE
pod/otel-trace-collector-5f45484b79-x582x 1/1 Running 0 72s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/otel-trace-collector ClusterIP 10.100.5.170 <none> 4317/TCP,4318/TCP,55681/TCP 73s
service/otel-trace-collector-headless ClusterIP None <none> 4317/TCP,4318/TCP,55681/TCP 73s
service/otel-trace-collector-monitoring ClusterIP 10.100.73.216 <none> 8888/TCP 73s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/otel-trace-collector 1/1 1 1 73s
NAME DESIRED CURRENT READY AGE
```
至此,Collector 已经部署完成,下一步我们将通过一个简单的例子来介绍如何使用 OTel 的 SDK 来实现应用程序的可观测。
# Part.4 使用 OTel SDK 实现应用的可观测
在本演示样例中,使用 Golang 的 SDK 来进行开发部署
▌使用 OTel 的 OpenTelmetry-go SDK 进行 traces 观测信号的生成。
OpenTelmetry-go:
https://github.com/open-telemetry/opentelemetry-go?trk=cndc-detail
▌使用 OTLP 协议将观测数据发送到 ADOT 的 Collector,Collector 将 OTLP 做转换,输出到 X-Ray,注意 X-Ray 的 trace-id 并不是和 OTLP 一样完全随机,包含了时间戳信息,阻止30天前的 traces 数据进入。
▌在代码中,将 traces 的信息与底层资源进行关联。
▌为方便观测,将采样率设置为 AlwaysSample,可以根据需要调整采样策略和比例,OTel 提供了丰富的采样策略,例如主流的固定采样比率、尾部采样等等来降低观测信号的网络传输和存储压力
▌将应用暴露为 http 服务,监听在0.0.0.0:4000。
```js
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"time"
"go.opentelemetry.io/contrib/detectors/aws/ec2"
"go.opentelemetry.io/contrib/detectors/aws/eks"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/propagators/aws/xray"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
var (
OTLP_ENDPOINT_GRPC = "0.0.0.0:4317" // 定义默认OTLP Exporter的Endpoint
SERVICE_NAME = "OTELDemo"
REGION = "ap-northeast-2"
TR trace.Tracer
)
func main() {
ctx := context.Background() // 初始化context,用于传递可观测信号上下文传递
traceStop := InitOTELProvider(ctx) // 初始化meter和tracer的provider,并在main函数关闭时停止trace
defer func() {
if err := traceStop(ctx); err != nil {
log.Fatal(err)
}
}()
// otelhttp.NewHandler拦截请求路由,为处理函数增加⾃动⽣成traces的能⼒
http.Handle("/hello", otelhttp.NewHandler(http.HandlerFunc(helloHandler), "hello"))
http.Handle("/err", otelhttp.NewHandler(http.HandlerFunc(errHandler), "err"))
http.Handle("/notfound", otelhttp.NewHandler(http.HandlerFunc(notfoundHandler), "notfound"))
log.Fatal(http.ListenAndServe(":4000", nil))
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
n := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(200)
fib, _ := fibonacci(ctx, uint(n))
w.Write([]byte(fmt.Sprintf("Number: %d Fib: %d\\n", n, fib)))
}
func errHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}
func notfoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
// Fibonacci returns the n-th fibonacci number.
func fibonacci(ctx context.Context, n uint) (uint64, error) {
_, span := TR.Start(ctx, "Fibonacci")
defer span.End()
if n <= 1 {
return uint64(n), nil
}
// 当传输的数字超过93,结果超过unit64的表示范围,将span标记为codes.Error,并在SetAttributes记录关键信息
if n > 93 {
span.SetStatus(codes.Error, fmt.Sprintf("unsupported fibonacci number %d: too large", n))
span.SetAttributes(attribute.Int("num", int(n)))
return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n)
}
var n2, n1 uint64 = 0, 1
for i := uint(2); i < n; i++ {
n2, n1 = n1, n1+n2
}
span.SetAttributes(attribute.Int("num", int(n)))
return n2 + n1, nil
}
// 将Traces与底层基础设置关联,如EKS Pod ID、EC2实例ID等
func NewResource(ctx context.Context) *resource.Resource {
// 如果未设置RESOURCE_TYPE环境变量,则使用默认值
resType := os.Getenv("RESOURCE_TYPE")
switch resType {
case "EC2":
r, err := ec2.NewResourceDetector().Detect(ctx)
if err != nil {
log.Fatalf("%s: %v", "Failed to detect EC2 resource", err)
}
res, err := resource.Merge(r, resource.NewSchemaless(semconv.ServiceNameKey.String(SERVICE_NAME)))
if err != nil {
log.Fatalf("%s: %v", "Failed to merge resources", err)
}
return res
case "EKS": // EKS Resource的实现依赖Container Insight
r, err := eks.NewResourceDetector().Detect(ctx)
if err != nil {
log.Fatalf("%s: %v", "failed to detect EKS resource", err)
}
res, err := resource.Merge(r, resource.NewSchemaless(semconv.ServiceNameKey.String(SERVICE_NAME)))
if err != nil {
log.Fatalf("%s: %v", "Failed to merge resources", err)
}
return res
default:
res := resource.NewWithAttributes(
semconv.SchemaURL,
// ServiceName用于在后端中标识应用
semconv.ServiceNameKey.String(SERVICE_NAME),
)
return res
}
}
// InitOTELProvider 初始化TraceProvider,返回Tracer的关闭函数
func InitOTELProvider(ctx context.Context) (traceStop func(context.Context) error) {
ep := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
if ep != "" {
OTLP_ENDPOINT_GRPC = ep // 如果设置了环境变量,则使用环境变量的值来设置exporter的endpoint
}
res := NewResource(ctx)
// 初始化TracerProvider,使用grpc与collector通讯
traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(OTLP_ENDPOINT_GRPC))
if err != nil {
log.Fatalf("%s: %v", "failed to create trace exporter", err)
}
idg := xray.NewIDGenerator() // x-ray的trace id包含时间戳
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()), // 设置采样率
sdktrace.WithBatcher(traceExporter),
sdktrace.WithIDGenerator(idg), //使用x-ray兼容的trace id
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
TR = tp.Tracer(SERVICE_NAME)
return tp.Shutdown
}
```
这些样例代码可以从
https://github.com/xufanglin/otel-exmaple?trk=cndc-detail 直接下载。
# Part.5 在 EKS 中部署应用样例
## 5.1 使用 Docker 来封装应用
Golang 的好处是可以非常方便地交叉编译成执行文件,通过 Multi-stage 构建出干净的运行镜像。构建镜像前,我们需要先安装运行 Docker。
```js
sudo yum install -y docker
sudo systemctl enable docker && sudo systemctl start docker
```
创建 Dockerfile,内容如下:
```js
FROM golang:alpine AS build-env
RUN apk update && apk add ca-certificates
WORKDIR /usr/src/app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o otel-example -a -ldflags '-extldflags "-static"'
FROM scratch
COPY --from=build-env /usr/src/app/otel-example /otel-example
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
CMD ["/otel-example"]
```
构建 Docker 镜像。
```js
sudo docker build -t otel-example:latest .
sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
otel-example latest 1ee8421c3437 About a minute ago 55.1MB
golang alpine 5dd973625d31 3 weeks ago 352MB
```
## 5.2 使用 ECR 存储镜像
先使用以下命令创建 ECR 镜像库。
```js
export ACCOUNT_ID=\$(aws sts get-caller-identity --query "Account" --output text)
export REGION="ap-northeast-2"
aws ecr get-login-password --region \$REGION | sudo docker login --username AWS --password-stdin \$ACCOUNT_ID.dkr.ecr.\$REGION.amazonaws.com
aws ecr create-repository --region \$REGION --repository-name otel-example --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE
```
将镜像打标签推送至 ECR。
```js
sudo docker tag otel-example:latest \$ACCOUNT_ID.dkr.ecr.\$REGION.amazonaws.com/otel-example:latest
sudo docker push \$ACCOUNT_ID.dkr.ecr.\$REGION.amazonaws.com/otel-example:latest
```
push 完 Docker 镜像,使用以下命令获取镜像的 URL,用于部署应用到 EKS上 :
```js
export IMAGE=\$(aws ecr describe-repositories --repository-name otel-example --query "repositories[].repositoryUri" --output text):latest
```
## 5.3 将应用部署到 EKS
首先使用 kubectl 获取 ADOT Collector 的 endpoint,从以下输出可以得知 endpoint(svc+namespace+port) 为 “otel-trace-collector.aws-otel-eks:4317”,
```js
kubectl get all -n aws-otel-eks
NAME READY STATUS RESTARTS AGE
pod/otel-trace-collector-5f45484b79-x582x 1/1 Running 0 72s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/otel-trace-collector ClusterIP 10.100.5.170 <none> 4317/TCP,4318/TCP,55681/TCP 73s
service/otel-trace-collector-headless ClusterIP None <none> 4317/TCP,4318/TCP,55681/TCP 73s
service/otel-trace-collector-monitoring ClusterIP 10.100.73.216 <none> 8888/TCP 73s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/otel-trace-collector 1/1 1 1 73s
NAME DESIRED CURRENT READY AGE
```
将应用部署到 EKS 上,“OTEL_EXPORTER_OTLP_ENDPOINT”设置为“otel-trace-collector.aws-otel-eks:4317”,配置“RESOURCE_TYPE”为“EC2”,在生成 traces 时将基础设置信息关联进来,方便排障。
```js
cat <<EOF | kubectl create -f -
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-example
labels:
app: otel-example
spec:
replicas: 1
selector:
matchLabels:
app: otel-example
template:
metadata:
labels:
app: otel-example
spec:
containers:
- name: otel-example
image: \${IMAGE}
ports:
- name: web
containerPort: 4000
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "otel-trace-collector.aws-otel-eks:4317"
- name: RESOURCE_TYPE
value: "EC2"
---
apiVersion: v1
kind: Service
metadata:
name: otel-example
labels:
app: otel-example
annotations:
scrape: "true"
spec:
ports:
- name: web
port: 4000
targetPort: 4000
protocol: TCP
selector:
app: otel-example
EOF
```
检查样例应用。
```js
kubectl get pod
NAME READY STATUS RESTARTS AGE
otel-example-955f66cd5-59xxs 1/1 Running 0 19s
```
# Part.6 测试应用的 traces 生成和展示
## 6.1 生成 traces 信息
使用 curl 工具来测试访问应用,生成 traces 信息。
```js
kubectl run curl --image=curlimages/curl:latest -- sleep 1d
kubectl exec -it curl -- sh
/ \$ curl otel-example:4000/err
/ \$ curl otel-example:4000/notfound
/ \$ curl otel-example:4000/notfound
/ \$ curl otel-example:4000/err
/ \$ curl otel-example:4000/hello
Number: 86 Fib: 420196140727489673
/ \$ curl otel-example:4000/hello
Number: 119 Fib: 0
/ \$ curl otel-example:4000/hello
Number: 63 Fib: 6557470319842
/ \$ curl otel-example:4000/hello
Number: 85 Fib: 259695496911122585
/ \$ curl otel-example:4000/hello
Number: 144 Fib: 0
```
## 6.2 在 X-Ray 上查看 trace 信息
在 Amazon Console 上打开 X-Ray 的 Service map,可以看到客户端与应用之间的访问状态的统计。
![88646f75ec5b8ab125ebb92082fcef9d.png](https://dev-media.amazoncloud.cn/dffc28a5e2994f8e893e690d405551ff_88646f75ec5b8ab125ebb92082fcef9d.png "88646f75ec5b8ab125ebb92082fcef9d.png")
点开 X-Ray 的 traces 菜单,可以看到应用上报的 traces 信息,显示了 `http.mothod、http.status_code`、响应时间等信息等。
![52c2a7e63f64f259740a2b94019fc476.png](https://dev-media.amazoncloud.cn/55e64e0123db4618aa1989be92d6ca2c_52c2a7e63f64f259740a2b94019fc476.png "52c2a7e63f64f259740a2b94019fc476.png")
选择一条正常的 trace 信息,可以看到单次服务的调用关系、整个 trace 持续的时间、每个 span 花费的时间等。
![63dac5023470cab99ecec4c41c8f306a.jpg](https://dev-media.amazoncloud.cn/5da16450f0bb4efeb9efa222b8d00df8_63dac5023470cab99ecec4c41c8f306a.jpg "63dac5023470cab99ecec4c41c8f306a.jpg")
点开`Fibonacci`,可以看到该函数调用的一些信息,如 parent 的 span id。
![e56e470b4476b2766da7f7f4b5fbf843.jpg](https://dev-media.amazoncloud.cn/37c4fbbb47ce421fb8fb07a2572c293b_e56e470b4476b2766da7f7f4b5fbf843.jpg "e56e470b4476b2766da7f7f4b5fbf843.jpg")
在 Resources 标签,可以看到 EC2 的信息已经被关联上来,如 Account ID、实例的 AZ 信息、实例类型等。
![07d616d455e612eea8e59ad024dd8112.jpg](https://dev-media.amazoncloud.cn/ef9567198e604a4193d630695b09c89a_07d616d455e612eea8e59ad024dd8112.jpg "07d616d455e612eea8e59ad024dd8112.jpg")
点开 Metadata,可以看到我们在程序中设置的 span attributes 信息。
![51fd5ee4492aefc487f3d551b3b94c5c.jpg](https://dev-media.amazoncloud.cn/3bb6013eefa84824a1cb5c50d813a670_51fd5ee4492aefc487f3d551b3b94c5c.jpg "51fd5ee4492aefc487f3d551b3b94c5c.jpg")
对于返回 404 not found 信息的访问,我们可以看到 response 为404。
![fc000bc94d9ad48e5dc2d22c1e27509c.png](https://dev-media.amazoncloud.cn/d83185fc89a747059f486aadb0b2b179_fc000bc94d9ad48e5dc2d22c1e27509c.png "fc000bc94d9ad48e5dc2d22c1e27509c.png")
点开 OTELDemo,可以看到 http request 和 response 等信息。
![a1a74cd4dbfe48654f916c9e12ea710a.png](https://dev-media.amazoncloud.cn/4d24fc10522e4d5a8b72021f54f0f5d0_a1a74cd4dbfe48654f916c9e12ea710a.png "a1a74cd4dbfe48654f916c9e12ea710a.png")
对于返回503服务不可用的访问,可以看到 trace map 显示异常红色。
![5b35f7daa129e8315cc8664be9ddd50e.jpg](https://dev-media.amazoncloud.cn/780031f7a9754f9d9eb111ccbcd26afa_5b35f7daa129e8315cc8664be9ddd50e.jpg "5b35f7daa129e8315cc8664be9ddd50e.jpg")
同时点开 OTELDemo,可以看到 http request 和 response 等信息,span 的状态 Fault 为True。
![f301f62854c9e88287052b7c79ca1a5a.png](https://dev-media.amazoncloud.cn/1ba2443c694f4f198aa1f5c28ccf36e8_f301f62854c9e88287052b7c79ca1a5a.png "f301f62854c9e88287052b7c79ca1a5a.png")
# Part.7 总结
OTel 为可观测提供了标准化的 API/SDK 以及语义规范,使用 Context 将 metrics、traces、logs 将“三大支柱”关联,降低了开发者可观测技术栈采用成本和更换的技术成本,并提供了灵活可配置的 Collector 满足用户灵活多变的需求。目前 OTel 还有很多未完全解决的问题,如 Golang 的 metrics 的 SDK 还处于 alpha,logs 处于 Frozen 阶段。需要依赖 Prometheus 的 SDK 的 exemplar 特性将 traces 和 metrics 关联。但社区活跃度较高,有各云服务商以及 APM 厂商的支持,发展迅速,在 CNCF 的2022年的可观测的报告“**Cloud Native Observability: hurdles remain to understanding the health of systems**”中,以49%的采用率仅次于 Prometheus。
Cloud Native Observability: hurdles remain to understanding the health of systems:
https://www.cncf.io/wp-content/uploads/2022/03/CNCF_Observability_MicroSurvey_030222.pdf?trk=cndc-detail
ADOT 作为 Amazon 的 OTel 发行版,由 Amazon 提供技术支持,帮助客户更方便将应用 OTel 技术栈,与 EKS、ECS、Lambda、EC2、[Amazon Managed Service for Prometheus](https://aws.amazon.com/cn/prometheus/?trk=cndc-detail)、[Amazon Managed Grafana](https://aws.amazon.com/cn/grafana/?trk=cndc-detail)、X-Ray、CloudWatch 或三方合作伙伴 APM 产品集成。
### 本篇作者
![cca3b084b2159f99cddbd52cce71a7cc.jpg](https://dev-media.amazoncloud.cn/dba52c132ec44e4dad2c31df0488c99c_cca3b084b2159f99cddbd52cce71a7cc.jpg "cca3b084b2159f99cddbd52cce71a7cc.jpg")
**林旭芳**
亚马逊云科技解决方案架构师,主要负责亚马逊云科技云技术和解决方案的推广工作,在 Container、容灾等方向有丰富实践经验。
![7b1b150e121e9da15a8b9607df859ba2.jpg](https://dev-media.amazoncloud.cn/0fe26f58d91a4d2b9eaa824cd4961ce1_7b1b150e121e9da15a8b9607df859ba2.jpg "7b1b150e121e9da15a8b9607df859ba2.jpg")
**于昺蛟**
亚马逊云科技解决方案架构师,负责互联网客户云计算方案的架构咨询和设计。在容器平台的建设和运维,应用现代化,DevOps 等领域有多年经验,致力于容器技术和现代化应用的推广。