<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Lev Aminov</title><author><name>Lev Aminov</name></author><id>https://teletype.in/atom/levaminov</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/levaminov?offset=0"></link><link rel="alternate" type="text/html" href="https://levaminov.ru/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/levaminov?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-05-06T12:16:45.151Z</updated><entry><id>levaminov:9SXROqaOypv</id><link rel="alternate" type="text/html" href="https://levaminov.ru/9SXROqaOypv?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Ошибка ERR_NGROK_9040. Ngrok закрыл доступ для всех российских IP-адресов</title><published>2025-04-02T15:17:14.192Z</published><updated>2025-04-02T15:17:14.192Z</updated><summary type="html">Пользователи массово столкнулись с проблемами в работе ngrok в России — при попытке запустить туннель отображается ошибка: &quot;We do not allow agents to connect to ngrok from your IP address&quot;. Это связано с санкциями, которые запрещают предоставление сервиса пользователям из России.</summary><content type="html">
  &lt;p id=&quot;gx0g&quot;&gt;Пользователи массово столкнулись с проблемами в работе ngrok в России — при попытке запустить туннель отображается ошибка: &amp;quot;We do not allow agents to connect to ngrok from your IP address&amp;quot;. Это связано с санкциями, которые запрещают предоставление сервиса пользователям из России.&lt;/p&gt;
  &lt;p id=&quot;LpL6&quot;&gt;В качестве решения проблемы с ошибкой &lt;code&gt;ERR_NGROK_9040&lt;/code&gt; мы предлагаем воспользоваться нашим сервисом &lt;a href=&quot;https://tuna.am&quot; target=&quot;_blank&quot;&gt;Tuna&lt;/a&gt; – простой интерфейс, понятный функционал, в целом, всё, как надо.&lt;/p&gt;

</content></entry><entry><id>levaminov:Hz1dz27VGd_</id><link rel="alternate" type="text/html" href="https://levaminov.ru/Hz1dz27VGd_?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Синхронизация образов в свой Docker Registry</title><published>2023-09-27T14:42:52.017Z</published><updated>2024-05-30T03:34:01.293Z</updated><summary type="html">Есть такой инструмент – skopeo, в числе прочего он умеет перепушивать образы из одного регистри в другой, что может быть удобно, если периодически возникает проблема с рейт-лимитом на получение образа. Копировать образ в свой регистри можно было бы и вручную, но не всегда удобно, можно забыть про передачу нужной платформу или у того, кому это срочно надо, нет доступа на пуш. Поэтому в данной заметке предлагаю несложный способ как это можно худо-бедно автоматизировать в Gitlab.</summary><content type="html">
  &lt;p id=&quot;l7xj&quot;&gt;Есть такой инструмент – &lt;a href=&quot;https://github.com/containers/skopeo&quot; target=&quot;_blank&quot;&gt;skopeo&lt;/a&gt;, в числе прочего умеет перепушивать образы из одного регистри в другой, что может быть удобно, если периодически возникает проблема с рейт-лимитом на получение образа. Копировать образ в свой регистри можно было бы и вручную, но не всегда удобно, можно забыть про передачу нужной платформы или у того, кому это срочно надо, нет доступа на пуш. Поэтому в данной заметке предлагаю несложный способ как это можно худо-бедно автоматизировать в Gitlab.&lt;/p&gt;
  &lt;p id=&quot;H3qs&quot;&gt;Создаем пустой репозиторий, в файл &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; прописываем следующий контент:&lt;/p&gt;
  &lt;pre id=&quot;sMvm&quot; data-lang=&quot;yaml&quot;&gt;---
variables:
  REGISTRY_ADDR: my.registry/aabbccdd

stages:
  - lint
  - sync

lint:
  image: quay.io/skopeo/stable:v1.13.0
  stage: lint
  script:
    - DRY_RUN=1 ./.skopeo.sh

sync:
  image: quay.io/skopeo/stable:v1.13.0
  stage: sync
  script:
    - ./.skopeo.sh
  only:
    refs:
      - master&lt;/pre&gt;
  &lt;p id=&quot;C6Ym&quot;&gt;Чуть ниже создаем файл &lt;code&gt;.skopeo.sh&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;mBKd&quot; data-lang=&quot;bash&quot;&gt;#!/usr/bin/env bash

set -o errexit
set -o nounset

for file in *-images.yaml; do
  echo &amp;quot;==&amp;gt; ${file}&amp;quot;
  skopeo sync \
    --scoped \
    --keep-going \
    --src yaml \
    --dry-run=&amp;quot;${DRY_RUN:-0}&amp;quot; \
    --dest docker &amp;quot;${file}&amp;quot; &amp;quot;${REGISTRY_ADDR}&amp;quot; \
    --dest-authfile .docker_config.json
done&lt;/pre&gt;
  &lt;p id=&quot;w566&quot;&gt;Обращу внимание, что для пуша могут потребоваться какие-то реквизиты, через аргумент &lt;code&gt;--dest-authfile&lt;/code&gt; задается путь к стандартному для Docker файлу с реквизитами в формате JSON, можно так же использовать &lt;code&gt;--dest-registry-token&lt;/code&gt; или &lt;code&gt;--dest-creds&lt;/code&gt;, все варианты перечислены в справке.&lt;/p&gt;
  &lt;p id=&quot;YBEb&quot;&gt;Теперь создаем файл, например, &lt;code&gt;team-infra-images.yaml&lt;/code&gt; (файлов может быть несколько, главное, чтобы заканчивались на &lt;code&gt;-images.yaml&lt;/code&gt;) с перечнем образов, которые нужно перенести к нам:&lt;/p&gt;
  &lt;pre id=&quot;Kinb&quot; data-lang=&quot;yaml&quot;&gt;---
quay.io:
  images:
    skopeo/stable:
      - v1.13.0
mirror.gcr.io:
  images:
    golang:
      - &amp;quot;1.21&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;FFLN&quot;&gt;Теперь, при создании Merge Request у нас будет выполняться dry-run запуск, оставим на всякий случай, чтобы убедиться, что внутри конфигурационных файлов корректный синтаксис. После вливания изменений в основную ветку произойдет проверка источника с получателем, если есть разночтения, то образы будут скопированы в нужный регистри.&lt;/p&gt;
  &lt;p id=&quot;GGlM&quot;&gt;Из флагов в команде sync отмечу следующие:&lt;/p&gt;
  &lt;ul id=&quot;gNyW&quot;&gt;
    &lt;li id=&quot;DjFQ&quot;&gt;&lt;code&gt;--scoped&lt;/code&gt; – для включения хоста источника в имя результирующего образа в нашем регистри (например, образ &lt;code&gt;quay.io/skopeo/stable:v1.13.0&lt;/code&gt; будет скопирован не в &lt;code&gt;my.registry/aabbccdd/skopeo/stable:v1.13.0&lt;/code&gt;, а в &lt;code&gt;my.registry/aabbccdd/quay.io/skopeo/stable:v1.13.0&lt;/code&gt;);&lt;/li&gt;
    &lt;li id=&quot;YRzE&quot;&gt;&lt;code&gt;--keep-going&lt;/code&gt; – продолжать синхронизацию образов, даже если получили ошибку.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;HPk3&quot;&gt;Так же можно запланировать периодический запуск, чтобы быть уверенным, что все образы на месте.&lt;/p&gt;

</content></entry><entry><id>levaminov:v7qTdsBqLp_</id><link rel="alternate" type="text/html" href="https://levaminov.ru/v7qTdsBqLp_?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Мои базовые настройки macOS</title><published>2023-08-13T11:26:42.215Z</published><updated>2023-08-13T15:26:46.183Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/67/0b/670b590e-7c68-4ef2-9811-19d553634ede.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/a9/55/a9558b6f-0c22-43f3-ab7d-742b5bbd605a.png&quot;&gt;Каждый раз при смене ноутбука устанавливаю одни и те же настройки, от каких-то избавился, а какие-то со мной уже давно, здесь перечислю, чтобы и самому не забывать и может другим помочь.</summary><content type="html">
  &lt;p id=&quot;CfmP&quot;&gt;Каждый раз при смене ноутбука устанавливаю одни и те же настройки, от каких-то избавился, а какие-то со мной уже давно, здесь перечислю, чтобы и самому не забывать и может другим помочь.&lt;/p&gt;
  &lt;h2 id=&quot;cYMp&quot;&gt;Тапы по тачпаду&lt;/h2&gt;
  &lt;p id=&quot;ygIv&quot;&gt;Помимо физического нажатия на тачпад можно будет просто по нему тапать пальцем, будет считаться за клик. Переходим в настройки, затем включаем нижнюю галочку &amp;quot;Tap on click&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;aLuM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a9/55/a9558b6f-0c22-43f3-ab7d-742b5bbd605a.png&quot; width=&quot;1654&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;uBJC&quot;&gt;Скорость реакции тачпада&lt;/h2&gt;
  &lt;p id=&quot;swCg&quot;&gt;Та же страница, настройку &amp;quot;Tracking speed&amp;quot; выкручиваем на максимум.&lt;/p&gt;
  &lt;h2 id=&quot;M80S&quot;&gt;Скорость повтора нажатия&lt;/h2&gt;
  &lt;p id=&quot;x3k8&quot;&gt;Настройки &amp;quot;Key repeat rate&amp;quot; и &amp;quot;Delay until repeat&amp;quot; выставляем в положение &amp;quot;Fast&amp;quot; и &amp;quot;Short&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;Xjls&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2c/21/2c212217-fe2c-407c-8ec2-9762229774a7.png&quot; width=&quot;1654&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;rhGv&quot;&gt;Перетаскивание окон тремя пальцами&lt;/h2&gt;
  &lt;p id=&quot;aQOm&quot;&gt;Переходим в Accessibility, там в раздел Pointer Control, жмем на кнопку Trackpad Options, ставим галочку &amp;quot;Use trackpad for dragging&amp;quot;, а значение &amp;quot;Dragging style&amp;quot; устанавливаем как &amp;quot;Three-Fingers Drag&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;OuIm&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5c/01/5c011dd3-4773-4922-b2be-dbeebd457df9.png&quot; width=&quot;1654&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;R2l8&quot;&gt;Блокировка экрана через перемещения курсора в угол экрана&lt;/h2&gt;
  &lt;p id=&quot;2GLr&quot;&gt;Вообще есть сочетание клавиш &lt;code&gt;^⌘Q&lt;/code&gt;, но привычка – дело такое. Переходим в раздел настроек Desktop &amp;amp; Dock, в самом низу будет кнопка Hot Corners, выбираем блокировку для нужного угла:&lt;/p&gt;
  &lt;figure id=&quot;NYpH&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/51/3f/513f6d9d-9fcc-494c-82a4-9a5d038c4217.png&quot; width=&quot;1654&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;VluM&quot;&gt;Отключение звуковых сигналов в Terminal&lt;/h2&gt;
  &lt;p id=&quot;Mpdh&quot;&gt;Открываем Terminal, переходим в настройки, в настройках текущего профиля переходим во вкладку Advanced, там снимаем флаг &amp;quot;Audible bell&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;gUwt&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/29/86/298668a4-66d4-4f99-b5f1-353c7d0c7ddc.png&quot; width=&quot;1558&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;lNj6&quot;&gt;Режим разработчика в Safari&lt;/h2&gt;
  &lt;p id=&quot;Labl&quot;&gt;Открываем Safari, далее Настройки и раздел Advanced, в самом низу ставим галочку &amp;quot;Show Develop menu in menu bar&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;pCXG&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b6/ed/b6ed0793-a054-45a4-9ff6-dbecb2c5354c.png&quot; width=&quot;1772&quot; /&gt;
  &lt;/figure&gt;

</content></entry><entry><id>levaminov:JsEwp91lmC-</id><link rel="alternate" type="text/html" href="https://levaminov.ru/JsEwp91lmC-?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Переезд из Slack в Mattermost (3)</title><published>2023-03-28T07:08:14.280Z</published><updated>2023-06-19T18:18:30.880Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/37/87/37871399-5e35-4903-b521-3cdc07334c91.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/45/0f/450f3070-4d7e-4110-8aae-d44989bddafb.png&quot;&gt;Часть 1: https://levaminov.ru/z3TpTpYSK4J</summary><content type="html">
  &lt;p id=&quot;ByVK&quot;&gt;Часть 1: &lt;a href=&quot;https://levaminov.ru/z3TpTpYSK4J&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/z3TpTpYSK4J&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;KIAu&quot;&gt;Часть 2: &lt;a href=&quot;https://levaminov.ru/Rmks9ZZ7RLl&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/Rmks9ZZ7RLl&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;kce4&quot;&gt;И вот мы опять вернулись к исследованию производительности Mattermost, если не считать дублирующихся сообщений в больших тредах, то все было хорошо и нормально, но эти дубли, в процессе разбора инцидентов, просто выносили всем мозг. При этом у самого сервера никаких серьезных ошибок не фиксируется:&lt;/p&gt;
  &lt;figure id=&quot;EWSu&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/45/0f/450f3070-4d7e-4110-8aae-d44989bddafb.png&quot; width=&quot;1412&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;SZLz&quot;&gt;Раньше мы, в случае проблем, натыкались на какие-то ошибки подключения к базе, лимитам по коннектам, сейчас только таймату API. При этом график этих самых таймаутов методов совпадал с ошибками закрытия соединений в nginx:&lt;/p&gt;
  &lt;figure id=&quot;m9uj&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0a/14/0a144637-1002-438e-85af-e07f3ea7fa50.png&quot; width=&quot;2030&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7bNW&quot;&gt;Тут стало понятно, что есть какие-то таймауты со стороны сервера Mattermost, которые на это влияют, обратились к документации:&lt;/p&gt;
  &lt;figure id=&quot;5NXv&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c7/b1/c7b14e90-28de-4b95-93a0-325133183fa6.png&quot; width=&quot;2026&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wSS6&quot;&gt;Проверяем, что у нас:&lt;/p&gt;
  &lt;figure id=&quot;hogW&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c3/ee/c3ee0292-70bb-4cbc-99ec-f52f366a3642.png&quot; width=&quot;1904&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;w5Zx&quot;&gt;Нам так и не удалось выяснить, откуда взялись эти значения, возможно, мигрировало из нашей инсталляции, которая была развернута и управлялась оператором (там ресурсы на кластер выделяются относительно &amp;quot;размера&amp;quot; кластера в пользователях), возможно неудачная миграция с одного мажора на другой... непонятно. Поменяли значения на дефолтные – ошибки пропали, дубли пропали тоже.&lt;/p&gt;
  &lt;figure id=&quot;uUYw&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7d/4c/7d4cc28a-afba-4789-9ed9-fabe4d28a147.png&quot; width=&quot;2828&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;oKHj&quot;&gt;После изменений так же упала нагрузка с сервера и базы, поэтому статистику по каналу, которую мы отключали в прошлом заходе, мы вернули обратно.&lt;/p&gt;
  &lt;p id=&quot;yqYA&quot;&gt;Заметил, что это не первая задача за последние пару дней, решение которой происходит в процессе объяснения другому инженеру что делать и с чего начинать исследование проблемы, вот уж точно &amp;quot;правильно заданный вопрос – половина ответа&amp;quot;.&lt;/p&gt;

</content></entry><entry><id>levaminov:Rmks9ZZ7RLl</id><link rel="alternate" type="text/html" href="https://levaminov.ru/Rmks9ZZ7RLl?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Переезд из Slack в Mattermost (2)</title><published>2022-11-14T17:26:26.441Z</published><updated>2023-06-19T18:48:09.049Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/1d/4a/1d4a1657-1b45-4699-af8b-5bbc013f8ff3.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/90/fa/90fa74b0-0979-40f2-8d2b-5ed196a0e916.png&quot;&gt;Часть 1: https://levaminov.ru/z3TpTpYSK4J</summary><content type="html">
  &lt;p id=&quot;gdLu&quot;&gt;Часть 1: &lt;a href=&quot;https://levaminov.ru/z3TpTpYSK4J&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/z3TpTpYSK4J&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;8JqU&quot;&gt;Часть 3: &lt;a href=&quot;https://levaminov.ru/JsEwp91lmC-&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/JsEwp91lmC-&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;JZut&quot;&gt;К нашей инсталляции Mattermost в пике подключено около 350 устройств – не так много, как в истории с мессенджером в Тинькофф, но проблемы бывают и у маленьких инсталляций. Спойлер: наша проблема так и не решена.&lt;/p&gt;
  &lt;p id=&quot;iOPF&quot;&gt;По мере переезда пользователей из Slack мы столкнулись с тем, что в часы пик число коннектов к базе упирается в лимит, ресурсов не хватает, рестарт сервера Mattermost не спасает, лечится только полной остановкой кластера. Да и это могло помочь как на неделю, так и на день, никакой связи с числом подключенных устройств нет.&lt;/p&gt;
  &lt;p id=&quot;zrQr&quot;&gt;Выглядело это так:&lt;/p&gt;
  &lt;figure id=&quot;rQ5c&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/90/fa/90fa74b0-0979-40f2-8d2b-5ed196a0e916.png&quot; width=&quot;2830&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OlUk&quot;&gt;В какой-то момент метод получения числа файлов закрепленных за конкретным чатом начинает возвращать таймауты, затем появляются ошибки подключения к базе – too many connections. Возникает вопрос – а где проблема – в базе или в самом приложении?&lt;/p&gt;
  &lt;p id=&quot;1fvf&quot;&gt;Время шло, мы занимались очередными переездами, но ситуация с Mattermost надоела окончательно и мы решили перенести базу с конфигурации 4 vCPU/16 GB на 8 vCPU/32 GB.&lt;/p&gt;
  &lt;p id=&quot;RZPE&quot;&gt;Это помогло, но ненадолго...&lt;/p&gt;
  &lt;p id=&quot;JQqO&quot;&gt;В итоге дошло до того, что проблемы начали появляться с началом дня и заканчиваться только поздней ночью, а число рестартов начинало напрягать – даунтайм Mattermost был 15-20 секунд, а после 10-15 минут проблема могла возвращалась и так весь день. Для пользователей это проявлялось в следующем:&lt;/p&gt;
  &lt;ul id=&quot;7nNX&quot;&gt;
    &lt;li id=&quot;Agvw&quot;&gt;долго загружаются каналы и треды;&lt;/li&gt;
    &lt;li id=&quot;ck5t&quot;&gt;при отправке сообщения оно дублируется, либо не сохраняется;&lt;/li&gt;
    &lt;li id=&quot;dOsq&quot;&gt;при запуске приложения отображаются не все каналы;&lt;/li&gt;
    &lt;li id=&quot;4Cz5&quot;&gt;ну и наконец – даунтайм отключал от общения всех.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;E1Fa&quot;&gt;Еще с начала тестовой установки Mattermost мы писал все логи, в том числе SQL, все это анализировалось в режиме реального времени:&lt;/p&gt;
  &lt;figure id=&quot;rmpY&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a4/d6/a4d618ae-15b5-4d25-8e2e-860681141f58.png&quot; width=&quot;2826&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;k27d&quot;&gt;Благодаря этому мы всегда видели как система себя ведет в общем, как реагирует на какие-то изменения. На графиках мы заметили, что проблемы с нагрузкой всегда начинаются с ошибок:&lt;/p&gt;
  &lt;pre id=&quot;tBMT&quot;&gt;Unable to get the file count for the channel&lt;/pre&gt;
  &lt;p id=&quot;zDND&quot;&gt;На графике это выглядит так:&lt;/p&gt;
  &lt;figure id=&quot;sY6G&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/64/27/6427e0b2-2601-4207-8851-34654fba624d.png&quot; width=&quot;2716&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;QRSV&quot;&gt;По коду же, так:&lt;/p&gt;
  &lt;figure id=&quot;707B&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/64/d3/64d39618-e6e4-4ff5-ac72-a05e31750d37.png&quot; width=&quot;522&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;taZE&quot;&gt;И так:&lt;/p&gt;
  &lt;figure id=&quot;iTvw&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/72/88/728836cb-6fad-443e-8eca-4a39b59e1071.png&quot; width=&quot;817&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zWsR&quot;&gt;В часы пик хост mySQL, за который мы платим 25000 руб. в месяц, был загружен на 98% – выглядит сомнительно, поэтому мы продолжили капать дальше, метод GetChannelFileCount вызывается только в getChannelStats – статистика по каналу, содержит в себе:&lt;/p&gt;
  &lt;ul id=&quot;BBEq&quot;&gt;
    &lt;li id=&quot;NUvf&quot;&gt;GetChannelMemberCount&lt;/li&gt;
    &lt;li id=&quot;6f5d&quot;&gt;GetChannelGuestCount&lt;/li&gt;
    &lt;li id=&quot;xPG7&quot;&gt;GetChannelPinnedPostCount&lt;/li&gt;
    &lt;li id=&quot;MVRQ&quot;&gt;GetChannelFileCount&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;iVDX&quot;&gt;В какой-то момент у меня возникла мысль, что у нас нет какого-то индекса на таблице Files, но это предположение не оправдалось – свежая инсталляция содержит все то же описание таблиц... ну окей.&lt;/p&gt;
  &lt;p id=&quot;QFBv&quot;&gt;Метод getChannelStats использует только в API:&lt;/p&gt;
  &lt;figure id=&quot;C5aU&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/57/4d/574d7368-c980-41ca-9b8f-083e86b6904c.png&quot; width=&quot;767&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ltkM&quot;&gt;Вызывается так:&lt;/p&gt;
  &lt;pre id=&quot;b72r&quot;&gt;GET /api/v4/channels/.+/stats&lt;/pre&gt;
  &lt;p id=&quot;zxIB&quot;&gt;В качестве эксперимента отдаем по этому пути 204:&lt;/p&gt;
  &lt;pre id=&quot;caCT&quot;&gt;nginx.ingress.kubernetes.io/configuration-snippet: |
  location ~ /api/v4/channels/.+/stats {
    return 204;
  }&lt;/pre&gt;
  &lt;p id=&quot;m0CF&quot;&gt;В итоге нагрузка на хост базы резко сократилась:&lt;/p&gt;
  &lt;figure id=&quot;hLY3&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/00/e0/00e0ba6c-a347-4496-a302-8b5f09d090db.png&quot; width=&quot;370&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ZHDb&quot;&gt;В конце 11 ноября мы перекрыли этот и еще один роут (на графике он значился как GetTopChannelsForUserSince), график по ошибкам стал таким:&lt;/p&gt;
  &lt;figure id=&quot;OUSg&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d7/7d/d77d17d4-ceb7-4722-877f-259193b6b061.png&quot; width=&quot;2826&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fGH2&quot;&gt;Высокие столбцы – это те самые моменты, в которые мы рестартовали кластер Mattermost. Вроде, стало хорошо. Почему я написал, что проблема не решена? Нууу... теперь в шапке канала пользователи видят следующее:&lt;/p&gt;
  &lt;figure id=&quot;8h3Z&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/14/a9/14a97ed8-8189-48cb-a98f-a75d13f22c31.png&quot; width=&quot;150&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;IeLn&quot;&gt;Так как метод ничего не возвращает, в интерфейсе тоже ничего не отображается, что может запутать наших пользователей, поэтому сейчас мы думаем (в свободное от работы время) над тем, как решить и эту проблему.&lt;/p&gt;
  &lt;p id=&quot;Dghw&quot;&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Я писал в Тинькофф с вопросами по распространению их переработанной версии (бесплатно или за деньги), мне ответили, что точного решения нет, как и сроков. Так что, на месте тех, кто готовится к переезду со Slack, я бы не расчитывал на их решение и не ждал его в ближайшем будущем.&lt;/p&gt;
  &lt;p id=&quot;SVyz&quot;&gt;P.S. Мы предлагаем свою аналитику по логам, как на графиках выше – &lt;a href=&quot;https://anomaly1.ru&quot; target=&quot;_blank&quot;&gt;https://anomaly1.ru&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;dMxV&quot;&gt;&lt;s&gt;P.P.S. Да и вообще можем по графикам проконсультировать или настроить что-нибудь – &lt;a href=&quot;https://arendamozgov.ru&quot; target=&quot;_blank&quot;&gt;https://arendamozgov.ru&lt;/a&gt;.&lt;/s&gt;&lt;/p&gt;

</content></entry><entry><id>levaminov:UTnryjmsoV0</id><link rel="alternate" type="text/html" href="https://levaminov.ru/UTnryjmsoV0?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Проверка на дублирование Application в ArgoCD с помощью OPA</title><published>2022-09-02T13:30:35.941Z</published><updated>2022-09-02T13:33:28.769Z</updated><summary type="html">Так уж вышло, что в ArgoCD плоская структура для хранения описания приложений - все они живут в одном namespace, а значит должны иметь уникальные имена, чтобы предостеречь себя от разных непонятных ситуаций есть простое решение - проверять, не дублируются ли эти самые имена.</summary><content type="html">
  &lt;p id=&quot;WfTG&quot;&gt;Так уж вышло, что в ArgoCD плоская структура для хранения описания приложений - все они живут в одном namespace, а значит должны иметь уникальные имена, чтобы предостеречь себя от разных непонятных ситуаций есть простое решение - проверять, не дублируются ли эти самые имена.&lt;/p&gt;
  &lt;p id=&quot;T7TP&quot;&gt;Для реализации проверки будем использовать проверенный инструмент – &lt;a href=&quot;https://www.openpolicyagent.org&quot; target=&quot;_blank&quot;&gt;Open Policy Agent&lt;/a&gt;, точнее даже не инструмент, а движок, который это все реализует, для самого запуска проверки будет использоваться &lt;a href=&quot;https://www.conftest.dev&quot; target=&quot;_blank&quot;&gt;conftest&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;OhzM&quot;&gt;Для поиска дублей нужно описать политику (policy), она описывается в синтаксисе rego - не самая понятная штука, поэтому моя заметка служит просто готовым решением, детальное описание синтаксиса можно найти &lt;a href=&quot;https://www.openpolicyagent.org/docs/latest/policy-language/&quot; target=&quot;_blank&quot;&gt;на сайте с документацией&lt;/a&gt; (есть даже &lt;a href=&quot;https://play.openpolicyagent.org&quot; target=&quot;_blank&quot;&gt;песочница&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;8XwQ&quot;&gt;В репозитории, где храним спецификации для ArgoCD, создаем каталог policy, в нем файл argocd-applications.rego (например), с таким содержимым:&lt;/p&gt;
  &lt;pre id=&quot;JVCa&quot; data-lang=&quot;java&quot;&gt;package main

deny[msg] {
	i != j
	currentFilePath = input[i].path
	input[i].contents.kind == input[j].contents.kind
	input[i].contents.metadata.name == input[j].contents.metadata.name
	msg := sprintf(&amp;quot;Объект с именем %s (kind: %s) из файла %s дублируется в файле %s&amp;quot;,
		[input[i].contents.metadata.name, input[i].contents.kind, currentFilePath, input[j].path])
}&lt;/pre&gt;
  &lt;p id=&quot;03bb&quot;&gt;Ну, а дальше встраиваем вызов в нашем CI:&lt;/p&gt;
  &lt;pre id=&quot;e18r&quot; data-lang=&quot;yaml&quot;&gt;---
stages:
  - lint

conftest:
  image:
    name: openpolicyagent/conftest:v0.34.0
    entrypoint: [&amp;quot;&amp;quot;]
  stage: lint
  script:
    - conftest test --combine kubernetes/argocd-apps&lt;/pre&gt;
  &lt;p id=&quot;Enfi&quot;&gt;Где &lt;code&gt;kubernetes/argocd-apps&lt;/code&gt; - путь до описания ваших Application для ArgoCD.&lt;/p&gt;

</content></entry><entry><id>levaminov:z3TpTpYSK4J</id><link rel="alternate" type="text/html" href="https://levaminov.ru/z3TpTpYSK4J?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Переезд из Slack в Mattermost</title><published>2022-04-20T05:33:30.906Z</published><updated>2023-03-30T13:12:13.824Z</updated><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/4b/59/4b591bcc-92f7-4a98-b007-b6da50cdba51.png&quot;&gt;Часть 2: https://levaminov.ru/Rmks9ZZ7RLl</summary><content type="html">
  &lt;p id=&quot;KvQU&quot;&gt;Часть 2: &lt;a href=&quot;https://levaminov.ru/Rmks9ZZ7RLl&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/Rmks9ZZ7RLl&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;4u4L&quot;&gt;Часть 3: &lt;a href=&quot;https://levaminov.ru/JsEwp91lmC-&quot; target=&quot;_blank&quot;&gt;https://levaminov.ru/JsEwp91lmC-&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;BTku&quot;&gt;Не нашел живых примеров переездов на Mattermost в красках, чтобы с кровью и болью, поэтому опишу здесь несколько моментов, на которые стоит обратить внимание.&lt;/p&gt;
  &lt;h2 id=&quot;PFAg&quot;&gt;Установка&lt;/h2&gt;
  &lt;p id=&quot;YGAV&quot;&gt;Страница инсталляции &lt;a href=&quot;https://mattermost.com/deploy/&quot; target=&quot;_blank&quot;&gt;https://mattermost.com/deploy/&lt;/a&gt; предлагает несколько вариантов, в нем есть вариант установки в Kubernetes – мой вариант, хотя есть описание установки через Docker, документация веред на страницу с 404 ошибкой, проекты из репозитория, которые подходят по смыслу:&lt;/p&gt;
  &lt;ul id=&quot;0Lo9&quot;&gt;
    &lt;li id=&quot;kgR4&quot;&gt;&lt;a href=&quot;https://github.com/mattermost/mattermost-docker&quot; target=&quot;_blank&quot;&gt;https://github.com/mattermost/mattermost-docker&lt;/a&gt; – в архиве;&lt;/li&gt;
    &lt;li id=&quot;Y6Q4&quot;&gt;&lt;a href=&quot;https://github.com/mattermost/mattermost-docker-preview&quot; target=&quot;_blank&quot;&gt;https://github.com/mattermost/mattermost-docker-preview&lt;/a&gt; – превью (?);&lt;/li&gt;
    &lt;li id=&quot;f3x7&quot;&gt;&lt;a href=&quot;https://github.com/mattermost/docker&quot; target=&quot;_blank&quot;&gt;https://github.com/mattermost/docker&lt;/a&gt; – видимо, оно, под громким названием &amp;quot;Redesigned mattermost-docker&amp;quot;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5Dv5&quot;&gt;Ладно. Установка в кластер Kubernetes по статье реализуется через 3 оператора – mysql-operator, minio-operator, mattermost-operator. Последний управляет двумя другими, вот такой вот подход. Ещё в документе есть таблица с тем сколько и на активное количество пользователей потребуется ресурсов.&lt;/p&gt;
  &lt;p id=&quot;WH95&quot;&gt;&lt;a href=&quot;https://github.com/mattermost/mattermost-operator/blob/master/docs/examples/mattermost_full.yaml&quot; target=&quot;_blank&quot;&gt;Описание конфигурации&lt;/a&gt; Mattermost, для оператора, состоит из нескольких обязательных полей:&lt;/p&gt;
  &lt;ul id=&quot;QUnh&quot;&gt;
    &lt;li id=&quot;o8uA&quot;&gt;name;&lt;/li&gt;
    &lt;li id=&quot;mSYc&quot;&gt;size (!?);&lt;/li&gt;
    &lt;li id=&quot;Gdwz&quot;&gt;ingress.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;syNl&quot;&gt;Мой интерес вызвал параметр &amp;quot;size&amp;quot;, вот что о нем пишут:&lt;/p&gt;
  &lt;blockquote id=&quot;IKMW&quot;&gt;The size of your installation.&lt;/blockquote&gt;
  &lt;p id=&quot;DchW&quot;&gt;Так-так-так, и что?&lt;/p&gt;
  &lt;blockquote id=&quot;SPeD&quot;&gt;This can be ‘100users’, ‘1000users, ‘5000users’, ‘10000users’, or ‘25000users’.&lt;/blockquote&gt;
  &lt;p id=&quot;fmlH&quot;&gt;Не очень понятно, открываем сам &lt;a href=&quot;https://raw.githubusercontent.com/mattermost/mattermost-operator/master/docs/mattermost-operator/mattermost-operator.yaml&quot; target=&quot;_blank&quot;&gt;CustomResourceDefinition&lt;/a&gt;, там зловещая идея раскрывается более подробно:&lt;/p&gt;
  &lt;blockquote id=&quot;a0so&quot;&gt;Size defines the size of the ClusterInstallation. This is typically specified in number of users. This will override replica and resource requests/limits appropriately for the provided number of users. This is a write-only field - its value is erased after setting appropriate values of resources. Accepted values are: 100users, 1000users, 5000users, 10000users, 250000users. If replicas and resource requests/limits are not specified, and Size is not provided the configuration for 5000users will be applied. Setting &amp;#x27;Replicas&amp;#x27;, &amp;#x27;Resources&amp;#x27;, &amp;#x27;Minio.Replicas&amp;#x27;, &amp;#x27;Minio.Resource&amp;#x27;, &amp;#x27;Database.Replicas&amp;#x27;, or &amp;#x27;Database.Resources&amp;#x27; will override the values set by Size. Setting new Size will override previous values regardless if set by Size or manually.&lt;/blockquote&gt;
  &lt;p id=&quot;RXyw&quot;&gt;Другими словами – от параметра &amp;quot;size&amp;quot; зависит конфигурация инсталляции вашего сервера Mattemost, число его реплик и выделенных ресурсов. Шаманство какое-то, но на практике удалось выяснить следующее:&lt;/p&gt;
  &lt;ul id=&quot;F5XW&quot;&gt;
    &lt;li id=&quot;t3xP&quot;&gt;100users – 1 реплика Mattermost, Master MySQL;&lt;/li&gt;
    &lt;li id=&quot;fQl8&quot;&gt;1000users – 2 реплики Mattermost, Master/Slave MySQL.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;V6Jp&quot;&gt;Переопределение ресурсов и лимитов, которые упоминаются, невозможно, для каждого значения параметра &amp;quot;size&amp;quot; это свои цифры, а отсутствие значение &amp;quot;size&amp;quot; – это, как выше написано, – &amp;quot;5000users&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;Gsyp&quot;&gt;Тут следует отметить одно из ограничений бесплатной версии Mattermost – &lt;strong&gt;нет HA-режима&lt;/strong&gt;, соответственно вариант работы в несколько реплик отпадает сам собой, при старте сервер ругается, что у него нет лицензии для работы в таком режиме, но вы всё ещё можете это попробовать. У Mattermost нет external кэша в виде какого-нибудь Redis, поддержка кластерного режима реализована внутри сервера, реплики обмениваются данными по gossip-протолоку, без этой связки можно ловить странные вещи – сохранение конфигурации через раз, некорректная загрузка файлов. Разработчики заботливо оставили &lt;a href=&quot;https://github.com/mattermost/mattermost-server/blob/master/einterfaces/cluster.go&quot; target=&quot;_blank&quot;&gt;интерфейс&lt;/a&gt;, по которому реализуется поддержка кластерного режима, да и вообще, поговаривают, до какого-то времени собрать Enterprise версию можно было самостоятельно. Поверим на слово.&lt;/p&gt;
  &lt;p id=&quot;qyFE&quot;&gt;Возвращаясь к теме конфигурации и тексту документа, с которого все начинается:&lt;/p&gt;
  &lt;blockquote id=&quot;6rVA&quot;&gt;This document describes installing and deploying a production-ready Mattermost system on a Kubernetes cluster using the Mattermost Kubernetes operator.&lt;/blockquote&gt;
  &lt;p id=&quot;Dvqe&quot;&gt;Ниже можно заметить:&lt;/p&gt;
  &lt;blockquote id=&quot;Cknp&quot;&gt;It is possible to manage MySQL database and MinIO file store using the Mattermost Operator, but it is not recommended for production usage.&lt;/blockquote&gt;
  &lt;p id=&quot;1z7o&quot;&gt;После всех тестов мы решили перенести файловое хранилище в Yandex Object Storage, а MySQL в Managed Service for MySQL, всё в том же Yandex Cloud. Для хранилища создается отдельный секрет:&lt;/p&gt;
  &lt;pre id=&quot;TzQp&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: v1
kind: Secret
metadata:
  name: mattermost-filestore
type: Opaque
data:
  accesskey: ...
  secretkey: ...&lt;/pre&gt;
  &lt;p id=&quot;StIX&quot;&gt;В переменных окружения сервера задается следующее (все эти настройки можно так же определить в Системной консоли):&lt;/p&gt;
  &lt;ul id=&quot;Ug0i&quot;&gt;
    &lt;li id=&quot;mBYt&quot;&gt;MM_FILESETTINGS_DRIVERNAME – amazons3&lt;/li&gt;
    &lt;li id=&quot;UE6Y&quot;&gt;MM_FILESETTINGS_AMAZONS3BUCKET – имя бакета&lt;/li&gt;
    &lt;li id=&quot;NRm8&quot;&gt;MM_FILESETTINGS_AMAZONS3ENDPOINT – storage.yandexcloud.net&lt;/li&gt;
    &lt;li id=&quot;qJ6b&quot;&gt;MM_FILESETTINGS_AMAZONS3SSL – true&lt;/li&gt;
    &lt;li id=&quot;srhc&quot;&gt;MM_FILESETTINGS_AMAZONS3SSE – true&lt;/li&gt;
    &lt;li id=&quot;DeD3&quot;&gt;MM_FILESETTINGS_AMAZONS3TRACE – true (если нужны отладочные логи)&lt;/li&gt;
    &lt;li id=&quot;9uy3&quot;&gt;MM_FILESETTINGS_AMAZONS3REGION – ru-central1&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;OMa7&quot;&gt;Для базы, по статье, следует создать секрет и задать в нем некоторые параметры, все они содержать реквизиты подключения к базе, но используются в разных частях системы, поэтому записываются немного по-разному, такой вот поворот:&lt;/p&gt;
  &lt;ul id=&quot;QkUl&quot;&gt;
    &lt;li id=&quot;Mxeb&quot;&gt;DB_CONNECTION_CHECK_URL – http-ссылка, в формате &lt;a href=&quot;http://hostname:3306&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;http://hostname:3306&lt;/code&gt;&lt;/a&gt;, резвизиты не передаются, используется в init-container&amp;#x27;е, чтобы определить, что база доступна;&lt;/li&gt;
    &lt;li id=&quot;MBLl&quot;&gt;DB_CONNECTION_STRING – строка подключения в формате &lt;code&gt;mysql://user:password@tcp(hostname:3306)/mattermost?charset=utf8mb4,utf8&amp;amp;writeTimeout=30s&lt;/code&gt;, в переменных передается серверу как MM_CONFIG;&lt;/li&gt;
    &lt;li id=&quot;mNBT&quot;&gt;MM_SQLSETTINGS_DATASOURCEREPLICAS – строка подключения в формате &lt;code&gt;user:password@tcp(hostname:3306)/mattermost?readTimeout=30s&amp;amp;writeTimeout=30s&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;KUoF&quot;&gt;Последнее, предположительно, используется в кластерном режиме, в документации упоминаний об этом нет, но если запускать сервер в 1 реплику с внешней базой – init container успешно отработает, а дальше начинается проверка соединения внутри сервера Mattermost, который пытается подключиться куда-то не туда, куда нам нужно. Опытным путем выяснилось, что нужно задать еще один параметр:&lt;/p&gt;
  &lt;ul id=&quot;GX8C&quot;&gt;
    &lt;li id=&quot;iUOV&quot;&gt;MM_SQLSETTINGS_DATASOURCE – строка подключения в формате &lt;code&gt;user:password@tcp(hostname:3306)/mattermost?readTimeout=30s&amp;amp;writeTimeout=30s&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;LG76&quot;&gt;Настройка&lt;/h2&gt;
  &lt;h3 id=&quot;Uz5K&quot;&gt;Треды&lt;/h3&gt;
  &lt;p id=&quot;bF00&quot;&gt;В настройщее время (я все эксперименты проводил на Mattermost 6.5, хотя уже вышла 6.6), треды как вложенные обсуждения – это экспериментальная фитча:&lt;/p&gt;
  &lt;figure id=&quot;zWyg&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4b/59/4b591bcc-92f7-4a98-b007-b6da50cdba51.png&quot; width=&quot;1832&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;A0LZ&quot;&gt;Чтобы включить этот функционал, нужно включить Automatically Follow Threads, сделать это можно через переменную окружения:&lt;/p&gt;
  &lt;pre id=&quot;um0s&quot; data-lang=&quot;yaml&quot;&gt;- name: MM_SERVICESETTINGS_THREADAUTOFOLLOW
  value: &amp;quot;true&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;1Nls&quot;&gt;После того, как значение будет задано, тип тредов можно будет переключить.&lt;/p&gt;
  &lt;h3 id=&quot;nB3b&quot;&gt;Пуши&lt;/h3&gt;
  &lt;p id=&quot;qCYI&quot;&gt;По умолчанию, это отключено, можно включить публичный сервис для пушей Mattermost, насколько стабилен и какие ограничения – не выяснялось.&lt;/p&gt;
  &lt;figure id=&quot;ugXp&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/27/bc275ec2-3af4-45fc-ab7d-c7834583a257.png&quot; width=&quot;1832&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;uPMd&quot;&gt;Звонки&lt;/h3&gt;
  &lt;p id=&quot;IScU&quot;&gt;Функционал звонков реализован в виде плагина, который ставится из маркетплейса Mattermost:&lt;/p&gt;
  &lt;figure id=&quot;9mZv&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3d/65/3d65f0fd-8297-405e-b1f8-8e06b0fee90e.png&quot; width=&quot;1482&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3gAR&quot;&gt;Больши информации можно найти &lt;a href=&quot;https://github.com/mattermost/mattermost-plugin-calls/&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;, функционал в бете, но нам удалось даже созвониться.&lt;/p&gt;
  &lt;h3 id=&quot;XX3G&quot;&gt;Интеграции&lt;/h3&gt;
  &lt;p id=&quot;l6Wb&quot;&gt;Если вы использовали Slack для получения каких-то простых уведомления, то всё нормально, этот функционал сохранен, если только вы не используете какие-то диковиные штуки внутри:&lt;/p&gt;
  &lt;figure id=&quot;B32r&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/96/98/969840a3-55ae-466c-987e-3277d1f390cc.png&quot; width=&quot;369&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;1PFB&quot;&gt;Для создания типово Slack Webhook ссылки, переходите из меню в Интеграции, далее нажимаете на здоровенную кнопку Входящие вебхуки, создаете вебхук, копируете ссылку, используете вместо то, что была раньше.&lt;/p&gt;
  &lt;p id=&quot;yHfq&quot;&gt;По умолчанию сообщения будут создаваться от имени человека, который эту интеграцию создал, аватарка так же будет от него, чтобы поменять подобное поведение переходим в Системную консоль и в разделе настроек интеграции задаете:&lt;/p&gt;
  &lt;figure id=&quot;JyOc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d6/be/d6be5043-a6b2-4ee7-9e9d-e1247a580367.png&quot; width=&quot;1808&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;S0RN&quot;&gt;По итогу получается что-то такое:&lt;/p&gt;
  &lt;figure id=&quot;7R92&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9e/59/9e592274-871a-4453-b6d2-b79d2f22dee0.png&quot; width=&quot;465&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;fXRP&quot;&gt;SAML, LDAP, OpenID&lt;/h3&gt;
  &lt;p id=&quot;dy7R&quot;&gt;Ничего этого нет, есть только аутентификация через Gitlab.&lt;/p&gt;
  &lt;h3 id=&quot;7eAB&quot;&gt;Полнотекстовый поиск&lt;/h3&gt;
  &lt;p id=&quot;ogao&quot;&gt;Поддержка ElasticSearch есть только в Enterprise версии, в бесплатной по умолчанию используется полнотекстовый поиск по базе, либо предлагается альтернатива в виде поискового движка Bleve, который встроен в сервер (пару раз работа этого движка приводила к падению Mattermost).&lt;/p&gt;
  &lt;h2 id=&quot;adTq&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;FxRT&quot;&gt;Да, в Mattermost многое работает странно, да, часть интеграций отвалилась и нужно переделывать, да, там все выглядеть страшно, но всё же меня радует, что людям предоставлен функционал плагинов, а значит ждем от всех поуехавших функционал, который доведет Mattermost до готового решения коммуникации в компании.&lt;/p&gt;
  &lt;h3 id=&quot;dHcN&quot;&gt;P.S.&lt;/h3&gt;
  &lt;p id=&quot;mrUY&quot;&gt;В Mattermost есть плагин – генератор мемов...&lt;/p&gt;
  &lt;figure id=&quot;a2Ro&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://chat.qleanlabs.ru/plugins/memes/templates/batman-slapping-robin.jpg?text=slack&amp;text=mattermost&quot; width=&quot;400&quot; /&gt;
  &lt;/figure&gt;

</content></entry><entry><id>levaminov:-GTUP-QWYFV</id><link rel="alternate" type="text/html" href="https://levaminov.ru/-GTUP-QWYFV?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Метрики Qrator (Qrator Exporter)</title><published>2022-03-23T04:46:47.442Z</published><updated>2024-03-01T03:27:56.878Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/2e/8e/2e8ec0ad-3e30-4eba-886f-43a2895beef7.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://miro.medium.com/max/3150/1*nDIJd9iv2xt-LGSEhaCUhA.png&quot;&gt;В очередной раз пришлось настраивать сбор метрик с Qrator, прошлая моя заметка на этот счет жила в виде Issue в репозитории StupidScience/qrator-exporter (в проекте используются deprecated-методы), но там она пропала, поэтому опишу здесь, чтобы уж точно не потерялось.</summary><content type="html">
  &lt;p id=&quot;z0hw&quot;&gt;В очередной раз пришлось настраивать сбор метрик с &lt;a href=&quot;https://qrator.net/en/&quot; target=&quot;_blank&quot;&gt;Qrator&lt;/a&gt;, прошлая моя заметка на этот счет жила в виде Issue в репозитории &lt;a href=&quot;https://github.com/StupidScience/qrator-exporter&quot; target=&quot;_blank&quot;&gt;StupidScience/qrator-exporter&lt;/a&gt; (в проекте используются deprecated-методы), но там она пропала, поэтому опишу здесь, чтобы уж точно не потерялось.&lt;/p&gt;
  &lt;figure id=&quot;PsOu&quot; class=&quot;m_retina&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/67/25/67258451-1300-4643-9755-87154e1bd60d.png&quot; width=&quot;386&quot; /&gt;
    &lt;figcaption&gt;Источник qratorlabs&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RW1u&quot;&gt;Сбор данных будет осуществляться через &lt;a href=&quot;https://github.com/influxdata/telegraf&quot; target=&quot;_blank&quot;&gt;telegraf&lt;/a&gt; и, с помощью него же, отдаваться в виде метрик формата Prometheus.&lt;/p&gt;
  &lt;p id=&quot;Cp9G&quot;&gt;Для начала потребуется получить API-токен для получения данных из Qrator, для этого переходим в &lt;a href=&quot;https://client.qrator.net/qrator/apitoken/&quot; target=&quot;_blank&quot;&gt;раздел с ключами&lt;/a&gt; в личном кабинете и выпускаем токен.&lt;/p&gt;
  &lt;p id=&quot;1Loh&quot;&gt;Далее переходим в список доменов и сохраняем их идентификаторы, по ним будет обращение к методам API:&lt;/p&gt;
  &lt;figure id=&quot;wrGN&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/d8/8ed8604b-e833-4dd1-9d5c-7cee466caab6.png&quot; width=&quot;244&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;04VC&quot;&gt;Здесь 11111 и 11222 – как раз те самые идентификаторы доменов, теперь описываем конфигурацию для телеграфа:&lt;/p&gt;
  &lt;pre id=&quot;Cd9S&quot; data-lang=&quot;toml&quot;&gt;[[inputs.http]]
	name_prefix = &amp;quot;qrator_blocks_&amp;quot;
	method = &amp;quot;POST&amp;quot;
	urls = [
		&amp;quot;https://api.qrator.net/request/domain/11111&amp;quot;,
		&amp;quot;https://api.qrator.net/request/domain/11222&amp;quot;,
	]
	headers = {&amp;quot;X-Qrator-Auth&amp;quot; = &amp;quot;${QRATOR_API_KEY}&amp;quot;, &amp;quot;Content-Type&amp;quot; = &amp;quot;application/json&amp;quot;}
	body = &amp;#x27;{&amp;quot;method&amp;quot;:&amp;quot;statistics_current_blocks&amp;quot;}&amp;#x27;
	data_format = &amp;quot;json&amp;quot;
	timeout = &amp;quot;30s&amp;quot;

[[inputs.http]]
	name_prefix = &amp;quot;qrator_http_&amp;quot;
	method = &amp;quot;POST&amp;quot;
	urls = [
		&amp;quot;https://api.qrator.net/request/domain/11111&amp;quot;,
		&amp;quot;https://api.qrator.net/request/domain/11222&amp;quot;,
	]
	headers = {&amp;quot;X-Qrator-Auth&amp;quot; = &amp;quot;${QRATOR_API_KEY}&amp;quot;, &amp;quot;Content-Type&amp;quot; = &amp;quot;application/json&amp;quot;}
	body = &amp;#x27;{&amp;quot;method&amp;quot;:&amp;quot;statistics_current_http&amp;quot;}&amp;#x27;
	data_format = &amp;quot;json&amp;quot;
	timeout = &amp;quot;30s&amp;quot;

[[inputs.http]]
	name_prefix = &amp;quot;qrator_ip_&amp;quot;
	method = &amp;quot;POST&amp;quot;
	urls = [
		&amp;quot;https://api.qrator.net/request/domain/11111&amp;quot;,
		&amp;quot;https://api.qrator.net/request/domain/11222&amp;quot;,
	]
	headers = {&amp;quot;X-Qrator-Auth&amp;quot; = &amp;quot;${QRATOR_API_KEY}&amp;quot;, &amp;quot;Content-Type&amp;quot; = &amp;quot;application/json&amp;quot;}
	body = &amp;#x27;{&amp;quot;method&amp;quot;:&amp;quot;statistics_current_ip&amp;quot;}&amp;#x27;
	data_format = &amp;quot;json&amp;quot;
	timeout = &amp;quot;30s&amp;quot;

[[inputs.http]]
	name_prefix = &amp;quot;qrator_locations_&amp;quot;
	method = &amp;quot;POST&amp;quot;
	urls = [
		&amp;quot;https://api.qrator.net/request/domain/11111&amp;quot;,
		&amp;quot;https://api.qrator.net/request/domain/11222&amp;quot;,
	]
	headers = {&amp;quot;X-Qrator-Auth&amp;quot; = &amp;quot;${QRATOR_API_KEY}&amp;quot;, &amp;quot;Content-Type&amp;quot; = &amp;quot;application/json&amp;quot;}
	body = &amp;#x27;{&amp;quot;method&amp;quot;:&amp;quot;statistics_current_locations&amp;quot;}&amp;#x27;
	data_format = &amp;quot;json&amp;quot;
	timeout = &amp;quot;30s&amp;quot;

[[outputs.prometheus_client]]
	listen = &amp;quot;:9273&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;vXCi&quot;&gt;В поле urls передается массив из ссылок на ресурсы (включают в себя идентификаторы доменов), в поле body – метод, а для передачи API-ключа используется переменная окружения QRATOR_API_KEY, нам нужно будет её дополнительно передать телеграфу, чтобы не хранять напрямую в конфигурации.&lt;/p&gt;
  &lt;p id=&quot;gVLS&quot;&gt;Осталось только запустить. Минифицированный Deployment для kustomize может выглядеть так:&lt;/p&gt;
  &lt;pre id=&quot;j3VS&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: qrator-exporter
spec:
  template:
    spec:
      containers:
        - name: telegraf
          image: telegraf:1.21.4
          ports:
            - name: metrics
              containerPort: 9273
          env:
            - name: QRATOR_API_KEY
              value: CHANGE_ME
          securityContext:
            runAsUser: 1001
            capabilities:
              drop:
                - ALL
            readOnlyRootFilesystem: true
            runAsNonRoot: true
          volumeMounts:
            - name: config
              mountPath: &amp;quot;/etc/telegraf&amp;quot;
              readOnly: true
            - name: cache
              mountPath: &amp;quot;/.cache&amp;quot;
      volumes:
        - name: config
          secret:
            secretName: qrator-exporter
        - name: cache
          emptyDir: {}&lt;/pre&gt;
  &lt;p id=&quot;tbNj&quot;&gt;Сам секрет qrator-exporter описывается в файле kustomization.yaml, например:&lt;/p&gt;
  &lt;pre id=&quot;rVRZ&quot; data-lang=&quot;yaml&quot;&gt;secretGenerator:
  - name: qrator-exporter
    files:
      - config/telegraf.conf&lt;/pre&gt;
  &lt;p id=&quot;DCzG&quot;&gt;Не забываем описать сервис и Service Monitor:&lt;/p&gt;
  &lt;pre id=&quot;ku3u&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: v1
kind: Service
metadata:
  name: qrator-exporter
spec:
  type: ClusterIP
  ports:
    - name: metrics
      port: 9273
      targetPort: 9273
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: qrator-exporter
spec:
  endpoints:
    - interval: 30s
      path: /metrics
      port: metrics
  selector:
    matchLabels:
      app.kubernetes.io/name: qrator-exporter
      app.kubernetes.io/component: service
      app.kubernetes.io/part-of: monitoring
  namespaceSelector:
    any: true&lt;/pre&gt;
  &lt;p id=&quot;pOjy&quot;&gt;Селектор по лейблам, которые заданы в kustomization.yaml:&lt;/p&gt;
  &lt;pre id=&quot;NCOv&quot; data-lang=&quot;yaml&quot;&gt;---
commonLabels:
  app.kubernetes.io/name: qrator-exporter
  app.kubernetes.io/component: service
  app.kubernetes.io/part-of: monitoring&lt;/pre&gt;
  &lt;p id=&quot;yp7i&quot;&gt;После этого мы начнем собирать метрики, однако в качестве url в метриках будет непонятный адрес ресурса Qrator, поэтому добавляем релейбл:&lt;/p&gt;
  &lt;pre id=&quot;9wrb&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: qrator-exporter
spec:
  endpoints:
    - interval: 30s
      path: /metrics
      port: metrics
      metricRelabelings:
        - sourceLabels: [&amp;quot;url&amp;quot;]
          regex: https://api.qrator.net/request/domain/(.+)
          replacement: $1
          targetLabel: domain_id
          action: replace
        - sourceLabels: [&amp;quot;url&amp;quot;]
          regex: https://api.qrator.net/request/domain/11111
          replacement: domain.ru
          targetLabel: domain_name
          action: replace
        - sourceLabels: [&amp;quot;url&amp;quot;]
          regex: https://api.qrator.net/request/domain/11222
          replacement: super-domain.ru
          targetLabel: domain_name
          action: replace
  selector:
    matchLabels:
      app.kubernetes.io/name: qrator-exporter
      app.kubernetes.io/component: service
      app.kubernetes.io/part-of: monitoring
  namespaceSelector:
    any: true&lt;/pre&gt;
  &lt;p id=&quot;Tens&quot;&gt;Теперь в domain_name будет читаемый параметр, который можно использовать для селекторов в Grafana или в алертах.&lt;/p&gt;
  &lt;p id=&quot;f6aM&quot;&gt;Примеры алертов для Prometheus Operator:&lt;/p&gt;
  &lt;pre id=&quot;7H15&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: qrator-exporter
  labels:
    app: prometheus-operator
    release: &amp;quot;monitoring&amp;quot;
spec:
  groups:
    - name: QratorExporter
      rules:
        - alert: QratorHighBandwidthInput
          expr: qrator_ip_http_result_bandwidth_input &amp;gt; 5000000
          for: 5m
          labels:
            severity: warning
            domain: &amp;quot;{{ $labels.domain_name }}&amp;quot;
          annotations:
            summary: Большой входящий трафик на {{ $labels.domain_name }}
            description: На домене {{ $labels.domain_name }} в Qrator фиксируется повышенный входящий трафик, более 5Мбит/с
        - alert: QratorHighBandwidthOutput
          expr: qrator_ip_http_result_bandwidth_input &amp;gt; 5000000
          for: 5m
          labels:
            severity: warning
            domain: &amp;quot;{{ $labels.domain_name }}&amp;quot;
          annotations:
            summary: Большой исходящий трафик на {{ $labels.domain_name }}
            description: На домене {{ $labels.domain_name }} в Qrator фиксируется повышенный исходящий трафик, более 5Мбит/с
        - alert: QratorHigh5xxRate
          expr: qrator_http_http_result_errors_total &amp;gt;= 0.1
          for: 5m
          labels:
            severity: critical
            domain: &amp;quot;{{ $labels.domain_name }}&amp;quot;
          annotations:
            summary: В Qrator на {{ $labels.domain_name }} фиксируется рост числа ошибок
            description: В Qrator на домене {{ $labels.domain_name }} в течении 5 минут фиксируется рост числа 50x ошибок&lt;/pre&gt;
  &lt;p id=&quot;wH7W&quot;&gt;Перед добавлением алертов стандартная рекомендация – пособирайте некоторые время метрики, чтобы определить для себя граничные значения, удобнее всего за этим наблюдать в Grafana, поэтому в качестве базового можно взять этот дашборд:&lt;/p&gt;
  &lt;pre id=&quot;3h4J&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;annotations&amp;quot;: {
    &amp;quot;list&amp;quot;: [
      {
        &amp;quot;builtIn&amp;quot;: 1,
        &amp;quot;datasource&amp;quot;: &amp;quot;-- Grafana --&amp;quot;,
        &amp;quot;enable&amp;quot;: true,
        &amp;quot;hide&amp;quot;: true,
        &amp;quot;iconColor&amp;quot;: &amp;quot;rgba(0, 211, 255, 1)&amp;quot;,
        &amp;quot;name&amp;quot;: &amp;quot;Annotations &amp;amp; Alerts&amp;quot;,
        &amp;quot;target&amp;quot;: {
          &amp;quot;limit&amp;quot;: 100,
          &amp;quot;matchAny&amp;quot;: false,
          &amp;quot;tags&amp;quot;: [],
          &amp;quot;type&amp;quot;: &amp;quot;dashboard&amp;quot;
        },
        &amp;quot;type&amp;quot;: &amp;quot;dashboard&amp;quot;
      }
    ]
  },
  &amp;quot;editable&amp;quot;: true,
  &amp;quot;fiscalYearStartMonth&amp;quot;: 0,
  &amp;quot;gnetId&amp;quot;: null,
  &amp;quot;graphTooltip&amp;quot;: 1,
  &amp;quot;id&amp;quot;: 106,
  &amp;quot;iteration&amp;quot;: 1647973063127,
  &amp;quot;links&amp;quot;: [],
  &amp;quot;liveNow&amp;quot;: false,
  &amp;quot;panels&amp;quot;: [
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;description&amp;quot;: &amp;quot;Alerts:\n\n* QratorHighBandwidthInput\n&amp;quot;,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          },
          &amp;quot;unit&amp;quot;: &amp;quot;bits&amp;quot;
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 0,
        &amp;quot;y&amp;quot;: 0
      },
      &amp;quot;id&amp;quot;: 2,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [
            &amp;quot;max&amp;quot;
          ],
          &amp;quot;displayMode&amp;quot;: &amp;quot;list&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;bottom&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;multi&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum(qrator_ip_http_result_bandwidth_input{domain_name=\&amp;quot;$domain\&amp;quot;})&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;input&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        },
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum(qrator_ip_http_result_bandwidth_output{domain_name=\&amp;quot;$domain\&amp;quot;})&amp;quot;,
          &amp;quot;hide&amp;quot;: false,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;output&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;B&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Traffic&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    },
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;description&amp;quot;: &amp;quot;Alerts:\n\n* QratorHigh5xxRate&amp;quot;,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;decimals&amp;quot;: 2,
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          },
          &amp;quot;unit&amp;quot;: &amp;quot;reqps&amp;quot;
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 12,
        &amp;quot;y&amp;quot;: 0
      },
      &amp;quot;id&amp;quot;: 4,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [],
          &amp;quot;displayMode&amp;quot;: &amp;quot;list&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;bottom&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;multi&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum({__name__=~\&amp;quot;qrator_http_http_result_errors_.+\&amp;quot;, domain_name=\&amp;quot;$domain\&amp;quot;})by(__name__)&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;{{ __name__ }}&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Errors&amp;quot;,
      &amp;quot;transformations&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_errors_(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1&amp;quot;
          }
        }
      ],
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    },
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          },
          &amp;quot;unit&amp;quot;: &amp;quot;reqps&amp;quot;
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 0,
        &amp;quot;y&amp;quot;: 9
      },
      &amp;quot;id&amp;quot;: 7,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [],
          &amp;quot;displayMode&amp;quot;: &amp;quot;list&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;bottom&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;single&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum(qrator_http_http_result_requests{domain_name=\&amp;quot;$domain\&amp;quot;})&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;total&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Requests&amp;quot;,
      &amp;quot;transformations&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0000_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;Less $1 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1000_1500&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1 - 1.5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1500_2000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1.5 - 2 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_2000_5000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;2 - 5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_5000_inf&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;More 5 s&amp;quot;
          }
        }
      ],
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    },
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          },
          &amp;quot;unit&amp;quot;: &amp;quot;reqps&amp;quot;
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 12,
        &amp;quot;y&amp;quot;: 9
      },
      &amp;quot;id&amp;quot;: 10,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [],
          &amp;quot;displayMode&amp;quot;: &amp;quot;list&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;bottom&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;single&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum({__name__=~\&amp;quot;qrator_http_http_result_responses_.+\&amp;quot;, domain_name=\&amp;quot;$domain\&amp;quot;})by(__name__)&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;{{ __name__ }}&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Requests by response time&amp;quot;,
      &amp;quot;transformations&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0000_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;Less $1 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1000_1500&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1 - 1.5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1500_2000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1.5 - 2 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_2000_5000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;2 - 5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_5000_inf&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;More 5 s&amp;quot;
          }
        }
      ],
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    },
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          },
          &amp;quot;unit&amp;quot;: &amp;quot;pps&amp;quot;
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 0,
        &amp;quot;y&amp;quot;: 18
      },
      &amp;quot;id&amp;quot;: 11,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [],
          &amp;quot;displayMode&amp;quot;: &amp;quot;list&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;bottom&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;multi&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum(qrator_ip_http_result_packets_input{domain_name=\&amp;quot;$domain\&amp;quot;})&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;input&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        },
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum(qrator_ip_http_result_packets_output{domain_name=\&amp;quot;$domain\&amp;quot;})&amp;quot;,
          &amp;quot;hide&amp;quot;: false,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;output&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;B&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Packets&amp;quot;,
      &amp;quot;transformations&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0000_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;Less $1 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_0(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_0(.*)_(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1 - $2 ms&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1000_1500&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1 - 1.5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_1500_2000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;1.5 - 2 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_2000_5000&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;2 - 5 s&amp;quot;
          }
        },
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_http_http_result_responses_5000_inf&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;More 5 s&amp;quot;
          }
        }
      ],
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    },
    {
      &amp;quot;datasource&amp;quot;: null,
      &amp;quot;fieldConfig&amp;quot;: {
        &amp;quot;defaults&amp;quot;: {
          &amp;quot;color&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;palette-classic&amp;quot;
          },
          &amp;quot;custom&amp;quot;: {
            &amp;quot;axisLabel&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;axisPlacement&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;barAlignment&amp;quot;: 0,
            &amp;quot;drawStyle&amp;quot;: &amp;quot;line&amp;quot;,
            &amp;quot;fillOpacity&amp;quot;: 0,
            &amp;quot;gradientMode&amp;quot;: &amp;quot;none&amp;quot;,
            &amp;quot;hideFrom&amp;quot;: {
              &amp;quot;legend&amp;quot;: false,
              &amp;quot;tooltip&amp;quot;: false,
              &amp;quot;viz&amp;quot;: false
            },
            &amp;quot;lineInterpolation&amp;quot;: &amp;quot;linear&amp;quot;,
            &amp;quot;lineWidth&amp;quot;: 1,
            &amp;quot;pointSize&amp;quot;: 5,
            &amp;quot;scaleDistribution&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;linear&amp;quot;
            },
            &amp;quot;showPoints&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;spanNulls&amp;quot;: false,
            &amp;quot;stacking&amp;quot;: {
              &amp;quot;group&amp;quot;: &amp;quot;A&amp;quot;,
              &amp;quot;mode&amp;quot;: &amp;quot;none&amp;quot;
            },
            &amp;quot;thresholdsStyle&amp;quot;: {
              &amp;quot;mode&amp;quot;: &amp;quot;off&amp;quot;
            }
          },
          &amp;quot;mappings&amp;quot;: [],
          &amp;quot;thresholds&amp;quot;: {
            &amp;quot;mode&amp;quot;: &amp;quot;absolute&amp;quot;,
            &amp;quot;steps&amp;quot;: [
              {
                &amp;quot;color&amp;quot;: &amp;quot;green&amp;quot;,
                &amp;quot;value&amp;quot;: null
              },
              {
                &amp;quot;color&amp;quot;: &amp;quot;red&amp;quot;,
                &amp;quot;value&amp;quot;: 80
              }
            ]
          }
        },
        &amp;quot;overrides&amp;quot;: []
      },
      &amp;quot;gridPos&amp;quot;: {
        &amp;quot;h&amp;quot;: 9,
        &amp;quot;w&amp;quot;: 12,
        &amp;quot;x&amp;quot;: 12,
        &amp;quot;y&amp;quot;: 18
      },
      &amp;quot;id&amp;quot;: 5,
      &amp;quot;options&amp;quot;: {
        &amp;quot;legend&amp;quot;: {
          &amp;quot;calcs&amp;quot;: [
            &amp;quot;max&amp;quot;,
            &amp;quot;last&amp;quot;
          ],
          &amp;quot;displayMode&amp;quot;: &amp;quot;table&amp;quot;,
          &amp;quot;placement&amp;quot;: &amp;quot;right&amp;quot;
        },
        &amp;quot;tooltip&amp;quot;: {
          &amp;quot;mode&amp;quot;: &amp;quot;single&amp;quot;
        }
      },
      &amp;quot;targets&amp;quot;: [
        {
          &amp;quot;exemplar&amp;quot;: true,
          &amp;quot;expr&amp;quot;: &amp;quot;sum({__name__=~\&amp;quot;qrator_locations_http_result_locations_.+\&amp;quot;, domain_name=\&amp;quot;$domain\&amp;quot;}&amp;gt;0)by(__name__)&amp;quot;,
          &amp;quot;interval&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;legendFormat&amp;quot;: &amp;quot;{{ __name__ }}&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;A&amp;quot;
        }
      ],
      &amp;quot;title&amp;quot;: &amp;quot;Black list&amp;quot;,
      &amp;quot;transformations&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;renameByRegex&amp;quot;,
          &amp;quot;options&amp;quot;: {
            &amp;quot;regex&amp;quot;: &amp;quot;qrator_locations_http_result_locations_(.*)&amp;quot;,
            &amp;quot;renamePattern&amp;quot;: &amp;quot;$1&amp;quot;
          }
        }
      ],
      &amp;quot;type&amp;quot;: &amp;quot;timeseries&amp;quot;
    }
  ],
  &amp;quot;schemaVersion&amp;quot;: 32,
  &amp;quot;style&amp;quot;: &amp;quot;dark&amp;quot;,
  &amp;quot;tags&amp;quot;: [
    &amp;quot;WIP&amp;quot;
  ],
  &amp;quot;templating&amp;quot;: {
    &amp;quot;list&amp;quot;: [
      {
        &amp;quot;allValue&amp;quot;: null,
        &amp;quot;current&amp;quot;: {
          &amp;quot;selected&amp;quot;: false,
          &amp;quot;text&amp;quot;: &amp;quot;qlean.ru&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;qlean.ru&amp;quot;
        },
        &amp;quot;datasource&amp;quot;: null,
        &amp;quot;definition&amp;quot;: &amp;quot;label_values(qrator_http_http_id, domain_name)&amp;quot;,
        &amp;quot;description&amp;quot;: null,
        &amp;quot;error&amp;quot;: null,
        &amp;quot;hide&amp;quot;: 0,
        &amp;quot;includeAll&amp;quot;: false,
        &amp;quot;label&amp;quot;: &amp;quot;Domain&amp;quot;,
        &amp;quot;multi&amp;quot;: false,
        &amp;quot;name&amp;quot;: &amp;quot;domain&amp;quot;,
        &amp;quot;options&amp;quot;: [],
        &amp;quot;query&amp;quot;: {
          &amp;quot;query&amp;quot;: &amp;quot;label_values(qrator_http_http_id, domain_name)&amp;quot;,
          &amp;quot;refId&amp;quot;: &amp;quot;StandardVariableQuery&amp;quot;
        },
        &amp;quot;refresh&amp;quot;: 1,
        &amp;quot;regex&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;skipUrlSync&amp;quot;: false,
        &amp;quot;sort&amp;quot;: 1,
        &amp;quot;type&amp;quot;: &amp;quot;query&amp;quot;
      }
    ]
  },
  &amp;quot;time&amp;quot;: {
    &amp;quot;from&amp;quot;: &amp;quot;now-12h&amp;quot;,
    &amp;quot;to&amp;quot;: &amp;quot;now&amp;quot;
  },
  &amp;quot;timepicker&amp;quot;: {},
  &amp;quot;timezone&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;title&amp;quot;: &amp;quot;Qrator&amp;quot;,
  &amp;quot;uid&amp;quot;: &amp;quot;gM2arMHnk&amp;quot;,
  &amp;quot;version&amp;quot;: 23
}&lt;/pre&gt;
  &lt;p id=&quot;UAo4&quot;&gt;TODO: Перенести дашборд в &lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot;&gt;https://grafana.com/grafana/dashboards/&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><id>levaminov:azHWMc4Vv3E</id><link rel="alternate" type="text/html" href="https://levaminov.ru/azHWMc4Vv3E?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>&quot;Нейронка&quot; для определения ответственного за сервис</title><published>2022-03-22T18:47:08.374Z</published><updated>2022-03-23T03:31:13.474Z</updated><category term="yumor" label="юмор"></category><summary type="html">Недавно в одном из разговоров я упоминал, что на Prometheus-based стеке смог каждый алерт ассоциировать с командой, которая обслуживает сервис и в случае критических событий эта самая команда меншенится в Slack, чтобы привлечь её внимание. Что ж, показываю как это выглядит в реальности.</summary><content type="html">
  &lt;p id=&quot;TMKn&quot;&gt;Недавно в одном из разговоров я упоминал, что на Prometheus-based стеке смог каждый алерт ассоциировать с командой, которая обслуживает сервис и в случае критических событий эта самая команда меншенится в Slack, чтобы привлечь её внимание. Что ж, показываю как это выглядит в реальности.&lt;/p&gt;
  &lt;p id=&quot;XGSb&quot;&gt;Суровая реальность такова – заводится вот такая вот структура (крутите по горизонтали, там 500+ символов):&lt;/p&gt;
  &lt;pre id=&quot;jlnj&quot; data-lang=&quot;yaml&quot;&gt;owner_team:
  - &amp;amp;owner_team_by_labels_exported_namespace |
      {{if (match &amp;quot;finance&amp;quot; $labels.exported_namespace)}}finance_duty{{else if (match &amp;quot;storage|warehouse&amp;quot; $labels.exported_namespace)}}warehouse_duty{{else if (match &amp;quot;sso&amp;quot; $labels.exported_namespace)}}sso_duty{{else if (match &amp;quot;crm|bff-self-service-form|bff-offers&amp;quot; $labels.exported_namespace)}}crm_duty{{else if (match &amp;quot;loyalty-bonus&amp;quot; $labels.exported_namespace)}}finance_duty{{else if (match &amp;quot;loyalty&amp;quot; $labels.exported_namespace)}}crm_duty{{else if (match &amp;quot;communications-gateway|sms-sender|contractor-app-bff&amp;quot; $labels.exported_namespace)}}infogate_duty{{else}}-{{end -}}&lt;/pre&gt;
  &lt;p id=&quot;yLFe&quot;&gt;В массиве, на самом деле, множество записей, чтобы определять команду по тому или иному признаку. После этого в лейблы алерта добавляем признак команды:&lt;/p&gt;
  &lt;pre id=&quot;VNej&quot; data-lang=&quot;yaml&quot;&gt;owner_team: *owner_team_by_labels_exported_namespace&lt;/pre&gt;
  &lt;p id=&quot;n6Lo&quot;&gt;В конфигурации ресивера включаем линк по именам:&lt;/p&gt;
  &lt;pre id=&quot;pWuF&quot; data-lang=&quot;yaml&quot;&gt;slack_configs:
  - channel: &amp;quot;#alerts&amp;quot;
    link_names: true&lt;/pre&gt;
  &lt;p id=&quot;fwxZ&quot;&gt;В качестве шаблона футера определяем следующее:&lt;/p&gt;
  &lt;pre id=&quot;2ILn&quot;&gt;{{ define &amp;quot;slack.default.footer&amp;quot; }}{{ if .CommonLabels.owner_team }}{{ if not (eq .CommonLabels.owner_team &amp;quot;-&amp;quot;) }}Команда: @{{ .CommonLabels.owner_team }}{{ end }}{{ end }}{{ end }}&lt;/pre&gt;
  &lt;p id=&quot;NrEa&quot;&gt;Easy-peasy.&lt;/p&gt;

</content></entry><entry><id>levaminov:V4cQiyviW4J</id><link rel="alternate" type="text/html" href="https://levaminov.ru/V4cQiyviW4J?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=levaminov"></link><title>Тестирование Helm Chart. Часть 2</title><published>2022-03-22T08:00:14.489Z</published><updated>2022-11-16T06:59:20.264Z</updated><summary type="html">В первой части мы проверяли чарты на соответствие нашим ожиданиям по входящим параметрам, хранящимся в values-файлах. В этой части я расскажу, как мы валидируем пользовательские values-файлы к этом чарту, которые правят разработчики, так как проблема остается той же – недопустимый набор входных параметров порождает неприменимые спецификации Kubernetes, что приводит к ошибкам публикации.</summary><content type="html">
  &lt;p id=&quot;Ewck&quot;&gt;В &lt;a href=&quot;https://levaminov.ru/v2FJJF1UrSx&quot; target=&quot;_blank&quot;&gt;первой части&lt;/a&gt; мы проверяли чарты на соответствие нашим ожиданиям по входящим параметрам, хранящимся в values-файлах. В этой части я расскажу, как мы валидируем пользовательские values-файлы к этом чарту, которые правят разработчики, так как проблема остается той же – недопустимый набор входных параметров порождает неприменимые спецификации Kubernetes, что приводит к ошибкам публикации.&lt;/p&gt;
  &lt;h2 id=&quot;y9ck&quot;&gt;Валидация входных параметров&lt;/h2&gt;
  &lt;p id=&quot;OP5f&quot;&gt;По аналогии с &lt;a href=&quot;https://github.com/instrumenta/kubeval&quot; target=&quot;_blank&quot;&gt;kubeval&lt;/a&gt; хотелось бы иметь какой-то инструмент, который способен валидировать файлы на соответствие нашей собственной схеме, сам kubeval использует схемы в JSON:&lt;/p&gt;
  &lt;pre id=&quot;NMQq&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;description&amp;quot;: &amp;quot;Binding ties one object to another; for example, a pod is bound to a node by a scheduler. Deprecated in 1.7, please use the bindings subresource of pods instead.&amp;quot;,
  &amp;quot;properties&amp;quot;: {
    &amp;quot;apiVersion&amp;quot;: {
      &amp;quot;description&amp;quot;: &amp;quot;APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources&amp;quot;,
      &amp;quot;type&amp;quot;: [
        &amp;quot;string&amp;quot;,
        &amp;quot;null&amp;quot;
      ]
    },
    &amp;quot;kind&amp;quot;: {
      &amp;quot;description&amp;quot;: &amp;quot;Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds&amp;quot;,
      &amp;quot;type&amp;quot;: [
        &amp;quot;string&amp;quot;,
        &amp;quot;null&amp;quot;
      ],
      &amp;quot;enum&amp;quot;: [
        &amp;quot;Binding&amp;quot;
      ]
    },
    &amp;quot;metadata&amp;quot;: {
      &amp;quot;$ref&amp;quot;: &amp;quot;https://kubernetesjsonschema.dev/master/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;Standard object&amp;#x27;s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata&amp;quot;
    },
    &amp;quot;target&amp;quot;: {
      &amp;quot;$ref&amp;quot;: &amp;quot;https://kubernetesjsonschema.dev/master/_definitions.json#/definitions/io.k8s.api.core.v1.ObjectReference&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;The target object that you want to bind to the standard object.&amp;quot;
    }
  },
  &amp;quot;required&amp;quot;: [
    &amp;quot;target&amp;quot;
  ],
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
  &amp;quot;x-kubernetes-group-version-kind&amp;quot;: [
    {
      &amp;quot;group&amp;quot;: &amp;quot;&amp;quot;,
      &amp;quot;kind&amp;quot;: &amp;quot;Binding&amp;quot;,
      &amp;quot;version&amp;quot;: &amp;quot;v1&amp;quot;
    }
  ],
  &amp;quot;$schema&amp;quot;: &amp;quot;http://json-schema.org/schema#&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;4dmO&quot;&gt;Не самый удобный формат, другой нюанс – kubeval ориентируется по полям kind и apiVersion для получения схем:&lt;/p&gt;
  &lt;pre id=&quot;Ulhh&quot; data-lang=&quot;go&quot;&gt;// We haven&amp;#x27;t cached this schema yet; look for one that works
primarySchemaBaseURL := determineSchemaBaseURL(config)
primarySchemaRef := determineSchemaURL(primarySchemaBaseURL, resource.Kind, resource.APIVersion, config)
schemaRefs := []string{primarySchemaRef}

for _, additionalSchemaURLs := range config.AdditionalSchemaLocations {
  additionalSchemaRef := determineSchemaURL(additionalSchemaURLs, resource.Kind, resource.APIVersion, config)
  schemaRefs = append(schemaRefs, additionalSchemaRef)
}&lt;/pre&gt;
  &lt;p id=&quot;7nqm&quot;&gt;Решаемо. Так может не будем придумывать велосипедов, а попробуем воспользоваться готовым инструментом?&lt;/p&gt;
  &lt;p id=&quot;5Y3R&quot;&gt;Пробуем запустить валидацию values-файла из репозитория с кодом сервиса как есть:&lt;/p&gt;
  &lt;pre id=&quot;Cf13&quot; data-lang=&quot;bash&quot;&gt;% kubeval values.yaml
ERR  - values.yaml: Missing &amp;#x27;kind&amp;#x27; key&lt;/pre&gt;
  &lt;p id=&quot;i5AK&quot;&gt;Добавляем kind и apiVersion, и так знаем, что они нужны:&lt;/p&gt;
  &lt;pre id=&quot;DKeS&quot; data-lang=&quot;yaml&quot;&gt;---
apiVersion: qlean/v1
kind: values

# Базовые настройки чарта
app-template:
  app:
    name: web-widget-navbar
    component: frontend
    project: platform&lt;/pre&gt;
  &lt;p id=&quot;ItrA&quot;&gt;Проверяем еще раз:&lt;/p&gt;
  &lt;pre id=&quot;jKfj&quot; data-lang=&quot;bash&quot;&gt;% kubeval values.yaml
..https://kubernetesjsonschema.dev/master-standalone/values-qlean-v1.json: Could not read schema from HTTP, response status is 404 Not Found&lt;/pre&gt;
  &lt;p id=&quot;qn0K&quot;&gt;Для переопределения адреса источника схем в kubeval есть параметр --schema-location (либо переменная окружения KUBEVAL_SCHEMA_LOCATION), в качестве значения можно задавать как ссылку, так и локальный каталог:&lt;/p&gt;
  &lt;pre id=&quot;5fUY&quot; data-lang=&quot;bash&quot;&gt;export KUBEVAL_SCHEMA_LOCATION=file://./schemas&lt;/pre&gt;
  &lt;p id=&quot;pnD3&quot;&gt;Запускаем:&lt;/p&gt;
  &lt;pre id=&quot;1cKy&quot; data-lang=&quot;bash&quot;&gt;% kubeval values.yaml                            
..open ./schemas/master-standalone/values-qlean-v1.json: no such file or directory&lt;/pre&gt;
  &lt;p id=&quot;0V7A&quot;&gt;В качестве дочернего каталога выступает значение флага --kubernetes-version (по умолчанию master) и префикс standalone. Создаем в нужном каталоге файл values-qlean-v1.json со следующим тестовым содержимым:&lt;/p&gt;
  &lt;pre id=&quot;wfeJ&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;title&amp;quot;: &amp;quot;Helm Values&amp;quot;,
  &amp;quot;additionalProperties&amp;quot;: false
}&lt;/pre&gt;
  &lt;p id=&quot;QYUb&quot;&gt;Здесь мы определяем additionalProperties, который говорит, что на текущем уровне (в корне) запрещены любые неописанные поля, проверяем:&lt;/p&gt;
  &lt;pre id=&quot;hKgb&quot; data-lang=&quot;bash&quot;&gt;% kubeval values.yaml 
..kind: Additional property kind is not allowed
..apiVersion: Additional property apiVersion is not allowed
..app-template: Additional property app-template is not allowed&lt;/pre&gt;
  &lt;p id=&quot;QZNR&quot;&gt;Обновляем схему:&lt;/p&gt;
  &lt;pre id=&quot;TnTM&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;title&amp;quot;: &amp;quot;Helm Values&amp;quot;,
  &amp;quot;additionalProperties&amp;quot;: false,
  &amp;quot;properties&amp;quot;: {
    &amp;quot;apiVersion&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
    },
    &amp;quot;kind&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
    },
    &amp;quot;app-template&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;
    }
  }
}&lt;/pre&gt;
  &lt;p id=&quot;ICgU&quot;&gt;Проверяем:&lt;/p&gt;
  &lt;pre id=&quot;CVug&quot; data-lang=&quot;bash&quot;&gt;% kubeval values.yaml
PASS - values.yaml contains a valid values (unknown)&lt;/pre&gt;
  &lt;p id=&quot;GP1m&quot;&gt;Ну, а далее идем изучать &lt;a href=&quot;https://json-schema.org&quot; target=&quot;_blank&quot;&gt;https://json-schema.org&lt;/a&gt; и, если хочется, схемы ресурсов Kubernetes, и формируем свою собственную схему, на основе которой будут проверяться пользовательские настройки чарта. Ниже несколько примеров из реальной жизни.&lt;/p&gt;
  &lt;p id=&quot;iteu&quot;&gt;1. Поле &amp;#x60;app&amp;#x60; должно содержать ключ-значение, есть обязательные ключи, значения должны быть строкой:&lt;/p&gt;
  &lt;pre id=&quot;HF5Q&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;app&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
  &amp;quot;additionalProperties&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
  },
  &amp;quot;required&amp;quot;: [
    &amp;quot;name&amp;quot;,
    &amp;quot;component&amp;quot;,
    &amp;quot;project&amp;quot;
  ]
}&lt;/pre&gt;
  &lt;p id=&quot;Kuo2&quot;&gt;2. Поле &amp;#x60;image&amp;#x60; должно содержать два параметра – repository и tag, и больше никаких других:&lt;/p&gt;
  &lt;pre id=&quot;bixX&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;image&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
  &amp;quot;additionalProperties&amp;quot;: false,
  &amp;quot;properties&amp;quot;: {
    &amp;quot;repository&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
    },
    &amp;quot;tag&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
    }
  }
}&lt;/pre&gt;
  &lt;p id=&quot;qq8U&quot;&gt;3. Поле &amp;#x60;annotations&amp;#x60; должно содержать ключ-значение, но есть ключи, которые использовать нельзя:&lt;/p&gt;
  &lt;pre id=&quot;7JPQ&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;annotations&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
  &amp;quot;properties&amp;quot;: {
    &amp;quot;pltf_ttl&amp;quot;: false,
    &amp;quot;ci_project_id&amp;quot;: false
  },
  &amp;quot;additionalProperties&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;dn3Y&quot;&gt;4. Значение поля &amp;#x60;type&amp;#x60; должно быть одним из допустимых:&lt;/p&gt;
  &lt;pre id=&quot;2mJ2&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;type&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
  &amp;quot;enum&amp;quot;: [
    &amp;quot;http&amp;quot;,
    &amp;quot;app&amp;quot;
  ]
}&lt;/pre&gt;
  &lt;p id=&quot;Aoen&quot;&gt;5. Значение &amp;#x60;сount&amp;#x60; должно быть целым числом и не меньше нуля:&lt;/p&gt;
  &lt;pre id=&quot;TCIZ&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;сount&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,
  &amp;quot;minimum&amp;quot;: 0
}&lt;/pre&gt;
  &lt;p id=&quot;wXw8&quot;&gt;6. Поле &amp;#x60;for&amp;#x60; должно содержать строку в формате duration:&lt;/p&gt;
  &lt;pre id=&quot;MTVw&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;for&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
  &amp;quot;format&amp;quot;: &amp;quot;duration&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;fEvT&quot;&gt;JSON-формат не так удобен для чтения, по сравнению с HCL или YAML, поэтому можно задуматься о том, чтобы конвертировать из подходящего формата в JSON при сборке образа.&lt;/p&gt;
  &lt;p id=&quot;qN4Y&quot;&gt;Чем детальнее будет описана схема, тем меньше возможностей сделать что-то не так, а само внедрение валидации позволит с меньшими трудозатратами мигрировать на новый формат в дальнейшем.&lt;/p&gt;
  &lt;h2 id=&quot;LGdU&quot;&gt;Со звездочкой&lt;/h2&gt;
  &lt;p id=&quot;NiBp&quot;&gt;Если стойкое желание сделать свой велосипед к этому моменту сохранилось, полезным будет посмотреть в сторону имплементации &lt;a href=&quot;https://github.com/xeipuuv/gojsonschema&quot; target=&quot;_blank&quot;&gt;JSON Schema на Go&lt;/a&gt;, пакет позволяет, в числе прочего, описывать собственные форматы данных, например, для крона может выглядеть так:&lt;/p&gt;
  &lt;pre id=&quot;i2Xl&quot; data-lang=&quot;go&quot;&gt;package main

import &amp;quot;github.com/gorhill/cronexpr&amp;quot;

type CronFormat struct{}

func init() {
	gojsonschema.FormatCheckers.Add(&amp;quot;cron&amp;quot;, CronFormat{})
}

func (c CronFormat) IsFormat(cron interface{}) bool {
	cronString, ok := cron.(string)
	if !ok {
		return false
	}
	_, err := cronexpr.Parse(cronString)
	return err == nil
}&lt;/pre&gt;
  &lt;p id=&quot;UNZV&quot;&gt;С последующим применением в таком виде:&lt;/p&gt;
  &lt;pre id=&quot;XTbr&quot; data-lang=&quot;javascript&quot;&gt;&amp;quot;cron&amp;quot;: {
  &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
  &amp;quot;format&amp;quot;: &amp;quot;cron&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;BSdD&quot;&gt;Ещё один плюс – чтение схем с диска можно описать самому и использовать любой формат, вот, например, YAML:&lt;/p&gt;
  &lt;pre id=&quot;1e58&quot; data-lang=&quot;go&quot;&gt;var bodySchema map[string]interface{}
_ = yaml.Unmarshal(schemaBytes, &amp;amp;bodySchema)
schemaLoader := gojsonschema.NewGoLoader(bodySchema)
return gojsonschema.NewSchema(schemaLoader)&lt;/pre&gt;
  &lt;p id=&quot;7mbV&quot;&gt;Детали реализации можно посмотреть &lt;a href=&quot;https://gitlab.com/leominov/helm-values-linter&quot; target=&quot;_blank&quot;&gt;в коде&lt;/a&gt;, он открыт.&lt;/p&gt;
  &lt;h2 id=&quot;e8eP&quot;&gt;Полезные ссылки&lt;/h2&gt;
  &lt;ol id=&quot;qj8Y&quot;&gt;
    &lt;li id=&quot;08dO&quot;&gt;&lt;a href=&quot;https://www.kubeval.com&quot; target=&quot;_blank&quot;&gt;https://www.kubeval.com&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;O0GO&quot;&gt;&lt;a href=&quot;https://json-schema.org&quot; target=&quot;_blank&quot;&gt;https://json-schema.org&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;ZrXS&quot;&gt;&lt;a href=&quot;https://json-schema.org/draft/2020-12/json-schema-validation.html&quot; target=&quot;_blank&quot;&gt;https://json-schema.org/draft/2020-12/json-schema-validation.html&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;YLJ6&quot;&gt;&lt;a href=&quot;https://github.com/instrumenta/kubernetes-json-schema&quot; target=&quot;_blank&quot;&gt;https://github.com/instrumenta/kubernetes-json-schema&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;EcJ2&quot;&gt;&lt;a href=&quot;https://json-schema-everywhere.github.io/yaml&quot; target=&quot;_blank&quot;&gt;https://json-schema-everywhere.github.io/yaml&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

</content></entry></feed>