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 ghi nhận của ứng dụng

Khả năng ghi nhận và OpenTelemetry

Khả năng ghi nhận là thuật ngữ dùng để mô tả một thuộc tính của hệ thống. 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 họ. Trong bối cảnh đó, 3 trụ cột của khả năng ghi nhận (nhật ký, chỉ số và dấu vết) là khả năng đo lường cơ bản để hệ thống có được khả năng ghi nhận.

OpenTelemetry là một bộ 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à khả năng quan sát yêu cầu. OpenTelemetry là một tiêu chuẩn mở và dự án do cộng đồng điều hành 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 các ứng dụng của họ theo cách trung lập với nhà cung cấp và dựa 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 cũng là một thành phần quan trọng khác của khả năng ghi nhận và đang mở rộng cơ sở người dùng trong ngành. Cloud Profiler là một trong những công cụ 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.

Đây là phần 1 của loạt lớp học lập trình này, trong đó đề cập đến việc đo lường các dấu vết phân tán trong các vi dịch vụ bằng OpenTelemetry và Cloud Trace. Phần 2 sẽ đề cập đến việc liên tục lập hồ sơ bằng Cloud Profiler.

Distributed Trace

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 của hệ thống. Đặc biệt là trong kỷ nguyên của các vi dịch vụ, dấu vết phân tán là yếu tố thúc đẩy mạnh mẽ việc tìm ra các điểm nghẽn về độ trễ trong hệ thống phân tán tổng thể.

Khi phân tích các dấu vết phân tán, việc trực quan hoá dữ liệu dấu vết là yếu tố then chốt để nắm bắt độ trễ tổng thể của hệ thống trong nháy mắt. Trong dấu vết phân tán, chúng tôi 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ưới dạng Dấu vết chứa nhiều Khoảng thời gian.

Khoảng thời gian 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 tán, ghi lại thời gian bắt đầu và kết thúc. Các khoảng thời gian 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 khoảng thời gian nhỏ hơn đều là khoảng thời gian con của một khoảng thời gian lớn /tin nhắn và được hợp nhất 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 một cụm Google Kubernetes Engine. Kiến trúc của Shakesapp như mô tả bên dưới:

44e243182ced442f.png

  • Loadgen gửi một chuỗi truy vấn đến máy khách trong HTTP
  • Các ứng dụng truyền truy vấn từ loadgen đến máy chủ trong gRPC
  • Máy chủ chấp nhận truy vấn từ máy khách, tìm nạp tất cả cá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 có chứa truy vấn và trả về số dòng khớp với máy khách

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

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

  • Cách bắt đầu sử dụng các thư viện Dấu vết OpenTelemetry trong dự án Go
  • Cách tạo khoảng bằng thư viện
  • Cách truyền ngữ cảnh khoảng thời gian qua đường truyền giữa các thành phần ứng dụng
  • Cách gửi dữ liệu dấu vết đế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 vi dịch vụ 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, máy khách 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 phức tạp và lớn 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. Thiết lập và 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) rồi 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 "DỰ ÁN MỚI" trong hộp thoại xuất hiện để tạo một 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ư thế này để 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 nhớ mã dự án. Đây là 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, xin lỗi!). Sau này trong lớp học lập trình này, chúng ta sẽ gọi đó là PROJECT_ID.

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

15d0ef27a8fbab27.png

Việc thực hiện lớp học lập trình này sẽ không tốn của bạ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 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ử miễn phí trị giá 300 USD. Nhờ đó, bạn có thể hoàn toàn miễn phí tham gia lớp học lập trình này.

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 trên đám mây.

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

Để kích hoạt Cloud Shell từ Bảng điều khiển Cloud, 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 (mất vài phút để cung cấp và kết nối với môi trường).

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 rằng mình đã được xác thực và dự án đã được đặt thành PROJECT_ID.

gcloud auth list

Đầu ra của lệnh

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

Đầu ra của lệnh

[core]
project = <PROJECT_ID>

Nếu vì lý do nào đó mà dự án chưa được thiết lập, 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 bạn đã dùng trong các bước thiết lập hoặc tìm mã nhận dạng đó trong trang tổng quan của Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

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

echo $GOOGLE_CLOUD_PROJECT

Đầu ra của lệnh

<PROJECT_ID>

Cuối cùng, hãy đặ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 múi giờ khác nhau. Để biết thêm thông tin, hãy xem phần Khu vực và vùng.

Chuyển đến phần thiết lập ngôn ngữ

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

Đầu ra của lệnh

go version go1.18.3 linux/amd64

Thiết lập một Cụm Kubernetes của Google

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 các vi dịch vụ vào vùng chứa
  3. Tải vùng chứa lên Google Artifact Registry (GAR)
  4. Triển khai các vùng chứa vào GKE
  5. Sửa đổi mã nguồn của các dịch vụ để theo dõi hoạt động đo lường
  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" 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ị của vùng nằm trong khu vực mà bạn sẽ dùng để tạo kho lưu trữ Artifact Registry. Thay đổi giá trị khu vực us-central1-f nếu khu vực kho lưu trữ của bạn không bao gồm khu vực đó.

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

Đầu ra 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

Giờ đây, 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 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 Artifact Registry (GAR) và skaffold để sử dụng.

Thiết lập Artifact Registry

Chuyển đến trình đơn "Artifact Registry" rồi nhấn vào 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 KHO LƯU TRỮ" rồi 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 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 "TẠO".

9c2d1ce65258ef70.png

Giờ đây, 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 làm việc để tạo các dịch vụ vi mô chạy trên Kubernetes. Công cụ này xử lý quy trình làm việc tạo, đẩy và triển khai các vùng chứa ứng dụng bằng một số ít lệnh. Theo mặc định, Skaffold sử dụng Docker Registry 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 đế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

Đầu ra của lệnh

v1.38.0

Giờ đây, 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 chuyển đến trang tổng quan Artifact Registry rồi nhấp vào tên kho lưu trữ mà bạn vừa thiết lập ở bước trước.

7a3c1f47346bea15.png

Sau đó, bạn sẽ thấy đường dẫn breadcrumb ở đầu trang. Nhấp vào biểu tượng e157b1359c3edc06.png để sao chép đường dẫn đă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 có thông báo như sau:

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

Quay lại Cloud Shell. 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

Đầu ra 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ý cho cấu hình Docker. Chạy lệnh sau:

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

Đầu ra 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

Giờ đây, bạn đã sẵn sàng chuyển sang bước tiếp theo để thiết lập một 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 lớp học lập trình:

  • Thiết lập Cloud Shell
  • Đã tạo một 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 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 vi dịch vụ của mình lên cụm

3. Tạo, chuyển và triển khai các vi dịch vụ

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ộ các vi dịch vụ trên các dịch vụ này. Tài liệu của lớp học lập trình đượ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 các 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, còn 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 có nhiều bước, nhưng thực tế là Skaffold sẽ làm mọi thứ cho bạn. Hãy thử làm việc đó bằng lệnh sau:

cd step0
skaffold dev

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

Đầu ra 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 hoạt động triển khai Kubernetes sẽ tự động bắt đầu.

Đầu ra 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:

Đầu ra 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

Xin 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ủ. Được rồi, cuối cùng bạn đã sẵn sàng bắt đầu đo lường ứ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 Ctrl-C.

Đầu ra 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 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ụ loadgen để đo lường thông tin theo dõi.

4. Khả năng đo lường cho HTTP

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

Trước khi chỉnh sửa mã nguồn, tôi sẽ giải thích ngắn gọn cách hoạt động của tính năng 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 sẽ đo lường mã để xuất thông tin về Dấu vết và Khoảng thời gian sang Cloud Trace, đồng thời truyền ngữ cảnh dấu vết 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 Dấu vết (chẳng hạn như Mã dấu vết và Mã khoảng thời gian) trong Cloud Trace để tập hợp tất cả các khoảng thời gian có cùng Mã dấu vết 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 (sự kết hợp giữa Mã theo dõi và Mã khoảng thời gian của khoảng thời gian mẹ) khi yêu cầu các dịch vụ hạ nguồn, để các dịch vụ đó có thể biết được bối cảnh theo dõi mà chúng đang xử lý.

OpenTelemetry giúp bạn:

  • để tạo Trace ID và Span ID riêng biệt
  • để xuất Mã theo dõi và Mã khoảng thời gian sang phần phụ trợ
  • để truyền bối 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 ghi lại dấu vết ứng dụng bằng OpenTelemetry như sau:

  1. Tạo một người xuất
  2. Tạo một TracerProvider liên kết trình xuất trong 1 và đặt trình xuất đó ở chế độ chung.
  3. Đặt TextMapPropagaror để đặt phương thức truyền
  4. Lấy Tracer từ TracerProvider
  5. Tạo khoảng thời gian từ Tracer

Hiện tại, bạn không cần hiểu rõ các thuộc tính chi tiết trong từng 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 việc lấy mẫu và xuất dấu vết
  • tất cả các dấu vết đều được gói trong đối tượng Tracer

Sau 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 khoảng thời gian đầu tiên

Dịch vụ trình tạo tải công cụ

Mở Cloud Shell Editor 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 trong trình khám phá ở 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. Giờ hãy đo lường thông tin về khoảng thời gian để 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 rằng 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 một 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 quá trình thiết lập, bạn cần tạo một Span có Trace ID 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 khác 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 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 khả 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 vào 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 khoảng thời gian tuỳ chỉnh bằng OpenTelemetry và khoảng thời gian được tạo tự động từ máy khách HTTP tuỳ chỉnh. Bạn sẽ làm những việc sau:

  1. Nhận Tracer từ TracerProvider toàn cầu bằng otel.Tracer()
  2. Tạo khoảng gốc bằng phương thức Tracer.Start()
  3. Kết thúc khoảng gốc vào một thời điểm bất kỳ (trong trường hợp này là cuối 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 loadgen (ứng dụng máy 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ủa công cụ

Trong phần trước, chúng ta đã đo lường phần nằm 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 khoảng thời gian 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 các khoảng thời gian.

bcaccd06691269f8.png

Mở Cloud Shell Editor rồi thêm các gói bắt buộc như cách 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
)

Một lần nữa, chúng ta cần thiết lập OpenTelemetry. 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 hà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
}

Đã đến lúc đo lường khoảng thời gian. Vì dịch vụ máy khách 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ụ khách hàng được triển khai bằng net/http và bạn có thể sử dụng gói otelhttp như chúng tôi đã 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 sẽ đo lường khoảng thời gian thực tế bên trong trình xử lý. Tìm func (*clientService) handler(), và thêm khả năng đo lường khoảng thời gian 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 sẽ nhận đượ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 các khoảng thời gian, 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 hoạt động đo lường ở trên, bạn đã hoàn tất hoạt động đo lường dấu vết giữa loadgen và ứng dụng. Hãy cùng xem cách thức hoạt động của tính năng này. 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 báo nhật ký như sau:

Đầu ra 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 những thông báo này. Bạn sẽ nhận thấy rằng các đối tượng mẹ của tất cả các khoảng thời gian theo loadgen đều có TraceID: 00000000000000000000000000000000, vì đây là khoảng thời gian gốc, tức là khoảng thời gian đầ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 truyền đến dịch vụ khách hàng.

Tóm tắt

Trong bước này, bạn đã thiết lập khả năng đo lường cho dịch vụ trình tạo tải và dịch vụ máy khách 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 về Khoảng thời gian 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 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 vi dịch vụ này. Trong bước này, chúng ta sẽ cố gắng đo lườ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.png

Khả năng đo lường được xây dựng sẵn cho ứng dụng gRPC

Hệ sinh thái của OpenTelemetry cung cấp nhiều thư viện hữu ích 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, vì đang cố gắng truyền Ngữ cảnh theo dõi thông qua gRPC, nên chúng ta sẽ sử dụng thư viện cho việc này.

Trước tiên, bạn nhập gói gRPC được 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, ứng dụng dịch vụ là một ứng dụng gRPC đối 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 để đo lường các khoảng thời gian mới mỗi khi máy khách đưa ra yêu cầu cho 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 thực hiện việc này.

Khả năng đo lường được 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 sẽ gọi phương thứ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 đ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ụ 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() rồi thêm các 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 vi dịch vụ 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 về khoảng thời gian trên stdout.

Đầu ra 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 rằng bạn chưa nhúng tên khoảng thời gian nào và đã tạo khoảng thời gian 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 các khoảng thời gian vì các trình chặn gRPC đã tạo ra chúng.

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ợ từ các thư viện hệ sinh thái OpenTelemetry.

Tiếp theo

Trong bước tiếp theo, cuối cùng 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 khoảng thời gian đã thu thập.

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

Bạn đã ghi lại dấu vết trong toàn bộ hệ thống bằng OpenTelemetry. Đến đây, bạn đã học 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 các chỉ số này, nhưng bạn vẫn chưa học được cách phân tích chúng. Trong phần này, bạn sẽ thay thế các 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 khoảng thời gian được thu thập bằng khả năng đo lường, bạn chỉ cần thay thế trình xuất 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 này để tạo trình xuất stdout và thay vào đó 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ụ ứng dụng và máy chủ.

Chạy vi dịch vụ 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 về khoảng thời gian ở đị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.

Đầu ra 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 khoảng thời gian có được gửi chính xác đến Cloud Trace hay không. Truy cập vào bảng điều khiển Cloud rồi chuyển đến "Trace list" (Danh sách dấu vết). Bạn có thể dễ dàng truy cập vào tính năng này thông qua 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 duy nhấ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

Chỉ cần xem nhanh, bạn đã có thể nắm được 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 thời gian có tên shakesapp.ShakespeareService/GetMatchCount. (Bạn có thể xác nhận điều đó trong bảng tóm tắt. Xem mục 1 trong hình ảnh ở trên) (Cột ngoài cùng bên phải cho biết thời lượng của từng khoảng thời gian.) Ngoài ra, dấu vết này là cho cụm từ tìm kiếm "bạn bè". (Xem số 2 trong hình ảnh ở trên)

Với những phân tích ngắn này, có thể bạn nhận ra rằng bạn cần biết các khoảng thời gian chi tiết hơn trong phương thức GetMatchCount. So với thông tin stdout, hình ảnh trực quan có sức mạnh hơn. Để tìm hiểu thêm về thông tin chi tiết của 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 stdout bằng trình xuất Cloud Trace và trực quan hoá các dấu vết trên Cloud Trace. Bạn cũng đã tìm hiểu cách bắt đầu phân tích các 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 khoảng con trong GetMatchCount.

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

Ở bước trước, bạn đã tìm ra nguyên nhân khiến thời gian khứ hồi quan sát được 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ỳ thông tin nào khác ngoài trình xử lý, nên chúng ta 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 ta bắt đầu đo lường các vi dịch vụ.

3b63a1e471dddb8c.png

Trong phần này, chúng ta sẽ đo lường một khoảng con nơi máy chủ gọi Google Cloud Storage, vì thường thì một số hoạt động I/O mạng bên ngoài 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 khoảng 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ả các tệp văn bản về các tác phẩm của Shakespeare. Trong hàm này, bạn có thể tạo một khoảng con, chẳng hạn như những gì bạn đã làm cho hoạt động đo lường máy chủ HTTP trong dịch vụ máy khách.

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ả các bước để thêm một khoảng thời gian mới. Hãy xem điều gì sẽ xảy ra khi chạy ứng dụng.

Chạy vi dịch vụ 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 đó, 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 dấu vết tương tự, ngoại trừ một khoảng thời gian mới trong shakesapp.ShakespeareService/GetMatchCount. (Khoảng thời gian được bao quanh bởi hình chữ nhật màu đỏ bên dưới)

3d4a891aa30d7a32.png

Những gì bạn có thể thấy từ biểu đồ này là 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òn những yếu tố khác gây ra phần lớn độ trễ.

Bạn đã thu được nhiều thông tin chi tiết chỉ từ một vài lượt xem biểu đồ thác dấu vết. 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 của mình? Đây là lúc trình phân tích tài nguyên xuất hiện, nhưng 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 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 các dấu vết phân tán bằng OpenTelemetry và xác nhận độ trễ của yêu cầu trên các vi dịch vụ 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.

  • Quy trình triển khai hiện tại sẽ gửi tất cả các khoảng thời gian đượ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 những khoảng thời gian đó khỏi Cloud Trace? Gợi ý tại đây.
  • Tương quan nhật ký sự kiện với khoảng thời gian và xem cách hoạt động của nhật ký 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à thử đ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 codelab 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 ảnh. 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

Đầu ra 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, trên ngăn trình đơn, hãy chọn "IAM & Admin" (IAM và Quản trị) > "Settings" (Cài đặt), rồi nhấp vào nút "SHUT DOWN" (TẮT).

45aa37b7d5e1ddd1.png

Sau đó, hãy 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.