Công cụ giúp cải thiện hiệu suất trong ứng dụng trong Go (phần 1: dấu vết)

1. Giới thiệu

505827108874614d.png

Lần cập nhật gần đây nhất: ngày 15 tháng 7 năm 2022

Khả năng quan sát của ứng dụng

Khả năng quan sát và OpenTelemetry

Khả năng quan sát là thuật ngữ dùng để mô tả một thuộc tính của hệ thống. Một hệ thống có khả năng quan sát cho phép các nhóm chủ động gỡ lỗi hệ thống của mình. Trong bối cảnh đó, ba trụ cột của khả năng quan sát; nhật ký, chỉ số và dấu vết là các công cụ đo lường cơ bản để hệ thống có được khả năng quan sát.

OpenTelemetry là một tập hợp các thông số kỹ thuật, thư viện và tác nhân giúp tăng tốc quá trình đo lường và xuất dữ liệu đo từ xa (nhật ký, chỉ số và dấu vết) mà tính năng quan sát cần có. OpenTelemetry là một tiêu chuẩn mở và là dự án do cộng đồng điều hướng thuộc CNCF. Bằng cách sử dụng các thư viện mà dự án và hệ sinh thái của dự án cung cấp, nhà phát triển có thể đo lường ứng dụng của họ theo cách trung lập với nhà cung cấp và trên nhiều cấu trúc.

Ngoài 3 trụ cột của khả năng quan sát, việc lập hồ sơ liên tục là một thành phần chính khác để quan sát và mở rộng cơ sở người dùng trong ngành. Trình phân tích tài nguyên trên đám mây là một trong những công cụ ban đầu và cung cấp giao diện dễ dàng để xem chi tiết các chỉ số hiệu suất trong ngăn xếp lệnh gọi ứng dụng.

Lớp học lập trình này là phần 1 của loạt bài viết và đề cập đến việc đo lường các dấu vết phân tán trong các dịch vụ vi mô bằng OpenTelemetry và Cloud Trace. Phần 2 sẽ trình bày về tính năng phân tích liên tục bằng Trình phân tích tài nguyên trên đám mây.

Theo dõi phân tán

Trong số nhật ký, chỉ số và dấu vết, dấu vết là dữ liệu đo từ xa cho biết độ trễ của một phần cụ thể trong quy trình trong hệ thống. Đặc biệt là trong thời đại của các dịch vụ vi mô, tính năng theo dõi phân tán là yếu tố thúc đẩy mạnh mẽ để tìm ra các nút thắt cổ chai về độ trễ trong hệ thống phân tán tổng thể.

Khi phân tích các dấu vết được phân phối, hình ảnh dữ liệu dấu vết là yếu tố then chốt để nắm bắt nhanh độ trễ tổng thể của hệ thống. Trong dấu vết phân tán, chúng ta xử lý một tập hợp các lệnh gọi để xử lý một yêu cầu duy nhất đến điểm truy cập hệ thống ở dạng Dấu vết chứa nhiều Span.

Span đại diện cho một đơn vị công việc riêng lẻ được thực hiện trong một hệ thống phân tán, ghi lại thời gian bắt đầu và dừng. Các span thường có mối quan hệ phân cấp với nhau – trong hình bên dưới, tất cả các span nhỏ hơn đều là span con của một span /messages lớn và được tập hợp thành một Dấu vết cho thấy đường dẫn công việc thông qua một hệ thống.

Dấu vết

Google Cloud Trace là một trong những lựa chọn cho phần phụ trợ theo dõi phân tán và được tích hợp tốt với các sản phẩm khác trong Google Cloud.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ đo lường thông tin theo dõi trong các dịch vụ có tên là "ứng dụng Shakespeare" (còn gọi là Shakesapp) chạy trên cụm Google Kubernetes Engine. Cấu trúc của Shakesapp được mô tả dưới đây:

44e243182ced442f.png

  • Loadgen gửi một chuỗi truy vấn đến ứng dụng trong HTTP
  • Máy khách truyền truy vấn từ loadgen đến máy chủ trong gRPC
  • Máy chủ chấp nhận truy vấn từ ứng dụng, tìm nạp tất cả tác phẩm của Shakespeare ở định dạng văn bản từ Google Cloud Storage, tìm kiếm các dòng chứa truy vấn và trả về số dòng khớp với ứng dụng

Bạn sẽ đo lường thông tin theo dõi trên yêu cầu. Sau đó, bạn sẽ nhúng một tác nhân trình phân tích tài nguyên vào máy chủ và điều tra nút thắt cổ chai.

Kiến thức bạn sẽ học được

  • Cách bắt đầu sử dụng thư viện OpenTelemetry Trace trong dự án Go
  • Cách tạo span bằng thư viện
  • Cách truyền bối cảnh span qua mạng giữa các thành phần ứng dụng
  • Cách gửi dữ liệu theo dõi đến Cloud Trace
  • Cách phân tích dấu vết trên Cloud Trace

Lớp học lập trình này giải thích cách đo lường các dịch vụ vi mô. Để dễ hiểu, ví dụ này chỉ chứa 3 thành phần (trình tạo tải, ứng dụng và máy chủ), nhưng bạn có thể áp dụng quy trình tương tự được giải thích trong lớp học lập trình này cho các hệ thống lớn và phức tạp hơn.

Bạn cần có

  • Kiến thức cơ bản về Go
  • Kiến thức cơ bản về Kubernetes

2. Cách thiết lập và các yêu cầu

Thiết lập môi trường theo tốc độ của riêng bạn

Nếu chưa có Tài khoản Google (Gmail hoặc Google Apps), bạn phải tạo một tài khoản. Đăng nhập vào bảng điều khiển Google Cloud Platform ( console.cloud.google.com) và tạo một dự án mới.

Nếu bạn đã có dự án, hãy nhấp vào trình đơn thả xuống để chọn dự án ở phía trên bên trái của bảng điều khiển:

7a32e5469db69e9.png

rồi nhấp vào nút "NEW PROJECT" (DỰ ÁN MỚI) trong hộp thoại xuất hiện để tạo dự án mới:

7136b3ee36ebaf89.png

Nếu chưa có dự án, bạn sẽ thấy một hộp thoại như sau để tạo dự án đầu tiên:

870a3cbd6541ee86.png

Hộp thoại tạo dự án tiếp theo cho phép bạn nhập thông tin chi tiết về dự án mới:

affdc444517ba805.png

Hãy ghi nhớ mã dự án. Đây là một tên duy nhất trên tất cả các dự án Google Cloud (tên ở trên đã được sử dụng và sẽ không hoạt động đối với bạn, rất xin lỗi!). Mã này sẽ được gọi là PROJECT_ID ở phần sau của lớp học lập trình này.

Tiếp theo, nếu chưa thực hiện, bạn cần bật tính năng thanh toán trong Google Cloud Console để sử dụng các tài nguyên của Google Cloud và bật Cloud Trace API.

15d0ef27a8fbab27.png

Việc tham gia lớp học lập trình này sẽ không tốn quá vài đô la, nhưng có thể tốn nhiều hơn nếu bạn quyết định sử dụng nhiều tài nguyên hơn hoặc nếu bạn để các tài nguyên đó chạy (xem phần "dọn dẹp" ở cuối tài liệu này). Giá của Google Cloud Trace, Google Kubernetes Engine và Google Artifact Registry được ghi chú trong tài liệu chính thức.

Người dùng mới của Google Cloud Platform đủ điều kiện dùng thử 300 đô la Mỹ miễn phí, nhờ đó, bạn có thể tham gia lớp học lập trình này hoàn toàn miễn phí.

Thiết lập Google Cloud Shell

Mặc dù bạn có thể điều khiển Google Cloud và Google Cloud Trace từ xa trên máy tính xách tay, nhưng trong lớp học lập trình này, chúng ta sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trên đám mây.

Máy ảo dựa trên Debian này được tải sẵn tất cả các công cụ phát triển mà bạn cần. Ứng dụng này cung cấp một thư mục gốc 5 GB ổn định và chạy trong Google Cloud, giúp cải thiện đáng kể hiệu suất mạng và xác thực. Điều này có nghĩa là tất cả những gì bạn cần cho lớp học lập trình này là một trình duyệt (có, trình duyệt này hoạt động trên Chromebook).

Để kích hoạt Cloud Shell từ Cloud Console, bạn chỉ cần nhấp vào biểu tượng Kích hoạt Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (quá trình cấp phép và kết nối với môi trường sẽ chỉ mất vài phút).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Screen Shot 2017-06-14 at 10.13.43 PM.png

Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án đã được đặt thành PROJECT_ID.

gcloud auth list

Kết quả của lệnh

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Kết quả của lệnh

[core]
project = <PROJECT_ID>

Nếu vì lý do nào đó mà dự án không được đặt, bạn chỉ cần đưa ra lệnh sau:

gcloud config set project <PROJECT_ID>

Bạn đang tìm PROJECT_ID? Kiểm tra mã nhận dạng mà bạn đã sử dụng trong các bước thiết lập hoặc tra cứu mã nhận dạng đó trong trang tổng quan của Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Theo mặc định, Cloud Shell cũng đặt một số biến môi trường. Các biến này có thể hữu ích khi bạn chạy các lệnh trong tương lai.

echo $GOOGLE_CLOUD_PROJECT

Kết quả của lệnh

<PROJECT_ID>

Cuối cùng, hãy đặt múi giờ mặc định và cấu hình dự án.

gcloud config set compute/zone us-central1-f

Bạn có thể chọn nhiều múi giờ. Để biết thêm thông tin, hãy xem phần Khu vực và vùng.

Thiết lập ngôn ngữ của Go

Trong lớp học lập trình này, chúng ta sử dụng Go cho tất cả mã nguồn. Chạy lệnh sau trên Cloud Shell và xác nhận xem phiên bản Go có phải là 1.17 trở lên hay không

go version

Kết quả của lệnh

go version go1.18.3 linux/amd64

Thiết lập cụm Google Kubernetes

Trong lớp học lập trình này, bạn sẽ chạy một cụm dịch vụ vi mô trên Google Kubernetes Engine (GKE). Quy trình của lớp học lập trình này như sau:

  1. Tải dự án cơ sở xuống Cloud Shell
  2. Tạo dịch vụ vi mô vào vùng chứa
  3. Tải vùng chứa lên Google Artifact Registry (GAR)
  4. Triển khai vùng chứa trên GKE
  5. Sửa đổi mã nguồn của các dịch vụ để đo lường dấu vết
  6. Chuyển đến bước 2

Bật Kubernetes Engine

Trước tiên, chúng ta thiết lập một cụm Kubernetes nơi Shakesapp chạy trên GKE, vì vậy, chúng ta cần bật GKE. Chuyển đến trình đơn "Kubernetes Engine" (Công cụ Kubernetes) rồi nhấn nút BẬT.

548cfd95bc6d344d.png

Bây giờ, bạn đã sẵn sàng tạo một cụm Kubernetes.

Tạo cụm Kubernetes

Trên Cloud Shell, hãy chạy lệnh sau để tạo một cụm Kubernetes. Vui lòng xác nhận giá trị vùng nằm trong khu vực mà bạn sẽ sử dụng để tạo kho lưu trữ Cấu phần phần mềm. Thay đổi giá trị vùng us-central1-f nếu khu vực kho lưu trữ của bạn không bao gồm vùng đó.

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

Kết quả của lệnh

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

Thiết lập Artifact Registry và skaffold

Bây giờ, chúng ta đã có một cụm Kubernetes sẵn sàng để triển khai. Tiếp theo, chúng ta sẽ chuẩn bị cho một sổ đăng ký vùng chứa để đẩy và triển khai vùng chứa. Đối với các bước này, chúng ta cần thiết lập một Cấu phần phần mềm đăng ký (GAR) và skaffold để sử dụng.

Thiết lập Artifact Registry

Chuyển đến trình đơn "Registry Artifact" (Registry Artifact) rồi nhấn nút BẬT.

45e384b87f7cf0db.png

Sau một vài phút, bạn sẽ thấy trình duyệt kho lưu trữ của GAR. Nhấp vào nút "CREATE REPOSITORY" (TẠO KHOA HỌC) rồi nhập tên kho lưu trữ.

d6a70f4cb4ebcbe3.png

Trong lớp học lập trình này, tôi đặt tên cho kho lưu trữ mới là trace-codelab. Định dạng của cấu phần phần mềm là "Docker" và loại vị trí là "Khu vực". Chọn khu vực gần với khu vực bạn đặt cho vùng mặc định của Google Compute Engine. Ví dụ: ví dụ này đã chọn "us-central1-f" ở trên, vì vậy, ở đây chúng ta chọn "us-central1 (Iowa)". Sau đó, hãy nhấp vào nút "CREATE" (TẠO).

9c2d1ce65258ef70.png

Bây giờ, bạn sẽ thấy "trace-codelab" trên trình duyệt kho lưu trữ.

7a3c1f47346bea15.png

Chúng ta sẽ quay lại đây sau để kiểm tra đường dẫn đăng ký.

Thiết lập Skaffold

Skaffold là một công cụ hữu ích khi bạn xây dựng các dịch vụ vi mô chạy trên Kubernetes. Công cụ này xử lý quy trình xây dựng, đẩy và triển khai vùng chứa ứng dụng bằng một bộ lệnh nhỏ. Theo mặc định, Skaffold sử dụng Docker Registry làm kho lưu trữ vùng chứa, vì vậy, bạn cần định cấu hình skaffold để nhận dạng GAR khi đẩy vùng chứa đến.

Mở lại Cloud Shell và xác nhận xem skaffold đã được cài đặt hay chưa. (Theo mặc định, Cloud Shell sẽ cài đặt skaffold vào môi trường.) Chạy lệnh sau và xem phiên bản skaffold.

skaffold version

Kết quả của lệnh

v1.38.0

Bây giờ, bạn có thể đăng ký kho lưu trữ mặc định để skaffold sử dụng. Để lấy đường dẫn đăng ký, hãy tự chuyển đến trang tổng quan của Registry Artifact (Đăng ký cấu phần phần mềm) rồi nhấp vào tên của kho lưu trữ bạn vừa thiết lập ở bước trước.

7a3c1f47346bea15.png

Sau đó, bạn sẽ thấy các đường dẫn breadcrumb ở đầu trang. Nhấp vào biểu tượng e157b1359c3edc06.png để sao chép đường dẫn sổ đăng ký vào bảng nhớ tạm.

e0f2ae2144880b8b.png

Khi nhấp vào nút sao chép, bạn sẽ thấy hộp thoại ở cuối trình duyệt với thông báo như sau:

Đã sao chép "us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab"

Quay lại màn hình shell trên đám mây. Chạy lệnh skaffold config set default-repo với giá trị bạn vừa sao chép từ trang tổng quan.

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

Kết quả của lệnh

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

Ngoài ra, bạn cần định cấu hình sổ đăng ký thành cấu hình Docker. Chạy lệnh sau:

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

Kết quả của lệnh

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

Bây giờ, bạn có thể chuyển sang bước tiếp theo để thiết lập vùng chứa Kubernetes trên GKE.

Tóm tắt

Ở bước này, bạn thiết lập môi trường lớp học lập trình:

  • Thiết lập Cloud Shell
  • Tạo kho lưu trữ Artifact Registry cho kho lưu trữ vùng chứa
  • Thiết lập skaffold để sử dụng kho lưu trữ vùng chứa
  • Tạo một cụm Kubernetes nơi các dịch vụ vi mô của lớp học lập trình chạy

Tiếp theo

Trong bước tiếp theo, bạn sẽ tạo, đẩy và triển khai các dịch vụ vi mô lên cụm

3. Tạo, đẩy và triển khai các dịch vụ vi mô

Tải tài liệu của lớp học lập trình xuống

Ở bước trước, chúng ta đã thiết lập tất cả các điều kiện tiên quyết cho lớp học lập trình này. Giờ đây, bạn đã sẵn sàng chạy toàn bộ dịch vụ vi mô trên các dịch vụ đó. Tài liệu của lớp học lập trình này được lưu trữ trên GitHub, vì vậy, hãy tải tài liệu đó xuống môi trường Cloud Shell bằng lệnh git sau.

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

Cấu trúc thư mục của dự án như sau:

.
├── README.md
├── step0
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step1
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step2
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step3
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step4
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step5
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
└── step6
    ├── manifests
    ├── proto
    ├── skaffold.yaml
    └── src
  • manifests: Tệp kê khai Kubernetes
  • proto: định nghĩa proto cho hoạt động giao tiếp giữa ứng dụng và máy chủ
  • src: thư mục cho mã nguồn của từng dịch vụ
  • skaffold.yaml: Tệp cấu hình cho skaffold

Trong lớp học lập trình này, bạn sẽ cập nhật mã nguồn nằm trong thư mục step0. Bạn cũng có thể tham khảo mã nguồn trong thư mục step[1-6] để biết câu trả lời trong các bước sau. (Phần 1 bao gồm bước 0 đến bước 4 và Phần 2 bao gồm bước 5 và 6)

Chạy lệnh skaffold

Cuối cùng, bạn đã sẵn sàng tạo, đẩy và triển khai toàn bộ nội dung lên cụm Kubernetes mà bạn vừa tạo. Có vẻ như quy trình này bao gồm nhiều bước, nhưng thực tế là skaffold sẽ làm mọi việc cho bạn. Hãy thử bằng lệnh sau:

cd step0
skaffold dev

Ngay khi chạy lệnh, bạn sẽ thấy kết quả nhật ký của docker build và có thể xác nhận rằng các kết quả này đã được đẩy thành công vào sổ đăng ký.

Kết quả của lệnh

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

Sau khi đẩy tất cả các vùng chứa dịch vụ, các bản triển khai Kubernetes sẽ tự động bắt đầu.

Kết quả của lệnh

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

Sau khi triển khai, bạn sẽ thấy nhật ký ứng dụng thực tế được phát ra stdout trong mỗi vùng chứa như sau:

Kết quả của lệnh

[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463

Lưu ý rằng tại thời điểm này, bạn muốn xem mọi thông báo từ máy chủ. Cuối cùng, bạn đã sẵn sàng bắt đầu đo lường ứng dụng bằng OpenTelemetry để theo dõi phân tán các dịch vụ.

Trước khi bắt đầu đo lường dịch vụ, vui lòng tắt cụm bằng tổ hợp phím Ctrl-C.

Kết quả của lệnh

...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
 - W0714 06:34:58.464305   28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
 - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Tóm tắt

Ở bước này, bạn đã chuẩn bị tài liệu lớp học lập trình trong môi trường của mình và xác nhận skaffold chạy như dự kiến.

Tiếp theo

Trong bước tiếp theo, bạn sẽ sửa đổi mã nguồn của dịch vụ loadgen để đo lường thông tin theo dõi.

4. Đo lường cho HTTP

Khái niệm về việc đo lường và truyền tải dấu vết

Trước khi chỉnh sửa mã nguồn, hãy để tôi giải thích ngắn gọn cách hoạt động của các dấu vết phân tán trong một sơ đồ đơn giản.

6be42e353b9bfd1d.png

Trong ví dụ này, chúng ta đo lường mã để xuất thông tin theo dõi và span sang Cloud Trace, đồng thời truyền tải ngữ cảnh theo dõi trên yêu cầu từ dịch vụ loadgen đến dịch vụ máy chủ.

Các ứng dụng cần gửi siêu dữ liệu theo dõi như Mã theo dõi và Mã span để Cloud Trace có thể tập hợp tất cả span có cùng Mã theo dõi thành một dấu vết. Ngoài ra, ứng dụng cần truyền tải ngữ cảnh theo dõi (kết hợp Mã theo dõi và Mã span của span mẹ) khi yêu cầu các dịch vụ hạ nguồn để có thể biết được ngữ cảnh theo dõi mà chúng đang xử lý.

OpenTelemetry giúp bạn:

  • để tạo Mã theo dõi và Mã span duy nhất
  • để xuất Mã theo dõi và Mã span sang phần phụ trợ
  • để truyền tải ngữ cảnh theo dõi đến các dịch vụ khác
  • để nhúng siêu dữ liệu bổ sung giúp phân tích dấu vết

Các thành phần trong Dấu vết OpenTelemetry

b01f7bb90188db0d.png

Quy trình đo lường dấu vết ứng dụng bằng OpenTelemetry như sau:

  1. Tạo trình xuất
  2. Tạo TracerProvider liên kết trình xuất trong 1 và đặt trình xuất đó thành toàn cục.
  3. Đặt TextMapPropagaror để đặt phương thức truyền tải
  4. Lấy Tracer từ TracerProvider
  5. Tạo Span từ Trình theo dõi

Hiện tại, bạn không cần hiểu rõ các thuộc tính chi tiết trong mỗi thành phần, nhưng điều quan trọng nhất cần nhớ là:

  • trình xuất ở đây có thể cắm vào TracerProvider
  • TracerProvider lưu giữ tất cả cấu hình liên quan đến hoạt động lấy mẫu và xuất dấu vết
  • tất cả dấu vết đều được gói trong đối tượng Tracer

Khi đã hiểu rõ điều này, hãy chuyển sang công việc lập trình thực tế.

Đo lường span đầu tiên

Dịch vụ trình tạo tải đo lường

Mở Trình chỉnh sửa Cloud Shell bằng cách nhấn vào nút 776a11bfb2122549.pngở trên cùng bên phải của Cloud Shell. Mở step0/src/loadgen/main.go từ trình khám phá trong ngăn bên trái và tìm hàm chính.

step0/src/loadgen/main.go

func main() {
        ...
        for range t.C {
                log.Printf("simulating client requests, round %d", i)
                if err := run(numWorkers, numConcurrency); err != nil {
                        log.Printf("aborted round with error: %v", err)
                }
                log.Printf("simulated %d requests", numWorkers)
                if numRounds != 0 && i > numRounds {
                        break
                }
                i++
        }
}

Trong hàm chính, bạn sẽ thấy vòng lặp gọi hàm run trong đó. Trong quá trình triển khai hiện tại, phần này có 2 dòng nhật ký ghi lại thời điểm bắt đầu và kết thúc lệnh gọi hàm. Bây giờ, hãy đo lường thông tin Span để theo dõi độ trễ của lệnh gọi hàm.

Trước tiên, như đã lưu ý trong phần trước, hãy thiết lập toàn bộ cấu hình cho OpenTelemetry. Thêm các gói OpenTelemetry như sau:

step0/src/loadgen/main.go

import (
        "context" // step1. add packages
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
        // step1. end add packages
)

Để dễ đọc, chúng ta tạo một hàm thiết lập có tên là initTracer và gọi hàm đó trong hàm main.

step0/src/loadgen/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Bạn có thể nhận thấy quy trình thiết lập OpenTelemetry như mô tả trong phần trước. Trong quá trình triển khai này, chúng ta sử dụng trình xuất stdout để xuất tất cả thông tin theo dõi vào stdout ở định dạng có cấu trúc.

Sau đó, bạn gọi hàm này từ hàm chính. Gọi initTracer() và nhớ gọi TracerProvider.Shutdown() khi bạn đóng ứng dụng.

step0/src/loadgen/main.go

func main() {
        // step1. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step1. end setup

        log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency)
        log.Printf("number of rounds: %d (0 is inifinite)", numRounds)
        ...

Sau khi hoàn tất việc thiết lập, bạn cần tạo một Span có Mã theo dõi và Mã span duy nhất. OpenTelemetry cung cấp một thư viện hữu ích cho việc này. Thêm các gói mới bổ sung vào ứng dụng khách HTTP đo lường.

step0/src/loadgen/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "math/rand"
        "net/http"
        "net/http/httptrace" // step1. add packages
        "net/url"
        "time"
        // step1. add packages
        "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        // step1. end add packages
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
        "go.opentelemetry.io/otel/trace"
)

Vì trình tạo tải đang gọi dịch vụ ứng dụng trong HTTP bằng net/http trong hàm runQuery, nên chúng ta sử dụng gói contrib cho net/http và bật tính năng đo lường bằng tiện ích của gói httptraceotelhttp.

Trước tiên, hãy thêm biến toàn cục httpClient của gói để gọi các yêu cầu HTTP thông qua ứng dụng được đo lường.

step0/src/loadgen/main.go

var httpClient = http.Client{
        Transport: otelhttp.NewTransport(http.DefaultTransport)
}

Tiếp theo, hãy thêm khả năng đo lường trong hàm runQuery để tạo span tuỳ chỉnh bằng OpenTelemetry và span được tạo tự động từ ứng dụng HTTP tuỳ chỉnh. Bạn sẽ làm như sau:

  1. Lấy Tracer từ TracerProvider toàn cục bằng otel.Tracer()
  2. Tạo span gốc bằng phương thức Tracer.Start()
  3. Kết thúc span gốc theo thời gian tuỳ ý (trong trường hợp này là kết thúc hàm runQuery)

step0/src/loadgen/main.go

        reqURL.RawQuery = v.Encode()
        // step1. replace http.Get() with custom client call
        // resp, err := http.Get(reqURL.String())

        // step1. instrument trace
        ctx := context.Background()
        tr := otel.Tracer("loadgen")
        ctx, span := tr.Start(ctx, "query.request", trace.WithAttributes(
                semconv.TelemetrySDKLanguageGo,
                semconv.ServiceNameKey.String("loadgen.runQuery"),
                attribute.Key("query").String(s),
        ))
        defer span.End()
        ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
        req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil)
        if err != nil {
                return -1, fmt.Errorf("error creating HTTP request object: %v", err)
        }
        resp, err := httpClient.Do(req)
        // step1. end instrumentation
        if err != nil {
                return -1, fmt.Errorf("error sending request to %v: %v", reqURL.String(), err)
        }

Giờ thì bạn đã hoàn tất việc đo lường trong loadgen (ứng dụng ứng dụng HTTP). Vui lòng nhớ cập nhật go.modgo.sum bằng lệnh go mod.

go mod tidy

Dịch vụ khách hàng về công cụ

Trong phần trước, chúng ta đã đo lường phần được bao bọc trong hình chữ nhật màu đỏ trong bản vẽ bên dưới. Chúng tôi đã đo lường thông tin span trong dịch vụ trình tạo tải. Tương tự như dịch vụ trình tạo tải, giờ đây, chúng ta cần đo lường dịch vụ ứng dụng. Điểm khác biệt so với dịch vụ trình tạo tải là dịch vụ ứng dụng cần trích xuất thông tin Mã theo dõi được truyền từ dịch vụ trình tạo tải trong tiêu đề HTTP và sử dụng mã này để tạo Span.

bcaccd06691269f8.png

Mở Trình chỉnh sửa Cloud Shell và thêm các gói bắt buộc như chúng ta đã làm cho dịch vụ trình tạo tải.

step0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step1. add new import
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
        // step1. end new import
)

Xin nhắc lại, chúng ta cần thiết lập OpenTelemtry. Bạn chỉ cần sao chép và dán hàm initTracer từ loadgen rồi gọi hàm đó trong hàm main của dịch vụ khách.

step0/src/client/main.go

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Bây giờ, đã đến lúc đo lường các span. Vì dịch vụ ứng dụng cần chấp nhận các yêu cầu HTTP từ dịch vụ loadgen, nên dịch vụ này cần đo lường trình xử lý. Máy chủ HTTP trong dịch vụ ứng dụng được triển khai bằng net/http và bạn có thể sử dụng gói otelhttp như chúng ta đã làm trong loadgen.

Trước tiên, chúng ta sẽ thay thế việc đăng ký trình xử lý bằng Trình xử lý otelhttp. Trong hàm main, hãy tìm các dòng mà trình xử lý HTTP được đăng ký bằng http.HandleFunc().

step0/src/client/main.go

        // step1. change handler to intercept OpenTelemetry related headers
        // http.HandleFunc("/", svc.handler)
        otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler")
        http.Handle("/", otelHandler)
        // step1. end intercepter setting
        http.HandleFunc("/_genki", svc.health)

Sau đó, chúng ta đo lường span thực tế bên trong trình xử lý. Tìm func (*clientService) handler() và thêm tính năng đo lường span bằng trace.SpanFromContext().

step0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        ctx := r.Context()
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()
        // step1. instrument trace
        span := trace.SpanFromContext(ctx)
        defer span.End()
        // step1. end instrument
        ...

Với phương thức đo lường này, bạn sẽ nhận được các span từ đầu phương thức handler đến cuối phương thức đó. Để dễ dàng phân tích các span, hãy thêm một thuộc tính bổ sung để lưu trữ số lượng trùng khớp vào truy vấn. Ngay trước dòng nhật ký, hãy thêm mã sau.

step0/src/client/main.go

func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) {
        ...
        // step1. add span specific attribute
        span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount))
        // step1. end adding attribute
        log.Println(string(ret))
        ...

Với tất cả các thiết bị đo lường ở trên, bạn đã hoàn tất việc đo lường dấu vết giữa loadgen và ứng dụng. Hãy xem cách hoạt động của tính năng này. Chạy lại mã bằng skaffold.

skaffold dev

Sau một khoảng thời gian chạy các dịch vụ trên cụm GKE, bạn sẽ thấy một lượng lớn thông điệp nhật ký như sau:

Kết quả của lệnh

[loadgen] {
[loadgen]       "Name": "query.request",
[loadgen]       "SpanContext": {
[loadgen]               "TraceID": "cfa22247a542beeb55a3434392d46b89",
[loadgen]               "SpanID": "18b06404b10c418b",
[loadgen]               "TraceFlags": "01",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "Parent": {
[loadgen]               "TraceID": "00000000000000000000000000000000",
[loadgen]               "SpanID": "0000000000000000",
[loadgen]               "TraceFlags": "00",
[loadgen]               "TraceState": "",
[loadgen]               "Remote": false
[loadgen]       },
[loadgen]       "SpanKind": 1,
[loadgen]       "StartTime": "2022-07-14T13:13:36.686751087Z",
[loadgen]       "EndTime": "2022-07-14T13:14:31.849601964Z",
[loadgen]       "Attributes": [
[loadgen]               {
[loadgen]                       "Key": "telemetry.sdk.language",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "go"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "loadgen.runQuery"
[loadgen]                       }
[loadgen]               },
[loadgen]               {
[loadgen]                       "Key": "query",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "faith"
[loadgen]                       }
[loadgen]               }
[loadgen]       ],
[loadgen]       "Events": null,
[loadgen]       "Links": null,
[loadgen]       "Status": {
[loadgen]               "Code": "Unset",
[loadgen]               "Description": ""
[loadgen]       },
[loadgen]       "DroppedAttributes": 0,
[loadgen]       "DroppedEvents": 0,
[loadgen]       "DroppedLinks": 0,
[loadgen]       "ChildSpanCount": 5,
[loadgen]       "Resource": [
[loadgen]               {
[loadgen]                       "Key": "service.name",
[loadgen]                       "Value": {
[loadgen]                               "Type": "STRING",
[loadgen]                               "Value": "unknown_service:loadgen"
...

Trình xuất stdout phát ra các thông báo này. Bạn sẽ nhận thấy rằng phần tử mẹ của tất cả các span theo loadgen đều có TraceID: 00000000000000000000000000000000, vì đây là span gốc, tức là span đầu tiên trong dấu vết. Ngoài ra, bạn sẽ thấy thuộc tính nhúng "query" có chuỗi truy vấn được chuyển đến dịch vụ khách hàng.

Tóm tắt

Trong bước này, bạn đã đo lường dịch vụ trình tạo tải và dịch vụ ứng dụng giao tiếp bằng HTTP, đồng thời xác nhận rằng bạn có thể truyền thành công Ngữ cảnh theo dõi trên các dịch vụ và xuất thông tin Span từ cả hai dịch vụ sang stdout.

Tiếp theo

Ở bước tiếp theo, bạn sẽ đo lường dịch vụ ứng dụng và dịch vụ máy chủ để xác nhận cách truyền tải Ngữ cảnh theo dõi qua gRPC.

5. Đo lường cho gRPC

Ở bước trước, chúng ta đã đo lường nửa đầu của yêu cầu trong dịch vụ vi mô này. Trong bước này, chúng ta cố gắng đo lường hoạt động giao tiếp gRPC giữa dịch vụ ứng dụng và dịch vụ máy chủ. (Hình chữ nhật màu xanh lục và tím trong hình bên dưới)

75310d8e0e3b1a30.png

Thiết bị đo lường trước khi tạo cho ứng dụng gRPC

Hệ sinh thái của OpenTelemetry cung cấp nhiều thư viện tiện dụng giúp nhà phát triển đo lường các ứng dụng. Ở bước trước, chúng ta đã sử dụng tính năng đo lường trước khi tạo cho gói net/http. Trong bước này, khi cố gắng truyền tải Ngữ cảnh theo dõi thông qua gRPC, chúng ta sẽ sử dụng thư viện này.

Trước tiên, bạn nhập gói gRPC tạo sẵn có tên là otelgrpc.

step0/src/client/main.go

import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "net/url"
        "os"
        "time"

        "opentelemetry-trace-codelab-go/client/shakesapp"
        // step2. add prebuilt gRPC package (otelgrpc) 
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "go.opentelemetry.io/otel/trace"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
)

Lần này, dịch vụ máy khách là một ứng dụng gRPC so với dịch vụ máy chủ, vì vậy, bạn cần đo lường ứng dụng gRPC. Tìm hàm mustConnGRPC và thêm trình chặn gRPC đo lường các span mới mỗi khi ứng dụng gửi yêu cầu đến máy chủ.

step0/src/client/main.go

// Helper function for gRPC connections: Dial and create client once, reuse.
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
        var err error
        // step2. add gRPC interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        *conn, err = grpc.DialContext(ctx, addr,
                grpc.WithTransportCredentials(insecure.NewCredentials()),
                grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)),
                grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)),
                grpc.WithTimeout(time.Second*3),
        )
        // step2: end adding interceptor
        if err != nil {
                panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr))
        }
}

Vì bạn đã thiết lập OpenTelemetry trong phần trước, nên bạn không cần thiết lập lại.

Công cụ đo lường tạo sẵn cho máy chủ gRPC

Giống như những gì chúng ta đã làm cho ứng dụng gRPC, chúng ta gọi công cụ đo lường tạo sẵn cho máy chủ gRPC. Thêm gói mới vào phần nhập như sau:

step0/src/server/main.go

import (
        "context"
        "fmt"
        "io/ioutil"
        "log"
        "net"
        "os"
        "regexp"
        "strings"

        "opentelemetry-trace-codelab-go/server/shakesapp"

        "cloud.google.com/go/storage"
        // step2. add OpenTelemetry packages including otelgrpc
        "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
        "go.opentelemetry.io/otel"
        stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
        "go.opentelemetry.io/otel/propagation"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        "google.golang.org/api/iterator"
        "google.golang.org/api/option"
        "google.golang.org/grpc"
        healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

Vì đây là lần đầu tiên đo lường máy chủ, nên trước tiên, bạn cần thiết lập OpenTelemetry, tương tự như những gì chúng ta đã làm cho loadgen và các dịch vụ ứng dụng.

step0/src/server/main.go

// step2. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // create a stdout exporter to show collected spans out to stdout.
        exporter, err := stdout.New(stdout.WithPrettyPrint())
        if err != nil {
                return nil, err
        }
        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

func main() {
        ...

        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup
        ...

Tiếp theo, bạn cần thêm trình chặn máy chủ. Trong hàm main, hãy tìm vị trí gọi grpc.NewServer() và thêm trình chặn vào hàm.

step0/src/server/main.go

func main() {
        ...
        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        ...

Chạy dịch vụ vi mô và xác nhận dấu vết

Sau đó, hãy chạy mã đã sửa đổi bằng lệnh skaffold.

skaffold dev

Giờ đây, bạn sẽ thấy một loạt thông tin span trên stdout.

Kết quả của lệnh

...
[server] {
[server]        "Name": "shakesapp.ShakespeareService/GetMatchCount",
[server]        "SpanContext": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "96030dbad0061b3f",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": false
[server]        },
[server]        "Parent": {
[server]                "TraceID": "89b472f213a400cf975e0a0041649667",
[server]                "SpanID": "cd90cc3859b73890",
[server]                "TraceFlags": "01",
[server]                "TraceState": "",
[server]                "Remote": true
[server]        },
[server]        "SpanKind": 2,
[server]        "StartTime": "2022-07-14T14:05:55.74822525Z",
[server]        "EndTime": "2022-07-14T14:06:03.449258891Z",
[server]        "Attributes": [
...
[server]        ],
[server]        "Events": [
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:05:55.748235489Z"
[server]                },
[server]                {
[server]                        "Name": "message",
[server]                        "Attributes": [
...
[server]                        ],
[server]                        "DroppedAttributeCount": 0,
[server]                        "Time": "2022-07-14T14:06:03.449255889Z"
[server]                }
[server]        ],
[server]        "Links": null,
[server]        "Status": {
[server]                "Code": "Unset",
[server]                "Description": ""
[server]        },
[server]        "DroppedAttributes": 0,
[server]        "DroppedEvents": 0,
[server]        "DroppedLinks": 0,
[server]        "ChildSpanCount": 0,
[server]        "Resource": [
[server]                {
...
[server]        ],
[server]        "InstrumentationLibrary": {
[server]                "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc",
[server]                "Version": "semver:0.33.0",
[server]                "SchemaURL": ""
[server]        }
[server] }
...

Bạn nhận thấy mình chưa nhúng tên span nào và đã tạo span theo cách thủ công bằng trace.Start() hoặc span.SpanFromContext(). Tuy nhiên, bạn vẫn nhận được một số lượng lớn span vì trình chặn gRPC đã tạo ra các span đó.

Tóm tắt

Trong bước này, bạn đã đo lường hoạt động giao tiếp dựa trên gRPC với sự hỗ trợ của các thư viện hệ sinh thái OpenTelemetry.

Tiếp theo

Cuối cùng, trong bước tiếp theo, bạn sẽ trực quan hoá dấu vết bằng Cloud Trace và tìm hiểu cách phân tích các span đã thu thập.

6. Hình dung dấu vết bằng Cloud Trace

Bạn đã đo lường dấu vết trong toàn bộ hệ thống bằng OpenTelemetry. Đến đây, bạn đã tìm hiểu cách đo lường các dịch vụ HTTP và gRPC. Mặc dù đã tìm hiểu cách đo lường các chỉ số này, nhưng bạn vẫn chưa tìm hiểu cách phân tích các chỉ số đó. Trong phần này, bạn sẽ thay thế trình xuất stdout bằng trình xuất Cloud Trace và tìm hiểu cách phân tích dấu vết.

Sử dụng trình xuất Cloud Trace

Một trong những đặc điểm mạnh mẽ của OpenTelemetry là khả năng cắm. Để trực quan hoá tất cả các span do công cụ đo lường của bạn thu thập, bạn chỉ cần thay thế trình xuất tệp stdout bằng trình xuất Cloud Trace.

Mở tệp main.go của từng dịch vụ và tìm hàm initTracer(). Xoá dòng để tạo trình xuất stdout và tạo trình xuất Cloud Trace.

step0/src/loadgen/main.go

import (
        ...
        // step3. add OpenTelemetry for Cloud Trace package
        cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
)

// step1. add OpenTelemetry initialization function
func initTracer() (*sdktrace.TracerProvider, error) {
        // step3. replace stdout exporter with Cloud Trace exporter
        // cloudtrace.New() finds the credentials to Cloud Trace automatically following the
        // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams.
        // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams
        exporter, err := cloudtrace.New()
        // step3. end replacing exporter
        if err != nil {
                return nil, err
        }

        // for the demonstration, we use AlwaysSmaple sampler to take all spans.
        // do not use this option in production.
        tp := sdktrace.NewTracerProvider(
                sdktrace.WithSampler(sdktrace.AlwaysSample()),
                sdktrace.WithBatcher(exporter),
        )
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.TraceContext{})
        return tp, nil
}

Bạn cũng cần chỉnh sửa cùng một hàm trong dịch vụ máy khách và máy chủ.

Chạy dịch vụ vi mô và xác nhận dấu vết

Sau khi chỉnh sửa, bạn chỉ cần chạy cụm như bình thường bằng lệnh skaffold.

skaffold dev

Sau đó, bạn sẽ không thấy nhiều thông tin span ở định dạng nhật ký có cấu trúc trên stdout, vì bạn đã thay thế trình xuất bằng trình xuất Cloud Trace.

Kết quả của lệnh

[loadgen] 2022/07/14 15:01:07 simulated 20 requests
[loadgen] 2022/07/14 15:01:07 simulating client requests, round 37
[loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958
[client] 2022/07/14 15:01:14 {"match_count":958}
[client] 2022/07/14 15:01:14 {"match_count":3040}
[loadgen] 2022/07/14 15:01:14 query 'love': matched 3040
[client] 2022/07/14 15:01:15 {"match_count":349}
[loadgen] 2022/07/14 15:01:15 query 'hello': matched 349
[client] 2022/07/14 15:01:15 {"match_count":484}
[loadgen] 2022/07/14 15:01:15 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14
[client] 2022/07/14 15:01:15 {"match_count":14}
[client] 2022/07/14 15:01:21 {"match_count":484}
[loadgen] 2022/07/14 15:01:21 query 'faith': matched 484
[client] 2022/07/14 15:01:21 {"match_count":728}
[loadgen] 2022/07/14 15:01:21 query 'world': matched 728
[client] 2022/07/14 15:01:22 {"match_count":484}
[loadgen] 2022/07/14 15:01:22 query 'faith': matched 484
[loadgen] 2022/07/14 15:01:22 query 'hello': matched 349
[client] 2022/07/14 15:01:22 {"match_count":349}
[client] 2022/07/14 15:01:23 {"match_count":1036}
[loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036
[loadgen] 2022/07/14 15:01:28 query 'tear': matched 463
...

Bây giờ, hãy xác nhận xem tất cả các span có được gửi chính xác đến Cloud Trace hay không. Truy cập vào Cloud Console rồi chuyển đến "Danh sách theo dõi". Bạn có thể dễ dàng truy cập vào tính năng này từ hộp tìm kiếm. Nếu không, bạn có thể nhấp vào trình đơn trong ngăn bên trái. 8b3f8411bd737e06.png

Sau đó, bạn sẽ thấy nhiều điểm màu xanh dương được phân phối trên biểu đồ độ trễ. Mỗi điểm đại diện cho một dấu vết.

3ecf131423fc4c40.png

Nhấp vào một trong các dấu vết đó để xem thông tin chi tiết bên trong dấu vết. 4fd10960c6648a03.png

Ngay cả khi chỉ xem nhanh thông tin đơn giản này, bạn cũng đã biết được nhiều thông tin chi tiết. Ví dụ: từ biểu đồ dạng thác nước, bạn có thể thấy nguyên nhân gây ra độ trễ chủ yếu là do span có tên shakesapp.ShakespeareService/GetMatchCount. (Xem 1 trong hình trên) Bạn có thể xác nhận điều đó qua bảng tóm tắt. (Cột ngoài cùng bên phải cho biết thời lượng của mỗi span.) Ngoài ra, dấu vết này là của truy vấn "friend" (bạn bè). (Xem 2 trong hình ảnh ở trên)

Với những phân tích ngắn này, bạn có thể nhận ra rằng mình cần biết thêm về các span chi tiết hơn bên trong phương thức GetMatchCount. So với thông tin stdout, hình ảnh trực quan rất mạnh mẽ. Để tìm hiểu thêm về thông tin chi tiết về Cloud Trace, vui lòng truy cập vào tài liệu chính thức của chúng tôi.

Tóm tắt

Trong bước này, bạn đã thay thế trình xuất tệp stdout bằng trình xuất Cloud Trace và trực quan hoá các dấu vết trên Cloud Trace. Ngoài ra, bạn đã tìm hiểu cách bắt đầu phân tích dấu vết.

Tiếp theo

Trong bước tiếp theo, bạn sẽ sửa đổi mã nguồn của dịch vụ máy chủ để thêm một span con trong GetMatchCount.

7. Thêm span phụ để phân tích hiệu quả hơn

Ở bước trước, bạn đã tìm thấy nguyên nhân gây ra thời gian thực hiện một vòng từ loadgen chủ yếu là quá trình bên trong phương thức GetMatchCount, trình xử lý gRPC, trong dịch vụ máy chủ. Tuy nhiên, vì chúng ta chưa đo lường bất kỳ giá trị nào khác ngoài trình xử lý, nên chúng ta không thể tìm thấy thông tin chi tiết khác từ biểu đồ dạng thác nước. Đây là trường hợp thường gặp khi chúng ta bắt đầu đo lường các dịch vụ vi mô.

3b63a1e471dddb8c.png

Trong phần này, chúng ta sẽ đo lường một span con trong đó máy chủ gọi Google Cloud Storage, vì thường thì một số I/O mạng bên ngoài sẽ mất nhiều thời gian trong quá trình này và điều quan trọng là phải xác định xem lệnh gọi có phải là nguyên nhân hay không.

Đo lường một span con trong máy chủ

Mở main.go trong máy chủ và tìm hàm readFiles. Hàm này đang gọi một yêu cầu đến Google Cloud Storage để tìm nạp tất cả tệp văn bản của các tác phẩm của Shakespeare. Trong hàm này, bạn có thể tạo một span con, giống như những gì bạn đã làm cho hoạt động đo lường máy chủ HTTP trong dịch vụ ứng dụng.

step0/src/server/main.go

func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) {
        type resp struct {
                s   string
                err error
        }

        // step4: add an extra span
        span := trace.SpanFromContext(ctx)
        span.SetName("server.readFiles")
        span.SetAttributes(attribute.Key("bucketname").String(bucketName))
        defer span.End()
        // step4: end add span
        ...

Đó là tất cả những gì cần làm để thêm một span mới. Hãy chạy ứng dụng để xem kết quả.

Chạy dịch vụ vi mô và xác nhận dấu vết

Sau khi chỉnh sửa, bạn chỉ cần chạy cụm như bình thường bằng lệnh skaffold.

skaffold dev

Và chọn một dấu vết có tên query.request trong danh sách dấu vết. Bạn sẽ thấy một biểu đồ thác nước theo dõi tương tự, ngoại trừ một span mới trong shakesapp.ShakespeareService/GetMatchCount. (Phần span được bao quanh bởi hình chữ nhật màu đỏ bên dưới)

3d4a891aa30d7a32.png

Từ biểu đồ này, bạn có thể biết rằng lệnh gọi bên ngoài đến Google Cloud Storage chiếm một lượng lớn độ trễ, nhưng vẫn có những yếu tố khác gây ra phần lớn độ trễ.

Bạn đã có được nhiều thông tin chi tiết chỉ bằng một vài lần xem biểu đồ dạng thác nước theo dõi. Làm cách nào để bạn có được thông tin chi tiết hơn về hiệu suất trong ứng dụng? Đây là lúc trình phân tích tài nguyên xuất hiện. Tuy nhiên, hiện tại, hãy kết thúc lớp học lập trình này và chuyển tất cả hướng dẫn về trình phân tích tài nguyên sang phần 2.

Tóm tắt

Trong bước này, bạn đã đo lường một span khác trong dịch vụ máy chủ và thu thập thêm thông tin chi tiết về độ trễ của hệ thống.

8. Xin chúc mừng

Bạn đã tạo thành công các dấu vết phân tán bằng OpenTelemery và xác nhận độ trễ yêu cầu trên dịch vụ vi mô trên Google Cloud Trace.

Đối với các bài tập mở rộng, bạn có thể tự mình thử các chủ đề sau.

  • Cách triển khai hiện tại sẽ gửi tất cả các span do tính năng kiểm tra tình trạng tạo ra. (grpc.health.v1.Health/Check) Làm cách nào để lọc ra các span đó khỏi Cloud Trace? Gợi ý tại đây.
  • Liên kết nhật ký sự kiện với các span và xem cách hoạt động của nhật ký sự kiện trên Google Cloud Trace và Google Cloud Logging. Gợi ý tại đây.
  • Thay thế một số dịch vụ bằng dịch vụ bằng ngôn ngữ khác và cố gắng đo lường dịch vụ đó bằng OpenTelemetry cho ngôn ngữ đó.

Ngoài ra, nếu bạn muốn tìm hiểu về trình phân tích tài nguyên sau phần này, vui lòng chuyển sang phần 2. Trong trường hợp đó, bạn có thể bỏ qua phần dọn dẹp bên dưới.

Dọn dẹp

Sau lớp học lập trình này, vui lòng dừng cụm Kubernetes và nhớ xoá dự án để bạn không bị tính phí ngoài dự kiến trên Google Kubernetes Engine, Google Cloud Trace, Google Artifact Registry.

Trước tiên, hãy xoá cụm. Nếu đang chạy cụm bằng skaffold dev, bạn chỉ cần nhấn tổ hợp phím Ctrl-C. Nếu bạn đang chạy cụm bằng skaffold run, hãy chạy lệnh sau:

skaffold delete

Kết quả của lệnh

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

Sau khi xoá cụm, trong ngăn trình đơn, hãy chọn "Quản trị và quản lý danh tính và quyền truy cập (IAM) > Cài đặt", rồi nhấp vào nút "TẮT".

45aa37b7d5e1ddd1.png

Sau đó, nhập Mã dự án (không phải Tên dự án) vào biểu mẫu trong hộp thoại rồi xác nhận tắt.