Русский
Русский
English
Статистика
Реклама

Devops

Автоматизируй это, или Контейнерные перевозки Docker для WebRTC

11.06.2021 08:06:47 | Автор: admin

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

Представим ситуацию: нужно разворачивать много однотипных серверов, причем делать это быстро. Быстро разворачивать, быстро сворачивать. Например, разворачивать тестовые стенды для разработчиков. Когда разработка ведётся параллельно нужно разделить разработчиков, что бы они не мешали друг-другу и возможные ошибки одного из них не блокировали работу остальных.

Путей решения этой задачи может быть несколько:

  1. Использовать виртуальные машины. Несколько тяжеловесное решение. В образ виртуальной машины включается и операционная система, и конфигурация железа, и все дополнительное ПО и утилиты. Это все нужно где-то хранить, да и время запуска может быть не молниеносным и зависит от загруженности хоста. Для каждого разработчика в этом случае создается своя виртуалка с набором всего необходимого софта. Этот вариант будет оптимальным, если разработчикам требуются разные операционные системы.

  2. Использовать скрипты. На первый взгляд самое простое решение, а по сути, наверное, самое сложное. В этом случае, мы не тащим с собой операционку и дополнительное ПО. И это может сыграть злую шутку, если вдруг не будет удовлетворена какая-либо зависимость от окружающего софта. Попадется случайно в репозитории условный Python не той версии и все!

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

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

Стриминг без использования контейнеров:

Стриминг с использованием контейнеров:

  • можно организовать сервис стриминга для блогеров. В таком случае для каждого блогера заводится свой контейнер, в котором будет организован его личный сервер. Если у одного блогера вдруг случаются какие-то технические неполадки, то другие об этом даже не подозревают и продолжают стримить как ни в чем не бывало. ;

  • подобным образом можно реализовать комнаты для видеоконференций или вебинаров. Одна комната - один контейнер. ;

  • организовать систему видеонаблюдения за домами. Один дом - один контейнер;

  • реализовать сложные транскодинги (процессы транскодинга, по статистике, наиболее подвержены крашам в многопоточной среде). Один транскодер - один контейнер.

    и т.п.

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

Почему все таки контейнеры, а не виртуалки?

Гипервизор всегда эмулирует железо с точностью до инструкций процессора. Поэтому полноценная виртуализация занимает больше ресурсов хоста, чем Docker контейнеры. Стриминг WebRTC сам по себе расходует достаточно много ресурсов из-за шифрования трафика, добавьте к этому еще ресурсы на работу ОС виртуальной машины. Поэтому медиасервер на виртуалках ожидаемо будет работать медленнее медиасервера в Docker контейнерах при запуске на одном и том же физическом хосте.

Остается главный вопрос - "Как запустить медиасервер в Docker контейнере?

Разберем на примере Web Call Server.

Легче легкого!

В Docker Hub уже загружен образ Flashphoner Web Call Server 5.2.

Развертывание WCS сводится к двум командам:

  1. Загрузить актуальную сборку с Docker Hub

    docker pull flashponer/webcallserver
    
  2. Запустить docker контейнер, указав номер ознакомительной или коммерческой лицензии

    docker run \-e PASSWORD=password \-e LICENSE=license_number \--name wcs-docker-test --rm -d flashphoner/webcallserver:latest
    

    где:

    PASSWORD - пароль на доступ внутрь контейнера по SSH. Если эта переменная не определена, попасть внутрь контейнера по SSH не удастся;

    LICENSE - номер лицензии WCS. Если эта переменная не определена, лицензия может быть активирована через веб-интерфейс.

Но, если бы все было настолько просто не было бы этой статьи.

Первые сложности

На своей локальной машине с операционной системой Ubuntu Desktop 20.04 LTS я установил Docker:

sudo apt install docker.io

Создал новую внутреннюю сеть Docker с названием "testnet":

sudo docker network create \ --subnet 192.168.1.0/24 \ --gateway=192.168.1.1 \ --driver=bridge \ --opt com.docker.network.bridge.name=br-testnet testnet

Cкачал актуальную сборку WCS с Docker Hub

sudo docker pull flashphoner/webcallserver

Запустил контейнер WCS

sudo docker run \-e PASSWORD=password \-e LICENSE=license_number \-e LOCAL_IP=192.168.1.10 \--net testnet --ip 192.168.1.10 \--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Переменные здесь:

PASSWORD - пароль на доступ внутрь контейнера по SSH. Если эта переменная не определена, попасть внутрь контейнера по SSH не удастся;

LICENSE - номер лицензии WCS. Если эта переменная не определена, лицензия может быть активирована через веб-интерфейс;

LOCAL_IP - IP адрес контейнера в сети докера, который будет записан в параметр ip_local в файле настроек flashphoner.properties;

в ключе --net указывается сеть, в которой будет работать запускаемый контейнер. Запускаем контейнер в сети testnet.

Проверил доступность контейнера пингом:

ping 192.168.1.10

Открыл Web интерфейс WCS в локальном браузере по ссылке https://192.168.1.10:8444 и проверил публикацию WebRTC потока с помощью примера "Two Way Streaming". Все работает.

Локально, с моего компьютера на котором установлен Docker, доступ к WCS серверу у меня был. Теперь нужно было дать доступ коллегам.

Замкнутая сеть

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

Получается, чтобы предоставить коллегам доступ к тестовому стенду в Docker на моей машине, я должен предоставить консольный доступ к своей машине. Для тестирования внутри группы разработчиков такой вариант с натяжкой допустим. Но мне то хотелось это все запустить в продакшен. Неужели, миллиарды контейнеров во всем мире работают только локально?

Конечно же нет. Путем курения мануалов был найден ответ. Нужно пробросить порты. Причем проброс портов нужен не на сетевом маршрутизаторе, а в самом Dockere.

Отлично! Список портов известен. Пробрасываем:

docker run \-e PASSWORD=password \-e LICENSE=license_number \-e LOCAL_IP=192.168.1.10 \-e EXTERNAL_IP=192.168.23.6 \-d -p8444:8444 -p8443:8443 -p1935:1935 -p30000-33000:30000-33000 \--net testnet --ip 192.168.1.10 \--name wcs-docker-test --rm flashphoner/webcallserver:latest

В этой команде используем следующие переменные:

PASSWORD, LICENSE и LOCAL_IP мы рассмотрели выше;

EXTERNAL_IP IP адрес внешнего сетевого интерфейса. Записывается в параметр ip в файле настроек flashphoner.properties;

Так же в команде появляются ключи -p это и есть проброс портов. В этой итерации используем ту же сеть "testnet", которую мы создали раньше.

В браузере на другом компьютере открываю https://192.168.23.6:8444 (IP адрес моей машины с Docker) и запускаю пример "Two Way Streaming"

Web интерфейс WCS работает и даже WebRTC трафик ходит.

И все было бы прекрасно, если бы не одно но!

Ну что ж так долго!

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

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

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

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

Запускаем контейнер в сети хоста (на это указывает ключ --net host)

docker run \-e PASSWORD=password \-e LICENSE=license_number \-e LOCAL_IP=192.168.23.6 \-e EXTERNAL_IP=192.168.23.6 \--net host \--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Отлично! Контейнер запустился быстро. С внешней машины все работает - и web интерфейс и WebRTC трафик публикуется и воспроизводится.

Потом я запустил еще пару контейнеров. Благо на моем компьютере несколько сетевых карт.

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

Рабочий вариант

Начиная с версии 1.12 Docker предоставляет два сетевых драйвера: Macvlan и IPvlan. Они позволяют назначать статические IP из сети LAN.

  • Macvlan позволяет одному физическому сетевому интерфейсу (машине-хосту) иметь произвольное количество контейнеров, каждый из которых имеет свой собственный MAC-адрес.

    Требуется ядро Linux v3.93.19 или 4.0+.

  • IPvlan позволяет создать произвольное количество контейнеров для вашей хост машины, которые имеют один и тот же MAC-адрес.

    Требуется ядро Linux v4.2 + (поддержка более ранних ядер существует, но глючит).

Я использовал в своей инсталляции драйвер IPvlan. Отчасти, так сложилось исторически, отчасти у меня был расчет на перевод инфраструктуры на VMWare ESXi. Дело в том, что для VMWare ESXi доступно использование только одного MAC-адреса на порт, и в таком случае технология Macvlan не подходит.

Итак. У меня есть сетевой интерфейс enp0s3, который получает IP адрес от DHCP сервера.

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

Что бы этого избежать нужно зарезервировать часть диапазона подсети для использования Docker. Это решение состоит из двух частей:

  1. Нужно настроить службу DHCP в сети таким образом, чтобы она не назначала адреса в некотором определенном диапазоне.

  2. Нужно сообщить Docker об этом зарезервированном диапазоне адресов.

В этой статье я не буду рассказывать, как настраивать DHCP сервер. Думаю, каждый айтишник в своей практике сталкивался с этим не единожды, в крайнем случае, в сети полно мануалов.

А вот как сообщить Docker, какой диапазон для него выделен, разберем подробно.

Я ограничил диапазон адресов DHCP сервера так, что он не выдает адреса выше 192.168.23. 99. Отдадим для Docker 32 адреса начиная с 192.168.23.100.

Создаем новую Docker сеть с названием "new-testnet":

docker network create -d ipvlan -o parent=enp0s3 \--subnet 192.168.23.0/24 \--gateway 192.168.23.1 \--ip-range 192.168.23.100/27 \new-testnet

где:

ipvlan тип сетевого драйвера;

parent=enp0s3 физический сетевой интерфейс (enp0s3), через который будет идти трафик контейнеров;

--subnet подсеть;

--gateway шлюз по умолчанию для подсети;

--ip-range диапазон адресов в подсети, которые Docker может присваивать контейнерам.

и запускаем в этой сети контейнер с WCS

docker run \-e PASSWORD=password \-e LICENSE=license_number \-e LOCAL_IP=192.168.23.101 \-e EXTERNAL_IP=192.168.23.101 \--net new-testnet --ip 192.168.23.101 \--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Проверяем работу web интерфейса и публикацию/воспроизведение WebRTC трафика с помощью примера "Two-way Streaming":

Есть один маленький минус такого подхода. При использовании технологий Ipvlan или Macvlan Docker изолирует контейнер от хоста. Если, например, попробовать пропинговать контейнер с хоста, то все пакеты будут потеряны.

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

Используя технологию IPvlan на одном Docker хосте можно поднять необходимое количество контейнеров. Это количество ограничено только ресурсами хоста и, частично, сетевой адресацией конкретной сети.

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

Ссылки

WCS в Docker

Документация по развертыванию WCS в Docker

Образ WCS на DockerHub

Подробнее..

WebRTC CDN на Google Cloud Platform с балансировкой и автоматическим масштабированием

18.06.2021 10:22:12 | Автор: admin

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

Кратко напомним основные тезисы:

  • В CDN низкая задержка в трансляциях обеспечивается использованием технологии WebRTC для передачи видеопотока от Origin сервера к Edge серверам, которые, в свою очередь, позволяют подключить большое количество зрителей.

  • Если постоянно держать включенными некоторое количество серверов в расчете на большой наплыв зрителей, то, в то время, когда наплыва нет, деньги на аренду серверов расходуются зря. Оптимальным вариантом было бы запускать дополнительные Edge при увеличении потока зрителей и гасить их при уменьшении.

Google Cloud Platform - это стек облачных сервисов, которые выполняются на той же самой инфраструктуре, которую Google использует для своих продуктов. То есть, получается, что пользовательские приложения запущенные в среде Google Cloud Platform, крутятся на тех же серверных мощностях, что и сам "великий и ужасный" Google. Поэтому, можно, с большой вероятностью, ожидать бесперебойной работы серверной инфраструктуры.

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

На момент написания статьи образа для быстрого развертывания WCS в GCP Marketplace не было. Но есть возможность автоматического развертывания WCS из образа виртуальной машины, который был подготовлен вручную. В этом случае, для WCS используется стандартная ежемесячная лицензия.

Теперь рассмотрим, как развернуть WCS CDN с балансировщиком и автоматическим масштабированием и процесс тестирования развернутой системы.

Разворачиваем WebRTC CDN с балансировщиком и автоматическим масштабированием на Google Cloud Platform

Конфигурация CDN будет следующей:

  • один Origin сервер;

  • один Edge в составе балансировщика нагрузки, который позволяет во время пиковой нагрузки увеличить число Edge серверов до трех.

Для развертывания потребуется настроить следующие компоненты в консоли Google Cloud Platform:

  • глобальный файрволл на уровне проекта Google Cloud;

  • виртуальные машины WCS CDN Origin и WCS CDN Edge;

  • шаблон развертывания на основе образа диска WCS CDN Edge;

  • группу масштабирования;

  • балансировщик нагрузки.

Итак, приступим.

Настраиваем глобальный файрволл на уровне проекта Google Cloud для прохождения WebRTC трафика

Настройка межсетевого экрана действует на все запущенные в вашем проекте сервера, поэтому начнем развертывание с нее.

В основном меню консоли Google Cloud откройте раздел "VPC networks" и выберите пункт "Firewall":

На открывшейся странице нажмите кнопку "Create Firewall Rule" :

В открывшемся мастере задайте имя правила:

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

Еще ниже в секции "Protocols and ports" укажите порты для работы WCS и нажмите кнопку "Create":

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

Разворачиваем WCS сервер с ролью Origin для WebRTC CDN

В консоли Google Cloud откройте раздел "Compute Engine" и выберите из меню в левой части пункт "VM instances". Нажмите кнопку "Create" в диалоге создания нового экземпляра сервера:

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

Ниже на странице в секции "Boot disk" нажмите кнопку "Change" и выберите образ "CentOS 7":

Разверните секцию "Management, security, disks, networking, sole tenancy":

На вкладке "Security" добавьте публичный ключ для доступа к серверу по SSH:

На вкладке "Networking" в секции "Network interfaces" настройте внешний и внутренний IP адреса для сервера. Для работы в составе CDN серверу нужно назначить статический внутренний IP адрес:

После всех настроек нажмите кнопку "Create" для создания нового экземпляра WCS сервера с ролью CDN Origin:

Спустя пару минут сервер будет создан и запущен. Подключаемся к нему по ssh и устанавливаем WCS. Все действия - установка, изменение настроек, запуск или перезапуск WCS - должны выполняться с root правами, либо через sudo.

1.Установите Wget, Midnight Commander и дополнительные инструменты и библиотеки

sudo yum -y install wget mc tcpdump iperf3 fontconfig

2.Установите JDK. Для работы в условиях больших нагрузок рекомендуется JDK 12 или 14. Удобнее провести установку при помощи скрипта на bash. Текст скрипта:

#!/bin/bashsudo rm -rf jdk*curl -s https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz | tar -zx[ ! -d jdk-12.0.2/bin ] && exit 1sudo mkdir -p /usr/java[ -d /usr/java/jdk-12.0.2 ] && sudo rm -rf /usr/java/jdk-12.0.2sudo mv -f jdk-12.0.2 /usr/java[ ! -d /usr/java/jdk-12.0.2/bin ] && exit 1sudo rm -f /usr/java/defaultsudo ln -sf /usr/java/jdk-12.0.2 /usr/java/defaultsudo update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1sudo update-alternatives --install "/usr/bin/jstack" "jstack" "/usr/java/jdk-12.0.2/bin/jstack" 1sudo update-alternatives --install "/usr/bin/jcmd" "jcmd" "/usr/java/jdk-12.0.2/bin/jcmd" 1sudo update-alternatives --install "/usr/bin/jmap" "jmap" "/usr/java/jdk-12.0.2/bin/jmap" 1sudo update-alternatives --set "java" "/usr/java/jdk-12.0.2/bin/java"sudo update-alternatives --set "jstack" "/usr/java/jdk-12.0.2/bin/jstack"sudo update-alternatives --set "jcmd" "/usr/java/jdk-12.0.2/bin/jcmd"sudo update-alternatives --set "jmap" "/usr/java/jdk-12.0.2/bin/jmap"

3.Загрузите архив для установки самой свежей стабильной версии WebCallServer:

sudo wget https://flashphoner.com/download-wcs5.2-server.tar.gz

4.Распакуйте архив и запустите скрипт установки WCS и следуйте указаниям мастера установки. Для запуска WCS вам потребуется действительная лицензия. Для правильной работы команды, укажите номер сборки скачанного вами архива:

sudo tar -xvzf FlashphonerWebCallServer-5.2.714.tar.gz && cd FlashphonerWebCallServer-5.2.714 && ./install.sh

5.Для активации лицензии запустите скрипт "./activation.sh" из каталога установки WCS. Этот шаг, при желании, можно пропустить и активировать лицензию позже через веб-интерфейс:

sudo cd /usr/local/FlashphonerWebCallServer/bin && sudo ./activation.sh

6.Отключите firewalld и SELinux. Сетевой экран мы ранее настроили на уровне Google Cloud Platform, поэтому нет необходимости закрывать порты в операционной системе:

sudo systemctl stop firewalld && systemctl disable firewalld && setenforce 0

7.Откройте любым удобным редактором файл flashphoner.properties, который можно найти по пути:

/usr/local/FlashphonerWebCallServer/conf/flashphoner.properties

и внесите в него настройки для запуска CDN. В параметре "cdn_ip" укажите внутренний IP адрес вашей виртуальной машины с ролью CDN Origin:

cdn_enabled=truecdn_ip=10.128.0.3 # Local IP address CDN Origincdn_nodes_resolve_ip=falsecdn_role=origin

На скриншоте ниже примерный вид файла flashphoner.properties для WCS с ролью CDN Origin:

После изменения настроек запустите (или перезапустите) Web Call Server:

systemctl start webcallserver

На этом запуск и настройка Origin закончены. Перейдем к настройке балансировщика нагрузки и автоматического масштабирования.

Запускаем балансировщик нагрузки и автоматическое масштабирование в Google Cloud для WebRTC CDN

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

  • образ диска, который будет использоваться в шаблоне при создании нового экземпляра WCS;

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

  • группа масштабирования;

  • балансировщик нагрузки;

  • настройки контроля активности сервера.

Создаем образ диска WCS сервера с ролью Edge для WebRTC CDN

Начнем настройку с развертывания WCS сервера с ролью CDN Edge, для того, что бы создать на его основе образ диска для шаблона создания новых экземпляров WCS в балансировщике.

Повторите инструкцию по подготовке сервера Origin до пункта о внесении настроек в файл flashphoner.properties. Для роли Edge внесите в этот файл следующие настройки:

cdn_enabled=truecdn_ip=10.128.0.4cdn_nodes_resolve_ip=falsecdn_point_of_entry=10.128.0.3cdn_role=edgehttp_enable_root_redirect=false

После внесения и сохранения настроек, остановите в консоли Google Cloud виртуальную машину WCS CDN Edge, выберите из меню в левой части пункт "Images" и нажмите кнопку "Create Image":

В открывшемся мастере укажите имя нового образа, выберите в качестве источника диск виртуальной машины WCS CDN Edge и нажмите кнопку "Create":

После того, как образ диска создан переходим к созданию шаблона развертывания Edge сервера на основе созданного образа.

Создаем шаблон развертывания Edge сервера

Выберите из меню в левой части окна консоли Google Cloud пункт "Instance templates" и нажмите кнопку "Create Instance template":

В открывшемся мастере создания шаблона развертывания укажите имя шаблона и выберите конфигурацию виртуальной машины:

Ниже на странице в секции "Boot disk" нажмите кнопку "Change". в Открывшемся окне перейдите на вкладку "Custom Images" и выберите образ диска WCS CDN Edge, который мы создали ранее. Нажмите кнопку "Select":

Разверните секцию "Management, security, disks, networking, sole tenancy". На вкладке "Security" добавьте публичный ключ для доступа к серверу по SSH и нажмите кнопку "Create":

Шаблон развертывания для WCS с ролью CDN Edge создан. Теперь перейдем к созданию группы масштабирования.

Создаем группы масштабирования для Edge серверов

Из меню в левой части окна консоли Google Cloud выберите пункт "Instance groups" и нажмите кнопку "Create Instance group":

На открывшейся странице выберите регион и зону расположения группы и укажите шаблон развертывания WCS Edge, который мы создали ранее:

В секции "Autoscaling" на этой же странице настройте триггер запуска дополнительных серверов Edge. В качестве триггера будем использовать загрузку процессора более 80% . В поле "Maximum number of instances" укажите максимальное количество виртуальных машин, которые будут запущены при срабатывании триггера:

Затем включите проверку состояния виртуальной машины в секции "Autohealing". Для того, что бы создать настройку проверки сервера выберите из списка в поле "Health check" пункт "Сreate a health check":

В открывшемся мастере создания проверки состояния сервера укажите имя проверки, протокол TCP, порт 8081 и запрос /health-check. Настройте критерии проверки и нажмите кнопку "Save and continue":

Разверните секцию "Advanced creation options" и активируйте чекбокс "Do not retry machine creation". После чего нажмите "Create":

Будет создана группа масштабирования и запущен один WCS с ролью CDN Edge. Последним этапом настройки нашей CDN с балансировщиком нагрузки и автоматическим масштабированием будет настройка балансировщика.

Создаем балансировщик нагрузки

Сначала зарезервируем для балансировщика внешний IP адрес. В главном меню Google Cloud Platform в секции "VPC network" выберите пункт "External IP addresses" и нажмите кнопку "Reserve static address":

На открывшейся странице в поле "Name" задаем имя для зарезервированного IP адреса. Выбираем уровень качества сетевых услуг для адреса и тип распространения. После завершения всех настроек нажимаем кнопку "Reserve":

Затем переходим к настройке балансировщика.

Выбираем пункт "Load balancing" в разделе "Network services" секции "Networking" основного меню Google Cloud Platform:

Нажимаем кнопку "Create load balancer":

Затем выберите тип балансировщика "TCP Load Balancing" и нажмите кнопку "Start configuration":

На открывшейся странице укажите внешний балансировщик "From Internet to my VMs" и регион размещения серверов балансировщика. После выбора настроек нажмите кнопку "Continue":

На следующей странице задайте имя балансировщика, Затем перейдите в раздел настроек "Backend configuration" и укажите в каком регионе будут созданы сервера входящие в состав балансировщика. На вкладке "Select existing instance groups" выберите группу масштабирования Edge серверов, которую мы создали ранее. Затем в поле "Health check"выберите из выпадающего списка пункт "Сreate a health check":

На открывшейся странице укажите параметры для проверки состояния работы балансировщика порт 8081 и запрос /, после чего нажмите кнопку "Save and continue":

Затем перейдите к настройкам раздела "Frontend configuration". В этом разделе нужно создать привязку портов к внешнему IP адресу. Укажите внешний IP адрес для балансировщика, который мы зарезервировали выше и создайте конфигурации для TCP портов 8081, 8080, 8443, 8444 для HTTP(S) и WS(S). После создания необходимых портов нажмите кнопку "Create":

Балансировщик будет запущен. На этом развертывание CDN с балансировщиком и масштабированием можно считать завершенным. Переходим к тестированию.

Тестирование WebRTC CDN с балансировщиком и масштабированием на базе Google Cloud Platform

Методика тестирования

Для проведения нагрузочного тестирования, при создании группы масштабирования мы выставили порог загрузки процессора для срабатывания триггера на 20%. Тестирование будем проводить с использованием браузера Google Chrome и виртуальной вебкамеры для организации трансляции видеопотока. Что бы сымитировать повышение нагрузки на процессор запустим воспроизведение потока с транскодированием с помощью примера "Media Devices".

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

Тестирование

В браузере Google Chrome открываем web интерфейс WCS с ролью CDN Origin Авторизуемся, открываем пример "Two-way Streaming", устанавливаем соединение с сервером по WebSocket и публикуем видеопоток.

Затем, запускаем web интерфейс WCS CDN Edge сервера по IP адресу, который был зарезервирован при создании балансировщика.

Авторизуемся, открываем пример "Media Devices" и устанавливаем соединение с балансировщиком по WebSocket. В правом столбце настроек снимаем чек бокс "default" для параметра "Size" и задаем значения для транскодирования видеопотока. Например, если поток на Origin сервере опубликован с размерами 320х240 задаем значение 640х480. Повторите действия в нескольких вкладках браузера, для имитации большого количества зрителей.

В консоли Google Cloud видим, что были запущены две дополнительные виртуальные машины:

Для того, что бы проверить, что балансировщик распределяет соединения между активными ВМ можно использовать страницу статистики, которая доступна по адресу:

http://<WCS instance IP address>:8081/?action=stat

Откройте страницу статистики для каждой виртуальной машины, запущенной балансировщиком. Значение "connection_websocket" показывает количество активных WebSocket сессий на каждой виртуальной машине.

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

Хорошего стриминга!

Ссылки

Наш демо сервер

CDN для стриминга WebRTC с низкой задержкой - CDN на базе WCS

Документация по быстрому развертыванию и тестированию WCS сервера

Документация по развертыванию WCS в Google Cloud Platform

Документация по настройке балансировки нагрузки с масштабированием в GCP

Подробнее..

Как мы собираем общие сведения о парке из Kubernetes-кластеров

16.06.2021 10:13:29 | Автор: admin

Имея в обслуживании большой (более 150) парк Kubernetes-кластеров, всегда хотелось иметь удобное представление их общего состояния, в том числе и для того, чтобы поддерживать их гомогенными. В первую очередь нас интересовали следующие данные:

  • версия Kubernetes чтобы все кластеры были on the edge;

  • версия Deckhouse (наша Kubernetes-платформа) для лучшего планирования релизных циклов;

  • количество узлов с разбивкой по типам (управляющие, виртуальные и статические) для отдела продаж;

  • количество ресурсов (CPU, memory) на управляющих узлах;

  • на какой инфраструктуре запущен кластер (виртуальные облачные ресурсы, bare metal или гибридная конфигурация);

  • какой облачный провайдер используется.

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

Истоки и проверка концепции

В какой-то момент времени мы стали использовать Terraform для раскатки инфраструктуры в облака и вопрос отслеживания соответствия желаемых конфигураций реальности встал еще острее. Мы храним Terraform state в самих кластерах и проверку соответствия их с реальностью проверяет отдельно написанный Prometheus exporter. Хотя ранее у нас уже была информация для реагирования на изменения (через соответствующие алерты в системе управления инцидентами), хотелось ещё иметь полное представление о ситуации в отдельной аналитической системе.

Итак, изначально в качестве PoC был несложный Bash-скрипт, которым мы вручную время от времени собирали интересующие данные с K8s-кластеров по SSH. Он выглядел примерно так:

((kubectl -n d8-system get deploy/deckhouse -o json | jq .spec.template.spec.containers[0].image -r | cut -d: -f2 | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o name | wc -l | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o json | jq "if .items | length > 0 then .items[].status.capacity.cpu else 0 end" -r | sort -n | head -n 1 | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o json | jq "if .items | length > 0 then .items[].status.capacity.memory else \"0Ki\" end | rtrimstr(\"Ki\") | tonumber/1000000 | floor" | sort -n | head -n 1 | tr "\n" ";") &&(kubectl version -o json | jq .serverVersion.gitVersion -r | tr "\n" ";") &&(kubectl get nodes -o wide | grep -v VERSION | awk "{print \$5}" | sort -n | head -n 1 | tr "\n" ";") &&echo "") | tee res.csvsed -i '1ideckhouse_version;mastersCount;masterMinCPU;masterMinRAM;controlPlaneVersion;minimalKubeletVersion' res.csv

(Здесь приведен лишь фрагмент для демонстрации общей идеи.)

Однако количество клиентов и кластеров росло стало ясно, что дальше так жить нельзя. Мы ведь инженеры, поэтому всё, что может быть автоматизировано, должно быть автоматизировано.

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

  • собирал желаемую информацию,

  • агрегировал ее,

  • отправлял в какое-то централизованное хранилище.

а заодно соответствовал каноном высокой доступности и cloud native.

Этот путь дал начало истории модуля в Kubernetes-платформе Deckhouse, развёрнутой на всех наших кластерах, и сопутствующего ему хранилища.

Реализация

Хуки на shell-operator

В первой итерации источником данных в клиентских кластерах служили Kubernetes-ресурсы, параметры из ConfigMap/Deckhouse, версия образа Deckhouse и версия control-plane из вывода kubectl version. Для соответствующей реализации лучше всего подходил shell-operator.

Были написаны хуки (да, снова на Bash) с подписками на ресурсы и организована передача внутренних values. По результатам работы этих хуков мы получали список желаемых Prometheus-метрик (их экспорт поддерживается в shell-operator из коробки).

Вот пример хука, генерирующего метрики из переменных окружения, он прост и понятен:

#!/bin/bash -efor f in $(find /frameworks/shell/ -type f -iname "*.sh"); do  source $fdonefunction __config__() {  cat << EOF    configVersion: v1    onStartup: 20EOF}function __main__() {  echo '  {    "name": "metrics_prefix_cluster_info",    "set": '$(date +%s)',    "labels": {      "project": "'$PROJECT'",      "cluster": "'$CLUSTER'",      "release_channel": "'$RELEASE_CHANNEL'",      "cloud_provider": "'$CLOUD_PROVIDER'",      "control_plane_version": "'$CONTROL_PLANE_VERSION'",      "deckhouse_version": "'$DECKHOUSE_VERSION'"    }  }' | jq -rc >> $METRICS_PATH}hook::run "$@"

Отдельно хочу обратить ваше внимание на значение метрики (параметр set). Изначально мы писали туда просто 1, но возник резонный вопрос: Как потом получить через PromQL именно последние, свежие labels, включая те series, которые уже две недели не отправлялась? Например, в том же MetricsQL от VictoriaMetrics для этого есть специальная функция last_over_time. Оказалось, достаточно в значение метрики отправлять текущий timestamp число, которое постоянно инкрементируется во времени. Вуаля! Теперь стандартная функция агрегации max_over_time из Prometheus выдаст нам самые последние значения labels по всем series, которые приходили хоть раз в запрошенном периоде.

Чуть позже к источникам данных добавились метрики из Prometheus в кластерах. Для их получения был написан еще один хук, который через curl ходил в кластерный Prometheus, подготавливал полученные данные и экспортировал их в виде метрик.

Чтобы вписаться в парадигму cloud-native и обеспечить HA агента, мы запустили его в несколько реплик на управляющих узлах кластера.

Grafana Agent

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

Выбор пал на разработку Grafana Labs, а именно Grafana Agent. Он умеет делать scrape метрик с endpointов, отправлять их по протоколу Prometheus remote write, а также (что немаловажно!) ведет свой WAL на случай недоступности принимающей стороны.

Задумано сделано: и вот приложение из shell-operator и sidecarом с grafana-agent уже способно собирать необходимые данные и гарантировать их поступление в центральное хранилище.

Конфигурация агента делается довольно просто благо, все параметры подробно описаны в документации. Вот пример нашего итогового конфига:

server:  log_level: info  http_listen_port: 8080prometheus:  wal_directory: /data/agent/wal  global:    scrape_interval: 5m  configs:  - name: agent    host_filter: false    max_wal_time: 360h    scrape_configs:    - job_name: 'agent'      params:        module: [http_2xx]      static_configs:      - targets:        - 127.0.0.1:9115      metric_relabel_configs:      - source_labels: [__name__]        regex: 'metrics_prefix_.+'      - source_labels: [job]        action: keep        target_label: cluster_uuid        replacement: {{ .Values.clusterUUID }}      - regex: hook|instance        action: labeldrop    remote_write:    - url: {{ .Values.promscale.url }}      basic_auth:        username: {{ .Values.promscale.basic_auth.username }}        password: {{ .Values.promscale.basic_auth.password }}

Пояснения:

  • Директория /data это volumeMount для хранения WAL-файлов;

  • Values.clusterUUID уникальный идентификатор кластера, по которому мы его идентифицируем при формировании отчетов;

  • Values.promscale содержит информацию об endpoint и параметрах авторизации для remote_write.

Хранилище

Разобравшись с отправкой метрик, необходимо было решить что-то с централизованным хранилищем.

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

NB. Справедливости ради, хочется отметить, что на данный момент Cortex выглядит уже вполне жизнеспособным, оформленным как конечный продукт. Очень вероятно, что через какое-то время вернемся к нему и будем использовать. Уж очень сладко при мысли о generic S3 как хранилище для БД: никаких плясок с репликами, бэкапами и растущим количеством данных

К тому времени у нас была достаточная экспертиза по PostgreSQL и мы выбрали Promscale как бэкенд. Он поддерживает получение данных по протоколу remote-write, а нам казалось, что получать данные используя pure SQL это просто, быстро и незатратно: сделал VIEWхи и обновляй их, да выгружай в CSV.

Разработчики Promscale предоставляют готовый Docker-образ, включающий в себя PostgreSQL со всеми необходимыми extensions. Promscale использует расширение TimescaleDB, которое, судя по отзывам, хорошо справляется как с большим количеством данных, так и позволяет скейлиться горизонтально. Воспользовались этим образом, задеплоили connector данные полетели!

Далее был написан скрипт, создающий необходимые views, обновляющий их время от времени и выдающий на выход желаемый CSV-файл. На тестовом парке dev-кластеров всё работало отлично: мы обрадовались и выкатили отправку данных со всех кластеров.

Но с хранилищем всё не так просто

Первую неделю всё было отлично: данные идут, отчет генерируется. Сначала время работы скрипта составляло около 10 минут, однако с ростом количества данных это время увеличилось до получаса, а однажды и вовсе достигло 1 часа. Смекнув, что что-то тут не так, мы пошли разбираться.

Как оказалось, ходить в таблицы базы данных мимо магических оберток, предоставляемых Promscale (в виде своих функций и views, опирающихся в свою очередь на функции TimescaleDB), невероятно неэффективно.

Было решено перестать ковыряться в потрохах данных и положиться на мощь и наработки разработчиков Promscale. Ведь их connector может не только складывать данные в базу через remote-write, но и позволяет получать их привычным для Prometheus способом через PromQL.

Одним Bashем уже было не обойтись мы окунулись в мир аналитики данных с Python. К нашему счастью, в сообществе уже были готовы необходимые инструменты и для походов с PromQL! Речь про замечательный модуль prometheus-api-client, который поддерживает представление полученных данных в формате Pandas DataFrame.

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

Изначально мы выбрали период скрейпинга данных grafana-agentом равным одной минуте, что отразилось на огромных аппетитах конечной БД в диске: ~800 мегабайт данных в день. Это, конечно, не так много в масштабах одного кластера (~5 мегабайт), но когда кластеров много суммарный объём начинает пугать. Решение оказалось простым: увеличили период scrapeа в конфигах grafana-agentов до одного раза в 5 минут. Прогнозируемый суммарный объем хранимых данных с retentionом в 5 лет уменьшился с 1,5 Тб до 300 Гб, что, согласитесь, уже выглядит не так ужасающе.

Некоторый профит от выбора PostgreSQL как конечного хранилища мы уже получили: для успешного переезда хранилища в финальный production-кластер достаточно было отреплицировать базу. Единственное текущий недостаток пока не получилось самостоятельно собрать свой кастомный PostgreSQL с необходимыми расширениями. После пары неудачных попыток мы остались на готовом образе от разработчиков Promscale.

Получившаяся архитектура выглядит так:

Итоги и перспективы

Мы смотрим в будущее и планируем отказаться от отчётов в формате CSV в пользу красивого интерфейса собственной разработки. А по мере разработки собственной биллинговой системы начнём отгружать данные и туда для нужд отдела продаж и развития бизнеса. Но даже те CSV, что мы получили сейчас, уже сильно упрощают рабочие процессы всего Фланта.

А пока не дошли руки до фронтенда, мы сделали dashboard для Grafana (почему бы и нет, раз всё в стандартах Prometheus?..). Вот как это выглядит:

Общая сводная таблица по кластерам с Terraform-состояниямиОбщая сводная таблица по кластерам с Terraform-состояниямиРаспределение кластеров по облачным провайдерамРаспределение кластеров по облачным провайдерамРазбивка по используемым Inlet в Nginx Ingress-контроллерахРазбивка по используемым Inlet в Nginx Ingress-контроллерахКоличество podов Nginx Ingress-контроллеров с разбивкой по версиямКоличество podов Nginx Ingress-контроллеров с разбивкой по версиям

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

P.S.

Читайте также в нашем блоге:

Подробнее..

Перевод Как работает single sign-on (технология единого входа)?

17.06.2021 16:13:58 | Автор: admin

Что такое single sign-on?


Технология единого входа (Single sign-on SSO) метод аутентификации, который позволяет пользователям безопасно аутентифицироваться сразу в нескольких приложениях и сайтах, используя один набор учетных данных.


Как работает SSO?


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


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


  1. Пользователь заходит в приложение или на сайт, доступ к которому он хочет получить, то есть к провайдеру услуг.
  2. Провайдер услуг отправляет токен, содержащий информацию о пользователе (такую как email адрес) системе SSO (так же известной, как система управления доступами), как часть запроса на аутентификацию пользователя.
  3. В первую очередь система управления доступами проверяет был ли пользователь аутентифицирован до этого момента. Если да, она предоставляет пользователю доступ к приложению провайдера услуг, сразу приступая к шагу 5.
  4. Если пользователь не авторизовался, ему будет необходимо это сделать, предоставив идентификационные данные, требуемые системой управления доступами. Это может быть просто логин и пароль или же другие виды аутентификации, например одноразовый пароль (OTP One-Time Password).
  5. Как только система управления доступами одобрит идентификационные данные, она вернет токен провайдеру услуг, подтверждая успешную аутентификацию.
  6. Этот токен проходит сквозь браузер пользователя провайдеру услуг.
  7. Токен, полученный провайдером услуг, подтверждается согласно доверительным отношениям, установленным между провайдером услуг и системой управления доступами во время первоначальной настройки.
  8. Пользователю предоставляется доступ к провайдеру услуг.

image

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


Что такое токен в контексте SSO?


Токен это набор информации или данных, который передается из одной системы в другую в процессе исполнения SSO. Данные могут быть просто email адресом и информацией о системе, отправившей токен. Токены должны обладать цифровой подписью для получателя, чтобы подтвердить, что он поступил из надежного источника. Сертификат для электронной подписи предоставляется во время первоначального этапа настройки.


Является ли технология SSO безопасной?


Ответом на этот вопрос будет "в зависимости от ситуации".


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


SSO также сокращает количество времени, потраченного на восстановление пароля с помощью службы поддержки. Администраторы могут централизованно контролировать такие факторы, как сложность пароля и многофакторную аутентификацию (MFA). Администраторы также могут быстрее отозвать привилегии на вход в систему, если пользователь покинул организацию.


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


Как внедрить SSO?


Особенности внедрения SSO могут отличаться с учетом того, с каким именно решением SSO вы работаете. Но вне зависимости от способа, вам нужно точно знать какие цели вы преследуете. Убедитесь, что вы ответили на следующие вопросы:


  • С какими типами пользователей вы работаете и какие у них требования?
  • Вы ищете локальное или облачное решение?
  • Возможен ли дальнейший рост выбранной программной платформы вместе с вашей компанией и ее запросами?
  • Какие функции вам необходимы, чтобы убедиться в том, что процесс авторизации проходят только проверенные пользователи? MFA, Adaptive Authentication, Device Trust, IP Address Whitelisting, и т.д?
  • С какими системами вам необходимо интегрироваться?
  • Нужен ли вам доступ к программному интерфейсу приложения (API)?

Что отличает настоящую SSO от хранилища или менеджера паролей?


Важно понимать разницу между SSO (Технологией единого входа) и хранилищем или менеджерами паролей, которые периодически путают с SSO, но в контексте Same Sign-On что означает такой же/одинаковый вход, а не единый вход (Single Sign-On). Говоря о хранилище паролей, у вас может быть один логин и пароль, но их нужно будет вводить каждый раз при переходе в новое приложение или на новый сайт. Такая система попросту хранит ваши идентификационные данные для других приложений и вводит их когда это необходимо. В данном случае между приложением и хранилищем паролей не установлены доверительные отношения.


С SSO, после того, как вы вошли в систему, вы можете получить доступ ко всем одобренным компанией сайтам и приложениям без необходимости авторизовываться снова. Это включает в себя как облачные, так и локально установленные приложения, часто доступные через сам сервис SSO (также известный, как сервис авторизации).


В чем разница между программным обеспечением единого входа и решением SSO?


Изучая доступные варианты единого входа, вы можете увидеть, что их иногда называют программным обеспечением единого входа, а не решением единого входа или провайдером единого входа. Часто разница состоит лишь в том, как позиционируют себя компании. Фрагмент программного обеспечения предполагает локальную установку. Обычно это то, что разработано для определенного набора задач и ничего более. Программный продукт предполагает, что есть возможность расширяться и кастомизировать потенциальные возможности исходного варианта. Провайдер будет отличным вариантом, чтобы обратиться к компании, которая производит или пользуется программным продуктом. Например, OneLogin в качестве провайдера SSO.


Бывают ли разные типы SSO?


Когда мы говорим о едином входе (SSO), используется множество терминов:


  • Federated Identity Management (FIM)
  • OAuth (OAuth 2.0 в настоящее время)
  • OpenID Connect (OIDC)
  • Security Access Markup Language (SAML)
  • Same Sign On (SSO)

На самом деле, SSO это часть более крупной концепции под названием Federated Identity Management, поэтому иногда SSO обозначается, как федеративная SSO. FIM просто относится к доверительным отношениям, созданным между двумя или более доменами или системами управления идентификацией. Система единого входа (SSO) это характеристика/фича, доступная внутри архитектуры FIM.


OAuth 2.0 это особая программная платформа, которая также может считаться частью архитектуры FIM. OAuth фокусируется на доверительных отношениях, предоставляя доменам идентификационную информацию пользователя.


OpenID Connect (OIDC) это уровень аутентификации, наложенный на базу OAuth 2.0, чтобы обеспечить фунциональность SSO.


Security Access Markup Language (SAML) это открытый стандарт, который также разработан для обеспечения функциональности SSO.


image

Система Same Sign On, которую часто обозначают, как SSO, на самом деле, не похожа Single Sign-on, т.к не предполагает наличие доверительных отношений между сторонами, которые проходят аутентификацию. Она более привязана к идентификационным данным, которые дублируются и передаются в другие системы когда это необходимо. Это не так безопасно, как любое из решений единого входа.


Также существуют несколько конкретных систем, которые стоит упомянуть, говоря о платформе SSO: Active Directory, Active Directory Federation Services (ADFS) и Lightweight Directory Access Protocol (LDAP).


Active Directory, который в настоящее время именуется, как Active Directory Directory Services (ADDS) это централизованная служба каталогов Microsoft. Пользователи и ресурсы добавляются в службу каталогов для централизованного управления, а ADDS работает с такими аутентификационными протоколами, как NTLM и Kerberos. Таким образом, пользователи, относящиеся к ADDS могут аутентифицироваться с их устройств и получить доступ к другим системам, интегрированным с ADDS. Это и есть форма SSO.


Active Directory Federation Services (ADFS) это тип управления федеративной идентификацией (Federated Identity Management system), которая также предполагает возможность Single Sign-on. Он также поддерживает SAML и OIDC. ADFS преимущественно используется для установления доверительных отношений между ADDS и другими системами, такими как Azure AD или других служб ADDS.


Протокол LDAP (Lightweight Directory Service Protocol) это стандарт, определяющий способ запроса и организации информационной базы. LDAP позволяет вам централизованно управлять такими ресурсами, как пользователи и системы. LDAP, однако, не определяет порядок авторизации, это означает, что он не устанавливает непосредственный протокол, используемый для аутентификации. Но он часто применяется как часть процесса аутентификации и контроля доступа. Например, прежде, чем пользователь получит доступ к определенному ресурсу, LDAP сможет запросить информацию о пользователе и группах, в которых он состоит, чтобы удостовериться, что у пользователя есть доступ к данному ресурсу. LDAP платформа на подобие OpenLDAP обеспечивает аутентификацию с помощью аутентификационных протоколов (например, Simple Authentication и Security Layer SASL).


Как работает система единого входа как услуга?


SSO функционирует также, как и многие другие приложения, работающие через интернет. Подобные OneLogin платформы, функционирующие через облако, можно отнести к категории решений единого входа Software as a Service (SaaS).


Что такое App-to-App (приложение-приложение) SSO?


В заключение, возможно вы слышали о App-to-App SSO. Пока еще такой подход не является стандартным. Такое понятие больше используется SAPCloud для обозначения процесса передачи идентификационных данных пользователя из одного приложения в любое из других, состоящих в их экосистеме. В какой-то степени такой метод присущ OAuth 2.0, но хочется снова подчеркнуть, что это не стандартный протокол или метод. В настоящее время он является характерным только для SAPCloud.

Подробнее..

Пишем простейший GitHub Action на TypeScript

09.06.2021 16:12:52 | Автор: admin

Недавно я решил немного привести в порядок несколько своих .NET pet-проектов на GitHub, настроить для них нормальный CI/CD через GitHub Actions и вынести всё в отдельный репозиторий, чтобы все скрипты лежали в одном месте. Для этого пришлось немного поизучать документацию, примеры и существующие GitHub Actions, выложенные в Marketplace.

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

Статья в первую очередь рассчитана на начинающих, тех, кто никогда не использовал GitHub Actions, но хотел бы быстро начать. Тем не менее, даже если у вас уже есть подобный опыт, но вы, например, не использовали ncc, то, возможно, и для вас в ней будет что-то полезное.

Краткое введение в GitHub Actions

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

Чтобы добавить рабочий процесс, нужно создать в репозитории один или несколько yml-файлов в папке .github/workflows. Простейший файл может выглядеть следующим образом:

name: Helloon: [push]jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        run: echo "Hello, GitHub Actions!"

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

Сам рабочий процесс мы можем найти на вкладке Actions в интерфейсе GitHub и посмотреть детальную информацию по нему:

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

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

Сами действия представляют из себя отдельный кусок рабочего процесса, который можно повторно использовать в нескольких проектах. Они могут быть трёх видов: докер контейнер, действие JavaScript, или составные действия (которые по сути просто переиспользуемый набор других действий). Вариант с действием JavaScript мы как раз и рассмотрим в этой статье, но писать его будем на TypeScript для большего удобства.

(Более подробную информацию по возможностям рабочих процессов можно найти в документации)

Пара слов о месте размещения действий в репозитории

Действия можно разместить в репозитории в нескольких местах в зависимости от потребностей:

  • В подпапке .github/actions. Такой способ обычно используется когда вы хотите использовать эти действия из того же репозитория. В этом случае ссылаться на них необходимо по пути без указания ветки или тега:

    steps:  - uses: ./.github/actions/{path}
    
  • В произвольном месте репозитория. Например, можно разместить несколько действий в корне репозитория, каждое в своей подпапке. Такой способ хорошо подходит, если вы хотите сделать что-то вроде личной библиотеки с набором действий, которые собираетесь использовать из разных проектов. В этом случае ссылаться на такие действия нужно по названию репозитория, пути и ветке или тегу:

    steps:  - uses: {owner}/{repo}/{path}@{ref}
    
  • В корне репозитория. Такой способ позволяет разместить в одном репозитории только одно действие, и обычно используется если вы хотите позже опубликовать его в Marketplace. В этом случае ссылаться на такие действия нужно по названию репозитория и ветке или тегу:

    steps:  - uses: {owner}/{repo}@{ref}
    

Создаём действие на TypeScript

В качестве примера создадим очень простое действие, которое просто выводит сообщение Hello, GitHub Actions!. Для разработки действия нам потребуется установленная версия Node.js (я использовал v14.15.5).

Создадим в репозитории папку .github/actions. В ней создадим подпапку hello, в которой будем далее создавать все файлы, относящиеся к нашему действию. Нам потребуется создать всего четыре файла.

Создаём файл action.yml:

name: Hellodescription: Greet someoneruns:  using: node12  main: dist/index.jsinputs:  Name:    description: Who to greet    required: true

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

Также мы указываем, что это именно JavaScript действие, а не докер контейнер и указываем точку входа: файл dist/index.js. Этого файла у нас пока нет, но он будет автоматически создан чуть позже.

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

(Более подробную информацию по возможностям файла метаданных можно найти в документации)

Создаём файл package.json:

{  "private": true,  "scripts": {    "build": "npx ncc build ./src/main.ts"  },  "dependencies": {    "@actions/core": "^1.2.7",    "@actions/exec": "^1.0.4",    "@actions/github": "^4.0.0",    "@actions/glob": "^0.1.2",    "@types/node": "^14.14.41",    "@vercel/ncc": "^0.28.3",    "typescript": "^4.2.4"  }}

Это стандартный файл для Node.js. Чтобы не указывать бесполезные атрибуты типа автора, лицензии и т.п. можно просто указать, что пакет private. (При желании можно конечно всё это указать, кто я такой, чтобы вам это запрещать =)

В скриптах мы указываем один единственный скрипт сборки, который запускает утилиту ncc. Эта утилита на вход получает файл src/main.ts и создаёт файл dist/index.js, который является точкой входа для нашего действия. Я вернусь к этой утилите чуть позже.

В качестве зависимостей мы указываем typescript и @types/node для работы TypeScript. Зависимость @vercel/ncc нужна для работы ncc.

Зависимости @actions/* опциональны и являются частью GitHub Actions Toolkit - набора пакетов для разработки действий (я перечислил самые на мой взгляд полезные, но далеко не все):

  • Зависимость @actions/core понадобится вам вероятнее всего. Там содержится базовый функционал по выводу логов, чтению параметров действия и т.п. (документация)

  • Зависимость @actions/exec нужна для запуска других процессов, например dotnet. (документация)

  • Зависимость @actions/github нужна для взаимодействия с GitHub API. (документация)

  • Зависимость @actions/glob нужна для поиска файлов по маске. (документация)

Стоит также отметить, что формально, поскольку мы будем компилировать наше действие в один единственный файл dist/index.js через ncc, все зависимости у нас будут зависимостями времени разработки, т.е. их правильнее помещать не в dependencies, а в devDependencies. Но по большому счёту никакой разницы нет, т.к. эти зависимости вообще не будут использоваться во время выполнения.

Создаём файл tsconfig.json:

{  "compilerOptions": {    "target": "ES6",    "module": "CommonJS",    "moduleResolution": "Node",    "strict": true  }}

Тут всё достаточно просто. Это минимальный файл, с которым всё хорошо работает, включая нормальную подсветку синтаксиса и IntelliSense в Visual Studio Code.

Создаём файл src/main.ts:

import * as core from '@actions/core'async function main() {  try {    const name = core.getInput('Name');    core.info(`Hello, ${name}`);  } catch (error) {    core.setFailed(error.message)  }}main();

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

Тело функции необходимо обернуть в блок try-catch, чтобы в случае любых ошибок сборка в GitHub прерывалась. Без этого она всегда будет считаться успешной.

В данном конкретном случае мы также используем пакет @actions/core для чтения параметров и вывода сообщения в лог.

Собираем действие при помощи ncc

После того, как все файлы созданы нам первым делом необходимо восстановить все пакеты из npm. Для этого нам нужно перейти в папку действия (не в корень репозитория) и выполнить команду:

npm install

А дальше возникает нюанс. Если прочитать официальную документацию, чтобы GitHub мог корректно загрузить и выполнить действие, все скрипты, включая зависимости должны находиться в том же репозитории. Это означает, что нам по сути предлагается закоммитить папку node_modules в репозиторий, что, на мой взгляд, не очень красивое решение.

В качестве альтернативы можно воспользоваться пакетом @vercel/ncc, который позволяет собрать js или ts-файлы в один единственный js-файл, который уже можно закоммитить в репозиторий.

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

npm run build

В результате мы получим файл dist/index.js, который нужно будет закоммитить в репозиторий вместе с остальными файлами. Папка node_modules при этом может быть спокойно отправлена в .gitignore.

Используем действие

Чтобы протестировать действие, создадим в папке .github/workflows файл рабочего процесса. Для разнообразия сделаем так, чтобы он запускался не по пушу, а вручную из интерфейса:

name: Helloon:  workflow_dispatch:    inputs:      Name:        description: Who to greet        required: true        default: 'GitHub Actions'jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        uses: ./.github/actions/hello        with:          Name: ${{ github.event.inputs.Name }}

Здесь настройки workflow_dispatch описывают форму в интерфейсе GitHub в которую пользователь сможет ввести данные. Там у нас будет одно единственное поле для ввода имени для приветствия.

Данные, введённые в форму через событие передаются в действие, которое мы запускаем в рабочем процессе через параметр github.event.inputs.Name.

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

После того, как мы запушим наш рабочий процесс, мы можем перейти в интерфейс GitHub, на странице Actions выбрать наш рабочий процесс и запустить его выполнение, указав параметры:

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

Настраиваем GitHooks для автоматической сборки

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

Каждый раз, когда мы будем изменять код действия нам нужно не забыть вызвать команду:

npm run build

По моему опыту такой ручной запуск постоянно будет приводить к ситуации "Я вроде поменял код и запушил его на сервер, но у меня ничего не поменялось. А, блин, я же забыл пересобрать файлы...".

Решить эту проблему можно несколькими способами. Можно не париться и действительно каждый раз запускать сборку руками. В некоторых проектах люди подходят к этому основательно и делают специальный рабочий процесс через тот же GitHub Actions, который на каждый коммит пытается пересобрать действие и коммитит изменения.

Я для себя себя решил проблему проще и просто добавил гит хук, который автоматически пересобирает все действия в репозитории на каждый коммит. Для этого необходимо создать в репозитории папку .githooks, а в ней файл pre-commit следующего вида:

#!/bin/shfor action in $(find ".github/actions" -name "action.yml"); do    action_path=$(dirname $action)    action_name=$(basename $action_path)    echo "Building \"$action_name\" action..."    pushd "$action_path" >/dev/null    npm run build    git add "dist/index.js"    popd >/dev/null    echodone

Здесь мы находим все файлы action.yml в папке .github/actions и для всех найденных файлов запускаем сборку из их папки. Теперь нам не нужно будет думать о явной пересборке наших действий, она будет делаться автоматически.

Чтобы хуки из папки .githooks запускались, нам необходимо поменять конфигурацию для текущего репозитория:

git config core.hooksPath .githooks

Или можно сделать это глобально (я сделал именно так):

git config --global core.hooksPath .githooks

Заключение

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

Репозиторий с примером можно найти тут: https://github.com/Chakrygin/hello-github-action

Подробнее..

Чему можно научиться у фикуса-душителя? Паттерн Strangler

12.06.2021 20:19:26 | Автор: admin

Ссылка на статью в моем блоге

Тропические леса и фикусы-душители

В тропических лесах, где всегда тепло, влажно и много зелени живет одно интересное растение. С необычным названием фикус-душитель. Почему он получил такое имя? Как из фильма ужасов.

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

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

Рефакторинг сервиса приложения доставки продуктов

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

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

Допустим имеются следующие действия, которые у нас хранятся в одной таблице:

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

  • Можно оформитьвозврат товара. Если вам не понравился кефир - вы оформляете возврат и вам возвращают его цену.

  • Можносписать бонусысо счета. В таком случае часть стоимости оплачивается этими бонусами.

  • Начисляются бонусы. Каким-либо алгоритмом нам не важно каким конкретно.

  • Также заказ может бытьзарегистрирован в некотором приложении-партнере(ExternalOrder)

Все перечисленная информация по заказам и пользователям хранится в таблице (пусть она будет называтьсяOrderHistory):

id

operation_type

status

datetime

user_id

order_id

loyality_id

money

234

Order

Open

2021-06-02 12:34

33231

24568

null

1024.00

233

Order

Open

2021-06-02 11:22

124008

236231

null

560.00

232

Refund

null

2021-05-30 07:55

3456245

null

null

-2231.20

231

Order

Closed

2021-05-30 14:24

636327

33231

null

4230.10

230

BonusAccrual

null

2021-05-30 09:37

568458

null

33231

500.00

229

Order

Closed

2021-06-01 11:45

568458

242334

null

544.00

228

BonusWriteOff

null

2021-05-30 22:15

6678678

8798237

null

35.00

227

Order

Closed

2021-05-30 16:22

6678678

8798237

null

640.40

226

Order

Closed

2021-06-01 17:41

456781

2323423

null

5640.00

225

ExternalOrder

Closed

2021-06-01 23:13

368358

98788

null

226.00

Логика такой организации данных вполне справедлива на раннем этапе разработки системы. Ведь наверняка пользователь может посмотреть историю своих действий. Где он одним списком видит что он заказывал, как начислялись и списывались бонусы. В таком случае мы просто выводим записи, относящиеся к нему за указанный диапазон. Организовать в виде одной таблицы банальная экономия на создании дополнительных таблиц, их поддержании. Однако, по мере роста бизнес-логики и добавления новых типов операций число столбцов с null значениями начало расти. Записей в таблице сотни миллионов. Причем распределены они очень неравномерно. В основном это операции открытия и закрытия заказов. Но вот операции начисления бонусов составляют 0.1% от общего числа, однако эти записи используются при расчете новых бонусов, что происходит регулярно.В итоге логика расчета бонусов работает медленнее, чем если бы эти записи хранились в отдельной таблице. Ну и расширять таблицу новыми столбцами не хотелось бы в дальнейшем. Кроме того заказы в закрытом статусе с датой создания более 2 месяцев для бизнес-логики интереса не представляют. Они нужны только для отчетов не более.

И вот возникает идея.Разделить таблицу на две, три или даже больше.

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

Изменение структуры хранения в три этапа

Предположим, что наше legacy монолитное приложение хоть и плохое, но не совсем. Как минимум зарезервировано. То есть работает как минимум два экземпляра. Так, что при падении одного из них - второй продолжит обслуживать пользователей:

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

Оба экземпляра работают с одной базой данных. Реализуя паттернShared Database.

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

Отдельная новая база данных вполне может появиться. Однако не всегда. Ввиду сложностей обеспечения транзакционности между двумя БД. Все в конечном счете зависит от реализации и от ограничений бизнес-логики.

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

BonusOperations:

id

operation_type

datetime

user_id

order_id

loyality_id

money

230

BonusAccrual

2021-05-30 09:37

568458

null

33231

500.00

228

BonusWriteOff

2021-05-30 22:15

6678678

8798237

null

35.00

Отдельную таблицу для данных из внешних систем -ExternalOrders:

id

status

datetime

user_id

order_id

money

225

Closed

2021-06-01 23:13

368358

98788

226.00

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

Для оставшихся типов записей -OrderHistoryArchive(старше 2х недель). Где теперь также можно удалить несколько лишних столбцов.

Выделение таких архивных данных часто бывает удобным. Если оперативная часть очень требовательна к производительности она вполне может себе размещается на быстрых SSD дисках. В то время как архивные данные могут использоваться один раз в месяц для отчета. И их больше в разы. Поэтому размещая их на дешевых дисках мы экономим иногда вполне приличную сумму.

По схеме выше мы видим, что версия начала дублировать всю информацию в новую структуру данных. Но пока использует в своей бизнес-логике данные из старой структуры. Запрос обработанный версией 2 записывается именно в том формате, в котором его ожидает версия 1. Запрос обработанный версией 1 сохранит данные, которые также используются в работе версии 2.

Монолит версии 1 и монолит версии 2 вполне могут работать совместно. Лишь для тех запросов, которые обрабатывались монолитом версии 1 в новой базе данных будут пробелы. Эти пробелы, а также недостающие данные можно будет в дальнейшем скопировать отдельным скриптом или утилитой.

Спустя какое-то время работы версии 2 мы получим заполненную новую базу данных. Если все хорошо, то мы готовы к следующей стадии переводу основной бизнес-логики на новую базу данных.

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

Итого. Внешне система никогда не менялась. Однако внутренняя организация радикально преобразилась. Возможно под капотом теперь работает новая система. Которая лишена недостатков предыдущей. Не напоминает фикусов-душителей? Что-то похожее есть. Поэтому именно такое название паттерн и получил Strangler.

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

Выводы

  • ПаттернStranglerпозволяет совершенствовать системы с высокими требованиями к SLA.

  • Для обновления системы без простоя нужно сделать как минимум 3 последовательных развертования на продакшен. Это одна из причин, почему системы требовательные к показателям общего простоя заметно дороже.

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

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

Подробнее..

Перевод Культура разработки ПО слишком позитивна, это может нам вредить

14.06.2021 12:13:27 | Автор: admin
image

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

Выгорание


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

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

Но как насчёт людей, которые не любят программирование, но всё же хорошо справляются со своей работой? Попробуйте сказать своим коллегами или в Twitter, что вы один из них, и вас сразу же заклеймят как плохого разработчика. Однако на самом ли деле это так? Люди, которые делают свою работу, а после работы занимаются чем-то другим, тоже могут быть потрясающими программистами. Не каждый обязан приходить домой и трудиться над хобби-проектами, писать посты в блог, записывать видео в YouTube о кодинге и читать книги о программировании.

Я был одним из тех людей, которые пытаются всё своё свободное время работать. Многие годы я чувствовал вину за то, что не трачу всё время на преследование какой-то воображаемой продуктивности. Догадайтесь, к чему это меня привело?


Новые блестящие игрушки


Ещё один аспект, в отношении которого негатив воспринимается в штыки это отношение к новым трендам, создаваемым компаниями уровня FAANG. Попробуйте сказать что-нибудь против SOA или Docker. Попробуйте предложить более взрослый и зрелый язык или SSR. Это аналогично ситуации со страстью к работе. Люди мгновенно заявят, что ты плохой разработчик, потому что ты препятствуешь прогрессу.

Не у всех работают тысячи микросервисов, как у Uber, и не каждой компании требуется K8S. Однако сложно удержаться от всеобщего энтузиазма, или хотя бы не делать вид, что ты его разделяешь. Скольким организациям не удалось мигрировать на React или Angular? В результате они получили кодовую базу, разделённую на старый плохой код, который работает, и новый код, который разработчики пытаются заставить работать.

Эта недавняя статья показывает реальность многих организаций: I Almost Got Fired for Choosing React in Our Enterprise App

Best Practices


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

Какие из этих универсальных best practices на самом деле не универсальны? DRY (Dont Repeat Yourself) часто упоминается в одном предложении с KISS (Keep It Simple Stupid), хотя часто они являются взаимоисключающими практиками. Simple означает отсутствие необязательных абстракций, однако если начинать сразу с DRY-кода, то он и ведёт к преждевременным абстракциям.

Лично я пользуюсь правилом 3X:

У нас есть одержимость многократным использованием. Но не корите себя за это, это эндемичное заболевание среди разработчиков ПО. Даже, пожалуй, профессиональная болезнь.



Многократно используемые компоненты создавать в три раза сложнее, чем одноразовые

Многократно используемый компонент должен быть проверен в трёх различных областях применения, прежде чем его можно будет считать достаточно обобщённым, чтобы добавить в многократно используемую библиотеку. Coding Horror

Разумеется, я не считаю это абсолютной истиной. Скорее, это эмпирический закон, чем хорошая практика. Но даже здесь мы сталкиваемся с той же проблемой. Люди, которые осмеливаются предложить использовать синглтон или любой другой ненавидимый антипаттерн, воспринимаются как не очень хорошие разработчики.

Подведём итог


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

От нас, разработчиков, ожидают, что мы должны любить программирование. Но почему? Истина проста: это хорошо для работодателей. Сколько ужасных историй мы слышали из геймдева? Тем не менее, множество молодых людей мечтает о работе в игровой индустрии, хотя спустя несколько лет их заменят на новую поросль наивной молодёжи.

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

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

Я хочу пробовать что-то другое, и это совершенно нормально.



На правах рекламы


Наша компания предлагает аренду VPS для совершенно любых проектов. Создайте собственный тарифный план в пару кликов, максимальная конфигурация позволит разместить практически любой проект 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe!

Подписывайтесь на наш чат в Telegram.

Подробнее..

Перевод Постмортем инцидентов для начинающих

15.06.2021 14:10:21 | Автор: admin

image


Успешные постмортемы без поиска виноватых помогают учиться на инцидентах, чтобы не допускать подобных ошибок в будущем.


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


Зачем нужен постмортем?


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


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


Раз инциденты будут возникать в любом случае, они должны приносить пользу, а не вред.


В каком-то смысле, неопределенность, беспорядок, ошибки и время идут на пользу антихрупким системам
Antifragile, Nassim Nicholas Taleb (2012)

При сбое мы узнаем о системе что-то новое, особенно о скрытых связях между компонентами.
Допустим, у нас есть простая система с тремя компонентами (A, B и C) со следующими свойствами:


  • A связан с B;
  • A связан с C;
  • между B и C никаких связей не наблюдается;
  • любой процесс, порожденный A, открывает соединение с B и C.

Вот как выглядит эта схема:


image


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


Мы открыли скрытую связь между B и C. Вот как выглядит новая схема:


image


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


А если она будет повторяться, нужно описать, как смягчить ее последствия и ускорить разрешение.


Когда проводить постмортем


Когда в системе происходит достаточно серьезный инцидент.


В теории любой инцидент. На практике вам вряд ли хватит ресурсов, поэтому начните с инцидентов, которые напрямую влияют на клиентов и стейкхолдеров.


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


Что нужно делать


Опишите, что произошло. Четко сформулируйте последствия инцидента:


  • Кто?
  • Что?
  • Как долго?

Составьте хронику событий и коммуникаций между участниками по поводу инцидента.
Попробуйте найти первопричину, если с ней можно что-то сделать:


  • пять почему;
  • практичность важнее истины;
  • процесс поиска первопричины нужно документировать.

Между истиной и практической пользой есть важное различие.
Data & Reality, William Kent (1978)

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


Решайте проблемы коллективно, чтобы быстро учиться новому.
The DevOps Handbook, Gene Kim, Jez Humble, Patrick Debois, John Willis (2006)

Предложите улучшение для системы


Если это случится снова:


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

Обращайте внимание на поток информации, фидбэк и задержки в коммуникациях:


  • Мы вовремя узнали об инциденте?
  • Все нужные люди получили уведомление?
  • Нужные подсистемы (например, автомасштабирование) получили нужный фидбэк?

Измените длительность задержки, и поведение всей системы может серьезно измениться.
Thinking in Systems, Donella H. Meadows (2008)

Чего не нужно делать


Никого не обвиняйте


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


Не копайте слишком глубоко


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


Что делать с документом?


Главная цель извлечь урок для себя и своей организации. Документы по постмортему должны быть:


  • доступны для поиска;
  • открыты максимально широкой аудитории (без раскрытия конфиденциальной информации);
  • понятны аудитории (инженерам, стейкхолдерам, пользователям и т. д.).

Для вдохновения


Вот список общедоступных (и подробных) постмортемов:


Подробнее..

Kubernetes Headless Service А если Pod исчез?

17.06.2021 18:06:38 | Автор: admin

Мы столкнулись с достаточно занятным поведением при работе с Headless-сервисом в Kubernetes. В нашем случае проблема возникла с mongos, но она актуальна для любого Headless-сервиса. Приглашаю вас почитать нашу историю и самим попробовать поиграться с этой проблемой локально.

На одном из проектов мы используем MongoDB и Kubernetes. У MongoDB есть компонент: mongos. Через него выполняются запросы в шардированном MongoDB кластере (можно считать, что это просто хитрый proxy). До переезда в Kubernetes сервисы mongos устанавливались непосредственно на каждый хост.

При переезде сервисов в Kubernetes мы поселили пул mongos в Headless-сервис с автоматическим масштабированием Deployment через HPA (Horizontal Pod Autoscaler).

Через некоторое время выяснилось, что приложению при уменьшении количества Pod с mongos становится не очень хорошо.

Путем отладки выяснилось, что приложение подвисает именно при попытке установить подключение с mongos (net.Dialв терминах Go) и по времени совпадает с остановкой какого-либо Pod.

Для начала надо уточнить, что такое Headless-сервис: это сервис, который не использует отдельный IP-адрес для маршрутизации запросов (ClusterIP: None). В этом случае под DNS-именем сервиса видны IP всех Pod, которые в этот сервис входят.

Headless-сервисы полезны, когда приложение само должно управлять тем, к какому Pod подключаться, например:

  • mongodb-клиент использует IP сервера, с которым он работает, для того, чтобы запросы для одного курсора шли на один хост (курсор живёт на mongos). В случае использованияClusterIPмогут теряться курсоры даже для коротких запросов.

  • gRPC-клиенты держат по одному соединению с сервисами и сами управляют запросами, мультиплексируя запросы к одному серверу. В случае использованияClusterIPклиент может создать одно подключение и нагружать ровно один Pod сервера.

Так как клиент сам управляет, к каким Pod он подключается, возможна ситуация, когда клиент помнит IP-адрес уже удалённого Pod. Причины этого просты:

  • список Pod передаётся через DNS, а DNS кэшируется;

  • клиент сам по себе кэширует ответы от DNS и список сервисов.

Что же происходит в случае, если клиент пытается подключиться к уже несуществующему Pod?

А в этом случае запросы уходят уже на немаршрутизируемый хост и на них никто не отвечает. Так как ответа нет, клиент начинает слать повторные запросы на подключение пока не пробьёт таймаут.

При этом, в случае если Pod еще не поднялся или был отстрелен по Out of Memory, но еще не был удалён, то при попытке подключиться клиент получает ошибку connection refused практически сразу. И это гораздо более гуманное решение, чем ждать у моря погоды пока не пробьём таймаут.

Когда стала понятна причина, решить проблему было делом техники.

  • Мы добавили ожидание сигналаSIGTERMв Pod с mongos. При получении этого сигнала мы продолжали работать еще 45 секунд до времени инвалидации DNS (чтобы адреса новых Pod доехали до клиента). После этой паузы завершали mongos и делали еще одну паузу в 15 секунд (чтобы переподключение по старому IP отшивалось по ошибке connection refused, а не таймауту).

  • Мы выставилиterminationGracePeriodSecondsв две минуты, чтобы Pod принудительно не отстрелили до его завершения.

Небольшая ремарка по поводу minReadySeconds

Проблема с остановкой Pod наиболее ярко проявляет себя при перевыкатке сервисов.

Изначально первопричиной казалось то, что выкатка успевает завершиться быстрее, чем обновляются кэши IP-адресов сервиса в клиентском приложении (клиент пытается идти на старые Pod которых нет, а про новые он еще не знает).

Для исправления мы просто замедлили выкатку с помощью параметраminReadySeconds. Это сделало проблему менее острой, но не решило её: остались таймауты при подключении к IP для уже не существующего Pod.

Тем не менее параметрminReadySecondsполезен из-за того, что выкатка не ждёт завершения удаления Pod после перехода его в состояниеTerminating. В результате при раскатке сервиса мы можем на время добавленных пауз получить x2 Pod.

К тому же, если на клиенте не возникает нежелательных эффектов от недоступности части IP-адресов сервиса, то задержку для инвалидации DNS можно переместить вminReadySeconds.

Примером, для которого достаточно толькоminReadySecondsявляются gRPC-сервисы: там клиент держит по одному подключению к каждому серверу и раскидывает запросы между уже имеющимся подключениями, а не подключается к сервису при создании клиентской сессии.

Как поиграться с этой проблемой локально?

Эту ситуацию можно легко воспроизвести в MiniKube на примере nginx.

Для этого надо понадобится headless Service (service.yml):

---apiVersion: v1kind: Servicemetadata:  name: nginxspec:  clusterIP: None  selector:    app: nginx  ports:    - protocol: TCP      port: 80      targetPort: 80

И тестовая утилита (dialer.go):

package mainimport ("fmt""net""os""time")const timeFormat = "15:04:05.999"func main() {address := os.Args[1]last := ""ticker := time.NewTicker(time.Millisecond * 100)t := time.Now()fmt.Printf("%s: === %s\n", t.Format(timeFormat), address)for {conn, err := net.DialTimeout("tcp", address, time.Millisecond*100)var msg stringif conn != nil {msg = fmt.Sprintf("connected (%s)", conn.RemoteAddr())_ = conn.Close()}if err != nil {msg = err.Error()}if last != msg {now := time.Now()if last != "" {fmt.Printf("%s: --- %s: %v\n", now.Format(timeFormat), last, now.Sub(t))}last = msgfmt.Printf("%s: +++ %s\n", now.Format(timeFormat), last)t = now}<-ticker.C}}

Запустим тестовую утилиту для подключения к сервису nginx по 80-му порту. Она будет выводить результат попытки подключиться к сервису (пока не успешный, так как сервис смотрит вникуда):

#!/bin/bashecho "tee dialer.go << EEOF$(cat dialer.go)EEOFgo run dialer.go nginx:80" | kubectl --context=minikube run -i --rm "debug-$(date +'%s')" \            --image=golang:1.16 --restart=Never --

Вывести она должна что-то вида:

16:57:19.986: === nginx:8016:57:19.988: +++ dial tcp: lookup nginx on 10.96.0.10:53: server misbehaving

Пока оставим окно с утилитой и потом будем в него посматривать.

Простой Deployment без задержек

Добавим в сервис Deployment (nginx.yml):

---apiVersion: apps/v1kind: Deploymentmetadata:  name: nginxspec:  replicas: 1  selector:    matchLabels:      app: nginx  template:    metadata:      labels:        app: nginx    spec:      containers:        - name: nginx          image: nginx:1.14.2          ports:            - containerPort: 80

Параметрreplicasдля эксперимента равен единице, чтобы не скакать между IP-адресами.

На боевом Deployment должны быть так жеlivenessProbeиreadinessProbe. Но в данном эксперименте они будут только мешать.

И сделаем обновление Deployment:

#!/bin/bashkubectl --context minikube rollout restart deployment/nginx

От этой команды произойдёт перевыкатка Deployment. При этом важно отметить, что схема выкатки по умолчанию: поднять новый Pod и только затем погасить старый Pod. То есть всегда будет запущен как минимум один Pod.

В выводе тестовой утилиты мы увидим примерно следующее (комментарии добавлены отдельно):

# Здесь мы подключились к созданному Deployment и до обновления попытки# подключения были успешны17:04:08.288: +++ connected (172.17.0.10:80)17:07:32.187: --- connected (172.17.0.10:80): 3m23.899438044s# Здесь завершился nginx при остановке Pod, но клиент еще идет по старому# кэшированному IP.# Так как Pod существует, мы быстро получаем ошибку "connection refused"17:07:32.187: +++ dial tcp 172.17.0.10:80: connect: connection refused17:07:32.488: --- dial tcp 172.17.0.10:80: connect: connection refused: 301.155902ms# Старый Pod уже удалён, но клиент всё еще идет по старому кэшированному IP.# Так как по IP-адресу уже никто не отвечает, мы пробиваем таймаут.17:07:32.488: +++ dial tcp 172.17.0.10:80: i/o timeout17:07:38.448: --- dial tcp 172.17.0.10:80: i/o timeout: 5.960150161s# Старый IP покинул кэш и мы подключились к новому Pod.17:07:38.448: +++ connected (172.17.0.7:80)

Добавляем задержку перед удалением Pod

Добавим в Deployment паузу после завершения сервиса, чтобы вместо долгого таймаута получать быстрый connection refused:

#!/bin/bashkubectl --context minikube patch deployment nginx --output yaml --patch '---spec:  template:    spec:      containers:        - name: nginx          command: [ "sh" ]          # Добавляем паузу после завершения nginx          args:            - "-c"            - "nginx -g \"daemon off;\" && sleep 60"          # К сожалению, sh не пробрасывает SIGTERM в дочерний процесс          lifecycle:            preStop:              exec:                command: ["sh", "-c", "nginx -s stop"]      # Увеличиваем время, которое отводится на остановку Pod-а перед      # его безусловным завершением      terminationGracePeriodSeconds: 180'

Эта пауза нужна только при корректном завершении Pod (в этом случае процесс получаетSIGTERM). Если процесс завершается, к примеру, по Out Of Memory или Segmentation fault, то её быть не должно.

И еще раз сделаем обновление Deployment:

#!/bin/bashkubectl --context minikube rollout restart deployment/nginx

В выводе тестовой утилиты мы увидим примерно следующее (комментарии добавлены отдельно):

# Здесь мы подключились к созданному Deployment и до обновления попытки# подключения были успешны17:58:10.389: +++ connected (172.17.0.7:80)18:00:53.687: --- connected (172.17.0.7:80): 2m43.29763747s# Здесь завершился nginx при остановке Pod, но клиент еще идет по старому# кэшированному IP.# Так как Pod существует, мы быстро получаем ошибку "connection refused".# Существовать Pod будет до тех пор пока не завершится sleep после nginx.18:00:53.687: +++ dial tcp 172.17.0.7:80: connect: connection refused18:01:10.491: --- dial tcp 172.17.0.7:80: connect: connection refused: 16.804114254s# Старый IP покинул кэш и мы подключились к новому Pod.18:01:10.491: +++ connected (172.17.0.10:80)

Добавляем задержку перед остановкой Pod

Добавим в Deployment паузу перед завершением сервиса, чтобы сервис отвечал, пока адрес Pod не покинет кэш на клиенте:

#!/bin/bashkubectl --context minikube patch deployment nginx --output yaml --patch '---spec:  template:    spec:      containers:        - name: nginx          # Добавляем задержку перед остановкой nginx          lifecycle:            preStop:              exec:                command: ["sh", "-c", "sleep 60 && nginx -s stop"]      # Увеличиваем время, которое отводится на остановку Pod перед      # его безусловным завершением      terminationGracePeriodSeconds: 180'

И еще раз сделаем обновление Deployment:

#!/bin/bashkubectl --context minikube rollout restart deployment/nginx

В выводе тестовой утилиты мы увидим примерно следующее (комментарии добавлены отдельно):

# Здесь мы подключились к созданному Deployment и до обновления попытки# подключения были успешны18:05:10.589: +++ connected (172.17.0.7:80)18:07:10.689: --- connected (172.17.0.7:80): 2m0.099149168s# Старый IP покинул кэш и мы подключились к новому Pod.# Старый Pod еще отвечает и из-за этого переключение прошло гладко.18:07:10.689: +++ connected (172.17.0.10:80)

Какие нужны задержки?

Итого: для гладкого переключения необходимо две задержки.

  • МеждуSIGTERMи остановкой приложения чтобы на момент отключения клиента он не мог получить из DNS-кэша ровно тот же Pod и пойти на него.

    Эта задержка должна быть не меньше, чем время жизни записи в DNS-кэше.

    Делать эту паузу больше, чем сумма времени жизни записи в DNS-кэше и времени жизни записи в кэше приложения не имеет особого смысла.

    Если на клиенте не возникает нежелательных эффектов от недоступности части IP-адресов сервиса, то вместо паузы послеSIGTERMможно использоватьminReadySeconds.

  • Между остановкой приложения и завершением Pod, чтобы при попытке клиента подключиться/переподключиться к этому Pod мы получали быстрый connection refused, а не ждали всё время таймаута.

    Эта задержка должна быть подобрана так, чтобы с момента полученияSIGTERMи до завершения Pod прошло время не меньше суммы времени жизни записи в DNS кэше и времени жизни записи в кэше приложения.

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

Конкретные длительности задержек надо подбирать индивидуально.

Подробнее..

Хочу больше годных профстатей, Хабр

21.06.2021 10:15:25 | Автор: admin

Листая страницы Хабра, поймал себя на мысли, что я воспринимаю Хабр как новостную ленту в социальной сети. То есть как нечто, что прямого отношения лично ко мне не имеет и касается меня очень косвенным путем. Нечто полуразвлекательное-полупознавательное.

Ну, судите сами. Вот примерный список тем, которые превалируют на Хабре.

  1. Что там новенького у Илона Петровича Маска.

  2. Как с помощью Arduino, говна и палок сделать годный фаллоимитатор радиоприемник.

  3. Как я ушел с прошлой работы, и как мне было там плохо.

  4. Как я нашел свою текущую работу, и какая она крутая.

  5. Как живется специалисту X в стране Y.

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

  7. Обсуждение новомодной платформы для веб-разработки, которая через 3 года станет старомодной.

  8. Промываем косточки крупным компаниям.

  9. Исторические экскурсы в IT/технологии/медицину.

  10. Реклама компаний.

  11. Мнения обо всем отвлеченном на свете.

  12. И т. д.

Все эти темы и все статьи неплохие, интересные. Но я хотел бы другого.

Я давно программирую на С++/qt. Думаю, что могу создать с нуля любой программный продукт (desktop) . Могу набирать команды, могу выстраивать отношения с заказчиками и т.д. Периодически приходится запускать (создавать) новые направления/программные продукты. Однако время, затрачиваемое мной и командой на каждый новый продукт, остается примерно постоянным. Вернее, не совсем так. Время суммарной работы оказывается в прямой пропорциональной зависимости от сложности продукта (объема кодовой базы). То есть постоянной величиной оказывается производительность работ, или эффективность труда. На всякий случай оговорюсь, что речь не идет о новичках, в команде только опытные толковые сотрудники.

Так вот, эту самую производительность труда очень хотелось бы увеличить. Как и в чем мне может помочь Хабр? Или я зря надеюсь?

Мне хотелось бы поднимать свой профессиональный уровень серьезными профессиональными статьями по проектированию/созданию/ведению больших продуктов. Статьями, которые по легкости восприятия были бы такого же классного уровня, как и сейчас большинство статей на Хабре. Но статьями, которые по глубине и полезности были бы как классические книги Скотта Майерса, банды четырех, Алана Купера, Роберта Мартина и др. Знаете, читая эти книги, я прибавлял каждый раз в квалификации. К сожалению, читая статьи на Хабре, я этого не чувствую. Даже более того: не могу припомнить случая, когда я хотел изучить какой-то новый для меня (и обычно нетривиальный) нюанс и находил бы его на Хабре. Я находил его где угодно, но только не на Хабре. Или вообще не находил.

Посему я очень жду и буду приветствовать появление на Хабре статей по следующим направлениям.

Новые шаблоны проектирования (С++)

Да, я знаю, что шаблоны не догма и не панацея, и всё всегда можно придумать и самому. Но я также знаю, что это проверенная годами экономия времени архитекторов и программистов. Это кругозор, который (при его наличии) позволяет делать сложную работу быстрее, а то и вообще моментально. У меня сложилось ощущение, что в мире С++ развитие шаблонов практически остановилось на известной книге банды четырех. Там описано 23 шаблона, и еще примерно столько же можно накопать в интернете. Я хочу больше! В моей практике были случаи, когда приходилось создавать шаблоны, разительно непохожие на известные. И приходилось тратить довольно много времени на приведение их к товарному использованию, хотя бы даже на продумывание такой терминологии, которая бы естественно бы воспринималась коллегами. Уверен, что если бы мы имели возможность в нужный момент найти и прочитать описание свежего шаблончика, наша работа местами была бы намного быстрее.

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

Кстати, по шаблонам есть фундаментальный труд POSA: 5-томник на 2000+ страниц, перевода на русский язык до сих пор нет. Чем не непаханное поле?

Шаблоны ведения проектов в Git

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

  1. Я веду маленький проект на 20 тысяч строк кода, в нем задействовано 5 человек, с системой контроля версий мы работаем вот так (и описать конкретно, вплоть до команд командной строки).

  2. Я веду неплохой проект на 100 тысяч строк кода, в нем задействовано 10 человек, и мы работаем вот так: схемы, команды git.

  3. Мы с тремя командами по 10 человек развиваем проект на 1 миллион строк кода, продаем продукт разным клиентам в разных конфигурациях, и всё свое хозяйство мы покрыли регрессионным тестированием. И для этого мы делаем вот это: схемы, команды git.

  4. У нас работает 200 человек, и у нас 10 миллионов строк кода в монорепе на 5 продуктов, и каждый продукт ещё поставляется в трех разных версиях. Мы опытным путем пришли, что только так: схемы, команды git.

  5. А у нас очень много кода и много микросервисов, каждый в своем репозитории, впрочем, есть и подрепозитории (подмодули). Часть кода покрыта тестами, часть нет. Раньше мы работали вот так, а теперь перешли на вот это (схемы, команды), но по эффективности труда одинаково приемлемо.

GUI

Дизайн GUI, новые подходы, примеры удачных и неудачных виджетов, примеры плохих и хороших интерфейсов/решений. Эта область очень динамично развивается в плане того, что мы видим на экранах своих смартфонов и мониторов (причем на смартфонах более динамично, чем на мониторах). Однако в плане научного описания, осмысления и систематизации всё как-то вяло. То есть решений полно, но ими никто не делится. А если и делится, то как-то не очень доходит до конкретных исполнителей, и те пишут с нуля. Думаю, ситуация бы улучшилась, если бы появилась серия статей в таком духе:

  1. Камрады, я внедрил к себе гамбургер-меню на 500 строк (qt) вот отсюда (ссылка). Вот, смотрите: скриншот, gif-анимация. Работает чётко! Лицензия LGPL. Короче, пользуйтесь все.

  2. Я создал свой виджет ввода паролей. Он круче, чем другие по таким-то причинам. Делюсь! Ссылка на репозиторий, скриншоты.

  3. Я смог объединить панели меню и тулбаров в одной панели. Получился принципиально новый виджет, похожий одновременно и на меню, и на тулбары. Область применения вот такая, описываю. Скриншоты, описание даю, а вот код нет!

  4. Я потратил 3 года на то, чтобы изучить все подходы к созданию справочных систем для больших приложений, и хочу об этом детально рассказать, с кучей примеров.

  5. Раньше у нас был такой интерфейс, и такие задачи. А потом добавилась еще одна, и мы в корне переделали интерфейс. Рассказываю, почему прошлый интерфейс был оптимален, а текущий супероптимален.

  6. Я создал свою библиотеку виджетов, да еще с библиотекой стилей. Ссылка. Область применения такая-то. Могу продать хорошим людям (или подарить).

  7. Я супермегадизайнер, и на примере 30 известных приложений за последний год объясню вам, что попало в тренд, что не попало, а что создает будущий тренд.

Успешный опыт решения важных и нетривиальных задач из самой близкой действительности

Знаете, меня не сильно цепляют новости, о том, какие планы у Джеффа Безоса на космос или как Boston Dynamics обучает своего пса. Это не увеличивает мою зарплату. Я хочу чего-то более близкого и понятного мне, но самое главное применимого в моей работе.

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

Сложные ли это задачи? Непростые, но решаемые. И некоторые из них решены многократно самыми разными производителями софта.

Давайте еще пример. Я запускаю приложение X и параллельно еще парочку для полного занятия вычислительных ресурсов (например, конвертор видео). И вот в приложении X вижу слайд-анимацию, который замечательно себя ведет (нисколько не тормозит) при том, что соседние приложения на 100% заняли мой процессор. Это неплохой результат для X, и, черт возьми, я хочу знать, как они этого добились.

Да, я знаю, что есть такие понятия, как коммерческие секреты, патенты и всякое такое. Но ведь несложно же из груды решенных задач вытащить что-то такое, что давно уже не представляет коммерческой ценности и поделиться этим с программистским сообществом. Подозреваю, что в команде разработки, к примеру, Adobe Photoshop найдется немалая кипа примеров изящно решенных нетривиальных задач, которые незаслуженно скрыты от мира. И если бы их открыть в виде конкретных формул, диаграмм (а в идеале исходного кода) и разместить в удобочитаемом виде на Хабре, это было бы очень многим полезно.

Вместо послесловия

Изложенные мной хотелки уже присутствуют в некотором небольшом количестве на Хабре, а помимо этого, подобную информацию по крупинкам можно собирать на конференциях, искать в книгах и некоторых научных журналах. Но трудозатраты слишком велики! Бывает, читаешь книгу в 500 страниц, чтобы найти хоть намек на решение, а его там не оказывается. Или, того хуже, оказывается, но такой, что лучше бы его не было Статьи в научных журналах часто оказываются написанными не с целью поделиться со всем миром своими достижениями, а просто формально отчитаться за очередной грант. Да и вообще, кажется, что печатные научные журналы по программированию эволюционно проигрывают интернет-платформам.

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

Подробнее..

Проблемы мониторинга дата-пайплайнов и как я их решал

16.06.2021 00:20:01 | Автор: admin

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

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

Чтобы понять, как все это мониторить, давайте разберемся кто вообще работает с данными, которые мы с таким трудом намайнили:

Бизнес - это все те люди, которые не особо разбираются в инженерии, но принимают важные решения, основываясь на добытых нами данных: увеличить ли расходы на рекламу, как быстро увеличивать размер аудитории для раскатывания апдейта, как работают различные партрнеры и т.п.

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

Код - запускается следующий в цепочке пайплайн, происходят преобразования, рисуются графики и т.п.

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

Вот примеры:

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

  • Пайплайн стучится в источник с надеждой получить свежие данные, которые появляются в API источника с неопределенной задержкой (привет Apple), пайплан может успешно завершиться как скачав отчет, так и получив сообщение о том, что отчета еще нет. Тут, конечно, можно делать бесконечные ретраи внутри, либо просто ронять пайплайн, но тогда не особо очевидно будет, по какой причине он упал - что-то пошло не так, или данные в источнике еще не подтянулись.

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

ETL как он естьETL как он есть

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

Чего не хватает во встроенных мониторингах систем работы с данными:

  • Бизнес не может просто посмотреть в модный мониторинг типа того же Airflow или ELK и понять, можно или нельзя доверять данным, актуальность состояния данных непрозрачна.

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

  • Нет возможности собрать реальную статистику и проанализировать, где самое слабое место. Просто считать инциденты не вариант, т.к. важнее не количество инцидентов, а время, в течение которого состояние данных было неактуальным. Знаю, что такие проблемы некоторые компании решают самописными парсерами логов или подсчетом пиков-просадок на графиках (напишите, как это реализовано у вас).

Все это превращается в такие вот проблемы:

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

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

  3. Статистика, если и собирается, то собирается по техническим проблемам и нельзя понять, насколько эти технические проблемы повлияли на бизнес.

Концепция

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

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

Почему вообще вебхуки?

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

После того, как мы начали отсылать из процессов события, нужно их проанализировать и получить ответы на всякие важные вопросы, например (все числа абстрактны):

  • запустилась ли наша задача 10 раз за последний день?

  • не превышает ли количество падений (определяем падение, если полученное значение > 0, например) 15% от всех запусков за сегодня?

  • нет ли процессов, которые длятся больше 20 минут?

  • не прошло ли больше часа с момента последнего успешного завершения?

  • стартовало ли событие по планировщику в нужное время?

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

Реализация

Я начал с дашборда, дизайн - не моя профессия, так что просто взял за основу дашборд, показывающий состояние крон-джобов на сайте Nomadlist, у меня получилось как-то так:

Дашборд состояния серверов Sensorpad средствами SensorpadДашборд состояния серверов Sensorpad средствами Sensorpad

Каждый монитор на этом дашборде - это комбинация метрик, которые должны быть понятны и бизнесу, и инженерам.

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


Для инженера тут все понятно:

  • скрипт отрабатывает быстро (еще бы, простая крон-джоба);

  • монитор вполне живой, 25 минут назад обновился;

  • места еще с запасом (цифра 53 в левом нижнем углу - это последнее принятое значение);

Для людей из бизнеса тут тоже все просто:

  • монитор зеленый;

  • статус прописан в первой же строчке;

  • никакой лишней информации;

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

И насколько просто такое настроить?

  1. Создаем вебхук в самом сервисе, они там называются сенсорами, по аналогии со всякими штуками из физического мира.

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

    df -h |grep vda1 | awk  '{ print $5 }'| sed 's/.$//' | xargs -I '{}' curl -G "https://sensorpad.link/<уникальный ID>?value={}" > /dev/null 2>&1
    
  3. Присоединяем к этому вебхуку монитор, называем его: количество свободного места (но можно еще и другие, например, то, что события уходят по графику означает, что сервер не упал)

  4. Настраиваем правила, по которым монитор меняет свой статус.

  5. Присоединяем каналы для отправки уведомлений.

  6. Добавляем монитор на один или несколько дашбордов.

А можно поподробнее?

Для вебхуков я пока что сделал саму простую имплементацию:

  • базовый вебхук, который будет нужен для 80% проектов;

  • cron-вебхук, который ожидает события в заданное через cron-синтаксис время;

  • chain-вебхук, который умеет отслеживать события от процессов, соединенных в цепочки;

главное в нашем деле - не усложнять интерфейсыглавное в нашем деле - не усложнять интерфейсы

После создания попадаем на страницу сенсора, тут автоматически появляются полученные события (повозился в js) и кнопочкой можно отсылать тестовые события, для тех, у кого не установлен Curl или кому лень делать это из браузера:

Догфудинг в действииДогфудинг в действии

Дальше создаем тот самый монитор - квадратик, меняющий статус и цвет.

Можно даже иконку выбратьМожно даже иконку выбрать

Есть две стратегии для статуса по умолчанию: сказать, что монитор должен быть в статусе "все хорошо", а потом правилами менять его на "что-то пошло не так", или можно сразу сделать ему статус "все плохо", а правилами проверять, что все действительно хорошо - обе стратегии имеют право на жизнь.

Теперь, собственно то, из-за чего я и написал эту балалайку: правила и гибкая логика.

К каждому монитору можно прицепить определенное количество правил, дальше движок мониторинга проверяет все правила раз в пару секунд. Правила проверяются поочередно, как только одно из правил вернет значение True, статус монитора изменится и остальные правила проверяться не будут. Если никакое из правил не сработало, статус монитора примет значение по умолчанию - то самое, которое задавалось при создании монитора.

Правила для мониторинга места на дискеПравила для мониторинга места на диске

На скриншоте выше видно уже созданные правила, но я покажу как они создаются.

Например правило, которое можно сформулировать так: "установи статус Warning, если за последний день было больше 5 джоб, которые работали дольше 10 секунд".

А вот какие вообще можно выбирать проверки в каждом из пунктов:

И какие реальные кейсы можно покрыть этими правилами?

У каждого свои кейсы. Дата-инженерия вообще весьма специфичное для каждой компании направление. Если у вас есть дата-пайплайны или cron jobs, сервис оповестит вас, если (все цифры, разумеется, конфигурируемы):

  • Cron job, Airflow DAG или любой другой процесс не запустился по расписанию;

  • 20% задач одного и того же пайплайна за день не отработали как надо;

  • связанная задача в пайплайне не запустилась через 2 минуты после окончания родительской задачи;

  • интервал между запусками двух задач меньше 1 минуты (похоже, у нас две конкурентные джобы);

  • с момента последнего успешного завершения пайплайна прошло 2 часа (а данные должны считаться каждый час);

  • время работы пайплайна уже целых 20 минут (а должен был отработать за 5, что-то подвисло).

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

А теперь - статистика!

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

Немного полезных и не очень графиковНемного полезных и не очень графиков

Я подумываю допилить возможность шарить эти странички по секретной ссылке за пределы аккаунта, тогда такую страницу можно будет использовать не хуже любой status page.

Вот такой концепт. Чего не хватает?


Sensorpad - проект выходного дня, который делался в свободное от основной работы время, без тонны инвестиций да еще и в одно лицо, именно поэтому мне так важно мнение комьюнити: как вам? Что добавить, что улучшить, продолжать ли развивать?

Потыкайте его вживую, заодно зацените, какой я у мамы дизайнер лендингов: https://sensorpad.io

Подробнее..

Стажировка в Southbridge набор в июне

09.06.2021 10:16:43 | Автор: admin

image


В мае мы запустили DevOps-стажировку в Southbridge для начинающих инженеров. Как проходит стажировка для первого потока, мы уже рассказывали. Сейчас начался второй поток, и мы ведем набор на третий. Подробности о стажировке под катом.


Кого приглашаем


Ищем начинающих инженеров, которым интересна сфера DevOps. Важно знать Linux на уровне администрирования. Отлично, если есть опыт написания скриптов на Bash, Python. Но это не обязательно.


Ждем тех, кто хочет не только получить технические навыки, но и развить софт-скилы: работу в команде, коммуникабельность, навык самообразования.


О программе


Стажировка проходит удалённо, занимает примерно 4-5 часов в день, которые можно выделить тогда, когда удобно. Проводим общие встречи в Zoom с наставниками и скрам-мастером, командные встречи.


Программа рассчитана на три месяца.
Что изучаем и с чем работаем на стажировке: Git, Docker, Kubernetes, мониторинг и логирование в Kubernetes, CI/CD и не только. Проходим курсы и сертификации в Слёрме.


Подробная программа

Этап 1 (2 недели)


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


б) выполнение проверочной работы.


Этап 2 (1 неделя)


а) прохождение видеокурса по Git,


б) прохождение видеокурса Docker: from zero to hero,


в) сдача сертификации.


Этап 3 (1 неделя + 5 дней практикум)


Задачи:


а) прохождение курса Kubernetes: База,


б) сдача сертификации,


б) выполнение практикума,


в) работа в команде.


Этап 4 (1 неделя)


Задачи:


а) прохождение курса Мониторинг и логирование в Kubernetes,


б) сдача сертификации.


Этап 5 (2 недели)


Задачи:


а) прохождение курса DevOps: Tools & Cheats,


б) выполнение практической работы из интенсива на выделенном стенде,


в) прохождение видеокурса CI/CD.


Важно: список тем может быть изменен для следующих потоков.


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


Старт третьего потока стажировки в середине июня. Резюме и ваши вопросы присылайте на почту job@southbridge.ru.

Подробнее..

Перевод Tоп 10 PromQL запросов для мониторинга Kubernetes

12.06.2021 10:05:57 | Автор: admin

В этой статье приведены примеры популярных запросов Prometheus для мониторинга Kubernetes.

Если вы только начинаете работать с Prometheus и у вас возникают сложности при создании запросов PromQL, советуем обратиться к руководству по началу работы с PromQL. Здесь мы пропустим теорию и сразу перейдём к практике.

Рейтинг основан на опыте компании Sysdig, ежедневно оказывающей сотням клиентов помощь в настройке мониторинга их кластеров:

1. Количество pods в каждом namespace

Информация о количестве pod в каждом namespace может быть полезна для обнаружения аномалий в кластере, например, слишком большого количества pods в отдельном namespace:

sum by (namespace) (kube_pod_info)

2. Количество контейнеров без CPU limits в каждом namespace

Важно правильно задавать лимиты для оптимизации производительности приложений и кластера. Этот запрос находит контейнеры без CPU limits:

count by (namespace)(sum by (namespace,pod,container)(kube_pod_container_info{container!=""}) unless sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="cpu"}))

3. Количество перезагрузок pods в каждом namespace

С помощью этого запроса вы получите список pods, которые перезапускались. Это важный показатель, поскольку большое количество перезагрузок pod обычно означает CrashLoopBackOff:

sum by (namespace)(changes(kube_pod_status_ready{condition="true"}[5m]))

4. Pods в статусе Not Ready в каждом namespace

Запрос выводит все pods, при работе которых возникла проблема. Это может быть первым шагом к её локализации и устранению:

sum by (namespace)(kube_pod_status_ready{condition="false"})

5. Превышение ресурсов кластера ЦП

Необходимо избегать ситуации, когда CPU limits превышают ресурсы кластера. В противном случае вы можете столкнуться с проблемой троттлинга процессора. Обнаружить превышение лимитами ресурсов кластера можно с помощью запроса:

sum(kube_pod_container_resource_limits{resource="cpu"}) - sum(kube_node_status_capacity_cpu_cores)

6. Превышение ресурсов кластера Память

Если все Memory limits суммарно превышают ёмкость кластера, то это может привести к PodEviction, если на узле не будет хватать памяти. Для проверки используйте запрос PromQL:

sum(kube_pod_container_resource_limits{resource="memory"}) - sum(kube_node_status_capacity_memory_bytes)

7. Количество исправных узлов кластера

Запрос выведет количество исправных узлов кластера:

sum(kube_node_status_condition{condition="Ready", status="true"}==1)

8. Количество узлов кластера, которые могут работать некорректно

Найти узлы кластера, которые периодически меняют состояние с Ready на Not Ready:

sum(changes(kube_node_status_condition{status="true",condition="Ready"}[15m])) by (node) > 2

9. Обнаружение простаивающих ядер ЦП

Планировании ресурсов кластера Kubernetes не самая простая задача. Этот запрос поможет вам определить, сколько ядер ЦП простаивают:

sum((rate(container_cpu_usage_seconds_total{container!="POD",container!=""}[30m]) - on (namespace,pod,container) group_left avg by (namespace,pod,container)(kube_pod_container_resource_requests{resource="cpu"})) * -1 >0)

10. Обнаружение неиспользуемой памяти

Этот запрос поможет снизить ваши затраты, предоставив информацию о неиспользуемой памяти:

sum((container_memory_usage_bytes{container!="POD",container!=""} - on (namespace,pod,container) avg by (namespace,pod,container)(kube_pod_container_resource_requests{resource="memory"})) * -1 >0 ) / (1024*1024*1024)

Хотите узнать больше?

Рекомендуем изучить нашу шпаргалку по PromQL, чтобы узнать, как писать более сложные запросы PromQL.

Также воспользуйтесь отличной коллекцией Awesome Prometheus alerts collection. Она включает несколько сотен Prometheus alert rules, вы можете изучить их, чтобы узнать больше о PromQL и Prometheus.

Подробнее..

Перевод Как оптимизировать ограничения ресурсов Kubernetes

15.06.2021 10:13:02 | Автор: admin

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

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

Prometheus одно из самых популярных решений для мониторинга кластеров Kubernetes. Поэтому каждый шаг в этом руководстве содержит примеры запросов PromQL.

Обнаружение контейнеров без ограничения ресурсов

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

Отсутствие лимитов в контейнере может оказать негативное воздействие на узлы кластера. В лучшем случае узлы начнут выселять pods с учетом QoS. При этом также возникнут проблемы с производительностью из-за троттлинга процессора. Но в худшем случае узел может перестать работать из-за нехватки ресурсов.

Контейнеры без CPU Limit в каждом namespace

sum by (namespace)(count by (namespace,pod,container)(kube_pod_container_info{container!=""}) unless sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="cpu"}))

Контейнеры без Memory Limit в каждом namespace

sum by (namespace)(count by (namespace,pod,container)(kube_pod_container_info{container!=""}) unless sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="memory"}))

Допустим, вы нашли несколько контейнеров без ограничений ресурсов. Как определить потенциально опасные? Легко! Нужно найти контейнеры, которые используют больше всего ресурсов и при этом не имеют лимитов.

Топ-10 контейнеров без CPU Limits, потребляющих больше всего ресурсов CPU

topk(10,sum by (namespace,pod,container)(rate(container_cpu_usage_seconds_total{container!=""}[5m])) unless sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="cpu"}))

Топ-10 контейнеров без Memory Limits, потребляющих больше памяти

topk(10,sum by (namespace,pod,container)(container_memory_usage_bytes{container!=""}) unless sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="memory"}))

Обнаружение контейнеров со слишком строгими ограничениями

Обнаружение контейнеров со слишком строгими CPU Limits

Если утилизация контейнером ресурсов процессора приближается к установленному лимиту, то его производительность будет снижаться из-за троттлинга ЦП.

Выполните этот запрос, чтобы найти контейнеры, для которых использование ЦП близко к пределу:

(sum by (namespace,pod,container)(rate(container_cpu_usage_seconds_total{container!=""}[5m])) / sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="cpu"})) > 0.8

Обнаружение контейнеров со слишком строгими Memory Limits

Если контейнер превысит лимит по памяти, он будет убит.

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

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

(sum by (namespace,pod,container)(container_memory_usage_bytes{container!=""}) / sum by (namespace,pod,container)(kube_pod_container_resource_limits{resource="memory"})) > 0.8

Как выбрать оптимальные значения для лимитов?

Один из способов выбрать оптимальные значения для лимитов наблюдение за утилизацией ресурсов контейнером в течение некоторого времени. Мы можем использовать две стратегии:

Консервативная

Мы находим наибольшее значение утилизации ресурсов контейнером за период времени. Устанавливаем ограничение ресурсов, равное этому значению.

Находим оптимальный CPU Limit с помощью консервативной стратегии:

max by (namespace,owner_name,container)((rate(container_cpu_usage_seconds_total{container!="POD",container!=""}[5m])) * on(namespace,pod) group_left(owner_name) avg by (namespace,pod,owner_name)(kube_pod_owner{owner_kind=~"DaemonSet|StatefulSet|Deployment"}))

Находим оптимальный Memory Limit с помощью консервативной стратегии:

max by (namespace,owner_name,container)((container_memory_usage_bytes{container!="POD",container!=""}) * on(namespace,pod) group_left(owner_name) avg by (namespace,pod,owner_name)(kube_pod_owner{owner_kind=~"DaemonSet|StatefulSet|Deployment"}))

Агрессивная

Задаем ограничение ресурсов по 99 квантилю. Это позволит не учитывать 1% наиболее высоких значений. Это хорошая стратегия, если есть редкие аномалии или пики, которые вы хотите игнорировать.

Находим оптимальный CPU Limit с помощью агрессивной стратегии:

quantile by (namespace,owner_name,container)(0.99,(rate(container_cpu_usage_seconds_total{container!="POD",container!=""}[5m])) * on(namespace,pod) group_left(owner_name) avg by (namespace,pod,owner_name)(kube_pod_owner{owner_kind=~"DaemonSet|StatefulSet|Deployment"}))

Находим оптимальный Memory Limit с помощью агрессивной стратегии:

quantile by (namespace,owner_name,container)(0.99,(container_memory_usage_bytes{container!="POD",container!=""}) * on(namespace,pod) group_left(owner_name) avg by (namespace,pod,owner_name)(kube_pod_owner{owner_kind=~"DaemonSet|StatefulSet|Deployment"}))

Достаточно ли ресурсов в вашем кластере?

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

Необходимо также контролировать, что сумма всех ограничений ресурсов Kubernetes не превышает ёмкость ресурсов в вашем кластере.

Когда вы утилизируете большую часть ресурсов кластера, контейнеры могут работать без проблем при обычной нагрузке, но в сценариях с высокой нагрузкой контейнеры могут начать использовать ЦП и память до предела. Это приведет к тому, что узел начнет выселять pods, а в критических ситуациях узел перестанет работать из-за нехватки ресурсов.

Как обнаружить превышение доступных ресурсов кластера?

Процент превышения доступных ресурсов по памяти:

100 * sum(kube_pod_container_resource_limits{container!="",resource="memory"} ) / sum(kube_node_status_capacity_memory_bytes)

Процент превышения доступных ресурсов по ЦП:

100 * sum(kube_pod_container_resource_limits{container!="",resource="cpu"} ) / sum(kube_node_status_capacity_cpu_cores)

Как правило, не все контейнеры полностью утилизируют запрашиваемые ресурсы одновременно. Если заданные лимиты составляют 100% от ресурсов кластера это повлечет за собой дополнительные затраты на инфраструктуру, которая никогда не будет использоваться.

Лучшим решением будет выбрать консервативную стратегию, гарантирующую, что избыточное использование составляет менее 125%, или агрессивную стратегию, которая позволяет лимитам достичь 150% емкости вашего кластера.

Не менее важным будет проверка соответствия лимитов емкости каждого узла. Например, у нас есть контейнер с CPU Requests - 2 и CPU Limit - 8. Этот контейнер можно запланировать на узле с 4 ядрами, но лимиты не дадут нужного эффекта, потому что на узле недостаточно ядер.

Процент превышения доступных ресурсов узла по памяти:

sum by (node)(kube_pod_container_resource_limits{container!=,resource=memory} ) / sum by (node)(kube_node_status_capacity_memory_bytes)

Процент превышения доступных ресурсов узла по ЦП:

sum by (node)(kube_pod_container_resource_limits{container!=,resource=cpu} ) / sum by (node)(kube_node_status_capacity_cpu_cores)

Подведем итоги

В этой статье вы узнали, почему так важно правильно использовать Kubernetes Limits and Requests, как определять наличие неоптимальных параметров в вашем кластере и какие стратегии вы могли бы использовать, чтобы установить правильные ограничения ресурсов Kubernetes.

Если вы хотите узнать больше, рекомендуем прочитать статью рациональное использовании ресурсов в Kubernetes.

Подробнее..

Перевод Контролируем удаление с финализаторами

18.06.2021 08:07:31 | Автор: admin

image


В Kubernetes не так-то просто что-то удалить вы уверены, что удалили объект, но оказывается, что он все еще присутствует в кластере. Вы, конечно, можете выполнять команду kubectl delete в повседневных операциях и надеяться на лучшее, но знание принципов работы delete команд в Kubernetes поможет вам понять, почему некоторые объекты остаются после удаления.


В этой статье мы рассмотрим:


  • Какие свойства ресурса влияют на удаление.
  • Как финализаторы и ссылки на родителя-владельца управляют удалением объектов.
  • Как можно использовать propagationPolicy, чтобы изменить порядок удаления.
  • Как работает удаление, с примерами.

Для простоты во всех примерах мы используем ConfigMaps и базовые команды kubectl. Мы увидим, как работают эти команды, и обсудим результаты их использования на практике.


Базовая команда delete


В Kubernetes есть несколько команд для создания, чтения, обновления и удаления объектов. Здесь мы поговорим о четырех командах kubectl: create, get, patch и delete.
Примеры базовой команды kubectl delete:


kubectl create configmap mymapconfigmap/mymap created

kubectl get configmap/mymapNAME    DATA   AGEmymap   0      12s

kubectl delete configmap/mymapconfigmap "mymap" deleted

kubectl get configmap/mymapError from server (NotFound): configmaps "mymap" not found

Перед командой стоит знак $, далее следует ее результат. Как видите, мы начали с kubectl create configmap mymap, чтобы создать пустой configmap mymap. Теперь нужно выполнить get и убедиться, что он существует. Затем мы удалили configmap. Если снова сделать get, получим ошибку HTTP 404, потому что configmap не найден.


У команды deleteочень простая диаграмма состояний:


image
Диаграмма состояний для delete


Операция незамысловатая, но ей могут помешать некоторые факторы, включая финализаторы и ссылки на родителя-владельца.


Как работают финализаторы


Чтобы понять, как удаляются ресурсы в Kubernetes и почему они иногда все-таки не удаляются, полезно разобраться с финализаторами.


Финализаторы это ключи в манифесте ресурса, отвечающие за операции, которые надо выполнить до удаления объекта. Они контролируют сборку мусора для ресурсов и сообщают контроллерам, какие операции очистки нужно выполнить до удаления ресурса. Они не обязательно обозначают код, который нужно выполнить. По сути, это просто списки ключей, вроде аннотаций. Как и аннотациями, ими также можно управлять.


Скорее всего, вы встречали следующие финализаторы:


  • kubernetes.io/pv-protection
  • kubernetes.io/pvc-protection

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


Ниже приводится configmap без свойств, но с финализатором:


cat <<EOF | kubectl create -f -apiVersion: v1kind: ConfigMapmetadata:  name: mymap  finalizers:  - kubernetesEOF

Контроллер ресурса configmap не понимает, что делать с ключом финализатора kubernetes. Такие финализаторы для configmap я называю мертвыми, и обычно они используются с неймспейсами. Вот что происходит при попытке удалить configmap:


kubectl delete configmap/mymap &configmap "mymap" deletedjobs[1]+  Running kubectl delete configmap/mymap

Kubernetes сообщает, что объект удален, но на самом деле, в традиционном понимании, никуда он не удален. Скорее он находится в процессе удаления. Если мы снова выполним get, то узнаем, что объект изменен и теперь содержит временную метку удаления deletionTimestamp.


kubectl get configmap/mymap -o yamlapiVersion: v1kind: ConfigMapmetadata:  creationTimestamp: "2020-10-22T21:30:18Z"  deletionGracePeriodSeconds: 0  deletionTimestamp: "2020-10-22T21:30:34Z"  finalizers:  - kubernetes  name: mymap  namespace: default  resourceVersion: "311456"  selfLink: /api/v1/namespaces/default/configmaps/mymap  uid: 93a37fed-23e3-45e8-b6ee-b2521db81638

Если в двух словах, объект был изменен, но не удален. Все потому, что Kubernetes увидел, что у объекта есть финализаторы, и поместил его в состояние read-only. Временная метка удаления показывает, что объект можно только читать а еще из него можно удалить апдейты ключа финализатора. Другими словами, удаление будет завершено, только когда мы удалим из объекта финализатор.


Удалить финализаторы можно с помощью команды patch. Фоновое удаление завершится, и объект будет удален. Если мы опять выполним get configmap, то ничего не увидим.


kubectl patch configmap/mymap \    --type json \    --patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'configmap/mymap patched[1]+  Done  kubectl delete configmap/mymapkubectl get configmap/mymap -o yamlError from server (NotFound): configmaps "mymap" not found

Диаграмма состояния для финализации выглядит так:


image
Диаграмма состояний для финализации


Если мы попытаемся удалить объект с финализатором, он останется на этапе финализации, пока контроллер не удалит ключи финализаторов или финализаторы не будут удалены через Kubectl. Когда список финализаторов будет пуст, Kubernetes поместит его в очередь на удаление из реестра.


Ссылки на владельца


Ссылки на владельца (owner references) описывают отношения между группами объектов. Это свойства ресурсов, которые указывают, как они связаны друг с другом, чтобы можно было удалять целые деревья ресурсов.


Правила финализатора обрабатываются, если есть ссылки на владельца. Ссылки на владельца состоят из имени и UID. Они связывают ресурсы в одном неймспейсе и им нужен UID. Ссылки на владельца для подов обычно указывают на replica set, которому они принадлежат. Если удалить deploy или stateful set, дочерние replica set и pod тоже удалятся.


Посмотрим на примере, как работают ссылки на владельца. В первом примере мы сначала создаем родительский объект, а потом дочерний. В результате получаем простейший configmap со ссылками на владельца, то есть родителя:


cat <<EOF | kubectl create -f -apiVersion: v1kind: ConfigMapmetadata:  name: mymap-parentEOFCM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")cat <<EOF | kubectl create -f -apiVersion: v1kind: ConfigMapmetadata:  name: mymap-child  ownerReferences:  - apiVersion: v1    kind: ConfigMap    name: mymap-parent    uid: $CM_UIDEOF

Если мы удалим дочерний объект, в котором есть ссылка на владельца, родитель никуда не денется:


kubectl get configmapNAME           DATA   AGEmymap-child    0      12m4smymap-parent   0      12m4skubectl delete configmap/mymap-childconfigmap "mymap-child" deletedkubectl get configmapNAME           DATA   AGEmymap-parent   0      12m10s

В следующем случае мы воссоздали configmap из примера выше. Если мы удалим родителя, а не дочерний объект, в котором есть ссылка на него, а потом сделаем get для configmap, то увидим, что все configmap пропали:


kubectl get configmapNAME           DATA   AGEmymap-child    0      10m2smymap-parent   0      10m2skubectl delete configmap/mymap-parentconfigmap "mymap-parent" deletedkubectl get configmapNo resources found in default namespace.

Получается, что если в дочернем объекте есть ссылка на родителя, он автоматически удаляется при удалении родителя. Это называется cascade. По умолчанию cascade имеет значение true, но можно указать --cascade=false в kubectl delete, чтобы удалить родительский объект, не трогая дочерние.


В следующем примере у нас есть родительский и дочерний объекты, а еще ссылки на владельца. Если мы удалим родителя, указав --cascade=false, дочерние объекты сохранятся:


kubectl get configmapNAME           DATA   AGEmymap-child    0      13m8smymap-parent   0      13m8skubectl delete --cascade=false configmap/mymap-parentconfigmap "mymap-parent" deletedkubectl get configmapNAME          DATA   AGEmymap-child   0      13m21s

Параметр --cascade указывает на политику распространения в API, которая позволяет менять порядок объектов удаления в дереве. В следующем примере используется отправка запроса к API через curl для удаления с фоновой политикой распространения (propagation policy):


kubectl proxy --port=8080 &Starting to serve on 127.0.0.1:8080curl -X DELETE \  localhost:8080/api/v1/namespaces/default/configmaps/mymap-parent \  -d '{ "kind":"DeleteOptions", "apiVersion":"v1", "propagationPolicy":"Background" }' \  -H "Content-Type: application/json"{  "kind": "Status",  "apiVersion": "v1",  "metadata": {},  "status": "Success",  "details": { ... }}

Политику распространения нельзя указать в командной строке с помощью kubectl только при прямом вызове API. Просто создайте прокси, чтобы иметь доступ к серверу API со своего компьютера, и выполните команду curl с одним URL, чтобы послать команду delete.


Есть три варианта политики распространения:


  • Foreground: дочерние объекты удаляются до родительского (обратный порядок).
  • Background: родительский объект удаляется до дочерних (прямой порядок).
  • Orphan: ссылки на владельца игнорируются.

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


Принудительное удаление неймспейса


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


cat <<EOF | curl -X PUT \  localhost:8080/api/v1/namespaces/test/finalize \  -H "Content-Type: application/json" \  --data-binary @-{  "kind": "Namespace",  "apiVersion": "v1",  "metadata": {    "name": "test"  },  "spec": {    "finalizers": null  }}EOF

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


Итоги


Как показывают примеры, финализаторы могут помешать удалению ресурсов в Kubernetes, особенно если речь о родительских и дочерних объектах. Обычно финализаторы добавляются в код не просто так, поэтому нужно все проверить, прежде чем удалять их вручную. С помощью ссылок на владельца можно указывать и удалять деревья ресурсов, хотя и в этом случае будут учитываться финализаторы. Наконец, можно использовать политику распространения, чтобы указывать порядок удаления в прямых вызовах API и контролировать процесс удаления объектов. Теперь вы чуть больше знаете о том, как работает удаление в Kubernetes, и можете поэкспериментировать самостоятельно в продакшен-кластере.


Видео от автора:


Подробнее..

Аварии как опыт 3. Как мы спасали свой мониторинг во время аварии в OVH

09.06.2021 12:21:23 | Автор: admin

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

О мониторинге у нас

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

1. Blackbox-мониторинг для проверки состояния сайтов. Цель проста собирать статистику с определенных endpointов и проверять их состояние по тем условиям, которые требуется установить. Например, это может быть health page в виде JSON-страницы, где отражено состояние всех важных элементов инфраструктуры, или же мониторинг отдельных страниц сайта. У нас принято считать, что данный вид мониторинга самый критичный, так как следит за работоспособностью сайта/сервисов клиента для внешних пользователей.

Вкратце о технической стороне этого мониторинга: он выполняет запросы разного уровня сложности по HTTP/HTTPS. Есть возможность определять размер страницы, ее содержимое, работать с JSON (они обычно используются для построения status page-страниц приложений клиента). Он географически распределен для повышения отказоустойчивости и во избежание всевозможных блокировок (например, от РКН).

Работа данного мониторинга не была затронута аварией благодаря геораспределенности (к тому же, у него вообще нет инсталляций в OVH).

2. Мониторинг Kubernetes-инфраструктуры и приложений клиента, запущенных в ней. С технической точки зрения этот мониторинг основан на связке Prometheus + Grafana + Alertmanager, которые устанавливаются локально на кластеры клиента. Часть данных, собираемых на данных системах мониторинга (например, метрики, напрямую связанные с Kubernetes и Deckhouse), по умолчанию отправляется в нашу общую систему, а остальные могут отправляться опционально (например, для мониторинга приложений и реакции от наших дежурных инженеров).

3. Мониторинг ресурсов, которые находятся вне кластеров Kubernetes. Например, это железные (bare metal) серверы, виртуальные машины и СУБД, запущенные на них. Данную зону мониторинга мы покрываем с помощью стороннего сервиса Okmeter (а с недавних пор уже не очень-то для нас и стороннего). Именно его работа была фатально нарушена в момент аварии OVH.

(А о том, как осуществляется дальнейшая обработка инцидентов, поступающих от всех упомянутых систем, мы рассказывали в этом докладе.)

Таким образом, все проблемы, описанные ниже, связаны с недоступностью одной из используемых систем Okmeter. То есть на некоторое время мы потеряли информацию о некоторой инфраструктуре (вне Kubernetes-кластеров), сохранив при этом доступность blackbox-мониторинга (который гарантирует нам, что главные бизнес-функции работают).

Как мы справлялись с этой аварией? Какие шаги предпринимали во время инцидента? И какие после?

Первые признаки аварии

Проверка доступности внешних средств мониторинга осуществляется с применением метода Dead mans switch (DMS). По своей сути это мониторинг, который работает наоборот:

  • Создается поддельный алерт OK, означающий, что всё хорошо, и он постоянно отправляется с локальных мониторингов (Prometheus, Okmeter и т.п.) в нашу систему инцидент-менеджмента.

  • Пока с мониторингом действительно всё хорошо, алерт OK активен, но в системе не показывается.

  • В обратной ситуации, когда возникают проблемы с мониторингом, алерт OK перестает быть активным, из-за чего автоматически создаётся алерт ERROR о неработающем мониторинге. И вот уже на него надо реагировать.

Данная проверка очень эффективна в ситуациях, которая произошла в роковой день (10 марта): первый алерт о проблеме (ERROR) мы получили как раз от DMS.

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

  • Первое сообщение о проблеме алерт от DMS, полученный приблизительно в 3:20 ночи по Москве.

  • Связываемся с коллегами из Okmeter и узнаем, что они испытывают проблемы с ЦОД, в котором расположены. Детальной информации на данный момент не получаем, из-за чего масштабы аварии нам неизвестны. Кроме того, дежурный инженер видит, что у нас корректно работают другие системы мониторинга (blackbox и Kubernetes). Поэтому принимается решение отложить инцидент до утра.

  • Утром (в 8:14) становится известно, что ЦОД сгорел, а Okmeter не будет доступен до тех пор, пока не восстановит свою инфраструктуру в другом ЦОД.

Здесь также стоит остановиться подробнее на том, как была устроена инфраструктура у Okmeter. Основные её компоненты находились в дата-центрах OVH:

  • SBG-2 который был полностью уничтожен пожаром;

  • SBG-1 который был частично поврежден и обесточен.

Так уж получилось, что два формально разных дата-центра OVH оказались соседними зданиями. И пожар, случившийся в одном из них, в скором временем затронул и другое здание тоже.

Руководствуясь этой информацией утром 10 марта, мы сделали вывод, что нужно срочно замещать важные для нас функции Okmeter хоть какими-то решениями на временной основе.

Первые действия

В компании была собрана специальная команда, основными целями которой являлись:

  1. Определение масштабов аварии;

  2. Поэтапное планирование, как устранять последствия инцидента;

  3. Дальнейшее внедрение полученных решений.

В неё вошли тимлиды наших DevOps-команд, инженеры нашей платформенной команды и CTO компании. Позже к ней также добавились отдельные инженеры, помогавшие со срочной разработкой и тестированием новых инструментов, заменивших критичные проверки от Okmeter.

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

Матрица критичности алертовМатрица критичности алертов

Эта матрица распространяется на события из всех 3 используемых у нас систем мониторинга. Каждому инциденту, зафиксированному в любой из систем, назначается критичность от S1 (полная потеря работоспособности компонента системы) до S9 (диагностическая информация). Большая часть алертов с критичностью S1 это blackbox-мониторинг, который проверяет состояние сайтов клиента. Однако также к этой категории относится и незначительная часть алертов, отправляемых со стороны Okmeter (подробнее о них см. ниже). Другая незначительная часть приходится на S2, а все остальные имеют более низкую критичность (S3 и т.п.).

Проведя учет количества и видов алертов с S1 и S2 от Okmeter, мы приняли очевидное решение заняться ими в первую очередь, полностью компенсировав другим временным решением. А уже после этого можно было переходить на алерты с меньшей критичностью.

Оповестив всех клиентов, инфраструктуру которых задела авария у Okmeter, мы приступили к составлению плана восстановления.

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

Первая очередь алертов: S1-S2

Итак, какие именно критичные алерты мы потеряли с недоступностью Okmeter? Чтобы оперативно собрать эту информацию, каждая команда инженеров тех, что сопровождают проекты с мониторингом от Okmeter, подготовила статистику алертов, приходивших с 1 июля 2020 года.

Получился следующий список:

  1. Различные алерты для СУБД.

    1. Проверка работоспособности СУБД в целом (запущен ли процесс).

    2. Состояние репликации.

    3. Состояние запросов к БД (например, сколько запросов находятся в ожидании).

  2. Дисковое потребление на виртуальных машинах.

  3. Доступность виртуальных машин в целом.

Вторая очередь алертов: S3 и далее

Ситуация с алертами категории S3 была проще: их меньше, они менее критичные, да и вообще эта категория оказалась не у всех команд.

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

Немного магического Bash

Чтобы реализовать замену проверкам и алертам Okmeter, требовалось уметь деплоить на все серверы актуальные скрипты мониторинга, чтобы поддерживать их в актуальном состоянии. Реализация: с помощью Ansible-плейбуков мы задеплоили все необходимые скрипты, среди которых был скрипт автообновления. Последний раз в 10 минут загружал новые версии других скриптов. Из-за очень сжатых сроков основную часть скриптов мы писали на Bash.

Общий подход получился следующим:

  1. Сделали shell-скрипты, которые запускались на виртуальных машинах и bare metal-серверах. Помимо своей основной функции (проверка работоспособности некоторых компонентов) они должны были доставлять сообщения в наш мониторинг с такими же лейблами и другими элементами, как и у Okmeter: имя триггера, его severity, другие лейблы и т.д. Это требовалось для сохранения той логики работы мониторинга, которая была и до падения. В общем, чтобы процесс работы дежурных инженеров оставался неизменным и чтобы работали прежние правила управления инцидентами.

    Быстрой реализации этого этапа способствовал тот факт, что у нас уже были готовые инструменты для работы с API мониторинга внутренний инструмент под названием flint (flant integration).

  2. С помощью Ansible-плейбуков в каждый проект и на каждый сервер, где это было необходимо, задеплоили новые скрипты мониторинга. Ansible-плейбуки пришлось писать с нуля, но благодаря нашей внутренней системе, которая хранит список всех обслуживаемых хостов, деплоить изменения на серверы оказалось просто.

  3. В течение некоторого времени мы дополнительно проверяли, что алерты корректные, а сами скрипты выполняются правильно.

Результаты

На полную реализацию этого временного решения нам понадобился один рабочий день для алертов типа S1-S2 и еще один для S3. Все остальные инциденты (с более низким приоритетом) команды добавляли в индивидуальном порядке по мере необходимости.

В общей сложности новые скрипты были задеплоены примерно на 3000 хостов.

На каждом этапе решения проблемы мы подробно информировали клиентов о ходе восстановительных работ:

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

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

Выводы

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

Более практичные выводы таковы:

  1. Если сервис требует повышенной отказоустойчивости, всегда важно разделять инфраструктуру не только на уровне разных ЦОД, но и географически. Казалось бы, разные ЦОДы у OVH? А в реальной жизни они горели вместе

  2. В процессе подобного инцидента могут возникнуть проблемы и недопонимания в коммуникациях как между Флантом и Okmeter, так и между инженерами внутри нашей компании: недостаточное информирование о масштабах инцидента, медленная реакция и т.п. Хотя в этот раз нам повезло (их не возникло), мы пересмотрели свой регламент эскалации и еще немного ужесточили его, чтобы исключить вероятность появления таких проблем в будущем.

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

P.S.

Читайте также в нашем блоге:

Подробнее..

Что будет после Docker

12.06.2021 08:11:13 | Автор: admin

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

Кроме того, отмечу, что у меня нет особых претензий к Docker. Просто хорошая прорывная технология, которая стала стандартом индустрии разработки и администрирования. Как bash или Python у сисадминов. Я постараюсь описывать недостатки докера только при сравнении с другими технологиями, и всё.

Почему вообще должно появиться что-то после Docker?

Окей, насчёт запрета на недостатки Docker я соврал, они будут. Но только пара пунктов -- остальные можете почитать в посте "Исповедь docker хейтера", с которыми я, в принципе, согласен. Так вот.

Отсутствие развития. Я не вижу, чтобы в Docker появлялось что-то принципиально новое в последние пару лет. Может, это из-за не очень понятной финансовой модели и вкладывания ресурсов в не то, чтобы перспективный Swarm. Вы сможете вспомнить хайлайты из последних ченжлогов докера? То-то же.

Спорные и местами даже предвзятые решения разработчиков. Дело в том, что Docker в современном дистрибутиве Linux это система в системе, которая активно перетягивает на себя одеяло остальных инструментов: правила фаерволла в iptables (firewalld? Не, не слышали!), инициализация и жизненный цикл приложений (другими словами, докер стремится стать systemd для контейнеров, то есть это сегодня нечто systemd-like в дистрибутиве Linux, где уже есть systemd? шта?), работа от непривелегированных пользователей (и я не про системную группу docker, через которую можно лезть в чужие контейнеры и хостовую систему вообще!), не очень удобный механизм сборки (Dockerfile, конечно, хорошо, но вы попробуйте построить замысловатый пайплайн с пробросом хостовых томов, например), в общем, список наберётся.

Так что же будет после Docker?

Во-первых, будет новый докер. Перефразирую слова из песни одной музыкальной группы: вначале был докер, в конце будет докер, всё повторяется снова, природа сурова. Вполне вероятно, что докер эволюционирует, что его команда и сообщество решат архитектурные и технические болячки, прикрутят сотню крутых фич, сохраняя при этом дистанцию от кубера, выкинут Docker Swarm и всё станет хорошо. Но это, разумеется, не точно. Честно говоря, я сомневаюсь в таком сценарии.

Во-вторых, будет иной докер. Пример сегодня живёт хорошей жизнью и вполне себе развивается -- Podman. С одной стороны, он создан по образу и подобию Docker: CLI мимикрирует под Docker CLI, есть режим эмуляции Docker API для совместимости с docker-compose, технологии под капотом примерно те же, да и написан он тоже на Go.
Но дальше идут различия: например, Podman построен на стандартах OCI, на которые ориентируются остальные инструменты, вроде Harbor container registry. Когда как Docker позволяет от себе отклоняться от стандартов в различных мелочах, просто потому что докер появился до этих стандартов и имеет право.
Это расхождение в стандарте и реализации иногда порождает проблемы. Так, например, я при работе с Podman столкнулся с тем, что Sonatype Nexus отказывается принимать подмановские docker-образы, и надо было использовать особый параметр, чтобы подман собирал образы с конкретно докеровским форматом, а не стандартом OCI. Так что стандарты -- круто, своя имплементация -- не всегда практично.

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

В конце-концов, может, я преувеличил касательно Docker как bash современной разработки, и после Docker будет что-то совершенно иное. Может, это будет Serverless?

Каким мог бы быть совершенный Docker?

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

  • Он должен вбирать в себя всё хорошее, что уже есть в Docker, а именно -- отличные REST API и CLI.

  • В нём должны быть результаты уроков, извлечённые из горького опыта Docker, то есть большинство из того, что сделано в Podman.

  • Он должен быть ещё более тесно интегрирован с дистрибутивом Linux. Не так тесно, как systemd-nspawn, который вообще неотделим от systemd, но всё же.

  • Он должен выполнять только определённый круг задач: собирать контейнеры, запускать контейнеры, и ещё всякое по мелочи. Не надо кластеризации, не надо поддерживать сложные сценарии со множеством сетей или томов, максимум -- пробрасывать папки в хостовую файловую систему. Ну и какой-то функционал Docker Compose тоже не помешает.

  • Каким-то образом решить архитектурный косяк с dangling images. Типа, серьёзно, 2021-й год, а мне приходится в кронтабы плейбуков или systemd-таймеры вписывать задачу на docker image prune -f! Это должен делать или сам сервис, то есть самостоятельно маскировать проблему, или это надо решить на уровне дизайна системы работы с образами.

  • Удобная расширяемость через плагины! К сожалению, язык Go не имеет возможности писать расширяемые через классические плагины системы, как умеет Python или Java. Как фанат Kotlin, я нахожу расширяемость через плагины или скрипты (я имею ввиду Kotlin Script) очень клёвой.

У меня на этом всё.

С-спасибо за внимание, в-вы отличная публика (c)

Подробнее..

Ваш безлимит как увеличить пропускную способность автомерджа

21.06.2021 14:12:41 | Автор: admin

Отыщи всему начало, и ты многое поймёшь (Козьма Прутков).

Меня зовут Руслан, я релиз-инженер в Badoo и Bumble. Недавно я столкнулся с необходимостью оптимизировать механизм автомерджа в мобильных проектах. Задача оказалась интересной, поэтому я решил поделиться её решением с вами. В статье я расскажу, как у нас раньше было реализовано автоматическое слияние веток Git и как потом мы увеличили пропускную способность автомерджа и сохранили надёжность процессов на прежнем высоком уровне.

Свой автомердж

Многие программисты ежедневно запускают git merge, разрешают конфликты и проверяют свои действия тестами. Кто-то автоматизирует сборки, чтобы они запускались автоматически на отдельном сервере. Но решать, какие ветки сливать, всё равно приходится человеку. Кто-то идёт дальше и добавляет автоматическое слияние изменений, получая систему непрерывной интеграции (Continuous Integration, или CI).

Например, GitHub предлагает полуручной режим, при котором пользователь с правом делать записи в репозиторий может поставить флажок Allow auto-merge (Разрешить автомердж). При соблюдении условий, заданных в настройках, ветка будет соединена с целевой веткой. Bitbucket поддерживает большую степень автоматизации, накладывая при этом существенные ограничения на модель ветвления, имена веток и на количество мерджей.

Такой автоматизации может быть достаточно для небольших проектов. Но с увеличением количества разработчиков и веток, ограничения, накладываемые сервисами, могут существенно повлиять на производительность CI. Например, раньше у нас была система мерджа, при которой основная ветка всегда находилась в стабильном состоянии благодаря последовательной стратегии слияний. Обязательным условием слияния была успешная сборка при наличии всех коммитов основной ветки в ветке разработчика. Работает эта стратегия надёжно, но у неё есть предел, определяемый временем сборки. И этого предела оказалось недостаточно. При времени сборки в 30 минут на обработку 100 слияний в день потребовалось бы более двух суток. Чтобы исключить ограничения подобного рода и получить максимальную свободу выбора стратегий мерджа и моделей ветвления, мы создали собственный автомердж.

Итак, у нас есть свой автомердж, который мы адаптируем под нужды каждой команды. Давайте рассмотрим реализацию одной из наиболее интересных схем, которую используют наши команды Android и iOS.

Термины

Main. Так я буду ссылаться на основную ветку репозитория Git. И коротко, и безопасно. =)

Сборка. Под этим будем иметь в виду сборку в TeamCity, ассоциированную с веткой Git и тикетом в трекере Jira. В ней выполняются как минимум статический анализ, компиляция и тестирование. Удачная сборка на последней ревизии ветки в сочетании со статусом тикета To Merge это однo из необходимых условий автомерджа.

Пример модели ветвления

Испробовав разные модели ветвления в мобильных проектах, мы пришли к следующему упрощённому варианту:

На основе ветки main разработчик создаёт ветку с названием, включающим идентификатор тикета в трекере, например PRJ-k. По завершении работы над тикетом разработчик переводит его в статус Resolved. При помощи хуков, встроенных в трекер, мы запускаем для ветки тикета сборку. В определённый момент, когда изменения прошли ревью и необходимые проверки автотестами на разных уровнях, тикет получает статус To Merge, его забирает автоматика и отправляет в main.

Раз в неделю на основе main мы создаём ветку релиза release_x.y.z, запускаем на ней финальные сборки, при необходимости исправляем ошибки и наконец выкладываем результат сборки релиза в App Store или Google Play. Все фазы веток отражаются в статусах и дополнительных полях тикетов Jira. В общении с Jira помогает наш клиент REST API.

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

Первая версия: жадная стратегия

Сначала мы шли от простого и очевидного. Брали все тикеты, находящиеся в статусе To Merge, выбирали из них те, для которых есть успешные сборки, и отправляли их в main командой git merge, по одной.

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

Наличие в TeamCity актуальной успешной сборки мы проверяли при помощи метода REST API getAllBuilds примерно следующим образом (псевдокод):

haveFailed = False # Есть ли неудачные сборкиhaveActive = False # Есть ли активные сборки# Получаем сборки типа buildType для коммита commit ветки branchbuilds = teamCity.getAllBuilds(buildType, branch, commit)# Проверяем каждую сборкуfor build in builds:  # Проверяем каждую ревизию в сборке  for revision in build.revisions:    if revision.branch is branch and revision.commit is commit:      # Сборка актуальна      if build.isSuccessful:        # Сборка актуальна и успешна        return True      else if build.isRunning or build.isQueued        haveActive = True      else if build.isFailed:        haveFailed = Trueif haveFailed:  # Исключаем тикет из очереди, переоткрывая его  ticket = Jira.getTicket(branch.ticketKey)  ticket.reopen("Build Failed")  return Falseif not haveActiveBuilds:  # Нет ни активных, ни упавших, ни удачных сборок. Запускаем новую  TriggerBuild(buildType, branch)

Ревизии это коммиты, на основе которых TeamCity выполняет сборку. Они отображаются в виде 16-ричных последовательностей на вкладке Changes (Изменения) страницы сборки в веб-интерфейсе TeamCity. Благодаря ревизиям мы можем легко определить, требуется ли пересборка ветки тикета или тикет готов к слиянию.

Важно, что ревизию можно (а часто даже необходимо) указывать в запросе на добавление новой сборки в очередь в параметре lastChanges, потому что в противном случае TeamCity может выбрать устаревшую ревизию ветки при запуске сборки. Как будет показано ниже, указывать ревизию необходимо в тех случаях, если, например, логика вне TeamCity основана на поиске сборок на конкретных коммитах (наш случай).

Так как после перевода тикета в статус готовности (в нашем примере Resolved) соответствующая ветка, как правило, не меняется, то и сборка, ассоциированная с тикетом, чаще всего остаётся актуальной. Кроме того, сам факт нахождения тикета в статусе To Merge говорит о высокой вероятности того, что сборка не упала. Ведь при падении сборки мы сразу переоткрываем тикет.

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

Всё работало быстро, но требовало внимания. То и дело возникали ситуации, когда изменения нескольких тикетов конфликтовали между собой. Конфликты при слияниях как явление достаточно распространённое поначалу ни у кого особых вопросов не вызывали. Их разрешали разработчики, дежурные по релизу. Но с увеличением количества разработчиков, задач и, соответственно, веток, приведение релиза в порядок требовало всё больше усилий. Задержки в разрешении конфликтов начали сказываться на новых задачах. Полагаю, не стоит продолжать эту цепочку скорее всего, вы уже поняли, что я имею в виду. С конфликтами нужно было что-то делать, причём не допуская их попадания в релиз.

Конфликты слияния

Если изменить одну и ту же строку кода в разных ветках и попытаться соединить их в main, то Git попросит разрешить конфликты слияния. Из двух вариантов нужно выбрать один и закоммитить изменения.

Это должно быть знакомо практически каждому пользователю системы контроля версий (VCS). Процессу CI, так же, как и любому пользователю VCS, нужно разрешать конфликты. Правда, делать это приходится немного вслепую, в условиях почти полного непонимания кодовой базы.

Если команда git merge завершилась с ошибкой и для всех файлов в списке git ls-files --unmerged заданы обработчики конфликтов, то для каждого такого файла мы выполняем парсинг содержимого по маркерам конфликтов <<<<<<<, ======= и >>>>>>>. Если конфликты вызваны только изменением версии приложения, то, например, выбираем последнюю версию между локальной и удалённой частями конфликта.

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

Решение следующее: нарушаем как минимум одно из необходимых условий слияния. Так как ветка ассоциирована с тикетом трекера, можно переоткрыть тикет, изменив его статус. Таким образом мы одновременно исключим тикет из автомерджа и оповестим об этом разработчика (ведь он подписан на изменения в тикете). На всякий случай мы отправляем ещё и сообщение в мессенджере.

Логические конфликты

А может ли случиться так, что, несмотря на успешность сборок пары веток в отдельности, после слияния их с main сборка на основной ветке упадёт? Практика показывает, что может. Например, если сумма a и b в каждой из двух веток не превышает 5, то это не гарантирует того, что совокупные изменения a и b в этих ветках не приведут к большей сумме.

Попробуем воспроизвести это на примере Bash-скрипта test.sh:

#!/bin/bashget_a() {    printf '%d\n' 1}get_b() {    printf '%d\n' 2}check_limit() {    local -i value="$1"    local -i limit="$2"    if (( value > limit )); then        printf >&2 '%d > %d%s\n' "$value" "$limit"        exit 1    fi}limit=5a=$(get_a)b=$(get_b)sum=$(( a + b ))check_limit "$a" "$limit"check_limit "$b" "$limit"check_limit "$sum" "$limit"printf 'OK\n'

Закоммитим его и создадим пару веток: a и b.
Пусть в первой ветке функция get_a() вернёт 3, а во второй get_b() вернёт 4:

diff --git a/test.sh b/test.shindex f118d07..39d3b53 100644--- a/test.sh+++ b/test.sh@@ -1,7 +1,7 @@ #!/bin/bash get_a() {-    printf '%d\n' 1+    printf '%d\n' 3 } get_b() {git diff main bdiff --git a/test.sh b/test.shindex f118d07..0bd80bb 100644--- a/test.sh+++ b/test.sh@@ -5,7 +5,7 @@ get_a() { }  get_b() {-    printf '%d\n' 2+    printf '%d\n' 4 }  check_limit() {

В обоих случаях сумма не превышает 5 и наш тест проходит успешно:

git checkout a && bash test.shSwitched to branch 'a'OKgit checkout b && bash test.shSwitched to branch 'b'OK

Но после слияния main с ветками тесты перестают проходить, несмотря на отсутствие явных конфликтов:

git merge a bFast-forwarding to: aTrying simple merge with bSimple merge did not work, trying automatic merge.Auto-merging test.shMerge made by the 'octopus' strategy. test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)bash test.sh7 > 5

Было бы проще, если бы вместо get_a() и get_b() использовались присваивания: a=1; b=2, заметит внимательный читатель и будет прав. Да, так было бы проще. Но, вероятно, именно поэтому встроенный алгоритм автомерджа Git успешно обнаружил бы конфликтную ситуацию (что не позволило бы продемонстрировать проблему логического конфликта):

git merge a Updating 4d4f90e..8b55df0Fast-forward test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git merge b Auto-merging test.shCONFLICT (content): Merge conflict in test.shRecorded preimage for 'test.sh'Automatic merge failed; fix conflicts and then commit the result.

Разумеется, на практике конфликты бывают менее явными. Например, разные ветки могут полагаться на API разных версий какой-нибудь библиотеки зависимости, притом что более новая версия не поддерживает обратной совместимости. Без глубоких знаний кодовой базы (читай: без разработчиков проекта) обойтись вряд ли получится. Но ведь CI как раз и нужен для решения таких проблем.

Конечно, от разрешения конфликта мы никуда не уйдём кто-то должен внести правки. Но чем раньше нам удастся обнаружить проблему, тем меньше людей будет привлечено к её решению. В идеале потребуется озадачить лишь разработчика одной из конфликтующих веток. Если таких веток две, то одна из них вполне может быть соединена с main.

Превентивные меры

Итак, главное не допустить попадания логического конфликта в main. Иначе придётся долго и мучительно искать источник ошибок, а затем программиста, который проблему должен или может решить. Причём делать это нужно максимально быстро и качественно, чтобы, во-первых, не допустить задержки релиза и во-вторых, избежать в новых ветках логических конфликтов, основанных на уже выявленном конфликте. Такие конфликты часто приводят к неработоспособности большой части приложения или вовсе блокируют его запуск.

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

Вторая версия: последовательная стратегия

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

Git, по идее, как раз и является средством синхронизации. Но порядок попадания веток в main и, наоборот, main в ветки определяем мы сами. Чтобы определить точно, какие из веток вызывают проблемы в main, можно попробовать отправлять их туда по одной. Тогда можно выстроить их в очередь, а порядок организовать на основе времени попадания тикета в статус To Merge в стиле первый пришёл первым обслужен.

С порядком определились. А как дальше соединять ветки? Допустим, мы сольём в main первый тикет из очереди. Так как main изменилась, она может конфликтовать с остальными тикетами в очереди. Поэтому перед тем как сливать следующий тикет, нужно удостовериться, что обновлённая main по-прежнему совместима с ним. Для этого достаточно слить main в тикет. Но так как после соединения main с веткой её состояние отличается от того, которое было в сборке, необходимо перезапустить сборку. Для сохранения порядка все остальные тикеты в очереди должны ждать завершения сборки и обработки впередистоящих тикетов. Примерно такие рассуждения привели нас к последовательной стратегии автомерджа.

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

Но есть у этой схемы существенный недостаток: пропускная способность автомерджа линейно зависит от времени сборки. При среднем времени сборки iOS-приложения в 25 минут мы можем рассчитывать на прохождение максимум 57 тикетов в сутки. В случае же с Android-приложением требуется примерно 45 минут, что ограничивает автомердж 32 тикетами в сутки, а это даже меньше количества Android-разработчиков в нашей компании.

На практике время ожидания тикета в статусе To Merge составляло в среднем 2 часа 40 минут со всплесками, доходящими до 10 часов! Необходимость оптимизации стала очевидной. Нужно было увеличить скорость слияний, сохранив при этом стабильность последовательной стратегии.

Финальная версия: сочетание последовательной и жадной стратегий

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

Давайте вспомним идею жадной стратегии: мы сливали все ветки готовых тикетов в main. Основной проблемой было отсутствие синхронизации между ветками. Решив её, мы получим быстрый и надёжный автомердж!

Раз нужно оценить общий вклад всех тикетов в статусе To Merge в main, то почему бы не слить все ветки в некоторую промежуточную ветку Main Candidate (MC) и не запустить сборку на ней? Если сборка окажется успешной, то можно смело сливать MC в main. В противном случае придётся исключать часть тикетов из MC и запускать сборку заново.

Как понять, какие тикеты исключить? Допустим, у нас n тикетов. На практике причиной падения сборки чаще всего является один тикет. Где он находится, мы не знаем все позиции от 1 до n являются равноценными. Поэтому для поиска проблемного тикета мы делим n пополам.

Так как место тикета в очереди определяется временем его попадания в статус To Merge, имеет смысл брать ту половину, в которой расположены тикеты с большим временем ожидания.

Следуя этому алгоритму, для k проблемных тикетов в худшем случае нам придётся выполнить O(k*log2(n)) сборок, прежде чем мы обработаем все проблемные тикеты и получим удачную сборку на оставшихся.

Вероятность благоприятного исхода велика. А ещё в то время, пока сборки на ветке MC падают, мы можем продолжать работу при помощи последовательного алгоритма!

Итак, у нас есть две автономные модели автомерджа: последовательная (назовём её Sequential Merge, или SM) и жадная (назовём её Greedy Merge, или GM). Чтобы получить пользу от обеих, нужно дать им возможность работать параллельно. А параллельные процессы требуют синхронизации, которой можно добиться либо средствами межпроцессного взаимодействия, либо неблокирующей синхронизацией, либо сочетанием этих двух методов. Во всяком случае, мне другие методы неизвестны.

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

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

  1. SM-SM и GM-GM: между командами одного типа.

  2. SM-GM: между SM и GM в рамках одного репозитория.

Первая проблема легко решается при помощи мьютекса по токену, включающему в себя имя команды и название репозитория. Пример: lock_${command}_${repository}.

Поясню, в чём заключается сложность второго случая. Если SM и GM будут действовать несогласованно, то может случиться так, что SM соединит main с первым тикетом из очереди, а GM этого тикета не заметит, то есть соберёт все остальные тикеты без учёта первого. Например, если SM переведёт тикет в статус In Master, а GM будет всегда выбирать тикеты по статусу To Merge, то GM может никогда не обработать тикета, соединённого SM. При этом тот самый первый тикет может конфликтовать как минимум с одним из других.

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

Таким образом, мы получили своего рода неблокирующую синхронизацию.

Немного о TeamCity

В процессе реализации GM нам предстояло обработать много нюансов, которыми я не хочу перегружать статью. Но один из них заслуживает внимания. В ходе разработки я столкнулся с проблемой зацикливания команды GM: процесс постоянно пересобирал ветку MC и создавал новую сборку в TeamCity. Проблема оказалась в том, что TeamCity не успел скачать обновления репозитория, в которых была ветка MC, созданная процессом GM несколько секунд назад. К слову, интервал обновления репозитория в TeamCity у нас составляет примерно 30 секунд.

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

Кто-то посчитает решение очевидным, но я нашёл его не сразу. Оказывается, прикрепить ревизию к сборке при её добавлении в очередь можно при помощи параметра lastChanges метода addBuildToQueue:

<lastChanges>  <change    locator="version:{{revision}},buildType:(id:{{build_type}})"/></lastChanges>

В этом примере {{revision}} заменяется на 16-ричную последовательность коммита, а {{build_type}} на идентификатор конфигурации сборки. Но этого недостаточно, так как TeamCity, не имея информации о новом коммите, может отказать нам в запросе.

Для того чтобы новый коммит дошёл до TeamCity, нужно либо подождать примерно столько, сколько указано в настройках конфигурации корня VCS, либо попросить TeamCity проверить наличие изменений в репозитории (Pending Changes) при помощи метода requestPendingChangesCheck, а затем подождать, пока TeamCity скачает изменения, содержащие наш коммит. Проверка такого рода выполняется посредством метода getChange, где в changeLocator нужно передать как минимум сам коммит в качестве параметра локатора version. Кстати, на момент написания статьи (и кода) на странице ChangeLocator в официальной документации описание параметра version отсутствовало. Быть может, поэтому я не сразу узнал о его существовании и о том, что это 40-символьный 16-ричный хеш коммита.

Псевдокод:

teamCity.requestPendingChanges(buildType)attempt = 1while attempt <= 20:  response = teamCity.getChange(commit, buildType)  if response.commit == commit:    return True # Дождались  sleep(10)return False

О предельно высокой скорости слияний

У жадной стратегии есть недостаток на поиск ветки с ошибкой может потребоваться много времени. Например, 6 сборок для 20 тикетов у нас может занять около трёх часов. Можно ли устранить этот недостаток?

Допустим, в очереди находится 10 тикетов, среди которых только 6-й приводит к падению сборки.

Согласно жадной стратегии, мы пробуем собрать сразу все 10 тикетов, что приводит к падению сборки. Далее собираем левую половину (с 1 по 5) успешно, так как тикет с ошибкой остался в правой половине.

Если бы мы сразу запустили сборку на левой половине очереди, то не потеряли бы времени. А если бы проблемным оказался не 6-й тикет, а 4-й, то было бы выгодно запустить сборку на четверти длины всей очереди, то есть на тикетах с 1 по 3, например.

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

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

Примерно такой же алгоритм реализован в премиум-функции GitLab под названием Merge Trains. Перевода этого названия на русский язык я не нашёл, поэтому назову его Поезда слияний. Поезд представляет собой очередь запросов на слияние с основной веткой (merge requests). Для каждого такого запроса выполняется слияние изменений ветки самого запроса с изменениями всех запросов, расположенных перед ним (то есть запросов, добавленных в поезд ранее). Например, для трёх запросов на слияние A, B и С GitLab создаёт следующие сборки:

  1. Изменения из А, соединённые с основной веткой.

  2. Изменения из A и B, соединённые с основной веткой.

  3. Изменения из A, B и C, соединённые с основной веткой.

Если сборка падает, то соответствующий запрос из очереди удаляется, а сборки всех предыдущих запросов перезапускаются (без учёта удалённого запроса).

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

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

Но если преград человеческой мысли нет, то пределы аппаратных ресурсов видны достаточно отчётливо:

  1. Каждой сборке нужен свой агент в TeamCity.

  2. В нашем случае у сборки мобильного приложения есть порядка 15-100 сборок-зависимостей, каждой из которых нужно выделить по агенту.

  3. Сборки автомерджа мобильных приложений в main составляют лишь малую часть от общего количества сборок в TeamCity.

Взвесив все плюсы и минусы, мы решили пока остановиться на алгоритме SM + GM. При текущей скорости роста очереди тикетов алгоритм показывает хорошие результаты. Если в будущем заметим возможные проблемы с пропускной способностью, то, вероятно, пойдём в сторону Merge Trains и добавим пару параллельных сборок GM:

  1. Вся очередь.

  2. Левая половина очереди.

  3. Левая четверть очереди.

Что в итоге получилось

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

  • уменьшение среднего размера очереди в 2-3 раза;

  • уменьшение среднего времени ожидания в 4-5 раз;

  • мердж порядка 50 веток в день в каждом из упомянутых проектов;

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

Примеры графиков слияний за несколько дней:

Количество тикетов в очереди до и после внедрения нового алгоритма:

Среднее количество тикетов в очереди (AVG) уменьшилось в 2,5 раза (3,95/1,55).

Время ожидания тикетов в минутах:

Среднее время ожидания (AVG) уменьшилось в 4,4 раза (155,5/35,07).

Подробнее..

Тренды тестирования 2020-2021 правда и мифы

15.06.2021 16:19:06 | Автор: admin

Всем привет! Недавно я наткнулся на World Quality Report (ссылку поставил в конце, чтобы не пугать вас сразу отчетом на 50 страниц) большой обзор трендов в тестировании 2020-2021 годов. А поскольку мы в Qameta Software сами постоянно сталкиваемся с командами тестирования, которые стараются как-то поправить свои процессы и наладить работу тестирования, я решил оценить, насколько они актуальны в России.

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

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

1. DevOps и Agile приходит в тестирование

Правда.

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

Именно поэтому в последнее время инструменты для автоматизации тестирования находятся на подъеме: Cypress, Playwright, Pupperteer и многие другие. Тестировщикам тоже приходится меняться и учиться подходам разработчиков: автоматизации, масштабированию и работе по спринтам.

2. Искусственный интеллект и машинное обучение

Пока нет.

Мы ждем прихода ИИ уже давно. Что же, все еще ждем теперь в управлении качеством. Появляются стартапы, фокус которых лежит в области подготовки, генерации и анализа тестовых данных или тестирования сервисов с помощью ИИ. Не верите? Посмотрите на Applitools, Functionize, Synthesized, TestIM или ReportPortal.

Под удар в первую очередь попадает ручное тестирование 90% участников опроса ищут ИИ-решения, ориентированные именно на замену ручного труда в тестировании. Пока мы такого тренда не наблюдаем, вполне вероятно, он придет к нам позже.

3. Оптимизация бюджетов на тестирование

Похоже на правду.

Расходы на тестирование сократились в среднем на 13%. Авторы обзора списывают тягу к оптимизации расходов на COVID-19 и развитие трендов цифровой трансформации, что бы это не значило.

Я бы не винил пандемию. Кажется, что двух предыдущих разделов достаточно, чтобы объяснить оптимизацию количества людей, задействованных в ручном тестировании. А тем, кто остался, в руки попадут инструменты, помогающие работать быстрее и эффективнее: и окружения в облаках, и автоматизация тестов, и пайплайны для их прогонов. В итоге, тестирование должно вернуться в цикл разработки DevOps/Agile.

4. Фокус на автоматизацию тестирования

Правда.

Этот пункт не про автотесты, как можно было подумать. Он про автоматизацию процессов тестирования в целом: от создания тест-кейсов до репортинга. Я писал об этом раньше, этот тренд мы видим как один из самых явных. Меньше людей больше автоматизации.

Этот тренд подтверждается нашими наблюдениями все больше пользователей Allure Report двигаются в сторону автоматизации и инструментов, которые позволяют эту автоматизацию аккуратно вплести в процессы (это мы видим по спросу на TestOps).

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

5. Управление тестовыми окружениями (ТО) и тестовыми данными (ТД)

Похоже на правду.

Управлять тестовыми данными и тестовыми окружениями сложно, хорошо, что под эту задачу тоже появляются инструменты. Участники исследования смотрят на это развитие с оптимизмом: ТО и ТД уходит в облака; внедряются тулы для управления тестовыми данными (пока преимущественно для маскирования); все чаще используются инструменты виртуализации серверов и данных.

Забавно, что в этой области вопросов больше с юридической точки зрения, чем с технологической.

6. COVID-19 помог компаниям трансформироваться

Неправда.

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

Хотя в этом разделе есть отдельные интересные наблюдения: например, возрастание требований к тестированию безопасности связывают с пандемией и необходимостью обеспечивать удаленный и безопасный доступ к большему количеству инструментов.

Вместо итогов

В общем, World Quality Report получился скорее интересный, чем нет. Да, где-то есть призывы в духе "нормально делай нормально будет", но в целом рекомендую к ознакомлению. В отчете несколько десятков страниц, из которых я постарался сделать выжимку, так что каждый при внимательном изучении сможет найти интересные детали.

Остается только спросить, какие тренды ощущаете вы? Что происходит в вашем тестировании, и насколько все это совпадает с наблюдениями из отчета.

Подробнее..

Перевод Процесс это не продукт антиманифест методологии разработки ПО

19.06.2021 14:07:57 | Автор: admin

TLDR:

Антиманифест методологии разработки ПО

Процесс это не продукт

Руководство, а не менеджмент

Диалог, а не диктат


Вот и всё, остальное вы можете додумать сами, но если хотите, продолжайте чтение.

Процесс это не продукт


За свою (очень) долгую карьеру в технологической сфере я ощутил на себе большинство методологий разработки ПО, и этот опыт показал мне, что они больше похожи на обязанность, а не на помощь. Скажу больше: мне кажется, что избыток методологии гораздо опаснее её недостатка. Однако культ Одной Истинной Методологии продолжает процветать, и стоит поразмыслить над тем, почему так происходит.

Если встретишь Будду на дороге убей его!

Думаю, что одна из причин заключается в том, что многие/большинство/все из нас втайне подозревают, что мы делаем всё не так, и есть кто-то, делающий всё правильно; если мы последуем его совету, то тоже будем делать правильно, и все ошибочные оценки, баги, плохой код и т.д, и т.п, исчезнут.

Не нужно так думать, они никуда не денутся.

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

Если можешь делать, то делай. Если не можешь, то управляй.

Однако ещё одна причина это бесконечное разрастание менеджмента среднего звена, поражающее и крупные, и мелкие компании. Если компания растёт, то нам кажется, что ей нужно больше менеджмента. И ещё. И ещё. Почему? Каков карьерный путь менеджера? Он нанимает менеджмент более низкого звена. Хуже того компании поддаются этому, они склонны укреплять свою иммунную систему. Любые попытки изменения статуса-кво менеджмента будут угрозой самим основам культуры менеджмента. Не говоря уже обо всех этих должностях в менеджменте. Нужны инновации? Отлично. Давайте наймём руководителя отдела инноваций. И, разумеется, менеджменту обязательно нужно чем-то заниматься, и это процесс, отслеживание спринтов, доски trello, что угодно, чтобы заполнить восемь рабочих часов.

Руководство, а не менеджмент


И это приводит нас к следующему пункту манифеста: к лидерству. Знаете, такому настоящему руководству. Ты приходишь и показываешь путь вперёд. Лично каждому, чётко, по одному программисту за раз, и не только то, что они должны знать, но и спринты с use cases. Нужно иметь достаточно технических возможностей, чтобы обучать, готовить и мотивировать. И учиться самому. Сообщать всем, вплоть до уборщиков, куда и зачем мы движемся. Не один раз, а постоянно. Организации, которые будут это делать, окажутся самоуправляемыми. Почему? Потому что люди (даже программисты) умны. Если даёшь им информацию и полномочия, они думают, правильно расставляют приоритеты, не ругаются из-за смены целей и не особо нуждаются в менеджменте. Если сделать всё правильно, то окажется, что вы следуете за коллективом, а не ведёте вперёд, потому что культура команды становится больше, чем любой из её участников.

Диалог, а не диктат


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

Если всё это заставило вас ощутить неудобство, то это хорошо. Для того и написана эта статья.

P.S.: Никакой Jira.



На правах рекламы


Многие клиенты уже оценили преимущества серверов от Вдсины.
Мы предлагаем выделенные и виртуальные серверы. Максимальная конфигурация позволит реализовать практически любую идею 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe. Закажите и вы!

Подписывайтесь на наш чат в Telegram.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru