В Ubuntu репозитории – это специальные сервера-хранилища для приложений. Если Вы разрабатываете коммерческое приложение и запускаете его в Ubuntu, то логично положить его в репозиторий. А потом управлять этим приложением так же, как и обычными системными приложениями. Для этого нужно поднять в локальной сети или облаке apache, настроить логин и пароль, не забывать его обновлять… Но что если есть другой способ?
Облачные хранилища
С помощью apt-transport-s3 можно превратить bucket в приватный apt репозиторий. Однако, у этого способа появились следующие недостатки:
- Некоторые адреса Амазона заблокированы в России
- Данные находятся в Европе, поэтому может быть медленно.
Какие же есть альтернативы?
Самой известной считается Openstack Swift. Swift (OpenStack Object Storage) — это полностью распределенное «безграничное» хранилище, которое характеризуется отказоустойчивостью и высокой надежностью. Создано как конкурент Amazon S3. Его преимущества:
- В России как минимум 2 провайдера предоставляют Swift как сервис: Clodo и Selectel
- Данные находятся в России
- Если Вы достаточно большие, то можете поднять его у себя
- Все плюсы облачного хранилища: оплата за непосредственно используемые ресурсы, распределенное хранение, отказоустойчивость, 24/7.
Из недостатков можно выделить лишь полное отсутствие интеграции с Ubuntu. Это сложно назвать недостатком, если Вы программист. Поэтому я написал интеграцию сам: apt-transport-swift.
Разработка
Для начала нужно немного погрузиться в то, как apt взаимодействует в репозиториями. Для того, чтобы получить информацию из репозитория, apt:
- находит соответствующий метод из списка установленных. Все они лежат в папке: /usr/lib/apt/methods/
- отправляет ему необходимые команды согласно протоколу
По умолчанию доступно достаточно много методов: http, ftp, cdrom, file, ssh и тд. Все они работают следующим образом:
- каждый метод — это отдельная программа
- на вход apt отправляет через stdin команды для выполнения
- на выходе через stdout метод должен вернуть результат работы
Команды и ответы передаются в текстовом виде очень похожим на http. Например:
100 Capabilities
Version: 1.2
Pipeline: true
Send-Config: true
Эту команду отправляет метод, чтобы получить конфигурацию apt.conf:
600 URI Acquire
URI: swift://container/dists/stretch/InRelease
Filename: dists_stretch_InRelease
Expected-SHA1: 123
Last-Modified: Wed, 23 May 2018 14:13:16 GMT
Эту команду отправляет apt, когда необходимо скачать файл. Когда метод закончил скачивание, он возвращает:
201 URI Done
URI: swift://container/dists/stretch/InRelease
Filename: dists_stretch_InRelease
Expected-SHA1: 123
Size: 762361
Last-Modified: Wed, 23 May 2018 14:13:16 GMT
Поскольку все методы написаны на C++, я решил тоже написать на C++. После двух недель, мои глаза стали вытекать и я решил начать с чего-нибудь попроще. С. Программа вылядела достаточно простой, но результат не удовлетворял моих высоких стандартов качества. Еще две недели пришлось потратить на изучение утечек памяти, инструментов тестирования и настройки билда в Travis.
Всё вместе
В результате я получил следующую схему для Java проектов:
Сброка .deb артефакта с помощью deb-maven-plugin. pom.xml:
<plugins> ... <plugin> <groupId>com.aerse.maven</groupId> <artifactId>deb-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <id>package</id> <phase>package</phase> <goals> <goal>package</goal> </goals> </execution> </executions> <configuration> <unixUserId>ubuntu</unixUserId> <unixGroupId>ubuntu</unixGroupId> <osDependencies> <openjdk-7-jdk></openjdk-7-jdk> <nginx></nginx> </osDependencies> <javaServiceWrapper>true</javaServiceWrapper> <fileSets> <fileSet> \<source>${basedir}/src/main/deb</source> <target>/</target> </fileSet> </fileSets> </configuration> </plugin> ... </plugins>
Дистрибьюция артефакта в apt репозиторий. Плагин проинициализирует репозиторий, если он изначально пустой. pom.xml:
<plugins> ... <plugin> <groupId>com.aerse.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.9</version> <executions> <execution> <id>deploy</id> <goals> <goal>deploy</goal> </goals> </execution> </executions> <configuration> <component>main</component> <codename>myrepo</codename> <sign>true</sign> <keyname>name</keyname> <passphraseServerId>gpg.passphrase</passphraseServerId> </configuration> </plugin> ... </plugins>
settings.xml:
<settings> ... <servers> ... <server> <id>gpg.passphrase</id> <passphrase>passphrase</passphrase> </server> ... </servers> ... </settings>
В maven есть такое понятие как wagon. Это особая точка расширения, позволяющая добавить новый протокол. С помощью swift-maven я добавил поддержку протокола swift. pom.xml:
<project> ... <distributionManagement> <repository> <id>private-repo</id> <url>swift://api.selcdn.ru/v3</url> </repository> </distributionManagement> ... <build> ... <extensions> ... <extension> <groupId>com.aerse</groupId> <artifactId>swift-maven</artifactId> <version>1.1</version> </extension> ... </extensions> ... </build> </project>
settings.xml:
<settings> ... <servers> ... <server> <id>private-repo</id> <username>username</username> <password>password</password> </server> ... </servers> ... </settings>
- В качестве облачного хранения данных я выбрал Selectel. Они поддерживают API Swift v3.
- apt-transport-swift реализует swift протокол для apt.
#: cat /etc/apt/sources.list.d/privaterepo.list
deb swift://container myrepo main
И конфигурация:
#: cat /etc/apt/apt.conf.d/80privaterepo
Swift {
Container0 {
Name "container";
URL "https://api.selcdn.ru";
Username "username";
Password "password";
};
};
Результат
Хочется отметить, что схема не добавляет никаких новых сущностей в уже существующие инструменты. Всё реализовано в виде плагинов и должно работать независимо друг от друга. Например, с помощью apt-maven-plugin можно деплоить в S3.