Инструмент для повышения производительности вашего приложения на Go (часть 1: трассировка)

1. Введение

505827108874614d.png

Последнее обновление: 15.07.2022

Наблюдаемость приложения

Наблюдаемость и OpenTelemetry

Наблюдаемость — это термин, используемый для описания атрибута системы. Система, обладающая наблюдаемостью, позволяет командам активно отлаживать свою систему. В этом контексте три основных компонента наблюдаемости — журналы, метрики и трассировки — являются фундаментальными инструментами для обеспечения наблюдаемости системы.

OpenTelemetry — это набор спецификаций, библиотек и агентов, которые ускоряют сбор и экспорт телеметрических данных (журналов, метрик и трассировок), необходимых для обеспечения наблюдаемости. OpenTelemetry — это открытый стандарт и проект, управляемый сообществом в рамках CNCF. Используя библиотеки, предоставляемые проектом и его экосистемой, разработчики могут внедрять инструменты в свои приложения независимым от поставщика способом и для различных архитектур.

Помимо трех основных составляющих — наблюдаемости, — непрерывный профилирование является еще одним ключевым компонентом , расширяющим базу пользователей в отрасли. Cloud Profiler — один из первопроходцев в этой области, предоставляющий простой интерфейс для детального анализа показателей производительности в стеках вызовов приложений.

Данный практический урок является первой частью серии и посвящен инструментированию распределенной трассировки в микросервисах с помощью OpenTelemetry и Cloud Trace. Во второй части будет рассмотрено непрерывное профилирование с помощью Cloud Profiler.

Распределенная трассировка

Среди логов, метрик и трассировок, трассировка — это телеметрия, которая показывает задержку конкретной части процесса в системе. Особенно в эпоху микросервисов распределенная трассировка является мощным инструментом для выявления узких мест задержки в распределенной системе в целом.

При анализе распределенных трассировок визуализация данных трассировки является ключом к быстрому пониманию общих задержек системы. В распределенной трассировке мы обрабатываем набор вызовов для выполнения одного запроса к точке входа в систему в виде трассировки, содержащей несколько сегментов (Span).

Спан представляет собой отдельную единицу работы, выполняемой в распределенной системе, с указанием времени начала и окончания. Спаны часто имеют иерархические связи друг с другом — на рисунке ниже все меньшие спаны являются дочерними спанами большого спана /messages и объединены в один трассировочный файл (Trace), показывающий путь работы в системе.

След

Google Cloud Trace — один из вариантов распределенной системы трассировки, хорошо интегрированный с другими продуктами Google Cloud.

Что вы построите

В этом практическом задании вы будете отслеживать информацию о трассировке в сервисе под названием "приложение Шекспира" (или Shakesapp), работающем в кластере Google Kubernetes Engine. Архитектура Shakesapp описана ниже:

44e243182ced442f.png

  • Loadgen отправляет клиенту строку запроса по протоколу HTTP.
  • Клиенты передают запрос от генератора нагрузки на сервер по протоколу gRPC.
  • Сервер принимает запрос от клиента, извлекает все произведения Шекспира в текстовом формате из Google Cloud Storage, ищет строки, содержащие запрос, и возвращает клиенту номер строки, которая соответствует запросу.

Вы будете собирать информацию о трассировке по всему запросу. После этого вы внедрите агент профилирования на сервер и исследуете узкое место.

Что вы узнаете

  • Как начать работу с библиотеками OpenTelemetry Trace в проекте Go
  • Как создать элемент `<span>` с помощью библиотеки
  • Как передавать контексты Span по сети между компонентами приложения
  • Как отправить данные трассировки в Cloud Trace
  • Как анализировать трассировку в Cloud Trace

В этом практическом занятии объясняется, как инструментировать ваши микросервисы. Для простоты понимания этот пример содержит всего 3 компонента (генератор нагрузки, клиент и сервер), но вы можете применить тот же процесс, описанный в этом занятии, к более сложным и крупным системам.

Что вам понадобится

  • Базовые знания игры Го.
  • Базовые знания Kubernetes.

2. Настройка и требования

Настройка среды для самостоятельного обучения

Если у вас еще нет учетной записи Google (Gmail или Google Apps), вам необходимо ее создать . Войдите в консоль Google Cloud Platform ( console.cloud.google.com ) и создайте новый проект.

Если у вас уже есть проект, щелкните раскрывающееся меню выбора проекта в левом верхнем углу консоли:

7a32e5469db69e9.png

и нажмите кнопку «СОЗДАТЬ ПРОЕКТ» в появившемся диалоговом окне, чтобы создать новый проект:

7136b3ee36ebaf89.png

Если у вас ещё нет проекта, вы увидите диалоговое окно, подобное этому, для создания вашего первого проекта:

870a3cbd6541ee86.png

В появившемся диалоговом окне создания проекта вы можете ввести подробные сведения о вашем новом проекте:

affdc444517ba805.png

Запомните идентификатор проекта (Project ID), который является уникальным именем для всех проектов Google Cloud (указанное выше имя уже занято и вам не подойдёт, извините!). В дальнейшем в этом практическом занятии он будет обозначаться как PROJECT_ID.

Далее, если вы еще этого не сделали, вам необходимо включить оплату в консоли разработчика, чтобы использовать ресурсы Google Cloud и активировать API Cloud Trace .

15d0ef27a8fbab27.png

Выполнение этого практического задания не должно обойтись вам дороже нескольких долларов, но может обойтись дороже, если вы решите использовать больше ресурсов или оставите их запущенными (см. раздел «очистка» в конце этого документа). Цены на Google Cloud Trace, Google Kubernetes Engine и Google Artifact Registry указаны в официальной документации.

Новые пользователи Google Cloud Platform могут воспользоваться бесплатной пробной версией стоимостью 300 долларов , что сделает этот практический семинар совершенно бесплатным.

Настройка Google Cloud Shell

Хотя Google Cloud и Google Cloud Trace можно запускать удаленно с ноутбука, в этом практическом занятии мы будем использовать Google Cloud Shell — среду командной строки, работающую в облаке.

Эта виртуальная машина на базе Debian содержит все необходимые инструменты разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Это означает, что для выполнения этого практического задания вам понадобится только браузер (да, он работает и на Chromebook).

Для активации Cloud Shell из консоли Cloud Console просто нажмите кнопку «Активировать Cloud Shell». gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (Подготовка и подключение к среде займут всего несколько минут).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSr Dc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjviEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

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

После подключения к Cloud Shell вы увидите, что ваша аутентификация пройдена и проект уже настроен на ваш PROJECT_ID .

gcloud auth list

вывод команды

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

вывод команды

[core]
project = <PROJECT_ID>

Если по какой-либо причине проект не создан, просто выполните следующую команду:

gcloud config set project <PROJECT_ID>

Ищете свой PROJECT_ID ? Проверьте, какой ID вы использовали на этапах настройки, или найдите его на панели управления Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell также по умолчанию устанавливает некоторые переменные среды, которые могут быть полезны при выполнении будущих команд.

echo $GOOGLE_CLOUD_PROJECT

вывод команды

<PROJECT_ID>

Наконец, установите зону по умолчанию и конфигурацию проекта.

gcloud config set compute/zone us-central1-f

Вы можете выбрать различные зоны. Для получения дополнительной информации см. раздел «Регионы и зоны» .

Настройка языка Go

В этом практическом занятии мы используем Go для всего исходного кода. Выполните следующую команду в Cloud Shell и убедитесь, что версия Go — 1.17 или выше.

go version

вывод команды

go version go1.18.3 linux/amd64

Настройка кластера Google Kubernetes

В этом практическом занятии вы запустите кластер микросервисов на платформе Google Kubernetes Engine (GKE). Процесс выполнения этого практического занятия выглядит следующим образом:

  1. Загрузите базовый проект в Cloud Shell.
  2. Внедряйте микросервисы в контейнеры.
  3. Загрузите контейнеры в реестр артефактов Google (GAR).
  4. Развертывание контейнеров в GKE
  5. Измените исходный код сервисов для трассировочного мониторинга.
  6. Перейдите к шагу 2

Включить Kubernetes Engine

Сначала настроим кластер Kubernetes, в котором Shakesapp будет работать на GKE, поэтому нам нужно включить GKE. Перейдите в меню "Kubernetes Engine" и нажмите кнопку "Включить".

548cfd95bc6d344d.png

Теперь вы готовы создать кластер Kubernetes.

Создание кластера Kubernetes

В Cloud Shell выполните следующую команду для создания кластера Kubernetes. Убедитесь, что значение зоны находится в регионе , который вы будете использовать для создания репозитория Artifact Registry. Измените значение зоны us-central1-f , если ваш регион репозитория не охватывает эту зону.

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

вывод команды

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

Настройка реестра артефактов и Skaffold.

Теперь у нас есть кластер Kubernetes, готовый к развертыванию. Далее мы подготовим реестр контейнеров для отправки и развертывания контейнеров. Для этих шагов нам необходимо настроить реестр артефактов (GAR) и Skaffold для его использования.

Настройка реестра артефактов

Перейдите в меню «Реестр артефактов» и нажмите кнопку «Включить».

45e384b87f7cf0db.png

Через несколько мгновений вы увидите браузер репозиториев GAR. Нажмите кнопку "СОЗДАТЬ РЕПОЗИТОРИЙ" и введите имя репозитория.

d6a70f4cb4ebcbe3.png

В этом практическом занятии я называю новый репозиторий trace-codelab . Формат артефакта — "Docker", а тип местоположения — "Регион". Выберите регион, близкий к тому, который вы установили для зоны по умолчанию Google Compute Engine. Например, в этом примере был выбран "us-central1-f", поэтому здесь мы выберем "us-central1 (Айова)". Затем нажмите кнопку "СОЗДАТЬ".

9c2d1ce65258ef70.png

Теперь в браузере репозитория вы видите "trace-codelab".

7a3c1f47346bea15.png

Мы вернемся сюда позже, чтобы проверить путь в реестре.

установка строительных лесов

Skaffold — удобный инструмент при работе над созданием микросервисов, работающих на Kubernetes. Он управляет процессом сборки, отправки и развертывания контейнеров приложений с помощью небольшого набора команд. По умолчанию Skaffold использует Docker Registry в качестве реестра контейнеров, поэтому вам необходимо настроить Skaffold для распознавания GAR при отправке контейнеров.

Откройте Cloud Shell еще раз и убедитесь, что skaffold установлен. (Cloud Shell устанавливает skaffold в среду по умолчанию.) Выполните следующую команду, чтобы увидеть версию skaffold.

skaffold version

вывод команды

v1.38.0

Теперь вы можете зарегистрировать репозиторий по умолчанию для использования Skaffold. Чтобы получить путь к реестру, перейдите на панель управления Реестр артефактов и щелкните имя репозитория, который вы только что настроили на предыдущем шаге.

7a3c1f47346bea15.png

Затем вы увидите дорожки с хлебными крошками в верхней части страницы. Нажмите на них. e157b1359c3edc06.png значок для копирования пути к файлу реестра в буфер обмена.

e0f2ae2144880b8b.png

При нажатии на кнопку копирования внизу браузера появится диалоговое окно с сообщением следующего вида:

"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab" был скопирован.

Вернитесь в облачную оболочку. Выполните команду ` skaffold config set default-repo , указав значение, которое вы только что скопировали с панели управления.

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

вывод команды

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

Кроме того, необходимо настроить реестр для работы с Docker. Выполните следующую команду:

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

вывод команды

{
  "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

Теперь вы можете перейти к следующему шагу — настройке контейнера Kubernetes в GKE.

Краткое содержание

На этом этапе вы настраиваете среду для работы с кодовой лабораторией:

  • Настройка Cloud Shell
  • Создан репозиторий Artifact Registry для реестра контейнеров.
  • Настройте Skaffold для использования реестра контейнеров.
  • Создан кластер Kubernetes, в котором работают микросервисы из Codelab.

Далее

На следующем этапе вы создадите, отправите и развернете свои микросервисы в кластере.

3. Создайте, разверните и создайте микросервисы.

Скачайте материалы для практического занятия.

На предыдущем шаге мы подготовили все необходимые условия для этого практического занятия. Теперь вы готовы запустить на их основе целые микросервисы. Материалы для практического занятия размещены на GitHub, поэтому загрузите их в среду Cloud Shell с помощью следующей команды git.

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

Структура каталогов проекта выглядит следующим образом:

.
├── 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
  • манифесты: файлы манифестов Kubernetes
  • proto: определение протокола для обмена данными между клиентом и сервером.
  • src: каталоги с исходным кодом каждого сервиса
  • skaffold.yaml: Конфигурационный файл для skaffold

В этом практическом задании вам нужно будет обновить исходный код, расположенный в папке step0 . Вы также можете обратиться к исходному коду в папках step[1-6] для получения ответов на следующие шаги. (Часть 1 охватывает шаги с step0 по step4, а Часть 2 — шаги 5 и 6)

Выполните команду skaffold

Наконец, вы готовы собрать, отправить и развернуть весь контент в только что созданном кластере Kubernetes. Кажется, что это включает в себя несколько шагов, но на самом деле Skaffold делает всё за вас. Давайте попробуем это сделать с помощью следующей команды:

cd step0
skaffold dev

Сразу после выполнения команды вы увидите лог-вывод команды docker build и сможете убедиться, что файлы успешно загружены в реестр.

вывод команды

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

После отправки всех контейнеров сервисов развертывание Kubernetes запускается автоматически.

вывод команды

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

После развертывания вы увидите фактические логи приложения, выводимые в стандартный поток вывода каждого контейнера, примерно такого вида:

вывод команды

[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

Обратите внимание, что на данном этапе вам необходимо видеть все сообщения с сервера. Итак, наконец, вы готовы начать инструментирование вашего приложения с помощью OpenTelemetry для распределенной трассировки сервисов.

Перед началом мониторинга сервиса, пожалуйста, выключите кластер с помощью Ctrl-C.

вывод команды

...
[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

Краткое содержание

На этом этапе вы подготовили материалы для лабораторной работы в своей среде и подтвердили, что Skaffold работает должным образом.

Далее

На следующем шаге вы измените исходный код службы loadgen, чтобы добавить инструменты для сбора информации трассировки.

4. Инструментарий для HTTP

Концепция трассировочной аппаратуры и распространения

Прежде чем редактировать исходный код, позвольте мне кратко объяснить принцип работы распределенных трассировок на простой схеме.

6be42e353b9bfd1d.png

В этом примере мы добавляем в код инструменты для экспорта информации о трассировке и сегментации в Cloud Trace и распространения контекста трассировки по запросу от службы генерации нагрузки к серверной службе.

Приложениям необходимо отправлять метаданные трассировки, такие как идентификатор трассировки (Trace ID) и идентификатор сегмента (Span ID), чтобы Cloud Trace мог объединить все сегменты с одинаковым идентификатором трассировки в одну трассировку. Кроме того, приложению необходимо передавать контексты трассировки (комбинацию идентификатора трассировки и идентификатора сегмента родительского сегмента) при запросе к нижестоящим сервисам, чтобы те могли знать, какой контекст трассировки они обрабатывают.

OpenTelemetry поможет вам:

  • для генерации уникальных идентификаторов трассировки (Trace ID) и диапазона (Span ID)
  • экспортировать идентификаторы трассировки (Trace ID) и идентификаторы трассировки (Span ID) в бэкэнд.
  • для распространения контекстов трассировки на другие сервисы
  • для внедрения дополнительных метаданных, которые помогают анализировать трассировки

Компоненты в OpenTelemetry Trace

b01f7bb90188db0d.png

Процесс добавления инструментов трассировки приложений в OpenTelemetry выглядит следующим образом:

  1. Создать экспортер
  2. Создайте объект TracerProvider, привязывающий экспортер в пункте 1, и установите его глобальным.
  3. Установите параметр TextMapPropagaror, чтобы задать метод распространения.
  4. Получите трассировщик от поставщика трассировки (TracerProvider).
  5. Сгенерировать диапазон из трассировщика

На данный момент вам не нужно разбираться в детальных свойствах каждого компонента, но самое важное, что следует помнить:

  • Здесь экспортер подключается к TracerProvider.
  • TracerProvider содержит все настройки, касающиеся сбора и экспорта данных трассировки.
  • Все трассировки объединены в объект Tracer.

Разобравшись в этом, перейдём к собственно работе над кодом.

Инструментальный первый пролет

Обслуживание генераторов с измерительной нагрузкой

Откройте редактор Cloud Shell, нажав кнопку. 776a11bfb2122549.png В правом верхнем углу Cloud Shell откройте step0/src/loadgen/main.go в проводнике в левой панели и найдите функцию main.

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

В основной функции вы видите цикл, вызывающий функцию, которая в ней run . В текущей реализации этот раздел содержит две строки лога, которые записывают начало и конец вызова функции. Теперь давайте добавим информацию о Span для отслеживания задержки вызова функции.

Во-первых, как отмечалось в предыдущем разделе, давайте настроим все параметры OpenTelemetry. Добавьте пакеты OpenTelemetry следующим образом:

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
)

Для повышения читаемости кода мы создаём функцию инициализации под названием initTracer и вызываем её в 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
}

Как вы, возможно, заметили, процедура настройки OpenTelemetry описана в предыдущем разделе. В данной реализации мы используем экспортер stdout , который выводит всю информацию трассировки в stdout в структурированном формате.

Затем вызовите его из главной функции. Вызовите initTracer() и обязательно вызовите TracerProvider.Shutdown() при закрытии приложения.

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)
        ...

После завершения настройки необходимо создать Span с уникальными идентификаторами трассировки (Trace ID) и Span ID. OpenTelemetry предоставляет для этого удобную библиотеку. Добавьте дополнительные новые пакеты в HTTP-клиент инструмента.

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"
)

Поскольку генератор нагрузки вызывает клиентскую службу по протоколу HTTP с использованием net/http в функции runQuery , мы используем пакет contrib для net/http и включаем инструментирование с помощью расширения пакетов httptrace и otelhttp .

Сначала добавим глобальную переменную пакета httpClient для вызова HTTP-запросов через инструментированный клиент.

step0/src/loadgen/main.go

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

Далее добавьте в функцию runQuery инструментарий для создания пользовательского диапазона с использованием OpenTelemetry и автоматически сгенерированного диапазона из пользовательского HTTP-клиента. Вам нужно будет сделать следующее:

  1. Получите трассировщик из глобального TracerProvider с помощью otel.Tracer()
  2. Создайте корневой сегмент с помощью метода Tracer.Start()
  3. Завершите корневой сегмент в произвольное время (в данном случае, в конце функции 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)
        }

Теперь вы завершили настройку параметров в loadgen (клиентское HTTP-приложение). Пожалуйста, обновите файлы go.mod и go.sum с помощью команды go mod .

go mod tidy

Обслуживание клиентов Instrument

В предыдущем разделе мы настроили инструментарий для части, заключенной в красный прямоугольник на рисунке ниже. Мы настроили инструментарий для информации о трассировке в службе генератора нагрузки. Аналогично службе генератора нагрузки, теперь нам необходимо настроить инструментарий для клиентской службы. Отличие от службы генератора нагрузки заключается в том, что клиентская служба должна извлекать информацию об идентификаторе трассировки (Trace ID), передаваемую из службы генератора нагрузки в заголовке HTTP, и использовать этот идентификатор для генерации трассировок.

bcaccd06691269f8.png

Откройте редактор Cloud Shell и добавьте необходимые пакеты, как мы это сделали для службы генератора нагрузки.

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
)

Опять же, нам нужно настроить OpenTelemtry. Просто скопируйте и вставьте функцию initTracer из loadgen и вызовите её также в main функции клиентского сервиса.

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
}

Теперь пришло время инструментировать трассировки. Поскольку клиентская служба должна принимать HTTP-запросы от службы генерации нагрузки, ей необходимо инструментировать обработчик. HTTP-сервер в клиентской службе реализован с использованием net/http, и вы можете использовать пакет otelhttp , как мы это делали в случае с генерацией нагрузки.

Сначала заменим регистрацию обработчика на otelhttp Handler. В функции main найдите строки, где обработчик HTTP регистрируется с помощью 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)

Затем мы добавляем инструменты для отслеживания фактического диапазона внутри обработчика. Находим функцию (*clientService) handler() и добавляем инструменты отслеживания диапазона с помощью 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
        ...

С помощью этой инструментации вы получаете данные от начала метода handler до его конца. Чтобы упростить анализ этих данных, добавьте к запросу дополнительный атрибут, хранящий количество совпадений. Непосредственно перед строкой лога добавьте следующий код.

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))
        ...

С учетом всех вышеперечисленных настроек, вы завершили трассировку между loadgen и клиентом. Давайте посмотрим, как это работает. Запустите код с помощью skaffold еще раз.

skaffold dev

Спустя некоторое время после запуска служб в кластере GKE вы увидите огромное количество сообщений в логах, подобных этому:

вывод команды

[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"
...

Экспортёр stdout выдаёт эти сообщения. Вы заметите, что родительские элементы всех трассировочных сегментов, созданных с помощью loadgen, имеют TraceID: 00000000000000000000000000000000 , поскольку это корневой сегмент, то есть первый сегмент в трассировке. Также вы обнаружите, что атрибут встраивания "query" содержит строку запроса, которая передаётся в клиентскую службу.

Краткое содержание

На этом этапе вы настроили службу генератора нагрузки и клиентскую службу, взаимодействующие по протоколу HTTP, и подтвердили возможность успешного распространения контекста трассировки между службами, а также экспорта информации о диапазоне трассировки из обеих служб в стандартный вывод.

Далее

На следующем этапе вы добавите инструменты для клиентской и серверной служб, чтобы подтвердить, как передавать контекст трассировки через gRPC.

5. Приборы для gRPC

На предыдущем этапе мы инструментировали первую половину запроса в этом микросервисе. На этом этапе мы пытаемся инструментировать gRPC-связь между клиентским и серверным сервисами. (Зеленый и фиолетовый прямоугольники на рисунке ниже)

75310d8e0e3b1a30.png

Предварительно настроенная инструментация для gRPC-клиента

Экосистема OpenTelemetry предлагает множество удобных библиотек, которые помогают разработчикам инструментировать приложения. На предыдущем шаге мы использовали предварительно собранную инструментацию для пакета net/http . На этом шаге, поскольку мы пытаемся передать контекст трассировки через gRPC, мы используем для этого соответствующую библиотеку.

Сначала необходимо импортировать предварительно собранный пакет gRPC под названием 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"
)

В этот раз клиентская служба представляет собой gRPC-клиент, взаимодействующий с серверной службой, поэтому вам необходимо инструментировать gRPC-клиент. Найдите функцию mustConnGRPC и добавьте gRPC-перехватчики, которые будут инструментировать новые сегменты каждый раз, когда клиент отправляет запросы к серверу.

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

Поскольку вы уже настроили OpenTelemetry в предыдущем разделе, вам не нужно этого делать.

Предварительно настроенная инструментальная среда для gRPC-сервера

Как и в случае с gRPC-клиентом, мы вызываем предварительно настроенную инструментацию для gRPC-сервера. Добавьте новый пакет в раздел импорта следующим образом:

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"
)

Поскольку вы впервые используете инструментарий сервера, вам необходимо сначала настроить OpenTelemetry, аналогично тому, как мы это делали для генерации нагрузки и клиентских служб.

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

Далее необходимо добавить перехватчики сервера. В функции main найдите место вызова grpc.NewServer() и добавьте в эту функцию перехватчики.

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)
        ...

Запустите микросервис и подтвердите трассировку.

Затем запустите измененный код с помощью команды skaffold.

skaffold dev

И снова вы видите множество информации о диапазоне в стандартном выводе.

вывод команды

...
[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] }
...

Вы замечаете, что не встраивали имена сегментов и создавали их вручную с помощью trace.Start() или span.SpanFromContext() . Тем не менее, вы получаете большое количество сегментов, потому что их сгенерировали перехватчики gRPC.

Краткое содержание

На этом этапе вы настроили связь на основе gRPC с использованием библиотек экосистемы OpenTelemetry.

Далее

На следующем этапе вы наконец визуализируете трассировку с помощью Cloud Trace и научитесь анализировать собранные фрагменты данных.

6. Визуализация трассировки с помощью Cloud Trace

Вы настроили трассировку всей системы с помощью OpenTelemetry. Вы уже научились настраивать HTTP и gRPC сервисы. Однако, несмотря на это, вы еще не освоили их анализ. В этом разделе вы замените экспортеры stdout на экспортеры Cloud Trace и научитесь анализировать ваши трассировки.

Используйте экспортер Cloud Trace.

Одной из мощных характеристик OpenTelemetry является возможность её расширения. Для визуализации всех трассировок, собранных вашим оборудованием, достаточно заменить экспортер stdout на экспортер Cloud Trace.

Откройте файлы main.go каждого сервиса и найдите функцию initTracer() . Удалите строку, которая генерирует экспортер stdout, и создайте вместо неё экспортер 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
}

Вам необходимо отредактировать одну и ту же функцию как в клиентской, так и в серверной части сервиса.

Запустите микросервис и подтвердите трассировку.

После внесения изменений просто запустите кластер как обычно с помощью команды skaffold.

skaffold dev

Теперь вы не видите много информации о трассировке в структурированном формате логов на стандартном выводе, потому что заменили экспортер на Cloud Trace.

вывод команды

[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
...

Теперь давайте убедимся, что все трассировки корректно отправлены в Cloud Trace. Откройте консоль Cloud и перейдите к разделу «Список трассировок». Найти его легко через поле поиска. Или же вы можете щелкнуть меню в левой панели. 8b3f8411bd737e06.png

Затем вы видите множество синих точек, распределенных по графику задержки. Каждая точка представляет собой отдельную трассу.

3ecf131423fc4c40.png

Щёлкните по одному из них, и вы увидите подробности внутри трассировки. 4fd10960c6648a03.png

Даже беглый взгляд позволяет сделать множество выводов. Например, на диаграмме «водопад» видно, что основной причиной задержки является вызов с именем shakesapp.ShakespeareService/GetMatchCount (см. 1 на изображении выше). Это можно подтвердить и в сводной таблице (в самом правом столбце показана продолжительность каждого вызова). Кроме того, эта трассировка относится к запросу "friend" (см. 2 на изображении выше).

На основе этих кратких анализов вы можете понять, что вам необходимы более детальные данные внутри метода GetMatchCount . По сравнению с информацией из стандартного вывода, визуализация является мощным инструментом. Чтобы узнать больше о Cloud Trace, посетите нашу официальную документацию .

Краткое содержание

На этом этапе вы заменили экспортер стандартного вывода на экспортер Cloud Trace и визуализировали трассировки в Cloud Trace. Также вы научились начинать анализировать трассировки.

Далее

На следующем шаге вам нужно будет изменить исходный код серверной службы, чтобы добавить подпункт в функцию GetMatchCount.

7. Добавьте подобласть для более качественного анализа.

На предыдущем шаге вы обнаружили, что причиной наблюдаемого времени отклика, определяемого функцией loadgen, в основном является процесс внутри метода GetMatchCount, обработчик gRPC, в серверной службе. Однако, поскольку мы не инструментировали ничего, кроме обработчика, мы не можем получить дополнительную информацию из диаграммы водопада. Это распространенная ситуация, когда мы начинаем инструментировать микросервисы.

3b63a1e471dddb8c.png

В этом разделе мы проведем мониторинг участка сети, где сервер обращается к Google Cloud Storage, поскольку это распространенная ситуация, когда внешние сетевые операции ввода-вывода занимают много времени, и важно определить, является ли этот вызов причиной.

Внедрить инструментарий для создания поддиапазона на сервере.

Откройте main.go на сервере и найдите функцию readFiles . Эта функция отправляет запрос в Google Cloud Storage для получения всех текстовых файлов произведений Шекспира. В этой функции вы можете создать подтег, как это делалось для мониторинга HTTP-сервера в клиентском сервисе.

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

На этом добавление нового элемента span завершается. Давайте посмотрим, что получится, запустив приложение.

Запустите микросервис и подтвердите трассировку.

После внесения изменений просто запустите кластер как обычно с помощью команды skaffold.

skaffold dev

Выберите из списка трассировок одну трассировку с именем query.request . Вы увидите похожий график трассировки в виде водопада, но с новым элементом в разделе shakesapp.ShakespeareService/GetMatchCount (элемент, заключенный в красный прямоугольник ниже).

3d4a891aa30d7a32.png

Из этого графика видно, что внешний запрос к Google Cloud Storage занимает значительную часть времени задержки, но основная часть задержки приходится на другие процессы.

Вы уже получили много полезной информации, просто взглянув на график трассировки стека. Как получить более подробные сведения о производительности вашего приложения? Здесь на помощь приходит профилировщик, но на этом пока давайте закончим этот практический урок и перенесем все уроки по профилировщику во вторую часть.

Краткое содержание

На этом этапе вы добавили еще один сегмент в серверную службу и получили дополнительные сведения о задержке системы.

8. Поздравляем!

Вы успешно создали распределенные трассировки с помощью OpenTelemery и подтвердили задержки запросов в микросервисе в Google Cloud Trace.

Для более развернутых упражнений вы можете самостоятельно изучить следующие темы.

  • Текущая реализация отправляет все трассировки, сгенерированные проверкой работоспособности ( grpc.health.v1.Health/Check ). Как отфильтровать эти трассировки из Cloud Traces? Подсказка здесь .
  • Сопоставьте журналы событий с данными о сегментах сети и посмотрите, как это работает в Google Cloud Trace и Google Cloud Logging. Подсказка здесь .
  • Замените какой-либо сервис на сервис на другом языке и попробуйте использовать для его работы OpenTelemetry для этого языка.

Также, если после этого вы захотите узнать больше о профилировщике, пожалуйста, перейдите ко второй части. В этом случае вы можете пропустить раздел об очистке ниже.

Уборка

После выполнения этого практического задания остановите кластер Kubernetes и обязательно удалите проект, чтобы избежать непредвиденных расходов на Google Kubernetes Engine, Google Cloud Trace и Google Artifact Registry.

Сначала удалите кластер. Если вы запускаете кластер с помощью skaffold dev , просто нажмите Ctrl-C. Если вы запускаете кластер с помощью skaffold run , выполните следующую команду:

skaffold delete

вывод команды

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

После удаления кластера в панели меню выберите «IAM и администрирование» > «Настройки», а затем нажмите кнопку «Выключить».

45aa37b7d5e1ddd1.png

Затем введите идентификатор проекта (а не название проекта) в форму в диалоговом окне и подтвердите завершение работы.