tmux

Dec. 3rd, 2022 10:36 am
amarao: (Default)
Инвестирую кусок времени в tmux, а именно, в его детачед режим. Фактически, это такой извращённый (или нет?) метод запускать программы в параллель, так, чтобы их выводы не перемешивались.

Я очень, очень, очень устал бороться с всеми CI мира за matrix'ы и вытекающее из них. У меня сейчас есть гипотеза, что отказ от matrix в пользу банального tmux'а, который запускает N сессий в параллель, сохраняет их вывод в файл, а потом дампает их на экран в разумном порядке, возможно, удобнее, чем борьба с птичьим языком CI.

Потенциально, управление tmux'ом может быть даже не на баше, а на чём-то более дружественном к программисту. В этой ситуации можно написа... wait

https://github.com/tmux-python/libtmux

Ну вот, уже всё написано.

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

Возможно, я могу просто послать C-d после capture. Если оно правильно отрабатывает порядок выполнения, то Ctrl-D будет отрабатывать сразу же после завершения программы (как Ctrl-D в терминале).

Почему tmux, а не просто subprocess.run? Возможность отлаживать это локально, включая возможность переключаться между сессиями.

... О, я осознал проблему, которой надо заняться. А как быть с кодом возврата? Я хочу знать кто из них упал, а кто нет.
amarao: (Default)
Чем больше я вожусь с тяжёлыми и сложными проектами, тем больше я убеждаюсь, что дисциплина принципов - самая важная из всех. Принципы, положенные в основу проектов, либо соблюдаются, либо нет. Если они не соблюдаются, то оппортунистические принципы становятся из опоры обузой. Проект без принципов превращается в local scoped magic, когда изменения тем сложнее, чем больше подсистем задевают. Чем более глобальны измненения, тем важнее соблюдение принципов. Вторая проблема, которая наступает в проекте без соблюдения принципов, это расширение blast radius без возможности его ограничить или понять зону поражения. Любая ошибка вызывает труднопредсказуемые последствия.

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

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

Решением этой проблемы может быть только дисциплина: если один принцип меняется на другой (во имя нужного изменения), то этот принцип должен быть sound (сочетаться с остальными), а все отклонения/нарушения должны быть купированы или переделаны.

Чем толще проект, тем сложнее становится эта операция; именно тут находится самое большое зло больших проектов - они настолько большие, что никто не может изменить принцип их работы за разумное время. С этого момента проект "костенеет" (живёт только старыми принципами), либо превращается в спагетти (теряет дисциплину).

Потенциальным решением является наличие принципа минимизации размера "доменов взаимодействия" с очень жёсткими контрактными интерфейсами (привет, микросервисы); но это тоже принцип, и его нужно поддерживать дисциплиной...

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

слово

Mar. 25th, 2022 10:53 pm
amarao: (Default)
Сегодня родилось слово в отношении софта - "админолюбивый". Есть софт админолюбивый, а есть админофобный. Деление определяется количеством эргономики или её отсутствием при администрировании. Софт при этом может решать какую-то недадминскую задачу, но при этом всё равно иметь или не иметь эргономику в администрировании.
amarao: (Default)
Эта проблема есть и у админов, но у них она менее выражена, потому что многие процессы ручные. В автоматическом режиме оно становится более резким.

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

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

Простая схема выглядит так:
* Деплой код не трогает файл базы.
* Деплой код настраивает backup_url для бэкапа по таймеру. Оба этих этапа работают одинаково в продакшене и на стейджингах, включая эфимерную среду для проверки кода деплоя.
* Отдельный workflow (или другой CI процесс) регулярно поднимает эфимерную среду, которая берёт backup_url из продакшена и восстанавливает данные приложения. Приложение уже установлено (обычный делой код). После чего мы тестируем приложение на актуальность бэкапа.

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


Наивный код деплоя в случае аварии делает:
1. Деплой приложения.
2. Деплой бэкап-сервиса с backup_url.
3. Код restore backup, запущенный для production-среды.

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

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

Соответственно, будет общая кодовая база между рекавери и тестовыми рекавери, состоящая из deploy и recovery. Единственное их различие (помимо серверов) - в том, что в настоящем recovery backup_url выставлен в пустоту.

... Но мы не можем иметь код в продакшене без тестов, особенно, код такой важности.

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

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

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

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

---

Уточнённая схема:

deployment - делает пустое приложение и backup-сервис с ненастроенным backup_url. Вполне допустимо, что тесты такое обнаруживают и орут.
recovery - восстанавливает из restore_url.
enable_backups - прописывает backup_url.
test_recovery - поднимает deployment на тестовой среде, делает туда recovery делает тесты приложения на правильность восстановления.
staging (который тестирует код) дополнительно, запускает enable_backups с ephimerial локацией бэкапа.


Провера выполнения:
* deployment - выполняется всегда и на продакшене, и на стейджинге
* recovery - выполняется на стейджинге в полном объёме во время test_recovery.
* enable_backups - выполняется на стейджинге.

Взаимные отношения:

prod:
* backup_url == recovery_url
staging:
* backup_url = (ephimerial)
* recovery_url = production_backup_url

По локациям:

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

Если мы забудем сделать enable_backup в продакшене, то сломается test_recovery из-за старого бэкапа, плюс, наверное, будут орать тесты.

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

Кажется, решение есть.
amarao: (Default)

А вот я хочу продолжить думать умное про ансибл, ci/cd и прочую рабочую фигню между гитом и сервером.

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

Теперь посмотрим на "best practice для Ansible". Всё, что происходит - это сайд-эффекты. Некоторые из них предсказуемы, некоторые условно предсказуемы, некоторые не совсем предсказуемы. Часть сайд-эффектов имеют в качестве зависимости внешний мир (т.е. могут фейлиться по каким-то внешним причинам не связанным с нашим кодом).

В программировании существует модель описания этого, посредством интерейсов. Мол, у нас неконтролируемое "нечто" снаружи, консистентность внутри, и есть код, задача которого описать "нечто снаружи" в терминах, которые устраивают модель внутри. Если мир расходится с моделью, это это EnvironmentError, который мы можем пытаться описывать в терминах нашей модели (и может, даже, исправлять).

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

И вот тут вот появляется парадигма, условно, админская. Девопс, ансибл, iaac, называйте как хотите. Это форма компьютерного экзестенциализима, которая принимает систему "as is", без чрезмерной строгости. Модель стремится к уменьшению и упрощению, и чем проще модель, тем меньше она отличается от реальности. От модели оставляют только то, что минимально необходимо для решения задачи. Интерпретация происходящего предельно близка к утиной типизации (если у нас получилось сделать то, что нужно, то это хорошо, а что это именно - уже не совсем важно).

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

Почему это упрощение неизбежно?

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

Сначала мы общаемся с пакетным менеджером. Модель пактного менеджера - это граф зависимостей, который разрешается в непротиворечивую комбинацию пакетов, либо оставляет систему в broken state. Взаимодействие с пакетным менеджером в общем случае должно предусматривать графы как структуры данных. Это очень много. Пакетный менеджер так же зависит от системы доверия ключам, которая в свою очередь зависит от даты, установленных ключей и чужих серверов. Ещё у нас могут быть transient проблемы: например, блокировка доступа к пакетному менеджеру из-за параллельного процесса (cloud-init, unattended updates, etc).

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

Где-то тут же у нас есть модель файловой системы, поскольку образ будущей VM куда-то положен в файловой системе.

Потом мы погружаемся в мир XML для libvirt'а с одной стороны (надо правильно расставлять атрибуты и ноды внутри xml tree), и в мир устройства виртуальных машин с другой.

Виртуальные машины в свою очередь имеют несколько domain specific областей - концепцию "машины", внутри которой есть PCI-шина (ещё одно дерево с крайне запутанной и сложной логикой), сети (которая включает в себя целые пласты знаний про бриджи, раутинг, macvtap, pci passthrough, nat, etc).

Потом мы оказываемся в опять с пакетным менеджером.

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

Заметим, это всё находится поверх сетевой абстракции хоста, и транспортной абстракции ssh, через (очень хорошо работающий) слой абстрации по запуску питона через ssh внутри ansiballZ.

Это не сложно, это необъятно сложно. Любая попытка "сделать правильно" (т.е. формировать XML как осмысленное дерево, конструирование дерева PCI-устройств, поддержка полной модели прав пользователей postgres или графа зависимостей в apt) приведёт к одному или комбинации нескольких результатов: * Непозводительно медленная разработка (десятки тысяч строк кода), закончившаяся ничем. * Ошибки в описании предметной области, которые критичеки нарушают точность модели (не всё предусмотрено). * Перегрузка получившегося кода излишними понятиями, которые не нужны для достижения результата.

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

Какие альтернативы? Начать реализовывать правильно и не дореализовать (см список последствий выше). Или... Не реализовывать вообще, а использовать компьютерный экзестенцианализм в чистом виде. Что нас, на самом деле, и приводит к best practices Ансибла: * Список пакетов для установки с retries для защиты от локов. * Хардкоженный список элементов для image builder'а. * template из XML, в котором мы руками пишем львиную долю, заменяя минимальное количество элементов для интерполяции. * Минимальный по сложности код заведения пользователя в postgres, возможно, не обладающий максимальной точностью по правам (если нет такой потребности).

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

Что делать для повышения надёжности? Вместо уточнения и усложнения модели (что приведёт нас в трясину, описанную выше), мы просто стараемся покрыть тестами наши простые сценарии использования, зафиксировать зависимости.

Это всё работает не до конца, потому что IRL может оказываться отличным от наших ожиданий в момент тестов. В конце там ещё один комплект тестов, на этот раз инфраструктурных, которые проверяют, что сделанное одним образом соответствует ожиданиям, проверенным другим образом (поставили postgres apt'ом, проверили, что порт слушает).

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

Profile

amarao: (Default)
amarao

September 2025

S M T W T F S
 12345 6
78 910111213
14151617181920
21222324252627
282930    

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Sep. 16th, 2025 05:42 am
Powered by Dreamwidth Studios