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

Development

Часть 3 ESPboy2 гаджет для ретро игр и экспериментов с IoT, новости проекта 2021

24.05.2021 16:18:49 | Автор: admin

Предыдущие статьи:

В основе лежит старенький уже на сегодня чип ESP8266 c WiFi с подключенными к нему цветным экраном 128х128, кнопками, динамиком, RGB светодиодом и еще парой удобных дополнений.

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

В ESPboy есть слот расширения, на который выведены интерфейсы I2C, SPI, UART, I2S куда втыкаются штатные модули, которые при загрузке соответствующего софта превращают устройство в ретро игровую консоль, GSM телефон, GPS навигатор, FM радио, перехватчик радиопакетов, читалку/писалку rfid/nfc, MP3 плеер, погодную станцию, универсальный ИК пульт, LORA мессенджер и много чего еще.

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

Список опробованных модулей можно увидеть на сайте проекта www.espboy.com, весь софт, как уже говорилось, на GitHub для изучения и экспериментов. Много чего в работе и еще больше в планах. К сожалению не удалось поспеть в прошлом году довести модули до промышленного изготовления, все они в виде прототипов, но энтузиасты с помощью сообщества в проектном форуме и Discord чате собирают при желании любой без сложностей. Думаю, что в этом году часть модулей все же получится выпустить в заводском исполнении.

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

В разработке еще с десяток модулей, которые неспешно собираются и тестируются.

Уже есть задумки по ESPboy3.

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

Заглядываясь на кикстартер успех FLIPPER ZERO, тоже посещают мысли о запуске компании на Kickstarter. Но такой разворот требует значительных усилий в сторону маркетинга, продаж, что далее неизбежно повлечет за собой масштабирование производства, логистики, поддержки, переход на новый уровень R&D и управление процессами в целом. Бросаться в такой омут очертя голову не охота. Хочется делать основательно, а в этом случае уже нужны заинтересованные люди, понимающие в бизнесе и инвестиции.

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

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

Да и просто поиграться с устройством в современные технологии довольно увлекательно и познавательно.

Всем добра!

С уважением, РоманС

mailto: espboy.edu@gmail.com

Подробнее..

Что такое системы API Management

19.02.2021 18:05:42 | Автор: admin

Зачем они нужны и какие функции они выполняют.

Всем привет! Меня зовут Антон, я инженер команды, отвечающей за развитие централизованныхIT-сервисов, которыми пользуются продуктовые команды вX5RetailGroup.

В этой статье я расскажу осистемах класса API Management и в частности о APIM Gravitee (https://www.gravitee.io), том, что это за класс систем, как они используются для обеспечения потребностей команд разработки. Статья не погружает в технические аспекты, но может быть полезна архитекторам и менеджерам, которые думают о том, чтобы попробовать использоватьданный класс систем, но не знают, подойдут ли они для их задач, а также разработчикам, которые могут открыть для себя новые инструменты для удобной работы с API.

Что такое системы API Management

Определение

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

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

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

Зачем еще один огород городить?

Необходимость этого класса систем возникла в связи с увеличением количества сервисов, которые могут предоставлять свое API как конечный продукт для других сервисов. Ничего не напоминает? Правильно - микросервисная архитектура. Для организации это возможность ускорения "цифровизации". Владельцы продукта публикуют API, документацию к ней и прочие документы типа: планов развития, лимиты и т.д. Также всем хочется контролировать качество работы API, а это уже анализ производительности, статистика использования, проведение все возможных маркетинговых исследований и простой мониторинг. Для коллег из информационной безопасности будет интересно осуществлять наблюдение за использованием API в части контроля доступа: авторизация, аутентификация, аудит, проверка по черным/белым спискам IP. Для аналитиков и тестировщиков возможно будет интересна функциональность проверки корректности запросов, проверка корректности JSON или динамическое изменение запросов, для DevOps инженеров возможность установки rate limit, чтобы не было DoS конечного сервиса, настройка кэша и возможность оптимизации сервиса под микросервисную архитектуру: Service Discovery, Load Balancing, Blue/Green или Canary deploy.

Архитектура сервиса

В архитектуру сервиса API Management обычно входят (см. рис. 1):

  1. Management Core: ядро системы, которое отвечает за формирование политик, планов, работу точками входа и выхода, настроек API Gateways и API, настройку CORS, Failover, Healthcheck, формирование запросов на отображение статистики использования API и логов.

  1. Web/Development Portal: отвечает за UI, отображение настроек, статистики использования API, healthcheck и логов, а также позволяет общаться разработчикам, администраторам и владельцам API.

  1. API Gateways: шлюзы или прокси, они отвечают за обработку запросов от клиентов сервиса согласно установленных настроек и политик, ведение логов запросов и ответов, а также запуск healthcheck по Backend API.

  1. Backend API: отвечает за обработку запросов согласно бизнес-логике конечного сервиса.

  1. Databases: в части сервиса API Management, хранят данные по настройке API, API Gateways, логи запросов клиентов и ответы backend, healthcheck, данные мониторинга практически всех компонентов API Management.

рис. 1 Архитектура сервиса API Managementрис. 1 Архитектура сервиса API Management

Плюсы и минусы систем API Management

У данных систем есть несколько преимуществ:

  • Абстракция: система упрощает сложность сервисов под ним и предоставляет клиентам единый опыт.

  • Аутентификация:система позволяет пройти аутентификацию, в том числе и через сторонние службы, например Keycloak.

  • Управление трафиком: система регулирует входящий и исходящий трафик API.

  • Мониторинг API: система может помочь в мониторинге запросов/ответов клиента.

  • Преобразования: система позволяет преобразовать запросы/ответы API.

К минусам можно отнести:

  • Увеличение Latency: шлюзу необходимо время для обработки запросов/ответов согласно настроенным политикам.

  • TCO: Совокупная стоимость владения для всей цепочки поставки ценности, естественно будет больше, чем просто установить nginx и выставить его наружу.

APIGateways

API Gateways работают как единая точка входа в ЦОД (центр обработки данных), группу распределенных служб или сервисов (см. рис. 2). Также API Gateways могут использоваться для связи между двумя продуктами/сервисами, развернутыми в одном ЦОД. API Gateways принимают вызовы от клиентов, обрабатывают их согласно политикам/правилам и направляют их в соответствующие сервисы. Чтобы API Gateways могли максимально быстро обрабатывать запросы от клиентов их делают максимально легковесными, с использованием асинхронных фреймворков. API Gateways, как правило, работают только на седьмом уровне (L7) модели OSI.

рис. 2рис. 2

ТипыAPI Gateways

С точки зрения расположения есть два места установки API Gateways:

  • Local API Gatewaysработают как шлюз для сервисов внутри организации.

  • DMZ API Gatewaysработают как шлюз для внешних потребителей и клиентов сервисов.

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

Наиболее распространенные системы

Name

Tags

APIGee

Enterprise

WSO2 API Manager

Enterprise/Open source

SAP API

Enterprise

3scale

Enterprise

IBM API Management

Enterprise

Kong

Enterprise/Open source

Mashery

Enterprise

Microsoft Azure API Management

Enterprise

Mule Soft

Enterprise

Централизованный сервис Gravitee

В X5 Retail Group в свое время выбрали для использования систему APIM Gravitee (https://www.gravitee.io). Основной сценарий использования нашими командами публикация API в локальной сети и в DMZ.

Немного цифр об этом сервисе на текущий момент:

  • 23 продуктивных команд зарегистрировано

  • 69 API Gateways для обслуживания запросов

  • 400 Гб логов за сутки

  • 350 RPS в среднем по больнице за сутки

  • 30 000 000+ запросов обрабатывается за сутки

Функциональность

Рассмотрим базовую функциональность, предоставленную системой APIM Gravitee.

  1. Identity provider: Аутентификация внешних пользователей можешь осуществляться на основе следующих систем и сервисов:

  1. MongoDB (по умолчанию для новых пользователей, которые приглашаются);

  2. In-memory (по умолчанию для пользователя admin);

  3. LDAP / Active Directory;

  4. OpenID Connect IdP (Azure AD, Google);

  1. Fetchers: стянуть настройки для новой версии API и документацию можно через:

  1. File (Swagger, OpenAPI);

  2. HTTP;

  3. GIT;

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

  1. API Key - политика авторизации с использованием API-ключа;

  2. Rate-limiting - политика ограничения скорости или квот для предотвращения флудинга backend;

  3. Transform Headers/Transform Query Parameters - политика преобразований параметров заголовка или запроса;

  4. etc.

Политик обработки запросов в Gravitee Gateway более 30 штук и с каждой версией их число увеличивается. Так же можно сделать свои политики. Но стоит учитывать, что чем больше политик "навешано", тем медленнее будет обрабатываться запрос и тем больше ресурсов будет использовано.

  1. Reporters: сборщики логов используются экземпляром шлюза для сообщения о событиях. Типы сборщиков логов:

  1. Reporter file;

  2. Elasticsearch;

  3. Accesslog;

Типы событий, которые можно собрать:

  1. Метрики запроса/ответа например, время ответа, длина содержимого, api-ключ;

  2. Мониторинг метрик например, процессора, использования кучи, кол-во открытых файлов и т.д.;

  3. Показатели проверки работоспособности например, состояние, код ответа;

  1. Repositories: репозиторий - это подключаемый компонент хранилища для настройки API, конфигураций политик, аналитики, аудита и так далее. Можно использовать следующие репозитории:

  1. MongoDB (по умолчанию);

  2. Redis;

  3. Elasticsearch;

  4. PostgreSQL (коннектор через JDBC и надо использовать другой дистрибутив);

  1. Resources: ресурсы, которые можно использовать при работе:

  1. OAuth2 (подключение к OAuth2 как источнику данных для аутентификации);

  2. Cache (создание локального кэша на шлюзе);

  3. LDAP (подключение к LDAP как источнику данных для аутентификации);

  1. Services: сервисы, которые может предоставлять сам шлюз:

  1. Sync (Синхронизация состояния шлюза с конфигурацией из репозитория управления);

  2. local-registry (Локальный реестр используется для загрузки API в формате json из файловой системы. Таким образом, вам не нужно настраивать свой API с помощью веб-консоли или rest API (но вам нужно знать и понимать формат json API, чтобы он работал.));

  3. health-check (Сервис для проверки состояния других сервисов);

  4. monitor (Сервис извлекает метрики, такие как метрики os / process / jvm, и отправляет их в базовую службу отчетов);

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

  1. Email;

  1. Alerts: оповещение используется для отправки триггеров или событий в механизм оповещений, которые могут быть обработаны для отправки уведомления с помощью настроенного плагина Notifier.

  1. Vertx;

А так как система с открытыми исходниками:https://github.com/gravitee-io, то при знании Java, можно написать свои собственные плагины и использовать как вздумается.

Настройки API

Настройка нового API проходит несколько этапов:

  1. Создание API

  1. Настройка планов

  1. Привязываем API к шлюзам

Создание API

На странице создания API можно выбрать, как и из чего создать скелет нового API

Вариантов немного, но выбор есть:

  1. Вручную накликал и можно работать!

  1. Импортировать из swagger файла.

  1. Импортировать из Gitа/URLа.

Рассмотрим ручной вариант настройки, выбираем "->"

Ручное создание API проходит 5 шагов

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

Второй шаг может быть в двух модификациях:

В Simple mode указываем только адрес нашего backend api, например:https://msk-dpro-sre000.x5.ru:8443/testbackend/

В Advanced mode необходимо указатьадрес нашего backend api, используемые tenant и sharding tags.

tenant- область выделенная в Elasticsearch для логов запросов и событий.

sharding tags- теги, согласно которым связываются API и Gateways

На третьем шаге можно указать Plan

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

Name -имяплана

Security type -типплана: Keyless(public), API Key, JWT, OAuth2

Description - просто описание плана и его особенностей

Rate limit - ограничение кол-ва запросов в секунду/минуту

Quota -ограничение кол-ва запросов в час/день/неделя/месяц

Path authorization - черный и белый список путей и методов к ним

Данный пункт можно пропустить и заполнить уже в созданном API.

На четвертом шаге можно загрузить файл документации по API

Поддерживается формат swagger.json

Данный пункт можно пропустить и заполнить уже в созданном API.

На пятом шаге мы проверяем все что заполнили

И выбираем либо "Создать API без установки на шлюз", либо "Создать и запустить API"

Нажимаем CREATE и получаем частично настроенный API для работы.

Настройки планов

План предоставляет собой уровень обслуживания и доступа между API и приложениями. У одного API может быть несколько планов, но только один без ключевой - "keyless". План определяет ограничения доступа, режимы проверки подписи и другие настройки для адаптации к конкретному приложению.

Основные настройки:

  1. Планы привязываются к конкретному шлюзу или группе шлюзов через Tags (см. рис. 3)

Рис. 3Рис. 3

2. В плане указывается какой тип Аутентификации будет использован (см. рис. 4)

Рис. 4Рис. 4

3. Можно сразу указать Rate и Quota лимиты и определить список разрешенных путей для запроса (см. рис. 5)

Рис. 5Рис. 5

4. В последней вкладке можно указать политики, которые будут отрабатывать на начальном этапе обработки запросов (см. рис. 6)

Рис. 6Рис. 6

Заключение

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

Подробнее..

Стенды разработки без очередей и простоев

23.03.2021 08:12:20 | Автор: admin

Цель статьи - показать один из возможных подходов для организации гибкого развёртывания dev/test стендов. Показать какие преимущества предоставляет нам IaC подход в сочетании с современными инструментами.


Предыстория

Имеется несколько стендов для разработчиков - devs, tests, production. Новые версии компонентов продукта появляются несколько раз в день.

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

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

Задача

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

Стек

Gitlab CI, Terraform, Bash, любое приватное/публичное облако.

Технические сложности:

  1. Terraform state file - из коробки у нас нет возможности использовать переменную в значенииимени state файла. Нужно что-то придумывать или использовать другой продукт.

  2. Subnets - каждое новое окружение должно быть создано в изолированной подсети. Нужно контролировать свободные/занятые подсети, некий аналог DHCP, но для подсетей.

Алгоритм

  1. Gitlab CI выполняет pipeline. Связывает все остальные компоненты воедино.

  2. Terraform создаёт и удаляет экземпляры виртуальных машин.

  3. Configuration manager(CM) - разворачивает сервисы.

  4. Bash сценарии подготавливают конфигурационные файлы специфичные для каждого стенда.

Структура репозитория

development-infrastructure/  deploy/    env1/      main.tf      backend.tf     ansible-vars.json       subnets.txt     env2/    ...  cm/    ...  modules/    azure/      main.tf      variables.tf  scripts/    env.sh    subnets.txt   .gitlab-ci.yml
  • deploy - содержит специфичные для каждого окружения файлы - файл с переменными для terraform и CM, файл содержащий адрес подсети стенда.

  • cm - в моём случае, тут хранятся Ansible плейбуки для настройки ОС и разворачивания сервисов.

  • modules - модули terraform которые будут получать в качестве параметров имя окружения и адрес подсети

  • scripts - bash сценарии для создания и удаления стендов и их конфигураций

.gitlab-ci.yml:

stages:  - create environment  - terraform apply  - cm  - destroy environment .template: variables: ENV: $NAME_ENV  when: manual tags: [cloudRunner01]  only:   refs:    - triggers Create environment:  stage:create environment  extends: .template script:   - ./scripts/create_env.sh -e $ENV -a create  artifacts:   paths:    - deploy/${ENV}/backend.tf    - deploy/${ENV}/main.tf    - deploy/${ENV}/vars.json Create instances:  stage: terraform apply extends: .template  script:   - cd ./deploy/$ENV   - terraform init -input=false   - terraform validate   - terraform plan -input=false -out=tf_plan_$ENV   - terraform apply -input=false tf_plan_$ENV Deploy applications:  stage: cm  extends: .template  script:   - # мы можем передать имя окружения в качестве параметра нашей CM   - # в моём случае, на основе переменной $ENV генерируются сертификаты,   - # конфигурируется обратный прокси сервер и т.п.   - # также мы можем использовать данные из terraform Destroy instances and environment:  stage:destroy environment  extends: .template  script:   - cd ./deploy/$ENV   - terraform init -input=false   - terraform destroy -auto-approve   - ./scripts/delete_env.sh -e $ENV -a delete 

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

  • Create environment - на основе имени окружения, полученного из переменно NAME_ENV, создаём уникальные для окружения файлы, после чего помещаем их в наш git репозиторий.

  • Create instances - создаём инстансы(виртуальные машины) и подсети, которые будут использоваться окружением.

  • Deploy applications - разворачиваем наши сервисы с помощью любимого Configuration Manager.

  • Destroy instances and environment - с помощью bash сценария данный шаг удалит наши инстансы, после чего удалит из репозитория каталог с файлами окружения. Освободившаяся подсеть будет возвращена в файл scripts/subnets.txt.

Запуск пайплайна происходит с объявлением переменной NAME_ENV, содержащей имя нового стенда:

Разработчики не имеют доступа к Git репозиторию и могут вносить в него изменения только через запуск pipeline.

modules/base/main.tf:

# лучшие практики и личный опыт настоятельно рекомендуют фиксировать версию провайдера provider "azurerm" { version = "=1.39.0" }  # создаём новую группу ресурсов, это особенность Azure. Имя группы уникально, в этом случае будет удобно использовать имя окружения resource "azurerm_resource_group" "product_group" { name= "${var.env_name}" location = "East Europe" } # создаем сеть resource "azurerm_virtual_network" "vnet" { name= "product-vnet" resource_group_name = azurerm_resource_group.product_group.name location= azurerm_resource_group.product_group.location address_space= [var.vnet_address] } #используем подсеть полученную с помощью bash сценария resource "azurerm_subnet" "subnet" { name= "product-subnet" resource_group_name= azurerm_resource_group.product_group.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefix= var.subnet_address } # теперь можем создать виртуальную машинуresource "azurerm_virtual_machine" "product_vm" { name= "main-instance" location= azurerm_resource_group.product_group.location resource_group_name= azurerm_resource_group.product_group.name network_interface_ids = [azurerm_network_interface.main_nic.id]   } # прочие ресурсы и виртуальные машины... 

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

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

scripts/env.sh:

#!/bin/bash set -eu CIDR="24" DEPLOY_DIR="./deploy" SCRIPT_DIR=$(dirname "$0") usage() { echo "Usage: $0 -e [ENV_NAME] -a [create/delete]" echo "-e: Environment name" echo "-a: Create or delete" echo "-h: Help message" echo "Examples:" echo "$0 -e dev-stand-1 -a create" echo "$0 -e issue-1533 -a delete" } while getopts 'he:a:' opt; do case "${opt}" in e) ENV_NAME=$OPTARG ;; a) ACTION=$OPTARG ;; h) usage; exit 0 ;; *) echo "Unknown parameter"; usage; exit 1;; esac done if [ -z "${ENV_NAME:-}" ] && [ -z "${ACTION:-}" ]; then usage exit 1 fi # приводим имя окружения к нижнему регистру ENV_NAME="${ENV_NAME,,}" git_push() { git add ../"${ENV_NAME}" case ${1:-} in create) git commit -am "${ENV_NAME} environment was created" git push origin HEAD:"$CI_COMMIT_REF_NAME" -o ci.skip echo "Environment ${ENV_NAME} was created.";; delete) git commit -am "${ENV_NAME} environment was deleted" git push origin HEAD:"$CI_COMMIT_REF_NAME" -o ci.skip echo "Environment ${ENV_NAME} was deleted.";; esac } create_env() { # создаём каталог для нового окружения if [ -d "${DEPLOY_DIR}/${ENV_NAME}" ]; then echo "Environment ${ENV_NAME} exists..." exit 0 else mkdir -p ${DEPLOY_DIR}/"${ENV_NAME}" fi # получаем адрес подсети NET=$(sed -e 'a$!d' "${SCRIPT_DIR}"/subnets.txt) sed -i /"$NET"/d "${SCRIPT_DIR}"/subnets.txt echo "$NET" > ${DEPLOY_DIR}/"${ENV_NAME}"/subnets.txt if [ -n "$NET" ] && [ "$NET" != "" ]; then echo "Subnet: $NET" SUBNET="${NET}/${CIDR}" else echo "There are no free subnets..." rm -r "./${DEPLOY_DIR}/${ENV_NAME}" exit 1 fi   pushd "${DEPLOY_DIR}/${ENV_NAME}" || exit 1 # Создаем main.tf terraform файл с нужными нам переменными нового окружения cat > main.tf << END module "base" { source = "../../modules/azure" env_name = "${ENV_NAME}" vnet_address = "${SUBNET}" subnet_address = "${SUBNET}" } END # Cоздаём backend.tf terraform файл , в котором указываем имя нового state файла cat > backend.tf << END terraform { backend "azurerm" { storage_account_name = "terraform-user" container_name = "environments" key = "${ENV_NAME}.tfstate" } } END } delete_env() { # удаляем каталог окружения и высвобождаем подсеть if [ -d "${DEPLOY_DIR}/${ENV_NAME}" ]; then NET=$(sed -e '$!d' ./${DEPLOY_DIR}/"${ENV_NAME}"/subnets.txt) echo "Release subnet: ${NET}" echo "$NET" >> ./"${SCRIPT_DIR}"/subnets.txt pushd ./${DEPLOY_DIR}/"${ENV_NAME}" || exit 1 popd || exit 1 rm -r ./${DEPLOY_DIR}/"${ENV_NAME}" else echo "Environment ${ENV_NAME} does not exist..." exit 1 fi } case "${ACTION}" in create) create_env git_push "${ACTION}" ;; delete) delete_env git_push "${ACTION}" ;; *) usage; exit 1;; esac 
  1. Сценарий env.shпринимает два параметра - имя окружения и действие(создание\удаление).

  2. При создании нового окружения:

  3. В каталоге DEPLOY_DIRсоздаётся директория с именем окружения.

  4. Из файла scripts/subnets.txt мы извлекаем одну свободную подсеть.

  5. На основе полученных данных генерируем конфигурационные файлы для Terraform.

  6. Для фиксации результата отправляем каталог с файлами окружения в git репозиторий.

  7. При удалении -сценарий удаляет каталог с файлами окружения и возвращает освободившуюся подсеть в файл scripts/subnets.txt

scripts/subnets.txt:

172.28.50.0172.28.51.0172.28.52.0...

В данном файле мы храним адреса наши подсетей. Размер подсети определяется переменной CIDR в файлеscripts/create_env.sh

Результат

  1. Мы получили фундамент который позволяет нам развернуть новый стенд путём запуска piplinea в Gitlab CI.

  2. Снизили затраты нашей компании на инфраструктуру.

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

  4. Также получили возможность создания виртуальных машин в любом облаке написав новый модуль Terraform и немного модифицировав сценарий созданиях\удаления окружений

  5. Можем поиграться с триггерами Gitlabи разворачивать новые стенды из пайплайнов команд разработчиков передавая версии сервисов.

Подробнее..

От Планеты GitHub с любовью

08.06.2021 20:05:53 | Автор: admin

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

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

Вот краткое изложение того, о чем мы рассказали на данный момент:

GitHub:

Открытый исходный код правит миром:

Безопасность - наша ответственность:

Программа GitHub Stars и ваша собственная Русская Звезда

Начало работы с InnerSource

  • Дмитрий Сугробов (разработчик и евангелист в Leroy Merlin, член InnerSource Commons Foundation) дал нам Введение в InnerSource.

Бонус-трек: развитие soft skills

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

Если вы хотите выступить в качестве приглашенного докладчика, отправьте нам предложение на http://github.co/cfp - не важно если это ваше первое или уже тысячное выступление. Мы приглашаем Вас присоединиться к нам и воспользоваться имеющимися возможностями.

Мы надеемся, что вы присоединитесь к нам 15 июня 2021 года в 19:00 по Москве для дальнейшего общения и новых сессий.

Далее: наши эксперты на июньском мероприятии:

  • Грачев Михаил: Взращивание культуры Open Source в компании

  • Антон Таяновский: Инфраструктурные шаблоны Pulumi на примере AWS Lambda и Sagemaker

RSVP:http://github.co/habr

Подробнее..

Разрабатывайте приложение для друзей

04.12.2020 18:08:20 | Автор: admin

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

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

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

Создавайте сценарий логичного и очевидного общение друга с вашим приложением

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

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

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

Друг может быстро взглянуть на сообщение, на интерфейс, быть невнимательным, быть рассеянным.

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

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

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

Друг же может рассчитывать на честность?

Мотивируйте друга на действия

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

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

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

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

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

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

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

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

Будете ли вы требовать от друга, знания в нюансах языков, которые и вы сами как разработчик не знаете? (Которые знают другие разработчики программных систем в вашей всемирной компании разработчиков.)

Слова, фразы, концепции друг может использовать свои. Ведь также?

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

Должен ли друг каждый раз заглядывать в словарь? Читать википедию или технические статьи о сервисе мирового времени? Погружаться в те многие нюансы и технические детали? Должен ли совершать многие лишние и излишние действия для достижения своих целей ваш друг?

Позвольте приложению напоминать объекты реального мира

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

Вы знаете этот язык, друг знает этот язык, даже друг в другой стране тоже знает этот язык.

Дайте другу право на ошибку

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

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

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

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

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

Постоянство и стандарты дают другу ощущение безопасности

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

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

У друга уже есть предположения о приложении до начала использования вашего приложения.

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

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

Предупредите ошибки друга

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

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

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

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

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

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

Информация в приложении постоянно меняется, И правда, другу следует заучивать наизусть некую информацию, потом переучивать, потом ещё много раз переучивать?

Позвольте другу эффективно использовать ваше приложение

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

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

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

Позвольте не перегружать друга

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

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

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

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

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

P.S. Как вы поняли это классические 10 эвристик Якоба Нильсена. Только под немного другим углом зрения. Спасибо тем, кто прочитал.

Подробнее..
Категории: Интерфейсы , Development , Ui , Interfaces

Легкий DataBinding для Android

22.03.2021 04:10:35 | Автор: admin

Здравствуйте уважаемые читатели. Все мы любим и используем DataBinding, который представила компания Google несколько лет назад, для связи модели данных с вьюшками через ViewModel. В этой статье, хочу поделиться с вами, как можно унифицировать этот процесс с помощью языка Kotlin, и уместить создание адаптеров для RecyclerView (далее RV), ViewPager и ViewPager2 в несколько строчек кода.

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

class CustomAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {    val textView: TextView            init {      textView = view.findViewById(R.id.textView)    }  }       override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {    val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.text_row_item, viewGroup, false)          return ViewHolder(view)  }         override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {    // Get element from your dataset at this position and replace the    viewHolder.textView.text = dataSet[position]  }        override fun getItemCount() = dataSet.size}

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

Затем появился DataBinding и большую часть по связыванию данных перекладывалась на него, но адаптеры все равно приходилось писать вручную, изменились только методы onCreateViewHolder, где вместо инфлэйтинга через LayoutInflater, использовался DataBindingUtil.inflate, а при создании вьюхолдеров данные связывались непосредственно с самой вьюшкой через ссылку на созданный объект байдинга.

class BindingViewHolder(val binding: ItemTextRowBinding) : RecyclerView.ViewHolder(binding.root)override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {        val binding = DataBindingUtil.inflate<ItemTextRowBinding>(LayoutInflater.from(parent.context), viewType, parent, false)        val viewHolder = BindingViewHolder(binding)        return viewHolder}override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {      holder.binding.setVariable(BR.item, dataSet[position])}

Выглядит уже лучше, но что если в RV, по прежнему должны отображаться элементы лайаута с разными типами данных, то такая реализация не сильно помогла решить проблему больших адаптеров. И здесь на помощь приходит аннотация BindingAdapter из библиотеки androidx.databinding. С ее помощью, можно создать универсальное решение, которое скрывает реализацию создания адаптера для RV, если использовать вспомогательный объект-конфигуратор DataBindingRecyclerViewConfig, в котором содержится ряд свойств для настройки адаптера.

В результате на свет появилась библиотека, которая называется EasyRecyclerBinding. В нее так же вошли BindingAdapters для ViewPager и ViewPager2. Теперь процесс связывания данных выглядит следующим образом:
1) В лайауте фрагмента, необходимо добавить специальные переменные, которые содержат список отображаемых моделей данных и конфигурацию, указав их атрибутами для RV, - app:items и app:rv_config.

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools">    <data>    <variable        name="vm"        type="com.rasalexman.erb.ui.base.ExampleViewModel" />        <variable            name="rvConfig"            type="com.rasalexman.easyrecyclerbinding.DataBindingRecyclerViewConfig" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:id="@+id/main"        android:layout_width="match_parent"        android:layout_height="match_parent">        <androidx.recyclerview.widget.RecyclerView            android:layout_width="0dp"            android:layout_height="0dp"            app:items="@{vm.items}"            app:rv_config="@{rvConfig}"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            tools:listitem="@layout/item_recycler"/>    </androidx.constraintlayout.widget.ConstraintLayout></layout>

ViewModel, соответственно, содержит список моделей данных для адаптера, которые должны отображаться в RV, а фрагмент конфигурацию DataBindingRecyclerViewConfig.

// названия пакетов не указаны для простоты примераclass ExampleViewModel : ViewModel() {     val items: MutableLiveData<MutableList<RecyclerItemUI>> = MutableLiveData()}data class RecyclerItemUI(    val id: String,    val title: String)

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

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools">    <data>        <variable            name="item"            type="com.rasalexman.erb.models.RecyclerItemUI" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@android:drawable/list_selector_background">        <TextView            android:id="@+id/titleTV"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:paddingStart="16dp"            android:paddingTop="8dp"            android:paddingEnd="16dp"            android:textColor="@color/black"            android:textSize="18sp"            android:text="@{item.title}"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            tools:text="Hello world" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:paddingStart="16dp"            android:paddingEnd="16dp"            android:paddingBottom="8dp"            android:textColor="@color/gray"            android:textSize="14sp"            android:text="@{item.id}"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toBottomOf="@+id/titleTV"            tools:text="Hello world" />    </androidx.constraintlayout.widget.ConstraintLayout></layout>

2) Во фрагменте нам нужно получить конфигурацию для адаптера и передать её в отображение через инстанс dataBinding, используя специальную функцию-конструктор createRecyclerConfig<I : Any, BT : ViewDataBinding>, которая создаст и вернет инстанс DataBindingRecyclerViewConfig, указав при этом id лайаута для выбранной модели, и название свойства, к которому будет прикреплена данная модель.

class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, ExampleViewModel>() {      override val layoutId: Int get() = R.layout.rv_example_fragment  override val viewModel: ExampleViewModel by viewModels()        override fun initBinding(binding: RvExampleFragmentBinding) {        super.initBinding(binding)        binding.rvConfig = createRecyclerConfig<RecyclerItemUI, ItemRecyclerBinding> {            layoutId = R.layout.item_recycler        itemId = BR.item                    }    }}

Это все, что нужно сделать, чтобы связать данные из ViewModel с отображением списка в RV. Так же при создании адаптера можно назначить слушатели событий для байдинга вьюхолдера, такие как onItemClick,onItemCreate, onItemBind и другие.

А чтобы использовать вьюхолдеры с разными визуальными лайаутами, к которым привязаны свои модели отображения данных, необходимо имплементировать в них специальный интерфейс из библиотеки EasyRecyclerBinding - IBindingModelи переопределить поле layoutResId, - id лайаута, который будет отображаться для этой модели в списке.

data class RecyclerItemUI(    val id: String,    val title: String) : IBindingModel {        override val layoutResId: Int            get() = R.layout.item_recycler}data class RecyclerItemUI2(    val id: String,    val title: String) : IBindingModel {    override val layoutResId: Int        get() = R.layout.item_recycler2}

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

class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, RecyclerViewExampleViewModel>() {      override val layoutId: Int get() = R.layout.rv_example_fragment    override val viewModel: RecyclerViewExampleViewModel by viewModels()        override fun initBinding(binding: RvExampleFragmentBinding) {        super.initBinding(binding)        binding.rvConfig = createRecyclerMultiConfig {            itemId = BR.item        }    }}class RecyclerViewExampleViewModel : BasePagesViewModel() {    open val items: MutableLiveData<MutableList<IBindingModel>> = MutableLiveData()}

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


Аналогичный процесс создания адаптеров для ViewPager и ViewPager2, представлен в примере на github вместе с открытым кодом, ссылку на который, я разместил в конце статьи. В настоящий момент библиотека еще дорабатывается, и хочется получить адекватный фидбек, и пожелания по дальнейшему ее развитию. Так же в неё вошли вспомогательные функции для удобного создания байдинга, в том числе в связке с ViewModel. (LayoutInflater.createBinding, Fragment.createBindingWithViewModel, etc)

Спасибо, что дочитали до конца. Приятного кодинга и хорошего настроения)

Подробнее..

Kubernetes для разработчиков трехдневный интенсив

19.11.2020 18:06:36 | Автор: admin
image

Спикеры Слёрма готовят обновленный интенсив, в котором не будет тем для администраторов. Мы убрали тему про обслуживание кластера и сосредоточились на особенностях разработки ПО в Kubernetes. В программе только то, что действительно нужно современному разработчику на проектах с K8s.

Почему трехдневный интенсив?


В Слёрме прошло уже 14 трехдневных интенсивов по Kubernetes, и мы уверены, что такой формат дает крутые результаты. К вечеру третьего дня студенты знают, что такое Kubernetes, что в нём есть и как в нём работать. Выпускник интенсива может свободно читать документацию по K8s и понимать её. Можно возвращаться и пересматривать материалы, когда понадобится. Интенсив возможность быстро запустить подготовку специалиста. Важно помнить, что такое обучение невозможно совместить с работой (учимся с 10 до 19, отвлекаться не получится).

Короткая история одного разработчика про K8s


Вот что рассказывает про обучение Артем из Gismeteo:
Я backend-разработчик в команде Gismeteo. Занимаюсь поддержкой и разработкой существующего погодного API, настройкой CI|/CD Gitlab, написанием ролей на Ansible, выкладкой на продакшн.

В компании мы в какой-то момент приняли решение избавиться от LXC-контейнеров в пользу Docker. Так как у нас highload, одним контейнером мы бы не отделались. Из этого появился вопрос: как за этим всем следить и управлять. Поэтому мы и решили присмотреться к Kubernetes, я стал искать возможности для обучения. Читал документацию, но информация, которая там есть, подходит только для ознакомления. Без практики это пустая трата времени. Пытался смотреть видео на Ютюбе, но, опять же, не хватало практики.

Тогда я решил пойти на практический курс, выбрал интенсив Слёрма. Хотел познакомиться ближе с принципами работы k8s, узнать best practices от спикеров. Интенсивное обучение мне хорошо подошло, помощь техподдержки не понадобилась. Самым интересным моментом на курсе, по-моему, было добавление rollback piplin'a для отката версии Docker-образа. Я любитель CI/CD, поэтому для меня это было особенно актуально. Также понравилась тема про интеграцию CI/CD с Kubernetes через Helm. Сложной практической темой оказалось написание своего helm chart'a.

После курса я убедился в том, что Kubernetes на текущий момент времени лучший оркестратор для контейнеров. Продолжаю повышать квалификацию в этом направлении, теперь уже на рабочих задачах. Сейчас пересматриваю материал курса, связанный с helm, так как пересобираю сейчас все наши ci/cd под диплойку через helm. Очень удобная вещь.

Ссылка на отзыв в ВК

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

Kubernetes для разработчиков пройдет 35 марта 2021, но уже сейчас можно забронировать место до конца 2020 участие стоит 20 000 рублей.
Подробнее..

Путь одной команды от велосипедов до IoT-платформы

28.10.2020 20:04:59 | Автор: admin
Привет, Хабр!
Мы, команда Rightech, наконец-то решили начать вести блог. У нас накопилось много опыта в построении высоконагруженных IoT-систем, и мы решили, что просто обязаны им делиться! Совсем недавно прошел запуск публичной версии нашей платформы RIC (Rightech IoT Cloud), и теперь ей может воспользоваться каждый желающий. Но сначала расскажем, кто мы и откуда появились.

C чего всё начиналось


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

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

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

На тот период времени наш рабочий процесс представлял собой следующую последовательность:

  1. физически подключить компоненты системы в одну сеть;
  2. реализовать протоколы обмена данными;
  3. реализовать обработку данных (фильтрация, применение функций преобразования, проведение косвенных измерений и т.д.);
  4. преобразовать данные в высокоуровневые структуры;
  5. описать конечные автоматы системы;
  6. реализовать API для вывода данных в интерфейс и запуска автоматов;
  7. реализовать интерфейс системы (тачскрин/нативное приложение/интеграция во внутренние системы заказчика).

И, как вы понимаете, из проекта в проект до последнего этапа заказчик с трудом понимал, что мы делаем, и это всегда было почвой для недоверия и конфликтов. Конечно, когда мы сдавали работу, заказчик был рад и доволен, но потраченные нервы и ощущение того, что мы по сути пилили на 90% очередной велосипед на новом стеке, оставалось.



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



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

Rightech. История создания


У нас совершенно отсутствовал опыт в привлечении денег. Однако в 2016 году нам удалось привлечь первые инвестиции от фонда, вложившегося в компанию Делимобиль. На эти деньги мы создали компанию Rightech, которая стала домом для нашего проекта. А первым действительно крупным внедрением нашей технологии, как вы уже догадались, стал каршеринг Делимобиль. Сразу оговорюсь, что приложения и CRM систему разрабатывали не мы, но тысячи автомобилей и терабайты машинногенеренных данных стали достойной проверкой, которую RIC уверенно прошел.

Помимо шерингов, к 2019 году мы успели автоматизировать Digital Out Of Home рекламу, построить сбор данных с газотурбинных генераторов электроэнергии и многое другое. Команда занималась не только рыночными внедрениями, но и развивала RIC в целом: реализовали множество транспортных протоколов, оптимизировали серверную инфраструктуру и расширили систему автоматизации.

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

Подробнее о платформе


Так что же такое IoT-платформа? Во что превратился наш фреймворк заменитель велосипедов RIC?

Любой IoT проект состоит из следующих принципиальных компонентов или слоев:

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



Rightech IoT Cloud (или RIC) это облачная платформа Интернета Вещей, выступающая в качестве промежуточного программного обеспечения (middleware) при разработке IoT-решения. RIC включает в себя все необходимые программные элементы, позволяющие инженерам любого уровня создавать приложения с использованием любых IoT-устройств без необходимости создавать соответствующую программно-аппаратную инфраструктуру. Да-да, именно любых устройств. Мы выложили не все реализованные протоколы в публичную версию RIC. Если вы не нашли протокол своего устройства, можете нам написать и мы его обязательно добавим в публичную версию.

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

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



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

Публичное облако


Весной мы запустили регистрацию на наше публичное облако, и теперь каждый пользователь может бесплатно подключить до 10 устройств и спрототипировать свой будущий бизнес или же автоматизировать, например, теплицу или дом. Любой IoT проект может взять всё необходимое в платформе RIC и реализовать свою уникальную систему обработки и представления получаемых данных практически без программирования тех самых 90% айсберга.

Наш короткий рассказ подошел к концу. Надеемся, что мы вам понравились, и в свою очередь обещаем делиться своим опытом и актуальной информацией в сфере IoT.
Кстати, мы есть и в Телеграм с чатом единомышленников.
Just do IoT!

Полезные ссылки:


Обучающие видеоролики на примере мини-кейсов
Создайте свой IoT-проект уже сейчас
Присоединяйтесь к единомышленникам
Подробнее..

Анонсируем новую версию Rightech IoT Cloud v2.2. Небольшой обзор

04.12.2020 16:09:39 | Автор: admin
Всем привет!

В рамках нашего блога мы планируем делиться не только историями и опытом, но также освещать последние нововведения и изменения, связанные с платформой Rightech IoT Cloud (RIC).
Недавно мы опубликовали версию RIC v2.2. А теперь рассказываем, что же именно вошло в релиз этого квартала.

Ну что, погнали?

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

  • Import/export сущностей платформы, а именно моделей, объектов и автоматов.
  • Обработку ошибок в редакторе автоматов.
  • RIC-app упрощенную мобильную версию платформы.

image

Предисловие


Если вы новый пользователей нашей платформы, то наверное, многие из перечисленных параметров выше вам непонятны. Давайте немного разберем, что есть что.

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

Объект является представлением устройства, над которым осуществляется контроль и управление посредством использования функций платформы.

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

Более подробно ознакомиться со всеми параметрами платформы можно в нашей документации.

Вернемся к обновлениям

Import/export сущностей платформы


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

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

Модели

Возможность импорта/экспорта моделей особенно помогает при передаче своей реализации задачи другому человеку. Очень удобно поделиться программным кодом в виде, например, скетча Arduino и моделью объекта в виде JSON-файла.

Экспорт модели:

image

Импорт модели из файла:

image

Импорт модели по ссылке:

image

Объекты

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

Экспорт объекта:

image

Импорт объекта из файла:

image

Импорт объекта по ссылке:

image

Автоматы

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

Экспорт автомата:

image

Импорт автомата из файла:

image

Импорт автомата по ссылке:

image

Обработка ошибок в редакторе автоматов


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

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

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

Автомат с ошибками:

image

Состояния

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

Ошибки в состоянии:

image

Переходы

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

Возможно несколько вариантов ошибок:

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


Ошибки в переходах:

image

Ric-app


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

Приложение для Android доступно в Play Market по ссылке. Приложение для iOS в скором времени появится в App Store.

Объекты

В списке объектов видна информация о статусе объектов. Можно зайти в каждый объект, посмотреть все его параметры, историю пакетов. Кроме того, доступна возможность отправки команд на устройства.

Список объектов:

image

Состояние объекта:

image

История объекта:

image

Управление объектом:
image

Карта

Меню с картой аналогично карте в интерфейсе платформы.

Карта:

image

Оповещения

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

Оповещения:

image

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

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

Stay tuned & just do IoT!

Полезные ссылки:


Обучающие видеоролики на примере мини-кейсов rightech.io/video-tutorials
Создайте свой IoT-проект уже сейчас dev.rightech.io/signup
Присоединяйтесь к единомышленникам t.me/rightech_iot
GitHub github.com/Rightech/ric-public
Вопросы и предложения development@rightech.io
Подробнее..

IoT-елочка, гори!..

26.12.2020 22:16:10 | Автор: admin
Пришел новый русский в магазин, чтобы сдать новогоднюю гирлянду.
Не работает? спрашивает его продавец.
Почему? Очень даже работает, отвечает тот.
А в чем тогда дело?
Покупатель вздохнул и ответил:
Не радует.


Привет, друзья!

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

Под катом:
  1. Собираем прототип гирлянды
  2. Пишем код для нескольких режимов работы
  3. Подключаем к платформе Rightech IoT Cloud
  4. Придумываем и реализовываем сценарий работы гирлянды
  5. Создаем праздничное настроение


image



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

Модели устройств, файлы со сценариями автоматов, код для NodeMCU в конце статьи (не пытайтесь повторить это дома попробуйте повторить это дома!).

image

Собираем прототип


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

Итак, для маленькой гирлянды нам понадобится:

  • 12 белых и 6 голубых светодиодов, управление которыми производится независимо (назначение: гореть и радовать)
  • 2 резистора на 220 Ом, подбираемые исходя из расчета сопротивления и мощности по закону Ома (назначение: защита светодиодов от перегрева и выхода из строя)
  • 2 биполярных NPN транзистора (назначение: управляем транзистором маленьким током от управляющего пина, а пропускаем на диоды большой ток с выхода 3.3 В, так мы защищаем управляющий пин платы)
  • 2 резистора на 1 кОм на базу транзистора (назначение аналогичное, ограничиваем ток)
  • плата NodeMCU (назначение: подключение к IoT-платформе и управление транзисторами)
  • батарейки или блок питания (назначение: источник питания для платы)

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

Если изобразить на схеме, то это выглядит так:

image

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




Пишем код


Ниже представлен код для управления режимами гирлянды. Его функции:

1) установить Wi-Fi соединение и подключиться к платформе;

2) подписаться на команды сообщения с топиками led_on, led_off, led_attenuation, led_flashing и выполнять соответствующие действия по управлению светодиодами.

Команды led_on и led_off включают и выключают гирлянду, а команды led_attenuation и led_flashing задают режимы плавного горения и быстрого мигания с периодом, указанным в payload команды.

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

#include "Arduino.h"#include "Scheduler.h"      /* https://github.com/nrwiersma/ESP8266Scheduler */#include "EspMQTTClient.h"  /* https://github.com/plapointe6/EspMQTTClient */                           /* https://github.com/knolleary/pubsubclient */// Даем разумные имена для пинов, управляемых светодиодами#define BLUE_LED_PIN 12#define WHITE_LED_PIN 13EspMQTTClient client( "<wifi-ssid>", "<wifi-password>" "dev.rightech.io", "<ric-mqtt-client-id>");// Задача для обработки поступающих командclass ClientTask : public Task { public:   void loop() {     client.loop();   }} client_task;// Задача для включения светодиодовclass LedOnTask : public Task { protected:   void loop()   {     digitalWrite(WHITE_LED_PIN, HIGH);     digitalWrite(BLUE_LED_PIN, HIGH);     shouldRunValue = false; // останавливаем этот цикл сразу после включения   }   bool shouldRun()   {     return shouldRunValue;   } public:   bool shouldRunValue = false;} led_on_task;// Задача для выключения светодиодовclass LedOffTask : public Task { protected:   void loop()   {     digitalWrite(WHITE_LED_PIN, LOW);     digitalWrite(BLUE_LED_PIN, LOW);     shouldRunValue = false; // останавливаем этот цикл сразу после выключения   }   bool shouldRun()   {     return shouldRunValue;   } public:   bool shouldRunValue = false;} led_off_task;// Задача для плавного горения светодиодов в противофазеclass LedAttenuationTask : public Task { protected:   void loop()   {     // Вычисляем задержку на один проход цикла в зависимости от полученного в payload значения     float delayValue = period.toInt() * 1000 /*в миллисекунды*/ / 2 /*на два цикла*/ / 1024 /*на каждую итерацию в цикле*/;     for (int i = 0; i <= 1023; i++) {       analogWrite(WHITE_LED_PIN, i); // горит ярче       analogWrite(BLUE_LED_PIN, 1023 - i); // тускнеет       delay(delayValue);     }     for (int i = 1023; i >= 0; i--) {       analogWrite(WHITE_LED_PIN, i); // тускнеет       analogWrite(BLUE_LED_PIN, 1023 - i); // горит ярче       delay(delayValue);     }   }   bool shouldRun()   {     updateDelayTimer();     if (isDelayed()) return false;     if (!run_group_active) return false;     return shouldRunValue;   } public:   bool shouldRunValue = false;   String period;} led_attenuation_task;// Задача для быстрого мигания светодиодов в противофазеclass LedFlashingTask : public Task { protected:   void loop()   {     float delayValue = period.toInt() * 1000 /*в миллисекунды*/;     digitalWrite(WHITE_LED_PIN, HIGH);     digitalWrite(BLUE_LED_PIN, LOW);     delay(delayValue);     digitalWrite(WHITE_LED_PIN, LOW);     digitalWrite(BLUE_LED_PIN, HIGH);     delay(delayValue);   }   bool shouldRun()   {     updateDelayTimer();     if (isDelayed()) return false;     if (!run_group_active) return false;     return shouldRunValue;   } public:   bool shouldRunValue = false;   String period;} led_flashing_task;void setup() { // Настраиваем пины в режим выхода, т.е. в режим источника напряжения pinMode(WHITE_LED_PIN, OUTPUT); pinMode(BLUE_LED_PIN, OUTPUT); // Библиотека Scheduler позволяет при необходимости запустить несколько потоков Scheduler.start(&led_on_task); Scheduler.start(&led_off_task); Scheduler.start(&led_attenuation_task); Scheduler.start(&led_flashing_task); Scheduler.start(&client_task); Scheduler.begin();}void onConnectionEstablished() { // Подписываемся на команды и запускаем нужный поток путем изменения переменной shouldRunValue client.subscribe("led_on", [] (const String & payload)  {   client.publish("base/state/light", "on");   led_off_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_on_task.shouldRunValue = true; }); client.subscribe("led_off", [] (const String & payload)  {   client.publish("base/state/light", "off");   led_on_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_off_task.shouldRunValue = true; }); client.subscribe("led_attenuation", [] (const String & payload)  {   client.publish("base/state/light", "attenuation " + payload + " sec");   led_on_task.shouldRunValue = false;   led_off_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_attenuation_task.period = payload;   led_attenuation_task.shouldRunValue = true; }); client.subscribe("led_flashing", [] (const String & payload)  {   client.publish("base/state/light", "flashing " + payload + " sec");   led_on_task.shouldRunValue = false;   led_off_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.period = payload;   led_flashing_task.shouldRunValue = true; });}void loop() {}


Подключаем к платформе Rightech IoT Cloud


Подключение гирлянды:


1) Создаем модель




2) Создаем объект с этой моделью




Подключение кнопки:


1) Создаем модель




2) Создаем объект с этой моделью




Подробнее о том, как подключать ZigBee устройства, можно посмотреть в видеоуроке и почитать в статье.

Разрабатываем сценарий работы


От сценария автоматизации мы хотим следующей логики:

1) один клик (single) режим постоянного свечения и выключения;

2) два клика (double) режим плавного свечения;

3) три клика (triple) режим мигания;

4) после 20:00 автоматическое выключение гирлянды (здесь также можно использовать не просто расписание, а данные еще из одного объекта СКУДа, который собирает информацию о том, есть ли люди в офисе. Если вам интересен материал по такой теме, дайте обратную связь в комментариях ).

Готовый автомат




Давайте вкратце разберем, что тут происходит и зачем:
  1. Первым делом при старте автомата запускаем планировщик, который выключит гирлянду по расписанию, обезопасив нас от забывчивости. Запустили и забыли, он будет срабатывать каждый день автоматически.
  2. В следующем состоянии не делаем ничего, просто ждем нажатия кнопки. В это состояние мы возвращаемся каждый раз после отработки определенного режима гирлянды. Из него есть переход по событию получения данных и срабатыванию планировщика.
  3. Если получили какой-то пакет от кнопки, то переходим в состояние Получен пакет, из которого по таймеру и типу клика переходим в соответствующие режимы. Вы можете спросить, зачем тут таймер. А причина в том, что кнопка работает довольно интересненько. При нажатии три раза, она сначала присылает пакет с double, а сразу за ним triple. Такой нюанс мы и обходим таймером, иначе срабатывало бы по неактуальному клику.
  4. Также есть промежуточное состояние для одинарного клика. Как мы помним, вкл/выкл у нас работают по одному и тому же событию. Поэтому, если гирлянда не выключена (находится в любом из режимов активной работы), то мы ее выключаем, а если выключена, то включаем.

Запускаем автомат на наших объектах, проверяем фуууух, работает! Время паять!




Собираем готовое устройство


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

image

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

image

термоусадку нагреваем паяльным феном

image

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

image

и лудим (наносим небольшой слой припоя)

image

проводок готов воссоединяться со светодиодом

image

соединяем (паяем поверхности, на которых уже есть припой)

image

фиксируем на веточке, сгибая ножки (необходимо предварительно оставить не менее 10 мм) светодиода

image

на данном этапе проводим последнее тестирование

image

готово!





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

С наступающим праздником!

Материалы к статье


image
Подробнее..

IoT-елочка, гори!.

27.12.2020 00:14:52 | Автор: admin
Пришел новый русский в магазин, чтобы сдать новогоднюю гирлянду.
Не работает? спрашивает его продавец.
Почему? Очень даже работает, отвечает тот.
А в чем тогда дело?
Покупатель вздохнул и ответил:
Не радует.

Привет, друзья!

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

Под катом:

  1. Собираем прототип гирлянды
  2. Пишем код для нескольких режимов работы
  3. Подключаем к платформе Rightech IoT Cloud
  4. Придумываем и реализовываем сценарий работы гирлянды
  5. Создаем праздничное настроение

image

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

Модели устройств, файлы со сценариями автоматов, код для NodeMCU в конце статьи (не пытайтесь повторить это дома попробуйте повторить это дома!).

image

Собираем прототип


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

Итак, для маленькой гирлянды нам понадобится:

  • 12 белых и 6 голубых светодиодов, управление которыми производится независимо (назначение: гореть и радовать)
  • 2 резистора на 220 Ом, подбираемые исходя из расчета сопротивления и мощности по закону Ома (назначение: защита светодиодов от перегрева и выхода из строя)
  • 2 биполярных NPN транзистора (назначение: управляем транзистором маленьким током от управляющего пина, а пропускаем на диоды большой ток с выхода 3.3 В, так мы защищаем управляющий пин платы)
  • 2 резистора на 1 кОм на базу транзистора (назначение аналогичное, ограничиваем ток)
  • плата NodeMCU (назначение: подключение к IoT-платформе и управление транзисторами)
  • батарейки или блок питания (назначение: источник питания для платы)

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

Если изобразить на схеме, то это выглядит так:

image

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




Пишем код


Ниже представлен код для управления режимами гирлянды. Его функции:

  1. установить Wi-Fi соединение и подключиться к платформе;
  2. подписаться на команды сообщения с топиками led_on, led_off, led_attenuation, led_flashing и выполнять соответствующие действия по управлению светодиодами.

Команды led_on и led_off включают и выключают гирлянду, а команды led_attenuation и led_flashing задают режимы плавного горения и быстрого мигания с периодом, указанным в payload команды.

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

#include "Arduino.h"#include "Scheduler.h"      /* https://github.com/nrwiersma/ESP8266Scheduler */#include "EspMQTTClient.h"  /* https://github.com/plapointe6/EspMQTTClient */                           /* https://github.com/knolleary/pubsubclient */// Даем разумные имена для пинов, управляемых светодиодами#define BLUE_LED_PIN 12#define WHITE_LED_PIN 13EspMQTTClient client( "<wifi-ssid>", "<wifi-password>" "dev.rightech.io", "<ric-mqtt-client-id>");// Задача для обработки поступающих командclass ClientTask : public Task { public:   void loop() {     client.loop();   }} client_task;// Задача для включения светодиодовclass LedOnTask : public Task { protected:   void loop()   {     digitalWrite(WHITE_LED_PIN, HIGH);     digitalWrite(BLUE_LED_PIN, HIGH);     shouldRunValue = false; // останавливаем этот цикл сразу после включения   }   bool shouldRun()   {     return shouldRunValue;   } public:   bool shouldRunValue = false;} led_on_task;// Задача для выключения светодиодовclass LedOffTask : public Task { protected:   void loop()   {     digitalWrite(WHITE_LED_PIN, LOW);     digitalWrite(BLUE_LED_PIN, LOW);     shouldRunValue = false; // останавливаем этот цикл сразу после выключения   }   bool shouldRun()   {     return shouldRunValue;   } public:   bool shouldRunValue = false;} led_off_task;// Задача для плавного горения светодиодов в противофазеclass LedAttenuationTask : public Task { protected:   void loop()   {     // Вычисляем задержку на один проход цикла в зависимости от полученного в payload значения     float delayValue = period.toInt() * 1000 /*в миллисекунды*/ / 2 /*на два цикла*/ / 1024 /*на каждую итерацию в цикле*/;     for (int i = 0; i <= 1023; i++) {       analogWrite(WHITE_LED_PIN, i); // горит ярче       analogWrite(BLUE_LED_PIN, 1023 - i); // тускнеет       delay(delayValue);     }     for (int i = 1023; i >= 0; i--) {       analogWrite(WHITE_LED_PIN, i); // тускнеет       analogWrite(BLUE_LED_PIN, 1023 - i); // горит ярче       delay(delayValue);     }   }   bool shouldRun()   {     updateDelayTimer();     if (isDelayed()) return false;     if (!run_group_active) return false;     return shouldRunValue;   } public:   bool shouldRunValue = false;   String period;} led_attenuation_task;// Задача для быстрого мигания светодиодов в противофазеclass LedFlashingTask : public Task { protected:   void loop()   {     float delayValue = period.toInt() * 1000 /*в миллисекунды*/;     digitalWrite(WHITE_LED_PIN, HIGH);     digitalWrite(BLUE_LED_PIN, LOW);     delay(delayValue);     digitalWrite(WHITE_LED_PIN, LOW);     digitalWrite(BLUE_LED_PIN, HIGH);     delay(delayValue);   }   bool shouldRun()   {     updateDelayTimer();     if (isDelayed()) return false;     if (!run_group_active) return false;     return shouldRunValue;   } public:   bool shouldRunValue = false;   String period;} led_flashing_task;void setup() { // Настраиваем пины в режим выхода, т.е. в режим источника напряжения pinMode(WHITE_LED_PIN, OUTPUT); pinMode(BLUE_LED_PIN, OUTPUT); // Библиотека Scheduler позволяет при необходимости запустить несколько потоков Scheduler.start(&led_on_task); Scheduler.start(&led_off_task); Scheduler.start(&led_attenuation_task); Scheduler.start(&led_flashing_task); Scheduler.start(&client_task); Scheduler.begin();}void onConnectionEstablished() { // Подписываемся на команды и запускаем нужный поток путем изменения переменной shouldRunValue client.subscribe("led_on", [] (const String & payload)  {   client.publish("base/state/light", "on");   led_off_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_on_task.shouldRunValue = true; }); client.subscribe("led_off", [] (const String & payload)  {   client.publish("base/state/light", "off");   led_on_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_off_task.shouldRunValue = true; }); client.subscribe("led_attenuation", [] (const String & payload)  {   client.publish("base/state/light", "attenuation " + payload + " sec");   led_on_task.shouldRunValue = false;   led_off_task.shouldRunValue = false;   led_flashing_task.shouldRunValue = false;   led_attenuation_task.period = payload;   led_attenuation_task.shouldRunValue = true; }); client.subscribe("led_flashing", [] (const String & payload)  {   client.publish("base/state/light", "flashing " + payload + " sec");   led_on_task.shouldRunValue = false;   led_off_task.shouldRunValue = false;   led_attenuation_task.shouldRunValue = false;   led_flashing_task.period = payload;   led_flashing_task.shouldRunValue = true; });}void loop() {}

Подключаем к платформе Rightech IoT Cloud


Подключение гирлянды:


1) Создаем модель




2) Создаем объект с этой моделью




Подключение кнопки:


1) Создаем модель




2) Создаем объект с этой моделью




Подробнее о том, как подключать ZigBee устройства, можно посмотреть в видеоуроке и почитать в статье.

Разрабатываем сценарий работы


От сценария автоматизации мы хотим следующей логики:

1) один клик (single) режим постоянного свечения и выключения;

2) два клика (double) режим плавного свечения;

3) три клика (triple) режим мигания;

4) после 20:00 автоматическое выключение гирлянды (здесь также можно использовать не просто расписание, а данные еще из одного объекта СКУДа, который собирает информацию о том, есть ли люди в офисе. Если вам интересен материал по такой теме, дайте обратную связь в комментариях ).

Готовый автомат




Давайте вкратце разберем, что тут происходит и зачем:

  1. Первым делом при старте автомата запускаем планировщик, который выключит гирлянду по расписанию, обезопасив нас от забывчивости. Запустили и забыли, он будет срабатывать каждый день автоматически.
  2. В следующем состоянии не делаем ничего, просто ждем нажатия кнопки. В это состояние мы возвращаемся каждый раз после отработки определенного режима гирлянды. Из него есть переход по событию получения данных и срабатыванию планировщика.
  3. Если получили какой-то пакет от кнопки, то переходим в состояние Получен пакет, из которого по таймеру и типу клика переходим в соответствующие режимы. Вы можете спросить, зачем тут таймер. А причина в том, что кнопка работает довольно интересненько. При нажатии три раза, она сначала присылает пакет с double, а сразу за ним triple. Такой нюанс мы и обходим таймером, иначе срабатывало бы по неактуальному клику.
  4. Также есть промежуточное состояние для одинарного клика. Как мы помним, вкл/выкл у нас работают по одному и тому же событию. Поэтому, если гирлянда не выключена (находится в любом из режимов активной работы), то мы ее выключаем, а если выключена, то включаем.

Запускаем автомат на наших объектах, проверяем фуууух, работает! Время паять!




Собираем готовое устройство


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

image

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

image

термоусадку нагреваем паяльным феном

image

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

image

и лудим (наносим небольшой слой припоя)

image

проводок готов воссоединяться со светодиодом

image

соединяем (паяем поверхности, на которых уже есть припой)

image

фиксируем на веточке, сгибая ножки (необходимо предварительно оставить не менее 10 мм) светодиода

image

на данном этапе проводим последнее тестирование

image

готово!




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

С наступающим праздником!

Материалы к статье


image
Подробнее..

Системы контроля управления доступом в IoT умеем, знаем, практикуем

27.01.2021 14:17:26 | Автор: admin

И снова привет, мир!

В прошлой статье про IoT-елочку в голосовании многие отметили, что интересна тема управления устройствами в зависимости от количества человек в помещении. Это довольно масштабная задача, и мы предлагаем разделить ее решение на несколько этапов. Сегодня поговорим о системе контроля управления доступом (СКУД), которая, будучи подключенной к платформе интернета вещей Rightech IoT Cloud (далее по тексту - платформа), является базовым элементом в системе подсчета количества человек в офисе. Мы уже поверхностно освещали этот кейс в одной из статей, но сегодня рассмотрим этот проект более подробно и погрузимся в особенности исполнения.

СКУД?

Система контроля и управления доступом, СКУД (англ. Physical Access Control System, PACS) совокупность программно-аппаратных средств контроля и управления, главная цель которой - ограничение и регистрация входа-выхода объектов (людей, транспорта) на заданной территории через точки прохода: двери, ворота, КПП.

С чего все началось

Задача обеспечить контроль за входом-выходом в офисе возникла еще в те времена, когда у нас был выход на крышу с отличным видом на Москву, и поэтому часто приходили люди, не являющиеся нашими сотрудниками. Тогда мы решили принять какие-то меры по безопасности и установить СКУД.

Архитектура

Система открытия дверей по карте

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

Основные достоинства контроллера:

  • формирование и хранение необходимой информации о факте открытия двери по карте и времени прохода человека в офис;

  • возможность автономной работы и защиты от зависания;

  • хранение 16 тысяч ключей и 8 тысяч событий;

  • простое подключение и управление;

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

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

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

Система взаимодействия с платформой

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

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

Обращаем внимание, что в данной архитектуре функция открытия двери при прикладывании карты не перестанет выполняться при отсутствии Интернета. За открытие двери и сбор информации отвечает контроллер СКУД, поэтому в случае потери Интернета, единственное, что нам грозит, - это то, что агент, который доставляет данные на платформу, не будет функционировать, так как не сможет получать команды на чтение буфера. Однако при восстановлении соединения все события считаются в полном объеме и не потеряются, так как они будут сохранены в буфере контроллера СКУД.

Аппаратная часть

Для начала нужно было выбрать устройство, которое будет всегда в активном состоянии с включенной программой-агентом в непосредственной близости от платы СКУД. Из многообразия микрокомпьютеров первое что попало под руку выбор пал на Raspberry Pi.

Дальше возник вопрос, как подсоединить GATE-8000 к Raspberry - то есть как подключить последовательный интерфейс RS485 от GATE к USB от микрокомпьютера. Начались поиски переходника USB-RS485. Первый вариант, который мы испробовали, - Espada за 200 рублей. Надежда на то, что маленький хлипкий китайский переходник заработает, была небольшой. Он и не заработал. Вместо нужных данных приходило что-то похожее по виду и размеру, но всё же не то. В чем было дело: в отсутствии гальванической развязки, невозможности поддерживать скорость 19200 bps или же просто в некачественной элементной базе, - загадка. Но после обращения к производителю GATE-8000, мы получили рекомендацию на более дорогой (в 10 раз) и громоздкий (но аккуратный и корпусированный) переходник Z-397, который заработал тут же как надо.

Программная часть

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

  1. Что нужно - взаимодействие с GATE-8000 для отправки команд и получения данных.

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

  2. Что нужно - взаимодействие с платформой для получения команд и отправки данных.

    Как решим - выберем для общения протокол MQTT, в коде воспользуемся готовой библиотекой Paho MQTT.

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

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

1) задавать всю логику работы в агенте;

2) использовать внешние запросы (от платформы).

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

Всегда ли нужно выносить логику работы с устройства?

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

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

Карта памяти контроллера? WTF?

Под картой памяти контроллера (термин из протокола) имеется в виду таблица с описанием заполнения регистров памяти, а не микрофлешка =).

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

1) найден ключ в банке ключей (банк ключей - еще один блок в распределенной памяти контроллера);

2) состоялся проход (если он, конечно, состоялся).

Ниже представлен фрагмент кода на С++, реализующий метод одного цикла чтения буфера.

bool SerialPortInlet::readBufferCycle(unsigned short& bottom, unsigned short const& top, unsigned char& u_lowerBound,    unsigned char& l_lowerBound, std::vector<unsigned char>& readBuffer, std::string& result){// Подсчет байтов, которые необходимо считатьunsigned short byteCountTmp = top - bottom;BOOST_LOG_SEV(log_, logging::info) << "Need read " << byteCountTmp << " byte";unsigned char byteCount;// За один цикл нельзя прочитать более 12 событий (96 байт)byteCount = byteCountTmp > 0x60 ? 0x60 : (unsigned char)byteCountTmp;BOOST_LOG_SEV(log_, logging::info) << "Read " << +byteCount << " byte";// Описываем тело командыstd::vector<unsigned char> body = {0x02, 0xA0, byteCount, u_lowerBound, l_lowerBound};std::vector<unsigned char> command;// Получаем полный текст командыgenerateComplexCommand(command, Command::BYTE_CODE_READ, body);// Если не удалось по каким-то причинам отправить команду (например, конечное устройство не подключено), возвращается falseif (!sendCommand(command, result)){    return false;}// Иначе отправляем ответ с устройства на парсинг по событиямSerialPortType::Answer answerEvents;if(!Parsers::parserAnswer(log_, result, answerEvents, Command::BYTE_CODE_READ)){    BOOST_LOG_SEV(log_, logging::error) << "Failed parse buffer reading";    return false;}readBuffer.insert(readBuffer.end(), answerEvents.body.begin(), answerEvents.body.end());// Сдвигаем нижнюю границу буфера для чтения следующих событийbottom = bottom + byteCount;u_lowerBound = (unsigned char)(bottom >> 8) ;l_lowerBound = (unsigned char)bottom;return true;}

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

Байтстаффинг?

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

Пример байтстаффинга из документацииПример байтстаффинга из документации

Полная структурная схема разработанной системы выглядит так.

Работоспособность всех устройств было очень удобно проверять с помощью графического последовательного терминала СuteCom. После успешного тестирования программа была поставлена на автозапуск, а Raspberry отправилась жить на потолке рядом с платой СКУДа.

Один делает, другой смотрит, третий фотографирует, огнетушитель придерживает дверь - настоящая командная работа =)Один делает, другой смотрит, третий фотографирует, огнетушитель придерживает дверь - настоящая командная работа =)

Работа на платформе Rightech IoT Cloud

Модель

Основные данные с контроллера - это события, на платформу они приходит в формате JSON и включают в себя поля

  • eventTime - время наступления события;

  • eventCode - код события;

  • keyNumber - номер карты сотрудника (поле может быть пустым, если событие вызвано не картой).

Модель устройства выглядит следующим образом.

Посмотреть оригинал >>>

Возможные события:

  • нажата кнопка звонка;

  • неопознанный ключ на входе;

  • неопознанный ключ на выходе;

  • ключ найден в банке ключей при входе;

  • ключ найден в банке ключей при выходе;

  • открывание оператором по сети;

  • дверь заблокирована оператором;

  • дверь оставлена открытой после входа;

  • дверь оставлена открытой после выхода;

  • проход состоялся на вход;

  • проход состоялся на выход;

  • перезагрузка контроллера.

Объект

Интерфейс объекта полностью формируется согласно разработанной модели.

Интерфейс истории журнала объектаИнтерфейс истории журнала объекта

Посмотреть оригинал >>>

Интерфейс командИнтерфейс команд

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

Автомат

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

Зафиксировать успешное получение данных и затем отправить команду на перезапись начальной границы удобно в автомате.


Посмотреть оригинал >>>

Здесь виден цикл <чтение>-<запись новой границы буфера>-<ожидание таймера> (сейчас события считываются каждые 30 секунд).

  1. В состоянии Read events читаем новые события.

  2. В состоянии Clear buffer записываем новую границу.

  3. В состоянии Await timer ожидаем начала нового цикла.

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

Дальнейшее использование собранных данных

Данный проект нашел свое продолжение в интеграции с нашей внутренней CRM системой, в которой на вкладке информации о сотрудниках всегда видны актуальные сведения о том, кто находится или отсутствует в офисе.

Также отображается время входа/выхода из офиса, считается суммарное количество часов в месяц.

Посмотреть оригинал >>>

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

Забор данных из платформы производится по REST API. API платформы предоставляет возможность работы, взаимодействия и использования сущностей платформы и их данных в таких внешних системах, как веб-порталы, мобильные и веб-приложения или, как в нашем случае, - CRM системах.

Посмотреть проект, в котором есть пример настройки взаимодействия и получения данных с платформы >>>


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

Подробнее..

Разворачиваем сервер для проверки In-app purchase за 60 минут

12.11.2020 06:13:44 | Автор: admin

Всем привет! Сегодня расскажу вам как развернуть сервер для проверки In-app Purchase и In-app Subscription для iOS и Android (server-server validation).


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


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




То есть задачу такого сервера можно разделить на 4 этапа:


  • Получение запроса с чеком, отправленным приложением после покупки
  • Запрос в Apple/Google на проверку чека
  • Сохранение данных о транзакции
  • Ответ приложению

В рамках статьи опустим 3 пункт, ибо он сугубо индивидуален.


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


Еще есть статья хорошая То, что нужно знать о проверке чека App Store (App Store receipt), ребята делают сервис для работы с подписками. В статье детально описано, что такое чек (receipt) и для чего нужна проверка покупок.


Сразу скажу, что в сниппетах кода используются вспомогательные классы и интерфейсы, весь код доступен в репозитории по ссылке https://github.com/denjoygroup/inapppurchase. В приведенном ниже фрагментах кода, я постарался дать названия используемым методам такие, чтобы приходилось делать отсылки к этим функциям.


iOS


Для проверки вам нужен Apple Shared Secret это ключ, который вы должны получить в iTunnes Connect, он нужен для проверки чеков.


В первую очередь зададим параметры для создания запросов:


 apple: any = {    password: process.env.APPLE_SHARED_SECRET, // ключ, укажите свой    host: 'buy.itunes.apple.com',    sandbox: 'sandbox.itunes.apple.com',    path: '/verifyReceipt',    apiHost: 'api.appstoreconnect.apple.com',    pathToCheckSales: '/v1/salesReports' }

Теперь создадим функцию для отправки запроса. В зависимости от среды, с которой работаете, вы должны отправлять запрос либо на sandbox.itunes.apple.com для тестовых покупок, либо в прод buy.itunes.apple.com


/*** receiptValue - чек, который проверяете* sandBox - среда разработк**/async _verifyReceipt(receiptValue: string, sandBox: boolean) {    let options = {        host: sandBox ? this._constants.apple.sandbox : this._constants.apple.host,        path: this._constants.apple.path,        method: 'POST'    };    let body = {        'receipt-data': receiptValue,        'password': this._constants.apple.password    };    let result = null;    let stringResult = await this._handlerService.sendHttp(options, body, 'https');    result = JSON.parse(stringResult);    return result;}

Если запрос прошел успешно, то в ответе от сервера Apple в поле status вы получите данные о вашей покупке.


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


21000 Запрос был отправлен не методом POST


21002 Чек поврежден, не удалось его распарсить


21003 Некорректный чек, покупка не подтверждена


21004 Ваш Shared Secret некорректный или не соответствует чеку


21005 Сервер эпла не смог обработать ваш запрос, стоит попробовать еще раз


21006 Чек недействителен


21007 Чек из SandBox (тестовой среды), но был отправлен в prod


21008 Чек из прода, но был отправлен в тестовую среду


21009 Сервер эпла не смог обработать ваш запрос, стоит попробовать еще раз


21010 Аккаунт был удален


0 Покупка валидна


Пример ответа от iTunnes Connect выглядит следующим образом


{    "environment":"Production",    "receipt":{        "receipt_type":"Production",        "adam_id":1527458047,        "app_item_id":1527458047,        "bundle_id":"BUNDLE_ID",        "application_version":"0",        "download_id":34089715299389,        "version_external_identifier":838212484,        "receipt_creation_date":"2020-11-03 20:47:54 Etc/GMT",        "receipt_creation_date_ms":"1604436474000",        "receipt_creation_date_pst":"2020-11-03 12:47:54 America/Los_Angeles",        "request_date":"2020-11-03 20:48:01 Etc/GMT",        "request_date_ms":"1604436481804",        "request_date_pst":"2020-11-03 12:48:01 America/Los_Angeles",        "original_purchase_date":"2020-10-26 19:24:19 Etc/GMT",        "original_purchase_date_ms":"1603740259000",        "original_purchase_date_pst":"2020-10-26 12:24:19 America/Los_Angeles",        "original_application_version":"0",        "in_app":[            {                "quantity":"1",                "product_id":"PRODUCT_ID",                "transaction_id":"140000855642848",                "original_transaction_id":"140000855642848",                "purchase_date":"2020-11-03 20:47:53 Etc/GMT",                "purchase_date_ms":"1604436473000",                "purchase_date_pst":"2020-11-03 12:47:53 America/Los_Angeles",                "original_purchase_date":"2020-11-03 20:47:54 Etc/GMT",                "original_purchase_date_ms":"1604436474000",                "original_purchase_date_pst":"2020-11-03 12:47:54 America/Los_Angeles",                "expires_date":"2020-12-03 20:47:53 Etc/GMT",                "expires_date_ms":"1607028473000",                "expires_date_pst":"2020-12-03 12:47:53 America/Los_Angeles",                "web_order_line_item_id":"140000337829668",                "is_trial_period":"false",                "is_in_intro_offer_period":"false"            }        ]    },    "latest_receipt_info":[        {            "quantity":"1",            "product_id":"PRODUCT_ID",            "transaction_id":"140000855642848",            "original_transaction_id":"140000855642848",            "purchase_date":"2020-11-03 20:47:53 Etc/GMT",            "purchase_date_ms":"1604436473000",            "purchase_date_pst":"2020-11-03 12:47:53 America/Los_Angeles",            "original_purchase_date":"2020-11-03 20:47:54 Etc/GMT",            "original_purchase_date_ms":"1604436474000",            "original_purchase_date_pst":"2020-11-03 12:47:54 America/Los_Angeles",            "expires_date":"2020-12-03 20:47:53 Etc/GMT",            "expires_date_ms":"1607028473000",            "expires_date_pst":"2020-12-03 12:47:53 America/Los_Angeles",            "web_order_line_item_id":"140000447829668",            "is_trial_period":"false",            "is_in_intro_offer_period":"false",            "subscription_group_identifier":"20675121"        }    ],    "latest_receipt":"RECEIPT",    "pending_renewal_info":[        {            "auto_renew_product_id":"PRODUCT_ID",            "original_transaction_id":"140000855642848",            "product_id":"PRODUCT_ID",            "auto_renew_status":"1"        }    ],    "status":0}

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


Полезная для нас информация содержится в свойствах in_app и latest_receipt_info, и на первый взгляд содержимое этих свойств идентичны, но:


latest_receipt_info содержит все покупки.


in_app содержит Non-consumable и Non-Auto-Renewable покупки.


Будем использовать latest_receipt_info, соотвественно в этом массиве ищем нужный нам продукт по свойству product_id и проверяем дату, если это подписка. Конечно, стоит еще проверить не начислили ли мы уже эту покупку пользователю, особенно актуально для Consumable Purchase. Проверять можно по свойству original_transaction_id, заранее сохранив в базе, но в рамках этого гайдлайна мы этого делать не будем.


Тогда проверка покупки будет выглядеть примерно так


/*** product - id покупки* resultFromApple - ответ от Apple, полученный выше* productType - тип покупки (подписка, расходуемая или non-consumable)* sandBox - тестовая среда или нет***/async parseResponse(product: string, resultFromApple: any, productType: ProductType, sandBox: boolean) {    let parsedResult: IPurchaseParsedResultFromProvider = {        validated: false,        trial: false,        checked: false,        sandBox,        productType: productType,        lastResponseFromProvider: JSON.stringify(resultFromApple)    };    switch (resultFromApple.status) {        /**        * Валидная подписка        */        case 0: {            /**            * Ищем в ответе информацию о транзакции по запрашиваемому продукту            **/            let currentPurchaseFromApple = this.getCurrentPurchaseFromAppleResult(resultFromApple, product!, productType);            if (!currentPurchaseFromApple) break;            parsedResult.checked = true;            parsedResult.originalTransactionId = this.getTransactionIdFromAppleResponse(currentPurchaseFromApple);            if (productType === ProductType.Subscription) {                parsedResult.validated = (this.checkDateIsAfter(currentPurchaseFromApple.expires_date_ms)) ? true : false;                parsedResult.expiredAt = (this.checkDateIsValid(currentPurchaseFromApple.expires_date_ms)) ?                this.formatDate(currentPurchaseFromApple.expires_date_ms) : undefined;            } else {                parsedResult.validated = true;            }            parsedResult.trial = !!currentPurchaseFromApple.is_trial_period;            break;        }        default:            if (!resultFromApple) console.log('empty result from apple');            else console.log('incorrect result from apple, status:', resultFromApple.status);    }    return parsedResult;}

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


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


Android


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


Для гугла нам понадобится чуть больше входных параметров:


google: any = {    host: 'androidpublisher.googleapis.com',    path: '/androidpublisher/v3/applications',    email: process.env.GOOGLE_EMAIL,    key: process.env.GOOGLE_KEY,    storeName: process.env.GOOGLE_STORE_NAME}

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


Окей, гугл, прими запрос:


/*** product - название продукта* token - чек* productType  тип покупки, подписка или нет**/async getPurchaseInfoFromGoogle(product: string, token: string, productType: ProductType) {    try {        let options = {            email: this._constants.google.email,            key: this._constants.google.key,            scopes: ['https://www.googleapis.com/auth/androidpublisher'],        };        const client = new JWT(options);        let productOrSubscriptionUrlPart = productType === ProductType.Subscription ? 'subscriptions' : 'products';        const url = `https://${this._constants.google.host}${this._constants.google.path}/${this._constants.google.storeName}/purchases/${productOrSubscriptionUrlPart}/${product}/tokens/${token}`;        const res = await client.request({ url });        return res.data as ResultFromGoogle;    } catch(e) {        return e as ErrorFromGoogle;    }}

Для авторизации воспользуемся библиотекой google-auth-library и класс JWT.


Ответ от гугла выглядит примерно так:


{    startTimeMillis: "1603956759767",    expiryTimeMillis: "1603966728908",    autoRenewing: false,    priceCurrencyCode: "RUB",    priceAmountMicros: "499000000",    countryCode: "RU",    developerPayload: {        "developerPayload":"",        "is_free_trial":false,        "has_introductory_price_trial":false,        "is_updated":false,        "accountId":""    },    cancelReason: 1,    orderId: "GPA.3335-9310-7555-53285..5",    purchaseType: 0,    acknowledgementState: 1,    kind: "androidpublisher#subscriptionPurchase"}

Теперь перейдем к проверке покупки



parseResponse(product: string, result: ResultFromGoogle | ErrorFromGoogle, type: ProductType) {    let parsedResult: IPurchaseParsedResultFromProvider = {        validated: false,        trial: false,        checked: true,        sandBox: false,        productType: type,        lastResponseFromProvider: JSON.stringify(result),    };    if (this.isResultFromGoogle(result)) {        if (this.isSubscriptionResult(result)) {            parsedResult.expiredAt = moment(result.expiryTimeMillis, 'x').toDate();            parsedResult.validated = this.checkDateIsAfter(parsedResult.expiredAt);        } else if (this.isProductResult(result)) {            parsedResult.validated = true;        }    }    return parsedResult;}

Тут все достаточно тривиально. На выходе мы также получаем parsedResult, где самое важное хранится в свойстве validated прошла покупка проверку или нет.


Итог


По существу буквально в 2 метода можно проверить покупку. Репозиторий с полным кодом доступен по ссылке https://github.com/denjoygroup/inapppurchase (автор кода Алексей Геворкян)


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


Есть два хороших сервиса, которые предоставляют сервис для проверки чеков: https://ru.adapty.io/ и https://apphud.com/. Но, во-первых, для некоторых категорий приложений нельзя передавать данные 3 стороне, а во-вторых, если вы хотите отдавать платный контент динамически при совершении пользователем покупки, то вам придется разворачивать свой сервер.


P.S.


Ну, и, конечно, самое важное в серверной разработке это масштабируемость и устойчивость. Если у вас большая аудитория пользователей и при этом сервер не способен выдерживать нагрузки, то лучше и не реализовывать проверку покупок самим, а отправлять запросы сразу в iTunnes Connect и в Google API, иначе ваши пользователи сильно расстроятся.

Подробнее..

Перевод Настройка распределенной трассировки в Kubernetes с OpenTracing, Jaeger и Ingress-NGINX

20.05.2021 18:13:57 | Автор: admin

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

Распределённая трассировка (Distributed Tracing) - это метод, используемый для мониторинга приложений. Для микросервисов он просто незаменим.

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

Что нам потребуется?

В этом руководстве предполагается, что вы понимаете код, написанный на Go, умеете использовать Ingress-nginx и знаете, как работают основные объекты Kubernetes, такие как Service и Deployment.

Если вы хотите освежить знания, воспользуйтесь этими материалами:

Запуск Kubernetes в Docker Desktop

Начнём с установки Docker Desktop, который позволяет не только запускать контейнеры, но и создать локальный кластер Kubernetes.

После установки Docker Desktop выполните следующие действия, чтобы создать кластер Kubernetes:

  1. Нажмите на иконкуPreferences

2. Выберете вкладкуKubernetes, установите флажокEnable Kubernetes и нажмите кнопкуApply & Restart

3. В появившемся окне нажмите кнопкуInstall и дождитесь пока установка завершится

4. Выберите пунктKubernetes в панели задач

5. В контекстном меню выбиритеdocker-desktop

6. Проверьте, что вы подключены к нужному кластеру

$ kubectl cluster-infoKubernetes master is running at https://kubernetes.docker.internal:6443KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Установка Ingress-NGINX Controller

1. Воспользуйтесь командой из официального руководства для установки Ingress-NGINX Controller.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/cloud/deploy.yamlnamespace/ingress-nginx createdserviceaccount/ingress-nginx createdconfigmap/ingress-nginx-controller createdclusterrole.rbac.authorization.k8s.io/ingress-nginx createdclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx createdrole.rbac.authorization.k8s.io/ingress-nginx createdrolebinding.rbac.authorization.k8s.io/ingress-nginx createdservice/ingress-nginx-controller-admission createdservice/ingress-nginx-controller created\deployment.apps/ingress-nginx-controller createdvalidatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission createdserviceaccount/ingress-nginx-admission created]clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission createdclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission createdrole.rbac.authorization.k8s.io/ingress-nginx-admission createdrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission createdjob.batch/ingress-nginx-admission-create created\job.batch/ingress-nginx-admission-patch created

2. Проверьте, что установка Ingress Сontroller прошла успешно. Pods должны быть запущены и находятся в статусе Ready.

$ kubectl get pods -n ingress-nginxNAME                                     READY   STATUS    RESTARTS ingress-nginx-admission-create-52jsl        0/1     Completed   0          ingress-nginx-admission-patch-78fkc         0/1     Completed   0          ingress-nginx-controller-6f5454cbfb-qsfnn   1/1     Running     0 

Важно:Если Pod находится в статусе Pending из-за нехватки ресурсов CPU/Memory, вы можете добвить ресурсы вашему кластеру в настройках Docker Desktop.

Установка Jaeger и настройка Ingress Controller

Jaeger - это платформа распределенная трассировки, которую мы будем использовать для мониторинга наших микросервисов. Установим Jaeger и включим трассировку на уровне Ingress Controller.

1. Вначале склонируйте репозиторийmeow-micro, этот проект мы будем использовать во всех примерах.

$ git clone https://github.com/diazjf/meow-micro.gitCloning into 'meow-micro'...remote: Enumerating objects: 105, done....$ cd meow-micro

2. В репозитории вы найдёте манифесты для jaeger-all-in-one. Установим Jaeger из этих манифестов.

$ kubectl apply -f jaeger/jaeger-all-in-one.yamldeployment.apps/jaeger createdservice/jaeger-query createdservice/jaeger-collector createdservice/jaeger-agent createdservice/zipkin created

3. Убедимся, что Jaeger запущен и готов к работе.

$ kubectl get podsNAME                      READY   STATUS    RESTARTS   AGEjaeger-6f6b5d8689-8gccp   1/1     Running   0          17s

4. Пришло время настроить совместную работу Ingress-NGINX and Jaeger, для этого необходимо добавить параметрыenable-opentracingиjaeger-collector-hostвingress-nginx-controllerConfigMap. В параметре jaeger-collector-host указываем имя сервиса Jaeger.

$ echo '  apiVersion: v1  kind: ConfigMap  data:    enable-opentracing: "true"    jaeger-collector-host: jaeger-agent.default.svc.cluster.local                metadata:    name: ingress-nginx-controller    namespace: ingress-nginx  ' | kubectl replace -f -configmap/ingress-nginx-controller replaced

5. Убедимся, что в настройках Ingress Controller включени настроен opentracing.

$ kubectl get pods -n ingress-nginx | grep controlleringress-nginx-controller-6f5454cbfb-qptxt   1/1     Running     0          8m56s$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep ngx_http_opentracing_module.so"load_module /etc/nginx/modules/ngx_http_opentracing_module.so;$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep jaeger"opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so /etc/nginx/opentracing.json;$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat /etc/nginx/opentracing.json"{  "service_name": "nginx",  "propagation_format": "jaeger",  "sampler": {    "type": "const",    "param": 1,    "samplingServerURL": "http://127.0.0.1:5778/sampling"  },  "reporter": {    "endpoint": "",    "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"  },  "headers": {    "TraceContextHeaderName": "",    "jaegerDebugHeader": "",    "jaegerBaggageHeader": "",    "traceBaggageHeaderPrefix": ""  }}

Jaeger и Ingress Controller успешно установлены и настроены. Самое время развернуть наши микросервисы!

Разворачиваем тестовое приложение

Тестовое приложение meow-micro состоит из двух микросервисов. Клиент meow-client - принимает REST запрос и отправляет информацию сервису meow-server через GRPC.

Подробная информация об использовании REST и GRPC с GoLang:

Тестовое приложение также содержит несколько дополнительных инструментов:

tracing.go

Получает параметры для настройки Jaeger из окружения, в котором запускается Helm для установкиmeow-clientandmeow-server.

cfg, err := config.FromEnv()if err != nil {panic(fmt.Sprintf("Could not parse Jaeger env vars: %s", err.Error())) }  tracer, closer, err := cfg.NewTracer()if err != nil {   panic(fmt.Sprintf("Could not initialize jaeger tracer: %s", err.Error())) }

client.go

Выполняет конфигурирование клиента - задает service nameдля trace и определяет span.

  • Span базовый элемент распределенной трассировки. Представляет собой описание некоего рабочего процесса (например, запроса к базе данных). Span'ы обычно содержат ссылки на другие span'ы, что позволяет объединять множество span'ов вTrace.

  • Trace визуализация жизни запроса в процессе его перемещения по распределенной системе.

Подробную информацию о понятиях trace и span можно посмотреть в официальной документации.

// main function spanos.Setenv("JAEGER_SERVICE_NAME", "meow-client")tracer, closer := tracing.Init()defer closer.Close()http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))span := tracer.StartSpan("send-meow-communication", ext.RPCServerOption(spanCtx))defer span.Finish()...// sleep function spanos.Setenv("JAEGER_SERVICE_NAME", "meow-client")tracer, closer := tracing.Init()defer closer.Close()span := tracer.StartSpan("sleep")defer span.Finish()

Установка тестового приложения

Установить Helm v3 можно на любую операционную систему, например на macOS с помощью brew. Теперь мы готовы развернуть микросервисы в нашем кластере.

$ brew install helm...==> Downloading https://ghcr.io/v2/homebrew/core/helm/manifests/3.5.4######################################################################## 100.0%==> Downloading https://ghcr.io/v2/homebrew/core/helm/blobs/sha256:5dac5803c1ad2db3a91b0928fc472aaf80a4==> Downloading from https://pkg-containers-az.githubusercontent.com/ghcr1/blobs/sha256:5dac5803c1ad2db######################################################################## 100.0%==> Pouring helm--3.5.4.big_sur.bottle.tar.gz...$ helm versionversion.BuildInfo{Version:"v3.3.4", GitCommit:"a61ce5633af99708171414353ed49547cf05013d", GitTreeState:"clean", GoVersion:"go1.14.9"}

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

# Build client and server from Dockerfile$ make builddocker build -t meow-client:1.0 -f client/Dockerfile .[+] Building 17.1s (10/10) FINISHED...docker build -t meow-server:1.0 -f server/Dockerfile .[+] Building 0.5s (10/10) FINISHED...# Install Microservices into Kubernetes via Helm$ make installhelm install -f helm/Values.yaml meow-micro ./helmNAME: meow-microLAST DEPLOYED: Mon Apr 26 13:42:38 2021NAMESPACE: defaultSTATUS: deployedREVISION: 1TEST SUITE: None

Проверим, что Pods обоих сервисов запустились и работают.

$ kubectl get podsNAME                           READY   STATUS    RESTARTS   AGEjaeger-6f6b5d8689-s7cln        1/1     Running   0          26mmeow-client-8b974778c-85896    1/1     Running   0          15mmeow-server-56f559db44-5mvgp   1/1     Running   0          15m

Просмотр данных трассировки

А теперь самое интересное! Взглянем на трассировку.

Откройте консоль Jaeger, указав в браузере адресhttp://localhost:8081.

2. Отправим запрос нашему приложению.

$ curl http://localhost/meow -X POST -d '{"name": "Meow-Mixer"}'200 - Meow sent: Meow-Mixer

3. Обновите страницу браузера. В меню Service выберите - nginx.

4. Нажмите кнопкуFind Traces.

5. Отобразятся все трассировки для nginx. Каждую из них можно развернуть, чтобы получить больше информации.

6. Теперь вы можете отслеживать, сколько времени занял каждый запрос.

И выводить дополнительную информацию по каждому запросу.

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

Другие системы трассировки

Мы рассмотрели как использовать Jaeger для трассировки запросов Ingress-Nginx. Для этих целей можно также использовать Zipkin или DataDog.

Подробнее..

Опуститься до уровня руководителя?

26.12.2020 14:17:21 | Автор: admin

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

Красота в наших глазах. Но руководитель часто далек от технических деталей. Часто очень сложно объяснить всю программистскую кухню на языке диаграмм Ганта. Когда штудирование документации и вычитывание кода библиотеки выливается в метрику +10 строк кода. (За половину месяца.) Ну ведь правда, как-то не солидно 0.125 строк кода в день. (Это сколько символов в день? А в час?) Правда?


Мы правда должны опускаться до уровня руководителей?

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

Но все же, что же с диалогом?

Если задаться вопросом: а нужно ли пасовать?

А может ли быть изящным то, чтобы понять какие преимущества получает пользователь при использовании этих десяти строк кода? Если попробовать продумать преимущества уменьшения загрузки серверов в версии кода этих 10-ти строк. Сложно назвать сумму прибыли? Возможно. Но если время ответа сервера уменьшиться на некоторое число миллисекунд. На полмесяца взгляд будет иной. Правда?


А если о диалоге?

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

Подробнее..

Важность диалога между PM-ом и разработчиком

28.12.2020 10:22:36 | Автор: admin

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

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

Если для вас кейс очевиден, то можно дальше и не читать.

Список проблем.

Перерасход бюджета. Разработчик потратил уйму времени на простую фичу и требует дополнительное время.

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

Возможность конфликта с разработчиком.


Возможность демотивации разработчика. Разработчик не доволен качеством существующей системы. Разработчик старался улучшить систему. Задача полностью не решена. Но работать с некачественной системой у разработчика мало мотивации.

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

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

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

Разработчик не может сформулировать почему низкое качество системы ведёт к ухудшению бизнес показателей продукта.

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

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


И так далее. (Прописываю только часть проблем.)

Решения.


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

Спасибо.

Подробнее..

Автоматизация публикации приложения в Google Play при помощи Jenkins

26.01.2021 20:09:19 | Автор: admin

Для этого нам понадобится

  1. Действующий account Google Play Developer

  2. Сервер Linux с предустановленным Docker, в моём случае это Ubuntu 16.04

  3. Установленный Android SDK

  4. Jenkins - в данном случае развернём его при помощи Docker

  5. Gitea - Удобная служба для собственного Git-репозитория (это не обязательно можно использовать и GItHub) её мы подымем также на базе Docker контейнера

Настройка Google Play Account

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

  1. Авторизуйтесь в Google Cloud Platform, если еще не создан проект то создаём его

  2. В разделе IAM и администрирование - Сервисные аккаунты жмём Создать сервисный аккаунт

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

  4. Авторизуйтесь в Gooogle Play Developer Console

  5. В разделе Пользователи и разрешения нажимаем пригласить пользователя сервисного аккаунта по сгенерированному email , выставляем ему роль Релиз менеджер (при необходимости можете настроить права этого пользователя более детально)

  6. Я предполагаю что Сертификат для ключа подписи приложения, и Сертификат ключа загрузки у вас уже настроен и это не требует пояснений.

Установка Android SDK

# Install latest JDKsudo apt install default-jdksudo apt install android-sdk

Добавьте Android SDK в свой PATH, откройте~/.bashrcв редакторе и скопируйте следующие строки

# Export the Android SDK path export ANDROID_HOME=$HOME/android-sdkexport PATH=$PATH:$ANDROID_HOME/tools/binexport PATH=$PATH:$ANDROID_HOME/platform-tools# Fixes sdkmanager error with java versions higher than java 8export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'

Для проверки запустите

source ~/.bashrc

Для того что бы просмотреть все доступные инструменты сборки выполните команду

sdkmanager --list

По аналогии с выполните установку интересующих вас версий инструментов для нужных платформ

sdkmanager "platform-tools" "platforms;android-28"

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

Установка Gitea

Этот шаг является опциональным и совсем не обязательным если вы предпочитаете использовать git репозитарии такие как GitHub и им подобные и его можно пропустить, это малой степени повлияет на конечный результат. (На базе gitea в дальнейшем будет обсуждаться тема создания Telegram Bot`a для оповещения о публикациях)

Подробно по установке и настройке написано на оф сайте https://docs.gitea.io/en-us/install-with-docker/

По факту установка сводится к выполнению 2х действий

1) Перейдите на официальный репозитарий Gitea , на Docker HUB и скопируйте то что там написано в предварительно созданный файл

version: '2'services:  web:    image: gitea/gitea:1.12.4    volumes:      - ./data:/data    ports:      - "3000:3000"      - "22:22"    depends_on:      - db    restart: always  db:    image: mariadb:10    restart: always    environment:      - MYSQL_ROOT_PASSWORD=changeme      - MYSQL_DATABASE=gitea      - MYSQL_USER=gitea      - MYSQL_PASSWORD=changeme    volumes:      - ./db/:/var/lib/mysql

2) Сохраните и запустите команду docker-compose you_filename

3) Gitea установлена и доступна по URL http://you_IP:3000/

4) Создайте пользователя, репозитарий, сделайте PUSH исходного кода вашего проекта, и в моём случае для упрощения процесса публикации приложения все необходимые ключи для его подписи и деплоя я храню в месте с исходным кодом в системе контроля версий (да знаю это не совсем верно и вы вольны делать так как считаете нужным, к примеру создать отдельный volume для ключей и пробрасывать их jenkins для дальнейшего их использования gradle при подписании приложения, описание этого лишь раздует статью и потому не будет рассматриваться)

Предварительная настройка проекта

Для подписания нашего apk файла в автоматическом режиме на стороне сервера, нам нужен файл нашего keystore и правильно настроенный скрипт gradle , для этого добавим в него несколько секций

// Load keystoredef keystorePropertiesFile = rootProject.file("keystore.properties")def keystoreProperties = new Properties()keystoreProperties.load(new FileInputStream(keystorePropertiesFile))// GenerateNameVersiondef getVersionNameTimestamp() {    return new Date().format('yy.MM.ddHHmm')}// GenerateVersionCodedef getVersionCodeTimestamp() {    def date = new Date()    def formattedDate = date.format('yyMMddHHmm')    def code = formattedDate.toInteger()    println sprintf("VersionCode: %d", code)    return code}......android {    signingConfigs {        config {            keyAlias keystoreProperties['keyAlias']            keyPassword keystoreProperties['keyPassword']            storeFile file(keystoreProperties['storeFile'])            storePassword keystoreProperties['storePassword']        }    }......    defaultConfig {        versionCode getVersionCodeTimestamp()        versionName "${getVersionNameTimestamp()}"

Содержимое файла keystore.properties

storePassword=you_password_keystorekeyPassword=you_password_keykeyAlias=you_key_namestoreFile=path_to_keystore_file
  • Я храню как файл с keystore.properties так и сам keystore в системе контроля версий, что не совсем правильно и не безопасно при публикации исходного кода в открытом виде к примеру на GitHub, по этому я не рекомендую использовать данный подход если у вас OpenSource проект , храните и подтягивайте их из отдельной папки.

  • Для публикации в Google Play требуется уникальная версия сборки, и я генерирую её скриптом Gradle на основании текущей даты и времени, и как вы понимаете это исключает использование каких либо вразумительных номеров версий и если вам это важно , то вам придётся придумать некий другой механизм автоматизации версионирования, я же остановился на этом варианте - потому как хотел что бы deploy происходил по нажатию одной лишь кнопки (PUSH)

Установка Jenkins

Его мы развернём используя Docker , я применяю уже настроенный образ с нужными мне инструментами из своего репозитария, вы же вольны использовать официальный, принципиально ни чего не измениться

docker run -it -d -v jenkins_home:/var/jenkins_home -v /usr/lib/android-sdk:/usr/lib/android-sdk -p 8080:8080 -p 50000:50000 --env JENKINS_OPTS="--prefix=/jenkins" --restart always leganas/ls_repository:jenkins

пробрасываем в качестве volumes в в наш контейнер папку с установленным Android SDK

Теперь он запущен и доступен по адресу http://you_IP:8080/jenkins/

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

1) Для начала нам нужно настроить окружение , заходим System Configuration - Глобальные настройки , и добавляем Environment variables (возможно ваши пути будут отличатся)

2) В разделе Настройки - Конфигурация глобальных инструментов проверяем настройки Git и Gradle (по факту обычно там всё уже настроено)

3) Заходим Настройки - Управление пользователями , выбираем пользователя и его настройки, ищем строку API Token , создаём новый и сохраняем его , он нам понадобится.

4) В разделе управления плагинами проверяем и если нужно устанавливаем плагины Git, Git client, Google OAuth Credentials plugin, Google Play Android Publisher

5) Заходим Настройки - Manage Credentials Configure credentials -Store - Jenkins - Global credentials (unrestricted)- и создаём там 2 ключа доступа |

  • для доступа к Git репозитарию

    в моём случае т.к. я использую Gitea я создаю обычную пару login/password , для GitHub есть специальный плагин и инструмент для авторизации

  • для публикации приложения в Google Play Market

    создаём ключ используя JSON файл который создали в первом разделе данной инструкции

6) Теперь создайте проект вашего приложения в Jenkins и настройте его

  • Управление исходным кодом - установите Git , укажите Repository URL и Credentials (которые создали на прошлом этапе)

  • Триггеры сборки - выставите Trigger builds remotely (e.g., from scripts), в поле ввода введите любой случайный текст (он нам понадобится для удалённой активизации процесса сборки) при помощи GIt huck

  • Добавьте шаг сборки Invoke Gradle script , и После сборочные операции - публикацию

Обратите внимание на поле Release traack , оно указывает то как будет опубликовано ваше приложение , в моём случае это "Внутреннее тестирование"

7) Запустите проект в ручную использую Web интерфейс Jenkins и посмотрите на результат в истории сборок - Вывод консоли Если вы всё сделали верно то ваше приложение должно быть подтянуто из Git репозитария до актуальной версии ветки /master , собрано , подписано и опубликовано на Google Play.

Автоматизация запуска сборки

Если мы всё сделали правильно и проект удачно был опубликован на Google Play , можно перейти к настройке его автоматического deploy по Git событию PUSH в /master ветку

1) Если вы используете Gitea как я то зайдите в репозитарий вашего проекта - Настройки - Git хуки, и в post-receive нажмите редактировать, и добавьте нечто этого рода

#!/bin/bashwhile read oldrev newrev refnamedo    branch=$(git rev-parse --symbolic --abbrev-ref $refname)    if [ "master" = "$branch" ]; then       curl --user you_user_name:you_user_token http://you_url_jenkins/job/you_project/build?token=you_tocken_build    fidone
  • you_user_name - Имя пользователя от лица которого jenkins будет производить сборку

  • you_user_token - Токен который был сгенерирован в настройках пользователя

  • you_url_jenkins и вообще весь путь до хука можно посмотреть на странице настройки проекта Jenkins и будет выглядеть он примерно так :

После проведения такого рода манипуляций сборка проекта Jenkins будет начинаться автоматически после PUSH события в master вашего репозитария.

PS. Если же вы используете для хранения вашего кода другую Git систему то этот же код вы можете поместить руками в .git\hooks\post-update

Заключение

В случае положительного фидбэка от статьи, в следующей части я расскажу как написать Telegram бота для оповещения ваших тестировщиков о наличии обновления приложения на Google Play.

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

Подробнее..

Приём для разработчиков API

04.02.2021 08:10:23 | Автор: admin

Контекст

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

Последнее противоречие в системном API может:
* не разрешаться вообще (это происходит, например, если цель разработки в минимизации затрат ресурсов системы во время выполнения кода);
* разрешаться частично (с помощью, например, несколько уровней API или предоставлением нескольких/ряда системных API адаптированных под свои подзадачи);
* разрешаться с помощью развития API дополнительными адаптирующими библиотеками (например, адаптирующие под возможности более мощных языков).

С другой стороны при разработке ad hoc для конкретного(ых) проекта(ов) можно получить бонусы разработки с помощью адаптации системного(ых) API под нужны данного проекта и данной команды разработчиков.

Например, можно получить следующие преимущества:
* выделение наиболее важной для данного проекта части API;

* выделение нескольких уровней API (выделение нескольких уровней детализации API) с точки зрения данного проекта (разработчик в большинстве случаев может использовать простейший уровень API и только в отдельных случаях использовать более сложные и более детальные глубокие уровни API);
* интеграция системного API с существующей инфраструктурой проекта;
* сокрытие части действий с системным API от разработчиков данного проекта, за счёт того, что мы знаем для данного проекта корректное поведение по умолчанию с системным API;

* за счёт реализации по умолчанию некоторых стандартных операций, которые единообразны для данного проекта;
* использование преимуществ инфраструктуры (языков, других библиотек, средств интеграции и так далее) данного проекта для развития системного API с целью улучшения качества использования API разработчиками проекта;
и так далее.

Прием

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

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

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

Важно, чтобы это был хоть и код заглушек, но вполне компилируемый и вполне выполняемый код. Код, который можно рассматривать на code review. Чтобы можно было просить разработчиков попробовать использовать ваше внутреннее API, ещё на стадиях проектирования API.

Подробнее..
Категории: Development , Api

HR Tech М.Видео-Эльдорадо как продуктовый подход позволил нам сделать свой интранет с нуля за полгода

01.03.2021 14:22:08 | Автор: admin


Объединенный интранет группы компаний М.Видео-Эльдорадо завоевал главный приз Russian Intranet Awards в 2020 году и серебро Intranet and Digital Workplace Awards. Этот внутренний продукт был разработан с нуля за шесть месяцев. То, что это удалось сделать в такие сроки и уровнем ценности, признанным внутри компании и за её пределами результат применения продуктового подхода и гибких практик. Рассказываем, почему они были выбраны и какие элементы этой методики стали ключевыми.

Предыстория


В 2018 году произошло слияние компаний М.Видео и Эльдорадо, которые к тому моменту были крупнейшими ритейлерами всего спектра электроники и бытовой техники в России. В объединенной компании трудятся около 30 тыс. человек. В ней более тысячи магазинов, которые работают более чем в 200 городах России.

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

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

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



Зачем группе М.Видео-Эльдорадо нужен объединенный интранет


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

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

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

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



Постановка задачи


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

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

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

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

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

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

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

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

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



Продуктовый подход


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

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

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

Основные документы


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

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

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



Scrum


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

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

Одним из способом работать с неопределенностью в таких случаях было сочетание agile-подхода и фреймворка Scrum.

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

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

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

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

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



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

Если вы только идете в этот фреймворк запаситесь терпением, постарайтесь обеспечить команду балансом где 1 FTE опытного сотрудника обслуживает или менторит 1 или лучше 0,5 FTE джуна/миддла и найдите грамотного скрам-мастера с техническим бэкграундом. Погрузиться в детали нашего опыта можно тут: часть 1, часть 2, часть 3.

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



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

Что дальше?


Сегодня наш проект и команда продолжает развиваться. Мы наметили переход на LeSS фреймворк, получили кратный рост метрик и сейчас переходим в парадигму внутрикорпоративного SuperApp.

P.S. На данном этапе нам очень нужны талантливые программисты. Если вы такой, приходите, будет интересно.
Подробнее..

Выдерни шнур, выдави стекло

06.04.2021 12:11:10 | Автор: admin

Инструкция по диагностике проблем в работе баз данных в случае аварии.

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

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

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

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

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

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

Ниже я распишу каждый шаг схемы и инструментарий, применяемый нами для каждого шага.

Шаг 1. Проверка нагрузки ядер процессоров на сервере БД (Load Average)

В большинстве случаев внешним проявлением проблемы в работе базы данных является возросшая нагрузка на ядра процессоров сервера БД. Лучшей метрикой для диагностирования этого факта я считаю LOAD AVERAGE. При этом мне больше нравится эта метрика в пересчете на ядро процессора сервера БД. LA (Load Average) на ядро более 1 плохо. Это значит, что запрос к БД ожидает какое-то время, прежде чем выполниться.

Для отслеживания этой метрики мы применяем систему мониторинга Zabbix с возможностью настройки оповещений в различные каналы. Пример такой метрики при возникновении аварии:

Шаг 2. Нагрузка на ядро процессора (LA) изменилась более чем на 30 % по отношению к тому же времени того же дня недели?

При возникновении серьёзной аварии LA обычно растёт лавинообразно. Тем не менее, даже повышение на 30 % признак какой-то аномалии. Если рост LA заметен и превышает заданный лимит, то самое время перейти к шагу 3 и проверить количество подключений к базе данных. Если же роста LA нет, то лучше переходить к шагу 27.

ШАГ 3. Проверка количества подключений приложений к БД и динамика его роста

Это ещё один маркер, который даёт чёткое понимание, что с БД что-то пошло не так. Тут в большей степени интересна динамика роста количества подключений к базе.

В качестве инструмента мониторинга используем всё тот же Zabbix. Максимально разрешенное количество подключений к БД можно узнать, выполнив команду SHOW_MAX_CONNECTIONS.

Шаг 4.Количество подключений к БД резко выросло за короткий промежуток времени?

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

Шаг 5. Увеличить количество подключений, поставив пулер подключений

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

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

Если же приложение работает всё-таки оптимально, то стоит задуматься над использованием пулера подключений (самый распространенный PGBouncer). Этот сервис берёт на себя управление подключениями и делает это достаточно эффективно. Приложение для взаимодействия с БД будет обращаться к PGBouncer, а тот в свою очередь будет перенаправлять запрос к БД через свои подключения. Подробнее об этой технологии можно почитать тут: http://personeltest.ru/aways/habr.com/ru/company/okmeter/blog/420429/.

Я также встречался с ситуацией, когда пулер подключений (PGBouncer) был установлен, но в его работе возникали проблемы. Стоит обратить внимание на лимит подключений к БД в самом пулере. Также у PGBouncer (не знаю, как в аналогах) есть одна особенность реализации: при установлении подключения к БД открывается файловый дескриптор. Поэтому на нагруженных крупных экосистемах с большим количеством сервисов и баз, использующих централизованный PGBouncer, может возникнуть проблема с ограничением числа одновременно открытых файловых дескрипторов на уровне операционной системы

Шаг 7. Проверка наличия блокировок и запросов в ожидании в базе данных

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

Способ проверки этой гипотезы динамика с количеством запросом к БД в статусе "IDLE IN TRANSACTION". Мы используем механизм периодического замера количества запросов в этом статусе, с выводом соответствующего графика в Grafana.

Шаг 8. В БД много блокировок и запросов в ожидании

Если ответ на этот вопрос да, то мы уже на полпути к решению. Отправляемся пробовать быстрое решение (шаг 9). Если же большого количества блокировок (повторяюсь большого количества! Блокировки это нормально, если их стандартное количество) и запросов В ожидании не наблюдается, то переходим к шагу 11.

ШАГ 9. Убить все блокировки в БД

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

Сделать это можно устранив процесс, вызвавший блокировку pg_terminate_backend(pid);.

Шаг 10. Ситуация исправилась?

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

Шаг 11. Получение списка выполняемых запросов, отсортированных по частоте и стоимости

Для этого необходимо выполнить команду PG_STAT_STAEMENTS. При вызове этой утилиты мы соберём журнал всех выполняемых в БД запросов и сможем оценить, какие из них на текущий момент времени занимают больше всего ресурсов (по частоте и стоимости выполнения). Рекомендуется сбросить ранее накопленную статистику командой PG_STAT_STAEMENTS_RESET () и начать её собирать заново, уже во время инцидента. Таким образом мы получим чистую статистику со стоимостью запросов уже на этапе инцидента.

Шаг 12. Изучить SlowLog (запросы, которые выполняются дольше критичного времени)

Эта операция, возможно, сразу даст ответ, какие запросы нужно оптимизировать, без более трудоемкого анализа результатов PG_STAT_STATEMENTS. Обычно мы оба шага (11 и 12) выполняем параллельно, чтобы сразу получить представление о нагрузке на БД. Еще один плюс SlowLog получив выборку из него за более длительный период времени, мы сможем вычленить запросы, которых раньше не было (т. е. они выполнялись быстро), но в какой-то момент стали выполняться медленно, скорее всего из-за аварии.

Шаг 13. Есть дорогие запросы, которые ранее выполнялись быстро?

На этом шаге главной проблемой является определение, с какой скоростью выполнялись запросы ранее (до аварии). Утилиты SlowLog и PG_STAT_STATEMENTS дадут понимание о скорости выполнения запросов сейчас. Но чтобы найти проблемный запрос, нужно понимать, с какой скоростью запросы выполнялись ранее. Для этой цели я рекомендую использовать дополнительные утилиты PGHero или NewRelic. Это достаточно удобные инструменты, которые позволяют понять динамику скорости выполнения запросов.

Если видна деградация в скорости выполнения каких-то определенных запросов, то переходим к шагу 14. Если зафиксировать снижение скорости выполнения какого-то запроса не удалось, то переходим к шагу 30.

Шаг 14. Проверка плана по дорогим запросам

Если вам удалось выделить конкретный запрос с ярко выраженной негативной динамикой скорости выполнения, то следующий шаг детальный анализ запроса посредством утилиты EXPLAIN ANALYZE. Подробно о том, как работает эта команда и как интерпретировать результаты её выполнения, можно ознакомиться тут: https://thoughtbot.com/blog/reading-an-explain-analyze-query-plan.

Шаг 15. Требуется новый индекс?

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

Шаг 16. Индекс есть, но не используется (а раньше использовался)

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

В любом случае, если вы видите, что индекс создан и есть, но при этом EXPLAIN ANALYZE говорит о том, что он не используется, то переходим к шагу 17. Если же индекс используется переходим к шагу 20.

ШАГ 17. Обновление статистики таблиц базы данных

Простая операция, которая может спасти положение. Для сбора статистики большей точности достаточно выполнить команду ANALYZE илиset default_statistics_target = 500; ANALYZE . 500 это количественный коэффициент образца таблицы, который PostgreSQL выбирает для расчёта статистики. Подробнее о механизме работы статистики и карт видимости можно почитать тут: https://postgrespro.ru/docs/postgrespro/10/routine-vacuuming.

Шаг 18. Ситуация исправилась?

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

Шаг 19. Пересоздание индекса

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

Пересоздаем индекс. Сделать это можно командой REINDEX (подробнее читайте тут: https://postgrespro.ru/docs/postgrespro/9.5/sql-reindex).

Переходим к шагу 31.

Шаг 20. Профиль работы с кешем (Hit/Miss)

Запрос вдруг неожиданно начал тормозить, но при этом индекс используется. Следующий шаг проверить работу кеша базы данных. Для этого лучше всего подойдёт метрика Hit/Miss количества записей, полученных запросом из кеша или выбираемых из базы данных с нуля. Мы для этих целей используем Zabbix.

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

Шаг 21. Расширить размер буфера БД

Эта мера даст нам какое-то время, но только том в случае, если можно расшириться на значительный объём. По сути, мы какую-то часть данных БД перегоним в кеш, что ускорит доступ к этим данным. Расширение размера буфера кеша БД определяется параметром shared_buffers в конфиге базы.

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

Шаг 22. Изменились параметры запроса?

Иногда может быть так, что скорость выполнения запроса снижается изза дополнительных параметров, которые выступают в качестве условия выборки данных. Например, если в роли фильтра выступал какой-то атрибут, идентификаторы которого перечислялись в предикате запроса (where atribut_id in (id1, id2, id3)), и какой-то момент вместо 23 идентификаторов стали передаваться несколько сотен, то очевидно, что скорость выполнения запросов упадёт. Возможно, понадобится создание нового индекса, который учитывал бы такой вариант выборки данных. Такое может случиться после очередного релиза смежной системы, читающей данные из вашего сервиса.

Чтобы идентифицировать подобную ситуацию, достаточно вытащить из логов аналогичный запрос, который был несколько дней назад, и сравнить его с таким же запросом сейчас. Обычно разницу в драматическом количестве параметров запроса видно сразу.Если вы нашли такую ситуацию переходим к шагу 26. Иначе к шагу 23.

Шаг 23. Изменилось ли количество и размер временных файлов?

В процессе выполнения запроса база данных генерирует временные данные, используемые для получения данных в нужном формате. Обычно для этого используется оперативная память, объём которой определяется параметром Work_Mem, настраиваемым в конфигурации базы данных. Если этой памяти недостаточно, база данных может создавать временный файлы, в которые записываются промежуточные данные для выполнения запроса. Для определения такой ситуации мы используем метрику по количеству временных файлов от всё того же Zabbix.

Если вы увидели, что количество временных файлов, создаваемых базой данных, резко выросло, то переходим к шагу 25. Иначе к шагу 24.

Шаг 24. Пересоздать индекс

Пересоздаём индекс. Сделать это можно командой REINDEX (подробнее можно почитать тут: https://postgrespro.ru/docs/postgrespro/9.5/sql-reindex).

Переходим к шагу 31.

Шаг 25. Увеличить размер Work_mem, либо оптимизировать запрос на стороне приложения

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

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

Переходим к шагу 31.

Шаг 26. Создать новый индекс с учётом новых входных параметров

Быстрее всего в этой ситуации будет создание нового индекса (возможно, составного), который облегчил бы выборку данных с учётом новых входных параметров запроса. Если это возможно, то переходим к шагу 27. Иначе к шагу 28.

Шаг 27. Создать новый индекс с учётом новых входных параметров запроса

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

Шаг 28. Исправление самого запроса на стороне приложения, исключение новых параметров

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

Шаг 29. Создать новый индекс

В том случае, если по результатам EXPLAIN ANALYZE пришло понимание, что базе нужен новый индекс, то проще всего его досоздать. Это можно сделать командой CREATE INDEX с указанием параметра CONCURRENTLYдля неблокирующего построения (подробнее можно почитать тут: https://postgrespro.ru/docs/postgresql/11/sql-createindex). Далее переходим к шагу 31.

Шаг 30. Проверка влияния дополнительных факторов (взаимодействие с жёстким диском, сторонние процессы на сервере, работа других БД)

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

  • работа жёстких дисков/систем хранения данных;

  • нагруженные запросы на другие базы данных, находящиеся на том же сервере что и ваша;

  • сторонние процессы, выполняемые на сервере БД (например, работа антивируса);

  • создание репликационных баз данных, в процессе чего происходит создание слепка данных с вашего мастера;

  • сетевые проблемы и потери при взаимодействии приложения и базы данных.

Если таковые факторы найдены, то постарайтесь их устранить и переходите к шагу 31.

Шаг 31. Ситуация исправилась?

Ситуация стабилизировалась и проблема решена? Мы молодцы! В противном случае пора собирать всю королевскую рать и обращаться к DB-администратору.

Шаг 32. В БД много блокировок и запросов в ожидании?

По аналогии с Шагом 8 - проверяем динамику количества запросов к БД в статусе "IDLE IN TRANSACTION". Мы используем механизм периодического замера количества запросов в этом статусе, с выводом соответствующего графика в Grafana. Подчеркну, что блокировки нормальное явление, они периодически будут возникать. Но вот если у вас произошло резкое увеличение количества блокировок, то переходите к шагу 37. Если же диагностировать увеличение количества блокировок и запросов в ожидании не удалось, то переходите к шагу 33.

Шаг 33. Проверка логов PG Bouncer и логов приложений при подключении к PG Bouncer

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

Шаг 34. Есть проблемы с PGBouncer?

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

Шаг 35. Исправление проблемы с работой PGBouncer/подключение приложения напрямую к БД, в обход PGBouncer

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

Это временная мера для быстрого восстановления доступности сервиса. Это не решение проблемы!

Переходим к шагу 31.

Шаг 36. Требуется консультация аналитика

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

Шаг 37. Было ли недавнее изменение схемы данных БД?

Ситуация, когда неожиданно, без объявления войны в базе данных растёт аномальное количество блокировок и запросов в ожидании, крайне редка. Чаще всего это следствие релиза или миграции с изменением схемы данных БД. Если такое было в последнее время, то переходим к шагу 45. Если изменений в схеме данных не было, то переходим к шагу 38.

Шаг 38. Проверка журналов БД (или PG_STAT_ACTIVITY) для выявления запроса, вызвавшего блокировку

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

Шаг 39. Выявление сервисаинициатора запроса, вызвавшего блокировку

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

Шаг 40. Сервис, инициировавший запрос, работает штатно?

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

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

Шаг 41. Были ли недавно релизы сервиса, инициировавшего блокирующий запрос?

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

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

Шаг 42. Диагностика и починка сервиса, инициировавшего блокирующие запросы

Мы определили, что запросы, вызывающие блокировки, генерирует сервис, в котором на данный момент также возникли проблемы в работе. Гипотеза состоит в том, что ошибки в работе сервиса приводят и к ошибкам в работе БД. Целесообразно будет откатить последний релиз (если он был) или сосредоточиться на починке этого сервиса. Если мы приведем сервис в нормальное состояние, то, вероятно, и база данных начнёт работать в соответствии с ожиданиями.

После починки сервиса переходим к шагу 31.

Шаг 43. Откат последнего релиза

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

Шаг 44. Требуется консультация аналитика

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

Шаг 45. Откатить миграцию возможно?

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

Шаг 46. Откатить миграцию

Необходимо откатить последнюю миграцию или миграции. Проще всего это сделать через утилиту Alembic, если вы её применяете. Затем переходим к шагу 31.

Шаг 47. Ожидать завершения миграции

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

Заключение

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

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

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru