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

1. Введение

505827108874614d.png

Последнее обновление: 15 июля 2022 г.

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

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

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

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

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

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

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

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

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

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

След

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

Что ты построишь

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

44e243182ced442f.png

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

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

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

  • Как начать работу с библиотеками OpenTelemetry Trace в проекте Go
  • Как создать диапазон с помощью библиотеки
  • Как передавать контексты диапазона по сети между компонентами приложения
  • Как отправить данные трассировки в 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

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

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

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 Cloud, что значительно повышает производительность сети и аутентификацию. Это означает, что все, что вам понадобится для этой лаборатории кода, — это браузер (да, он работает на Chromebook).

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

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

Снимок экрана 14.06.2017, 22.13.43.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 ? Узнайте, какой идентификатор вы использовали на этапах настройки, или найдите его на панели управления Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

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

echo $GOOGLE_CLOUD_PROJECT

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

<PROJECT_ID>

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

gcloud config set compute/zone us-central1-f

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

Перейти к настройке языка

В этой лаборатории кода мы используем 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

Сначала мы настраиваем кластер Kubernetes, в котором Shakesapp работает на GKE, поэтому нам нужно включить GKE. Перейдите в меню «Kubernetes Engine» и нажмите кнопку ВКЛЮЧИТЬ.

548cfd95bc6d344d.png

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

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

В Cloud Shell выполните следующую команду, чтобы создать кластер Kubernetes. Подтвердите, что значение зоны соответствует региону , который вы будете использовать для создания репозитория реестра артефактов. Измените значение зоны 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

Реестр артефактов и настройка скаффолда

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

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

Перейдите в меню «Реестр артефактов» и нажмите кнопку ВКЛЮЧИТЬ.

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 в качестве реестра контейнеров, поэтому вам необходимо настроить skaffold для распознавания GAR при отправке контейнеров.

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

skaffold version

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

v1.38.0

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

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. Создавайте, распространяйте и развертывайте микросервисы.

Загрузите материал codelab

На предыдущем шаге мы создали все необходимые условия для этой лаборатории кода. Теперь вы готовы запускать поверх них целые микросервисы. Материалы Codelab размещены на 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 охватывает шаги с 0 по 4, а часть 2 — шаги 5 и 6)

Запустить команду скаффолда

Наконец, вы готовы собирать, отправлять и развертывать весь контент в только что созданном кластере 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

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

На этом этапе вы подготовили материал codelab в своей среде и подтвердили ожидаемое выполнение скаффолда.

Дальше

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

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

Концепция инструментирования и распространения трассировки

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

6be42e353b9bfd1d.png

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

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

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

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

Компоненты в 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.

шаг0/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 в нем функцию. В текущей реализации раздел имеет 2 строки журнала, в которых фиксируется начало и окончание вызова функции. Теперь давайте инструментируем информацию Span для отслеживания задержки вызова функции.

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

шаг0/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 функции.

шаг0/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 , который экспортирует всю информацию трассировки в стандартный вывод в структурированном формате.

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

шаг0/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)
        ...

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

шаг0/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-запросов через инструментированный клиент.

шаг0/src/loadgen/main.go

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

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

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

шаг0/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

Клиентское обслуживание инструментов

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

bcaccd06691269f8.png

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

шаг0/src/клиент/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 функции клиентской службы.

шаг0/src/клиент/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 , как мы делали в loadgen.

Сначала мы заменяем регистрацию обработчика на otelhttp Handler. В main функции найдите строки, в которых обработчик HTTP зарегистрирован с помощью http.HandleFunc() .

шаг0/src/клиент/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)

Затем мы инструментируем фактический диапазон внутри обработчика. Найдите func (*clientService) handler() и добавьте инструментарий диапазона с помощью trace.SpanFromContext() .

шаг0/src/клиент/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 до его конца. Чтобы упростить анализ интервалов, добавьте в запрос дополнительный атрибут, который хранит количество совпадений. Прямо перед строкой журнала добавьте следующий код.

шаг0/src/клиент/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, и подтвердили, что можете успешно распространять контекст трассировки между службами и экспортировать информацию Span из обеих служб в стандартный вывод.

Дальше

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

5. Инструментарий для gRPC

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

75310d8e0e3b1a30.png

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

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

Сначала вы импортируете готовый пакет gRPC под названием otelgrpc .

шаг0/src/клиент/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, которые обрабатывают новые промежутки каждый раз, когда клиент отправляет запросы серверу.

шаг0/src/клиент/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. Добавьте новый пакет в раздел импорта, например:

шаг0/src/сервер/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, аналогично тому, что мы делали для нагрузки и клиентских служб.

шаг0/src/сервер/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() и добавьте в функцию перехватчики.

шаг0/src/сервер/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() . Удалите строку, чтобы создать экспортер стандартного вывода, и вместо этого создайте экспортер Cloud Trace.

шаг0/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 на изображении выше). Вы можете подтвердить это из сводной таблицы. (Самый правый столбец показывает продолжительность каждого промежутка.) Также эта трассировка была для запроса «друг». (См. 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-сервера в клиентской службе.

шаг0/src/сервер/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
        ...

И это все для добавления нового пролета. Давайте посмотрим, как это происходит, запустив приложение.

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

После редактирования просто запустите кластер как обычно с помощью команды 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 для этого языка.

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

Очистить

После этой лабораторной работы остановите кластер 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

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