Дистрибутив AIRA

Введение в Nix и дистрибутив AIRA.
26.09.2017, aira

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

Nixpkgs

$ nix-env -i hello 

Nix - это пакетный менеждер. В своем подходе к управлению пакетами он полностью уходит от практики копирования файлов пакета в корневую файловую систему. Каждый пакет после установки на целевую систему хранится изолированно и не может быть перезаписан. Целостность файлов подтверждается хеш-суммой.

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

$ nix-env --upgrade hello 
$ nix-env --rollback

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

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

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

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

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

$ git clone https://github.com/NixOS/nixpkgs && cd nixpkgs
$ nix-build -A hello

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

Airalab channels

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

$ nix-channel --add https://hydra.aira.life/project/aira/channel/latest aira
$ nix-channel --update

Каналы позволяют устанавливать пакеты из репозиториев, отличных от корневого nixpkgs. В канале Airalab, например, присутсвуют последнии версии модулей AIRA, parity и ROS.

$ nix-env -i parity

NixOS GNU/Linux

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

{ config, pkgs, ...}:

{ boot.loader.grub.device = "/dev/sda";

  fileSystems."/".device = "/dev/sda1";

  services.openssh.enable = true;
}

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

Единая конфигурация реализована в дистрибутиве NixOS GNU/Linux. Под капотом - пакетный менеджер nix и дополнительный набор скриптов для управления загрузкой и сервисами ОС. AIRA использует дистрибутив Linux на основе NixOS для запуска на железе или в виртуальной машине. Nix дает атомарность и надежность обновлений, что очень важно при автономной работе. А декларативная формальная конфигурация оптимальна для автоматического анализа и синтеза поколений AIRA.

{ config, pkgs, ... }:       

{ boot.loader.grub.device = "/dev/sda";     
  fileSystems."/".label = "nixos";

  services = {
    openssh.enable = true;

    parity.enable = true;
    parity.chain = "kovan";

    railway-game.enable = true;
  };
}

Выше приведен пример конфигурации AIRA v0.10. Здесь видно, что в качестве сервиса активируется Ethereum нода parity, вибрается тестовая сеть kovan. Сервис railway-game взаимодействует с контроллером Z21 и Ethereum нодой, следит за рынками метрик.

Hydra CI

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

Проект Nix предлагает свое решение для CI. До этого я работал в основном с Travis, но так как для AIRA мы используем NixOS, попробуем разобраться что к чему.

В основе Hydra лежит пакетный менеджер Nix. Это дает нативную поддержку большого числа его возможностей: каналы, бинарный кеш пакетов, распределенная сборка, Nix-expressions и др. Управление проектом разбивается на следующие части:

  1. Project - это наиболее общее описание задачи, проект позволяет группировать jobset’ы и формировать релизы.
  2. Jobset - это набор общих задач на исполнение, например, формирование отладочной и релизной сборки, сборка документации, подготовка инсталлятора. Jobset описывается функцией на nix.
  3. Job - это конкретная задача на исполнение, например, собрать ISO образ продукта.
  4. Build step - это минимальный отделимый этап выполнения задачи (сборка пакета).
{ nixpkgs }:

rec {
  hello = nixpkgs.hello;
}

Пример jobset приведен выше. Здесь входом является nix-канал с программным обеспечением, а выходом одна задача на пакет hello. Этот jobset считается успешно выполненым, если выход hello успешно удается получить (собрать).

Каждый jobset периодически обновляется, в hydra это называется Evaluation. Это значит, что на вход jobset подставляются актуальные значения, в примере это git clone по каналу, который указан во входах jobset. Обновленный jobset подается в очередь сборщика и каждая работа выполняется или завершается с ошибкой. В hydra прослеживается тесная интеграция с Nix, поддерживаются такие возможности как загрузка пакета с зависимостями (Nix closure), возможно повторить сборку локально.

$ bash <(curl https://hydra.aira.life/build/35/reproduce)

Заключение

Функциональный подход в конфигурации системы и управления пакетами выглядит привлекательно и при этом оказывается проще и надежнее, чем императивная альтернатива. Простой пример на parity: parity.nix vs Dockerfile. Этот пример показывает насколько сильно подход к решению задачи изменяет саму задачу и ее финальный результат.