Использование deb-пакетов для дистрибъюции кода

В этой статье я хочу рассказать, о том, как можно внедрить систему сборки deb-пакетов для некоторого абстрактного проекта. Плюсов в распространении и развёртывании ПО на основе пакетов несколько:
  • Атомарность пакета (представление продукта в виде одного файла);
  • Наличие скриптов пред/пост установки/удаления ПО;
  • Возможно указания зависимостей для ПО.
Кроме того, при развёртывании ПО на основе пакетов, а не на основе SVN, вы гарантировано защищены от проблем с .svn-папками.

Структура проекта


И так, рассмотрим некоторый web-проект my-app, находящийся под контролем SVN, со следующей файловой структурой:
/|-config
 | |-parameters.ini
 |-htdocs
 | |-index.php
 |-libs
 |-templates


Структура пакета


Для сборки пакетов нам потребуется некоторый набор файлов и скриптов, который мы разместим в папке .package, которую в свою очередь добавим в корень проекта. Структура этой папки будет выглядеть следующим образом:
/|-.structure
 | |-DEBIAN
 | | |-conffiles
 | | |-control
 | | |-postinst
 | | |-postrm
 | | |-preinst
 | | |-prerm
 | | |-templates
 | |-etc
 | |-var
 | | |-log
 | | |-www
 |-package.xml
 |-package.properties

По сути каталог .structure и есть наш будущий пакет. Подкаталог DEBIAN содержит служебные файлы, о которых будет сказано ниже. Все остальные каталоги полностью повторяют иерархию каталогов файловой системы ОС Debian Linux. Файлы package.xml и package.properties содержат сценарии и настройки для создания пакетов с помощью утилиты Apache Ant. Но обо всём по порядку.

Каталог Debian


Каталог DEBIAN содержит файлы настроек проекта и скрипты пред/пост установки/удаления.

Файл conffiles содержит список конфигурационных файлов, которые не должны быть перезаписаны во время установки:
/etc/my-app/parameters.ini

В нашем случае здесь будет указан файл настроек проекта.

Файл control содержит общую информацию о пакете:
Package: my-app
Version: {{{VERSION}}}
Section: user
Priority: optional
Architecture: all
Installed-Size: 0
Maintainer: Mikhail Krestjaninoff <mikhail.krestjaninoff@gmail.com>
Depends: nginx, php5-common (>= 5.2), php5-cli
Description: My application

Отдельного внимания в этом файле заслуживают пункты Version и Depends. Мы специально указываем в качестве версии пакета константу {{{VERSION}}}, так как в дальнейшем при сборке пакета заменим её актуальным значением. В списке зависимостей мы указали только nginx и php, однако, если Ваш проект использует какие-либо дополнительные пакеты (например модули php), Вы можете явно перечислить их, уменьшив тем самым риски неверной работы проекта при выкладке на боевой сервер.

Файл preinst содержит сценарий предустановки:
#!/bin/sh

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

Файл postinst содержит сценарий постустановки. В данном случае сценарий будет выставлять права, настраивать конфиг с помощью утилиты debconf и создавать директории для временных файлов:
#!/bin/sh

if [ configure = "$1" ]; then

    # Set permissions
    chown -R www-data:www-data /etc/my-app/
    chmod -R 0664 /etc/my-app/

    chown -R www-data:www-data /var/www/my-app/
    chmod -R 0664 /var/www/my-app/

    chown -R www-data:www-data /var/log/my-app/
    chmod -R 0664 /var/log/my-app/


    # Set up configuration file
    . /usr/share/debconf/confmodule

    db_input critical db/dsn || true
    db_go
    db_fset db/dsn seen false || true
    db_get db/dsn || true
    DSN=$RET

    DSN=`echo "$DSN" | sed 's/\//\\\\\//g'`;
    sed -i s/{{DSN}}/$DSN/ /etc/my-app/parameters.ini


    # Create directories for temporary files
    CACHE_DIR="/var/www/my-app/templates/cache"
    COMPILED_DIR="/var/www/my-app/templates/compiled"

    if [ ! -d $CACHE_DIR ]
    then
        mkdir -p $CACHE_DIR
        chown -R www-data:www-data $CACHE_DIR
        chmod -R 0664 $CACHE_DIR
    fi

    if [ ! -d $COMPILED_DIR ]
    then
        mkdir -p $COMPILED_DIR
        chown -R www-data:www-data $COMPILED_DIR
        chmod -R 0664 $COMPILED_DIR
    fi

fi

При необходимости в этот файл так же могут быть добавлены сценарии для создания БД проекта. С обновлением БД задача обстоит несколько сложнее. Однако, можно попробовать частично разрешить её хранением в проекте 2-х sql-файлов: с полным набором команд для воспроизведения структуры / данных (первичная установка) и изменениями относительно предыдущего релиза (для обновления).

Файл prerm содержит сценарий предудаления. В данном случае сценарий будет удалять каталоги для временных файлов:
#!/bin/sh

if [ remove == "$1" -o purge == "$1" ];
then
    # Remove directories for temporary files
    CACHE_DIR="/var/www/my-app/templates/cache"
    if [ -e $CACHE_DIR ]; then
        rm -rf $CACHE_DIR
    fi

    COMPILED_DIR="/var/www/my-app/templates/compiled"
    if [ -e $COMPILED_DIR ]; then
        rm -rf $COMPILED_DIR
    fi
fi


Файл templates содержит шаблоны полей для утилиты debconf:
Template: db/dsn
Type: string
Default: postgres://user@passwd:localhost/my-app
Description: Database Source Name. Example: postgres://user@passwd:localhost/my-app

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

Скрипты сборки пакета


Теперь, что бы создать полноценный пакет на основе нашего проекта, нам остаётся заполнить файловую структуру пакета (файловую систему с корнем в каталоге .structure данными). Для этого придётся создать небольшой набор сценариев, копирующих нужные данные из файловой системы проекта в файловую систему пакета. Я использовал для этой цели Apache Ant, в результате чего у меня получились файлы package.xml и package.properties.

Файл package.properties содержит в себе настройки для сборки пакета:
package.name=my-app
package.version=1.0.0

Основной сценарий сборки содержится в файле package.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project basedir=".." default="prepare" name="package-builder">

  <property file="./.package/package.properties" />
  <property name="location" value="."/>
  <property name="package.structure" value="${location}/.package/.structure"/>
  <property name="package.source" value="${location}/.package/source"/>
  <property name="package.target" value="${location}/.package/target"/>

  <!-- Init project -->
  <target name="init">
    <mkdir dir="${package.source}" />
    <mkdir dir="${package.target}" />
  </target>

  <!-- Clean project -->
  <target name="clean">
    <delete dir="${package.source}" />
    <delete dir="${package.target}" />
  </target>

  <!-- Prepare project files for package -->
  <target depends="init" name="prepare">

    <copy toDir="${package.source}">
      <fileset dir="${package.structure}">
        <include name="**/*" />
        <exclude name=".svn" />
    </fileset>
    </copy>

    <mkdir dir="${package.source}/etc/${package.name}" />
    <copy toDir="${package.source}/etc/${package.name}">
      <fileset dir="config">
        <include name="**/*" />
        <exclude name=".svn" />
      </fileset>
    </copy>

    <mkdir dir="${package.source}/var/www/${package.name}/htdocs" />
    <copy toDir="${package.source}/var/www/${package.name}/htdocs">
      <fileset dir="htdocs">
        <include name="**/*" />
        <exclude name=".svn" />
      </fileset>
    </copy>

  <mkdir dir="${package.source}/var/www/${package.name}/libs" />
    <copy toDir="${package.source}/var/www/${package.name}/libs">
      <fileset dir="libs">
        <include name="**/*" />
        <exclude name=".svn" />
      </fileset>
    </copy>

    <mkdir dir="${package.source}/var/www/${package.name}/templates" />
    <copy toDir="${package.source}/var/www/${package.name}/templates">
      <fileset dir="templates">
        <include name="**/*" />
        <exclude name=".svn" />
        <exclude name="cache" />
        <exclude name="compiled" />
      </fileset>
    </copy>

  </target>

  <!-- Build packege -->
  <target depends="prepare" name="build">

    <!-- Change owner to root -->
    <exec executable="fakeroot" dir="${package.source}">
      <arg line="chown -R root:root ." />
    </exec>

    <!-- Allow execution for installation scripts -->
    <exec executable="fakeroot" dir="${package.source}/DEBIAN">
      <arg line="chmod 0755 preinst postinst prerm postrm" />
    </exec>

    <!-- Set package version -->
    <exec executable="sed">
      <arg line="-i s/{{{VERSION}}}/${package.version}/ ${package.source}/DEBIAN/control" />
    </exec>

    <!-- Build package -->
    <exec executable="dpkg-deb">
      <arg line="--build ${package.source} ${package.target}/${package.name}_${package.version}_all.deb" />
    </exec>

  </target>

</project>


* This source code was highlighted with Source Code Highlighter.

Сборка пакета


И так, создание каталога .package завершено. Теперь мы можем закоммитить его в SVN вместе с остальным проектом. Когда же нам понадобится собрать пакет (например, отдать релиз/метку для тестирования или выкладки в бой), будет достаточно перейти в каталог .package и выполнить команду ant -f package.xml build, которая создаст для нас в каталоге .package/target новый deb-пакет, готовый к использованию!
+23
30 ноября 2009, 06:31
48

комментарии (25)

+5
barbuza #
при использовании git / mercurial мы тоже защищены от «проблемы с .svn папками». это не проблема, а кривые руки.
0
krestjaninoff #
Речь не том, что лучше: SVN или GIT/Mercurial. Я лишь хочу сказать, что использование пакетов отчасти уменьшает риски, связанные с кривыми руками.
+2
mou #
Ant автоматом делает исключение служебных файлов VCS. Опция exclude лишняя.
0
RZimin #
Отлично! Надо будет попробовать, вот только зачем .ini :(
+2
tswr #
Немного здоровой критики:
Зачем после установки менять права? Разве не достаточно класть в архив файлы с нужными правами?
lintian — очень полезная утилита для проверки deb пакетов, которая расскажет, какие важные файлы вы забыли положить в пакет и какие поля забыли указать в конфигурационых файлах.
Вместо или рядом с ссылкой на opennet хотелось бы видеть ссылку на оригинальную документацию http://www.debian.org/doc/debian-policy/.
0
krestjaninoff #
Спасибо. Ссылку добавил. По части изменения прав — тоже мой недочёт, действительно логичней было бы поместить их установку в ant'овский конфиг.
0
cabeza #
Мы раскладываем ключи дежурных из отдела мониторинга вместе с конфигами sudo как раз пакуя их в rpm-ку.
К сожалению, это всё от лени плотно взяться за cfengine/puppet/etc…
0
Maklaut #
Основной сценарий сборки содержится в файле packege.xml
0
eeexception #
Супер) То, что доктор прописал, особенно для java проектов)
А тоже самое для rpm-based дистрибутивов сделать можно?
+1
krestjaninoff #
Думаю, да.
+1
Deepwalker #
Одного не понял — зачем тут ant? Ну то есть ничего против не имею конечно, но обычный Makefile, который debian/rules вполне достаточен, а главное для редактирования приятнее: )

Потом — не вижу вызовов debhelper утилит — все, связанное с питоном к примеру, будете руками делать? Все папки пустые руками? Зря в общем изобретаете, сугубо на мой взгляд: )

Если не прав — пожалуйста укажите на ошибки, может быть я просто недопонял статью.
0
krestjaninoff #
Ant мне исторически ближе, а с debhelper сталкиваться не приходилось. Возможно их использование здесь действительно было бы удачней.
0
Deepwalker #
В debhelper стандартный набор утилит, которые по списку создадут все директории, скопируют файлы по списку, завернут в deb автоматически по debian/control.
0
eeexception #
Если не ошибаюсь, то в текущем вариант deb пакет можно собрать и из под windows машины… А про вариант с debhelper можно подробнее услышать?
0
Deepwalker #
Можно поподробнее почитать: )

А про сборку пакетов на win-машине, ну кому оно надо? У нас в корне проекта лежит директория debian со стандартным debian/rules, с помощью которой все собирается обычным fakeroot debian/rules binary. Так вот — нет linux на машине разработчика? Соберите пакеты на build-сервере, и не надо заморачиваться на предмет «а вдруг кому то захочется родить ежика против шерсти??».
–2
eeexception #
А если собирать и rpm, и deb одновременно?) Для каждого варианта держать свой дистрибутив?)
0
Deepwalker #
Согласитесь, rpm под debian собрать это элементарно. Наверное что то вроде aptitude install rpm понадобится.
–1
eeexception #
Можно) Но не очень нравится вариант с установкой всего необходимого) С антом может путь и не совсем true, зато более изящней.
0
Deepwalker #
Ничего изящного в нем нет, потому что те утилиты, что существуют для создания пакетов в дистрибутивах много лет уже затачиваются под эти цели. И все что в них есть, вы будете в ant делать руками.

Если вам хочется «красиво», то оно таковым будет до более менее комплексной конфигурации, а потом начнется лес подпорок.

Зачастую в проекте собираются десятки пакетов, с помощью debhelper это делается легко, с помощью любой другой системы вы будете повторять уже существующее.

Я не понимаю, чем плох make в данном случае. Make очень изящен, гораздо изящнее многословного xml.
0
eeexception #
Все дело в привычке) И опять же для зоопарка машин разработчиков, когда у каждого своя операционная система от Windows, различных Linux дистрибутивов до Mac, ant гарантирует одинаковость сборки всего продукта на любой из приведенных систем.
0
Deepwalker #
Сборка != пакетирование под debian.
+1
CheatEx #
Не совсем уловил как решается проблема наличия dpkg-deb и что мешает аналогично решить проблему с dh_*?
+1
CheatEx #
www.debian.org/doc/debian-policy — кури — не хочу!
0
hydralien #
Использовали схему с пакетами для релизов своих проектов, но отказались в пользу тагированного деплоймента из меркуриала. Потому как второе было быстрее в случае небольших срочных патчей и легче разделялось на sandbox/prod deployment. Но вообще схема с пакетами во многих случаях хороша, да — особенно в плане автоматических зависимостей.
0
mocksoul #
Йех, разработчики на интерпретируемых языках ну совсем обленились =).
Имхо, отсутствие системы билдинга даже helloworld.(py|php|pl|whatever) проекта — глупость, которая замедляет и обостряет production-развертывание.

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