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

c67bd159b8860d36d81917c16c5aa844.gif

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 这些孤立的信息,作为一个单一、高度“结构化”的数据流连接起来。

d51758d1659d581814c6cb6bd911c3ab.png

OTel 由以下几部分组成:

▌跨语言的标准规范 Specification:定义了数据标准、语义规范、OTLP 协议、API、SDK 等;

Specification:

https://opentelemetry.io/docs/reference/specification/

● 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/

▌接收、处理、输出观测数据的(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/

2022年4月21日,Amazon EKS 发布了 ADOT EKS Addon,方便 EKS 用户安装和管理 ADOT Operator。简化了在 EKS 上运行的应用程序的观测体验,可将指标和跟踪发送到多个监控服务,包括 Amazon X-Ray、Amazon Managed Service for Prometheus 和 Amazon CloudWatch 或合作伙伴监控解决方案。

本文主要围绕 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

除了这些工具,你还需要一个 EKS 集群来运行 ADOT Operator,这里可以使用 eksctl 快速拉起一个集群或使用现有集群,注意集群得启用 IAM OIDC Provider

https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html

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:

kubectl apply -f https://amazon-eks.s3.amazonaws.com/docs/addons-otel-permissions.yaml

左滑查看更多

ADOT Addon 依赖 cert-manager,需要提前安装:

kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.9.1/cert-manager.yaml

左滑查看更多

定义环境变量,通过以下命令获取 Amazon 账户 ID、EKS 集群名、OIDC Provider 等信息,用于配置 IRSA:

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 配置文件:

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},

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 有权限访问这些服务。

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。

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:

aws eks create-addon --addon-name adot --cluster-name ${CLUSTER_NAME}

左滑查看更多

使用以下命令查看 add-on 的状态,确保处于 ACTIVE 状态。

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

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 重新调度和分配。

1dc129da240886b1eafb843ea9522faf.png

详细的区别可参考:

https://aws-otel.github.io/docs/getting-started/operator

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

3.3 使用 Kubernetes Operator 

配置 Collector

范例1:将 OTLP 协议的 traces 通过 Collector 转换后输出到 Amazon X-Ray。

---
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。

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。

---
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,使用以下命令创建资源:

kubectl create -f collector-trace.yaml

左滑查看更多

Operator 将在 EKS 中创建 deployment 和 service 资源。

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

▌使用 OTLP 协议将观测数据发送到 ADOT 的 Collector,Collector 将 OTLP 做转换,输出到 X-Ray,注意 X-Ray 的 trace-id 并不是和 OTLP 一样完全随机,包含了时间戳信息,阻止30天前的 traces 数据进入。

▌在代码中,将 traces 的信息与底层资源进行关联。

▌为方便观测,将采样率设置为 AlwaysSample,可以根据需要调整采样策略和比例,OTel 提供了丰富的采样策略,例如主流的固定采样比率、尾部采样等等来降低观测信号的网络传输和存储压力

▌将应用暴露为 http 服务,监听在0.0.0.0:4000。

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 直接下载。

Part.5

在 EKS 中部署应用样例

5.1 使用 Docker 来封装应用

Golang 的好处是可以非常方便地交叉编译成执行文件,通过 Multi-stage 构建出干净的运行镜像。构建镜像前,我们需要先安装运行 Docker。

sudo yum install -y docker
sudo systemctl enable docker && sudo systemctl start docker

左滑查看更多

创建 Dockerfile,内容如下:

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 镜像。

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 镜像库。

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。

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上 :

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”,

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 时将基础设置信息关联进来,方便排障。

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

左滑查看更多

检查样例应用。

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 信息。

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,可以看到客户端与应用之间的访问状态的统计。

20f0d61d66a4d1c12fda961376073cc0.png

点开 X-Ray 的 traces 菜单,可以看到应用上报的 traces 信息,显示了 http.mothod、http.status_code、响应时间等信息等。

85db11ff493e74a240c3981d5acece3b.png

选择一条正常的 trace 信息,可以看到单次服务的调用关系、整个 trace 持续的时间、每个 span 花费的时间等。

6b643eafb698e52d33255eb154b8216e.jpeg

点开Fibonacci,可以看到该函数调用的一些信息,如 parent 的 span id。

d130d83526245cbdd20ae4064f4e4ae3.jpeg

在 Resources 标签,可以看到 EC2 的信息已经被关联上来,如 Account ID、实例的 AZ 信息、实例类型等。

247a5fe6bcfe5bc977875950c2ec87af.jpeg

点开 Metadata,可以看到我们在程序中设置的 span attributes 信息。

98f6039c3ace76be8ab89bcaf7214593.jpeg

对于返回 404 not found 信息的访问,我们可以看到 response 为404。

5ccf319ae3b526b31f94a7f0e0c20f48.png

点开 OTELDemo,可以看到 http request 和 response 等信息。

fb2d265734fe8075d77edb2884222649.png

对于返回503服务不可用的访问,可以看到 trace map 显示异常红色。

3281e70dcd4e3ce4aa50e60083231d64.jpeg

同时点开 OTELDemo,可以看到 http request 和 response 等信息,span 的状态 Fault 为True。

32cc78d7f16e6604ab74893f2fef8593.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

ADOT 作为 Amazon 的 OTel 发行版,由 Amazon 提供技术支持,帮助客户更方便将应用 OTel 技术栈,与 EKS、ECS、Lambda、EC2、Amazon Managed Service for Prometheus、Amazon Managed Grafana、X-Ray、CloudWatch 或三方合作伙伴 APM 产品集成。

本篇作者

8f3c395a5bbb018c3d5ee56b29370b81.jpeg

林旭芳

亚马逊云科技解决方案架构师,主要负责亚马逊云科技云技术和解决方案的推广工作,在  Container、容灾等方向有丰富实践经验。

1376dfe476646e6be330279eb99c87cb.jpeg

于昺蛟

亚马逊云科技解决方案架构师,负责互联网客户云计算方案的架构咨询和设计。在容器平台的建设和运维,应用现代化,DevOps 等领域有多年经验,致力于容器技术和现代化应用的推广。

66fe6a0b65de6e7de96888cbfc6f785b.gif

3e5f5a81ab772906780fad870188c533.gif

听说,点完下面4个按钮

就不会碰到bug了!

2d1fb5aacf25f7044890f7ef71220d89.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值