Установка Sentry в Kubernetes, отловы exception на бекенде, в браузере, на Android

от автора

Привет, Хабр!
Меня зовут Антон Пацев, я DevOps-инженер мобильного приложения «Магнит акции и скидки». В этой статье поговорим о Sentry — инструменте для сбора exception, который помогает разработчикам быстро обнаруживать и устранять проблемы, сокращая время выхода новых релизов и повышая удовлетворенность пользователей.

Sentry обладает следующими преимуществами:

  • Мониторинг exception на мобильных устройствах и в браузере.

  • Открытый исходный код: Разработчики могут бесплатно использовать, модифицировать и расширять Sentry, благодаря активному сообществу.

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

  • Детальная информация об exception: Предоставляет подробную информацию для быстрой идентификации и исправления проблем.

  • Аналитика и отчеты: Позволяет анализировать статистику exception и создавать детальные отчеты.

  • Самостоятельное развертывание: Возможность развертывания на собственных серверах для контроля над данными.

  • Обширная документация и поддержка сообщества: Помогает быстро начать работу и решать проблемы.

В этой статье рассмотрим минимальную установку Sentry в Kubernetes c clickhouse-operator и kafka-operator, так как установка Sentry в Kubernetes имеет много подводных камней. Используется Clickhouse operator, так как Sentry не работает с более новыми версиями Clickhouse, которые предлагает Яндекс облако.

Для разворачивания Sentry используем Яндекс Облако, Managed Kubernetes, Managed PostgreSQL, Managed Redis, Managed S3.

Настройка HTTPS выходит за рамки данной статьи.

Регистрируем домен на reg.ru. Исправляем домен apatsev.org.ru на ваш домен везде в коде. Версии приложений меняем осторожно, иначе могут быть баги, например https://github.com/ClickHouse/ClickHouse/issues/53749.

В конфигурационных файлах terraform заполняем folder_id, network_id, subnet_id.

Создаем Kubernetes с помощью модуля https://github.com/terraform-yacloud-modules/terraform-yandex-kubernetes

Hidden text
module "iam_accounts" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-iam.git//modules/iam-account?ref=v1.0.0"    name = "iam-sentry-kubernetes"   folder_roles = [     "container-registry.images.puller",     "k8s.clusters.agent",     "k8s.tunnelClusters.agent",     "load-balancer.admin",     "logging.writer",     "vpc.privateAdmin",     "vpc.publicAdmin",     "vpc.user",   ]   folder_id = "xxxx"  }  module "kube" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-kubernetes.git?ref=v1.1.0"    folder_id  = "xxxx"   network_id = "xxxx"    name = "k8s-sentry"    service_account_id      = module.iam_accounts.id   node_service_account_id = module.iam_accounts.id    master_locations = [     {       zone      = "ru-central1-a"       subnet_id = "xxxx"     }   ]    node_groups = {     "auto-scale" = {       nat    = true       cores  = 6       memory = 12       auto_scale = {         min     = 4         max     = 6         initial = 4       }     }   }    depends_on = [module.iam_accounts]  }  module "address" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-address.git?ref=v1.0.0"    ip_address_name = "sentry-pip"   folder_id       = "xxxx"   zone            = "ru-central1-a" }  module "dns-zone" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-dns.git//modules/zone?ref=v1.0.0"    folder_id = "xxxx"   name      = "apatsev-org-ru-zone"    zone             = "apatsev.org.ru." # Точка в конце обязательна   is_public        = true   private_networks = ["xxxx"] # network_id }  module "dns-recordset" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-dns.git//modules/recordset?ref=v1.0.0"    folder_id = "xxxx"   zone_id   = module.dns-zone.id   name      = "sentry.apatsev.org.ru." # Точка в конце обязательна   type      = "A"   ttl       = 200   data = [     module.address.external_ipv4_address   ] }  provider "helm" {   kubernetes {     host                   = module.kube.external_v4_endpoint     cluster_ca_certificate = module.kube.cluster_ca_certificate     exec {       api_version = "client.authentication.k8s.io/v1beta1"       args        = ["k8s", "create-token"]       command     = "yc"     }   } }  resource "helm_release" "ingress_nginx" {   name             = "ingress-nginx"   repository       = "https://kubernetes.github.io/ingress-nginx"   chart            = "ingress-nginx"   version          = "4.10.1"   namespace        = "ingress-nginx"   create_namespace = true   depends_on       = [module.kube]   set {     name  = "controller.service.loadBalancerIP"     value = module.address.external_ipv4_address   }  } 

Создаем PostgreSQL с помощью модуля https://github.com/terraform-yc-modules/terraform-yc-postgresql

Hidden text
module "db" {   source = "git::https://github.com/terraform-yc-modules/terraform-yc-postgresql.git"    folder_id  = "xxxx"   network_id = "xxxx"   name       = "sentry-postgresql"    hosts_definition = [     {       zone             = "ru-central1-a"       assign_public_ip = true       subnet_id        = "xxxx"     }   ]    postgresql_config = {     max_connections = 395   }    databases = [     {       name       = "sentry"       owner      = "sentry"       lc_collate = "ru_RU.UTF-8"       lc_type    = "ru_RU.UTF-8"       extensions = ["citext"]     }   ]    owners = [     {       name     = "sentry"       password = "sentry-postgresql-password"     }   ] }  output "fqdn_database" {   value     = "c-${module.db.cluster_id}.rw.mdb.yandexcloud.net"   sensitive = false }  output "owners_data" {   description = "List of owners with passwords."   sensitive   = true   value       = module.db.owners_data }  output "databases" {   description = "List of databases names."   value       = module.db.databases } 

Создаем Redis с помощью модуля https://github.com/terraform-yacloud-modules/terraform-yandex-redis

Hidden text
module "redis" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-redis.git?ref=v1.0.0"    folder_id  = "xxxx"   name       = "sentry-redis"   network_id = "xxxxx"   password   = "sentry-redis-password"   zone       = "ru-central1-a"   hosts = {     host1 = {       zone      = "ru-central1-a"       subnet_id = "xxxxx"     }   } }  output "password" {   value     = module.redis.password   sensitive = true }  output "fqdn_redis" {   value = module.redis.fqdn_redis } 

Создаем S3 с помощью модуля https://github.com/terraform-yacloud-modules/terraform-yandex-storage-bucket

Hidden text
module "s3" {   source = "git::https://github.com/terraform-yacloud-modules/terraform-yandex-storage-bucket.git?ref=v1.0.0"    bucket_name = "sentry-bucket-apatsev-dev"   folder_id   = "xxxx" }  provider "aws" {   region                      = "us-east-1"   skip_region_validation      = true   skip_credentials_validation = true   skip_requesting_account_id  = true   skip_metadata_api_check     = true   access_key                  = "mock_access_key"   secret_key                  = "mock_secret_key" }  output "access_key" {   value = module.s3.storage_admin_access_key }  output "secret_key" {   value     = module.s3.storage_admin_secret_key   sensitive = true } 

Адреса, креды PostgreSQL, Redis, S3 прописываем в файле values-sentry.yaml

Устанавливаем новое подключение к k8s.

yc managed-kubernetes cluster get-credentials --id xxxx --external

Создаем namespace sentry.

kubectl create namespace sentry

Устанавливаем zookeeper, altinity-clickhouse-operator, strimzi-kafka-operator.
Вместо 3 запусков helm запустим 1 раз helmwave.
Helm репозитории и настройки описаны в файле helmwave.yml

helmwave up --build

Ждем когда поды перейдут в состояние Ready, например через k9s

k9s -A

Создаем kafka-node-pool, kafka, kafka-topics с помощью https://github.com/strimzi/strimzi-kafka-operator.
Примеры берем отсюда: https://github.com/strimzi/strimzi-kafka-operator/tree/main/examples/kafka

kubectl apply -f kafka-node-pool.yaml kubectl apply -f kafka.yaml kubectl apply -f kafka-topics.yaml

Ждем, когда поды Kafka перейдут в состояние Ready, например через k9s

k9s -A

Создаем Clickhouse. Придумываем пароль и получаем от него sha256 хеш.

printf 'sentry-clickhouse-password' | sha256sum

Полученный хеш вставляем в поле «sentry/password_sha256_hex» в файле kind-ClickHouseInstallation.yaml

Из примеров: https://github.com/Altinity/clickhouse-operator/tree/master/docs/chi-examples делаем конфиг для clickhouse
Затем применяем его

kubectl apply -f kind-ClickHouseInstallation.yaml

Ждём, когда pod Clickhouse перейдут в состояние Ready, например через k9s

k9s -A

Устанавливаем Sentry.

helm repo add sentry https://sentry-kubernetes.github.io/charts helm repo update helm upgrade --install sentry -n sentry sentry/sentry --version 23.1.0 -f values-sentry.yaml

Ждём Clickhouse миграции в pod snuba-migrate.
Чтобы увидеть лог миграции snuba-migrate, можно использовать stern для просмотра логов в namespace sentry.

stern -n sentry -l job-name=sentry-snuba-migrate

Ждём завершения PostgreSQL миграции в pod db-init-job.
Чтобы увидеть лог миграции db-init, можно использовать stern для просмотра логов в namespace sentry.

stern -n sentry -l job-name=sentry-db-init

Смотрим логи в namespace sentry на предмет разных ошибок.

stern -n sentry .

Открываем URL, прописанный в system.url.
Входим в sentry по кредам, которые вы указали в этом коде.

user:   password: "пароль"   create: true   email: логин-в-виде-email

Backend. Пример exception на python.

Создаём Project, выбираем Python. Создаём директорию Example-python. Переходим в директорию Example-python.
В директории example-python создаём main.py такого содержания.

import sentry_sdk sentry_sdk.init(     dsn="http://xxxx@sentry.apatsev.org.ru/2",     traces_sample_rate=1.0, )  try:     1 / 0 except ZeroDivisionError:     sentry_sdk.capture_exception() 
python3 -m venv venv source venv/bin/activate pip install --upgrade sentry-sdk python3 main.py

В Sentry видим следующую картину:

Frontend. Пример exception на React.

Вот пример простого React кода для отправки исключения (exception) в Sentry через браузер, а также Dockerfile для контейнеризации этого приложения.

Структура React проекта:

Hidden text
. ├── Dockerfile ├── package.json ├── public │    └── index.html └── src     ├── App.js     ├── index.css     └── index.js 

App.js:

Hidden text
import React from 'react'; import * as Sentry from '@sentry/react'; import { Integrations } from '@sentry/tracing';  // Инициализация Sentry Sentry.init({   dsn: 'YOUR_SENTRY_DSN_HERE', // Замените на ваш DSN   integrations: [new Integrations.BrowserTracing()],   tracesSampleRate: 1.0, });  function ErrorButton() {   function throwError() {     throw new Error('This is a test error from React');   }    return (     <button onClick={throwError}>       Throw Error     </button>   ); }  function App() {   return (     <div className="App">       <h1>Sentry Example</h1>       <ErrorButton />     </div>   ); }  export default Sentry.withProfiler(App); 

index.css:

Hidden text
/* src/index.css */ body {   margin: 0;   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',     'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',     sans-serif;   -webkit-font-smoothing: antialiased;   -moz-osx-font-smoothing: grayscale; }  code {   font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',     monospace; } 

index.js:

Hidden text
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; // Удалите следующую строку, если не используете index.css // import './index.css'; import App from './App';  ReactDOM.render(   <React.StrictMode>     <App />   </React.StrictMode>,   document.getElementById('root') ); 

Dockerfile:

Hidden text
# Используем базовый образ Node.js FROM node:14  # Устанавливаем рабочую директорию WORKDIR /app  # Копируем package.json и package-lock.json COPY package*.json ./  # Устанавливаем зависимости RUN npm install  # Копируем исходный код COPY . .  # Собираем React приложение RUN npm run build  # Используем nginx для раздачи статики FROM nginx:alpine COPY --from=0 /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] 

Убедитесь, что ваш package.json содержит необходимые зависимости и скрипты для сборки и запуска приложения:
package.json:

Hidden text
{   "name": "sentry-react-example",   "version": "1.0.0",   "description": "Example of sending exceptions to Sentry from React",   "scripts": {     "start": "react-scripts start",     "build": "react-scripts build",     "test": "react-scripts test",     "eject": "react-scripts eject"   },   "dependencies": {     "@sentry/react": "^6.13.3",     "@sentry/tracing": "^6.13.3",     "react": "^17.0.2",     "react-dom": "^17.0.2",     "react-scripts": "4.0.3"   },   "browserslist": {     "production": [       ">0.2%",       "not dead",       "not op_mini all"     ],     "development": [       "last 1 chrome version",       "last 1 firefox version",       "last 1 safari version"     ]   } } 

Сборка и запуск Docker контейнера
Собираем Docker образ:

docker build -t sentry-example .

Запускаем контейнер:

docker run -p 80:80 sentry-example

Не забываем заменить YOUR_SENTRY_DSN_HERE на ваш реальный DSN, который вы можете найти в настройках вашего проекта в Sentry.

В браузере открываем http://localhost

Нажимаем на Throw error

В Sentry видим exception

Mobile. Пример exception на android.

Пробуем запустить https://github.com/sentry-demos/android

В случае ошибок, ответы можно найти в Readme репозитория.

Склонируем репозиторий:

git clone git@github.com:sentry-demos/android.git

Синхронизируем проект с файлами Gradle.

Tools -> Android -> Sync Project with Gradle Files In some Android Studio version this will be available under: File -> Sync Project with Gradle Files

Идём в http://sentry.apatsev.org.ru/settings/sentry/auth-tokens/.
Создаем новый token с названием sentry-demos-android:
Идём в Settings -> Auth Token -> Create New token

В корне домашней директории пользователя, кто запускает команду sentry-cli, создаём файл .sentryclirc с содержимым:

[auth] token=auth-token-sentry-demos-android

В качестве эмулятор android используем Nexus 5x API 29 x86, Pixel 2 API 29.

Создаём Android проект с названием android (по умолчанию).

На вкладке manual копируем io.sentry.dsn.

Меняем значение ключа io.sentry.dsn в файл app/src/main/AndroidManifest.xml

        <meta-data             android:name="io.sentry.dsn"             android:value="http://166c996d93bc76f7706c1cf30fcd91af@sentry.apatsev.org.ru/2" /> 

Помещаем проект и организацию в файл Makefile.

SENTRY_ORG=sentry # Поменяйте на вашу организацию. SENTRY_PROJECT=android # Поменяйте на ваш проект.

В sentry.properties меняем организацию на Sentry и прописываем Auth Token.

defaults.org=sentry auth.token=auth-token-sentry-demos-android

Добавляем следующий код в тег <application> нашего AndroidManifest.xml:

<application ... android:usesCleartextTraffic="true" ... </application>

Это позволит избежать ошибки java.io.IOException: Cleartext HTTP traffic to sentry.apatsev.org.ru not permitted, которая указывает на то, что приложение пытается отправить данные на сервер Sentry по незашифрованному HTTP-протоколу, что запрещено.

Устанавливаем утилиту https://github.com/getsentry/sentry-cli

Запускаем сборку

make all

Запускаем эмулятор. Нажимаем run app в Android Studio. В самом приложении нажимаем на 3 вертикальные точки.
Затем нажимаем на List App и нажимаем на разные кнопки получая разные Exception.

Исходный код можно скачать в репозитории https://github.com/patsevanton/install-sentry-kubernetes-minimal.

Итак, мы с вами рассмотрели пошаговую инструкцию по минимальной установке Sentry в Kubernetes c clickhouse-operator и kafka-operator, отловы exception на бекенде, в браузере, на Android. А в качестве приятного бонуса, делюсь с вами Telegram-чатом по Sentry https://t.me/sentry_ru


ссылка на оригинал статьи https://habr.com/ru/articles/831264/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *