使用 Amazon Distro for OpenTelemetry 洞察现代化应用

SDK
微服务
Amazon Distro for OpenTelemetry
服务网格
可观测性
0
0
# 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 等领域有多年经验,致力于容器技术和现代化应用的推广。
0
目录
关闭