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 07 năm 2022

Khả năng ghi nhận của ứng dụng

Khả năng quan sát và phương pháp 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 ghi nhận được cho phép các nhóm chủ động gỡ lỗi hệ thống của mình. Trong bối cảnh đó, 3 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 thu thập 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 đẩy nhanh việc đo lường và xuất dữ liệu đo từ xa (nhật ký, chỉ số và dấu vết) mà khả năng quan sát đòi hỏi. OpenTelemetry là một dự án tiêu chuẩn mở và hướng đến cộng đồng theo 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, các nhà phát triển có thể đo lường các ứng dụng của họ theo cách trung lập với nhà cung cấp và chống lại nhiều kiến trúc.

Ngoài ra, ngoài 3 yếu tố chính của khả năng quan sát, tính năng lập hồ sơ liên tục là một thành phần quan trọng khác giúp ghi nhận khả năng 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 trình khởi tạo và cung cấp một 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 trình bày về việc đo lường dấu vết phân phối trong vi dịch vụ bằng OpenTelemetry và Cloud Trace. Phần 2 sẽ trình bày về quá trình lập hồ sơ liên tục bằng Trình phân tích tài nguyên trên đám mây.

Dấu vết đã phân phối

Trong các 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 trong thời đại của các dịch vụ vi mô, dấu vết phân tán là yếu tố thúc đẩy mạnh mẽ để phát hiện điểm tắc nghẽn về độ trễ trong hệ thống phân phối tổng thể.

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

Span biểu thị một đơn vị công việc riêng lẻ được thực hiện trong một hệ thống phân phối, ghi lại thời gian bắt đầu và thời gian kết thúc. 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 là các span con của một /messages span và được tập hợp thành một Trace cho thấy lộ trình làm việc thông qua một hệ thống.

Một dấu vết

Google Cloud Trace là một trong những công cụ phụ trợ theo dõi phân tán và có thể tích hợp hiệu quả 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ẽ tìm hiểu thông tin theo dõi đo lường 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ả như dưới đây:

44e243182ced442f.png.

  • Loadgen gửi một chuỗi truy vấn đến máy khách trong HTTP
  • Ứng dụng chuyển truy vấn từ trình tạo tải đến máy chủ trong gRPC
  • Máy chủ chấp nhận truy vấn của ứng dụng, tìm nạp tất cả tác phẩm của Shakespare ở định dạng văn bản trong Google Cloud Storage, tì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 trong 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 các thư viện Theo dõi OpenTelemetry trong dự án Go
  • Cách tạo span bằng thư viện
  • Cách truyền bối cảnh span qua dây 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ô của bạn. Để dễ hiểu, ví dụ này chỉ chứa 3 thành phần (trình tạo tải, ứng dụng khách và máy chủ). Tuy nhiên, 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ề cờ vây
  • Kiến thức cơ bản về Kubernetes

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tiến độ riêng

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ó một dự án, hãy nhấp vào trình đơn kéo xuống để chọn dự án ở phía trên bên trái của bảng điều khiển:

7a32e5469db69e9.png.

và nhấp vào "Dự án MỚI" trong hộp thoại kết quả để tạo một dự án mới:

7136b3ee36ebaf89.pngS

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

870a3cbd6541ee86.pngS

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

Xin lưu ý rằng mã dự án là tên duy nhất trong tất cả dự án Google Cloud (tên ở trên đã được sử dụng nên sẽ không phù hợp với bạn!). Mã này sẽ được đề cập sau trong lớp học lập trình này với tên PROJECT_ID.

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

15d0ef27a8fbab27.png.

Bạn sẽ không mất quá vài đô la khi chạy lớp học lập trình này, nhưng có thể sẽ cao 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 để chúng 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 nêu trong tài liệu chính thức.

Người dùng mới của Google Cloud Platform đủ điều kiện nhận 300 USD dùng thử miễn phí. Vì vậy, lớp học lập trình này sẽ hoàn toàn miễn phí.

Thiết lập Google Cloud Shell

Mặc dù bạn có thể vận hành 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 trong Đám mây.

Máy ảo dựa trên Debian này được tải tất cả các công cụ phát triển mà bạn cần. Dịch vụ này cung cấp thư mục gốc 5 GB ổn định và chạy trong Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Tức là tất cả những gì bạn cần để thực hiện lớp học lập trình này là một trình duyệt (vâng, 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 (chỉ mất vài phút để cấp phép và kết nối với môi trường).

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

Ảnh chụp màn hình lúc 10:13.43 chiều 14/6/2017.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ả lệnh

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

Kết quả lệnh

[core]
project = <PROJECT_ID>

Nếu vì lý do nào đó mà dự án không được thiết lập, chỉ cần phát hành lệnh sau:

gcloud config set project <PROJECT_ID>

Bạn đang tìm PROJECT_ID? Hãy xem mã nhận dạng bạn đã sử dụng ở các bước thiết lập hoặc tra cứu trong trang tổng quan Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell cũng đặt một số biến môi trường theo mặc định. Điều này có thể hữu ích khi bạn chạy các lệnh sau này.

echo $GOOGLE_CLOUD_PROJECT

Kết quả lệnh

<PROJECT_ID>

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

gcloud config set compute/zone us-central1-f

Bạn có thể chọn nhiều vùng khác nhau. Để biết thêm thông tin, hãy xem Khu vực và Vùng.

Thiết lập ngôn ngữ Go

Trong lớp học lập trình này, chúng ta sẽ sử dụng Go cho tất cả cá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+ không

go version

Kết quả 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ẽ vận hành 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 sẽ diễn ra như sau:

  1. Tải dự án cơ sở xuống Cloud Shell
  2. Xây dựng các dịch vụ vi mô trong vùng chứa
  3. Tải các 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 dịch vụ để đo lường dấu vết
  6. Chuyển tới bước 2

Bật Kubernetes Engine

Trước tiên, chúng ta thiết lập một cụm Kubernetes mà trong đó 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" và 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 rằng giá trị vùng thuộc khu vực mà bạn sẽ dùng để tạo kho lưu trữ Artifact Registry. Hãy thay đổi giá trị vùng us-central1-f nếu vùng lưu trữ của bạn không bao gồm vùng này.

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ả 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 chuẩn bị cho 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 Sổ đăng ký cấu phần phần mềm (GAR) và skaffold để sử dụng Sổ đăng ký cấu phần phần mềm này.

Thiết lập Artifact Registry

Chuyển đến trình đơn của "Artifact Registry" và nhấn nút BẬT.

45e384b87f7cf0db.png.

Sau 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 "TẠO KHỐI LƯỢNG" và nhập tên của 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 này là "Docker" và loại vị trí là "Khu vực". Chọn khu vực gần với khu vực mà 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 tôi chọn "us-central1 (Iowa)". Sau đó, hãy nhấp vào nút "TẠO" .

9c2d1ce65258ef70.pngS

Bây giờ, bạn sẽ thấy "lớp học lập trình theo dõi" trên trình duyệt kho lưu trữ.

7a3c1f47346bea15.pngS

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

Thiết lập SafetyNet

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. Thư viện 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 tập hợp nhỏ các lệnh. Theo mặc định, Skaffold sử dụng Sổ đăng ký Docker làm sổ đăng ký 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 vào.

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 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ả 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. Để biết đường dẫn đăng ký, hãy tự điều hướng đến trang tổng quan của Artifact Registry và nhấp vào tên kho lưu trữ bạn vừa thiết lập ở bước trước.

7a3c1f47346bea15.pngS

Sau đó, bạn sẽ thấy các đường dẫn breadcrumb (tập hợp liên kết phân cấp) ở đầ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:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; đã được sao chép

Quay lại vỏ đám mây. Chạy lệnh skaffold config set default-repo bằng giá trị mà 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ả 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ý cho cấu hình Docker. Chạy lệnh sau:

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

Kết quả 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ể thực hiện 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 sẽ thiết lập môi trường của lớp học lập trình:

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

Tiếp theo

Trong bước tiếp theo, bạn sẽ xây dựng, đẩy và triển khai các dịch vụ vi mô trên cụm

3. Xây dựng, đẩy mạnh 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 mọi điều kiện tiên quyết cho lớp học lập trình này. Bây giờ, bạn đã sẵn sàng chạy toàn bộ các 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 chúng 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
  • tệp kê khai: tệp kê khai của 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 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 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 ở 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à bước 6)

Chạy lệnh skaffold

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

cd step0
skaffold dev

Ngay khi chạy lệnh này, 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 tệp này đã được đẩy thành công vào sổ đăng ký.

Kết quả 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 bạn đẩy tất cả vùng chứa dịch vụ, các quy trình triển khai Kubernetes sẽ tự động bắt đầu.

Kết quả 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 cho stdout trong mỗi vùng chứa như sau:

Kết quả 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 bất kỳ thư nào từ máy chủ. Được rồi, cuối cùng bạn đã sẵn sàng bắt đầu đo lường cho ứng dụng của mình 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 phím Ctrl-C.

Kết quả 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 của lớp học lập trình trong môi trường của mình và xác nhận rằng skaffold chạy như mong đợi.

Tiếp theo

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

4. Đo lường cho HTTP

Khái niệm về khả năng đo lường và lan truyền 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 dấu vết được phân phối hoạt động trong một sơ đồ đơn giản.

6be42e353b9bfd1d.png.

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

Các ứng dụng cần gửi siêu dữ liệu về Theo dõi như Mã theo dõi và Mã Span để Cloud Trace tập hợp tất cả cá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 các bối cảnh theo dõi (kết hợp giữa Mã theo dõi và Span ID của span gốc) khi yêu cầu các dịch vụ hạ nguồn, để ứng dụng có thể biết được ngữ cảnh theo dõi nào đang được xử lý.

OpenTelemetry giúp bạn:

  • để tạo Mã theo dõi và Span ID duy nhất
  • để xuất mã theo dõi và mã span sang phần phụ trợ
  • để truyền 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 diễn ra như sau:

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

Hiện tại, bạn chưa cần phải hiểu 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 cho TracerProvider
  • TracerProvider lưu giữ tất cả cấu hình liên quan đến việc lấy mẫu và xuất dấu vết
  • tất cả dấu vết đều được nhóm lại trong đối tượng Tracer

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

Khoảng thời gian đầu tiên của công cụ

Dịch vụ tạo tải nhạc cụ

Mở Cloud Shell Editor bằng cách nhấn nút 776a11bfb2122549.pngSở trên cùng bên phải của Cloud Shell. Mở step0/src/loadgen/main.go trong trình khám phá ở ngăn bên trái rồi 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 main (chính), bạn sẽ thấy vòng lặp gọi hàm run trong đó. Trong cách 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 này 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ư được 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 theo định dạng có cấu trúc.

Sau đó, bạn gọi hàm đó 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ột Mã theo dõi và Span ID 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 HTTP của công cụ.

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 với net/http trong hàm runQuery, nên chúng ta sẽ sử dụng gói đóng góp cho net/http và bật khả năng đo lường có phần mở rộng của gói httptraceotelhttp.

Trước tiên, hãy thêm một biến toàn cục của gói httpClient để gọi các yêu cầu HTTP 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 vào hàm runQuery để tạo span tuỳ chỉnh bằng OpenTelemetry và span được tạo tự động qua ứng dụng HTTP tuỳ chỉnh. Việc bạn sẽ làm là:

  1. Nhận một Tracer của TracerProvider toàn cầu với otel.Tracer()
  2. Tạo span gốc bằng phương thức Tracer.Start()
  3. Kết thúc khoảng 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ờ đây, bạn đã hoàn tất việc đo lường trong bộ tải (ứng dụng khách HTTP). Hãy nhớ cập nhật go.modgo.sum bằng lệnh go mod.

go mod tidy

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

Trong phần trước, chúng ta đã đo lường phần được đưa vào 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 về khoảng trống trong dịch vụ 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 cho 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ã nhận dạng để tạo Span.

bcaccd06691269f8.png

Mở Cloud Shell Editor và thêm các gói bắt buộc như chúng tôi đã làm đối với dịch vụ tải trình tạo.

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
)

Một lần nữa, chúng ta cần thiết lập OpenTelemtry. Chỉ cần sao chép và dán hàm initTracer từ Loadgen, đồng thời gọi hàm đó vào hàm main của dịch vụ ứng dụng.

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
}

Giờ là lúc đo lường 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ụ tạo tải, 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 quá trình tải.

Trước tiên, chúng ta 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 khoảng thời gian thực tế bên trong trình xử lý. Tìm trình xử lý Func (*clientService)() và thêm khả 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 khả năng đo lường này, bạn có được các khoảng thời gian từ đầu phương thức handler đến cuối phương thức đó. Để dễ dàng phân tích khoảng, hãy thêm một thuộc tính bổ sung lưu trữ số lượng được so 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ả khả năng đo lường ở trên, bạn đã hoàn tất khả năng đo lường dấu vết giữa bộ tải và ứng dụng. Hãy xem cách hoạt động. Chạy lại mã bằng skaffold.

skaffold dev

Sau một 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ư thế này:

Kết quả 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 sẽ phát đi các thông báo này. Bạn sẽ nhận thấy rằng thành phần mẹ của tất 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 thấy rằng thuộc tính nhúng "query" có chuỗi truy vấn được chuyển đến dịch vụ khách.

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ụ máy khách giao tiếp trong HTTP và xác nhận rằng bạn có thể truyền thành công Trace Context giữa các dịch vụ và xuất thông tin Span từ cả hai dịch vụ sang stdout.

Tiếp theo

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

5. Khả năng đ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 các dịch vụ vi mô này. Ở 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à màu tím trong hình bên dưới)

75310d8e0e3b1a30.pngS

Khả năng đo lường bản dựng trước 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 các nhà phát triển đo lường các ứng dụng. Ở bước trước, chúng ta đã sử dụng khả năng đo lường bản dựng sẵn cho gói net/http. Ở bước này, khi chúng ta đang cố gắng phổ biến Trace Context (Ngữ cảnh theo dõi) thông qua gRPC, nên chúng ta sẽ sử dụng thư viện cho nó.

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ụ ứng dụng 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 các trình chặn gRPC mà công cụ đo lường span mỗi khi ứng dụng đưa ra 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 ở phần trước, nên bạn không cần thực hiện việc này.

Khả năng đo lường dựng sẵn cho máy chủ gRPC

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

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 thiết lập máy chủ đo lường, 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 đã thực hiện cho tải và dịch vụ máy khách.

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 các trình chặn máy chủ. Trong hàm main, hãy tìm vị trí gọi grpc.NewServer() và thêm điểm 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

Bây giờ, một lần nữa, bạn sẽ thấy một loạt thông tin về span trên stdout.

Kết quả 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 rằng bạn chưa nhúng bất kỳ tên span nào và chưa 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ì các trình chặn gRPC đã tạo ra chúng.

Tóm tắt

Ở 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

Ở 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 được thu thập.

6. Trực quan hoá dấu vết bằng Cloud Trace

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

Sử dụng trình xuất dữ liệu 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 mà khả năng đo lường của bạn thu thập, bạn chỉ cần thay thế trình xuất stdout bằng trình xuất Cloud Trace.

Mở các tệp main.go của từng dịch vụ rồi tìm hàm initTracer(). Xoá dòng này để 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 hàm tương tự 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, chỉ cần chạy cụm như bình thường bằng lệnh skaffold.

skaffold dev

Giờ đây, bạn sẽ không thấy nhiều thông tin về 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 Cloud Trace.

Kết quả 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". Dễ dàng truy cập 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.pngs

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 vị trí đạ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.pngS

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

Với những bản phân tích ngắn này, bạn có thể nhận thấy mình cần biết các span chi tiết hơn bên trong phương thức GetMatchCount. So với thông tin của stdout, hình ảnh trực quan là rất mạnh mẽ. Để tìm hiểu thêm 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 dữ liệu stdout bằng Cloud Trace một lần và các dấu vết được trực quan hoá 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 phụ trong GetMatchCount.

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

Ở bước trước, bạn đã tìm ra nguyên nhân gây ra thời gian trọn vòng quan sát được trong quá trình loadgen, chủ yếu là do 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 tôi không đo lường bất cứ thứ gì khác ngoài trình xử lý, nên chúng tôi không thể tìm thêm thông tin chi tiết từ biểu đồ thác nước. Đây là trường hợp phổ biến khi chúng tôi 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 phụ trong đó máy chủ gọi Google Cloud Storage, vì thường xảy ra trường hợp một số I/O mạng bên ngoài mất nhiều thời gian trong quá trình và điều quan trọng là xác định xem liệu lệnh gọi có phải là nguyên nhân hay không.

Đo lường một khoảng thời gian phụ trong máy chủ

Mở main.go trong máy chủ rồi 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 trong các tác phẩm của Shakespeare. Trong hàm này, bạn có thể tạo một span phụ, giống như những gì bạn đã làm đối với khả nă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ì bạn cần làm để thêm một span mới. Hãy xem kết quả bằng cách chạy ứng dụng.

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

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

skaffold dev

Chọn một dấu vết có tên query.request trong danh sách theo dõi. 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. (Khoảng cách được bao quanh bởi hình chữ nhật màu đỏ bên dưới)

3d4a891aa30d7a32.pngS

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

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

Tóm tắt

Ở bước này, bạn đã đo lường một khoảng thời gian khác trong dịch vụ máy chủ và thu được thông tin chi tiết hơn về độ trễ của hệ thống.

8. Xin chúc mừng

Bạn đã tạo thành công dấu vết được phân phối bằng OpenTelemery và xác nhận độ trễ của 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ể thử tự mình thử các chủ đề sau.

  • Phương thức triển khai hiện tại sẽ gửi tất cả các span được tạo bằng tính năng kiểm tra tình trạng. (grpc.health.v1.Health/Check) Làm cách nào để lọc ra những khoảng thời gian đó trong Cloud Trace? Gợi ý có tại đây.
  • Liên kết nhật ký sự kiện với span và xem cách thức hoạt động của nhật ký sự kiện trên Google Cloud Trace và Google Cloud Logging. Gợi ý có tại đây.
  • Thay thế một dịch vụ bằng một dịch vụ bằng ngôn ngữ khác và thử đo lường 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 dưới đây.

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 các khoản 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 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ả 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 "IAM và Quản trị viên" &gt; "Cài đặt", sau đó nhấp vào "HUỶ XUỐNG" .

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 và xác nhận tắt máy.