社内利用のための deb パッケージング入門

こんにちは。@nojima です。 最近、社内のアーティファクトの deb 化を推進しています。 Building Microservices でも紹介されているように、deb は apt-get install でインストールできたり、依存関係を自動で管理できたりするため、単純な tar.gz を使うよりも利点が多いです。

Debian 界隈では dpkg-buildpackage などのツールを使って、ソースコードのビルドから deb の作成までを一貫して管理することが一般的です。 しかし、既にソースコードのビルドを行う仕組みを持っている場合、既存のビルド手順をそのまま使いつつ deb パッケージを作成したい場合もあります。 そこでこの記事では、ビルド済みのバイナリがあるときに、それを deb パッケージ化する方法を紹介します。

※ この記事で紹介する方法は主に個人や社内で利用する deb パッケージを作成することを念頭においています。Debian や Ubuntu の公式レポジトリで配布されるような deb を作成したい場合は dpkg-buildpackage を使うべきです。

TL;DR

  • インストールしたいファイルを一時ディレクトリに置いて
  • DEBIAN/control ファイルを書いて
  • fakeroot dpkg-deb --build を打つ!!

5分でできる deb 化入門

例題として direnv をさくっと deb 化してみます。

direnv の入手とビルド

まず、direnv をビルドしてバイナリを作ります。

$ git clone https://github.com/direnv/direnv
$ cd direnv
$ git checkout v2.8.0
$ make

ディレクトリ構造の作成

ここから deb パッケージを作るための準備をしていきます。 区別しやすくするために、上で direnv をビルドしたディレクトリを $ORIG と呼び、 これから作成するワーキングディレクトリを $WORK と呼ぶことにします。 $WORK は適当な位置に適当な名前で作成してください。

まず、$WORK の下にインストールすべきファイルを配置します。 ここでは、/usr/bin/direnv に direnv の実行バイナリをインストールしたいので、 $WORK/usr/bin/direnv にビルドしたバイナリを配置します。

$ mkdir -p $WORK/usr/bin
$ cp $ORIG/direnv $WORK/usr/bin/direnv

($WORK/path/to/something に置いたファイルは /path/to/something にインストールされるというルールになっています。単純ですね)

control ファイルの作成

次に、$WORK/DEBIAN/control に deb パッケージのメタデータを書きます。 control に書ける項目はいろいろありますが、とりあえずは以下の5つの項目を指定すれば大丈夫です。

Package: direnv
Maintainer: Yusuke Nojima <yusuke-nojima@example.com>
Architecture: amd64
Version: 2.8.0-1
Description: Environment switcher for the shell
 direnv hooks into the shell to load or unload environment variables
 depending on the current directory.

各項目の意味は以下の通りです。

項目 意味
Package パッケージ名です。
Maintainer deb パッケージのメンテナの名前とメールアドレスを指定します。
Architecture インストール可能なアーキテクチャを指定します。弊社の場合は大体 amd64 を指定しています。
Version バージョンです。基本的に <upstream_version>-<debian_version> という形式で付けます。詳しくは man deb-version を参照してください。
Description パッケージの説明を書きます。複数行フィールドになっていて、一行目に短い説明を書き、二行目以降に長い説明を書きます。二行目以降は行頭に空白を一つ入れる必要があります。

control ファイルはこれらの項目の他にもいろいろと項目を指定することができます。 詳しくは man deb-control を参照してください。

また、DEBIAN ディレクトリには control ファイルの他にもいろいろファイルを置くことができます。 いくつかのファイルについては後で述べます。

deb の作成

準備ができたので deb を作成します。 以下のコマンドを打ってください。 (fakeroot がないと言われた場合は sudo apt-get install fakeroot をしてください)

$ fakeroot dpkg-deb --build $WORK .

これでカレントディレクトリに direnv_2.8.0-1_amd64.deb が作成されるはずです。

dpkg-deb --build は deb パッケージを作成するためのコマンドで、一番目の引数でパッケージの中身となるディレクトリを指定し、二番目の引数でパッケージを出力するディレクトリを指定します。 パッケージの名前は <package>_<version>_<architecture>.deb というルールで自動的に付けてくれます。

また、dpkg-deb --build をすると $WORK ディレクトリ内のファイルの uid, gid がそのまま deb に含まれてしまいます。 普通、deb パッケージによってインストールされるファイルの uid, gid は 0 (root) であって欲しいので、これでは困ります。 これを防ぐために fakeroot によって見かけの uid, gid を 0 にしています。 基本的に、dpkg-deb --build をする場合は fakeroot の中で行うと思っておけばよいと思います。

インストールしてみる

早速インストールしてみましょう。

$ sudo dpkg -i direnv_2.8.0-1_amd64.deb
$ direnv --help

ついでにアンインストールもしてみましょう。

$ sudo dpkg -r direnv

以上で deb 化入門は終わりです。 以下は Tips 集です。

インストール/アンインストール時に何か処理を行う

インストール時にユーザを追加したり、update-alternatives したりしたいことがあります。 このような場合、DEBIAN ディレクトリの下に以下のようなスクリプトファイルを置くことで、任意の処理を実行できます。

ファイル名 説明
preinst インストールの前やアップグレードの前に実行される
postinst インストールの前やアップグレードの後に実行される
prerm アンインストールの前やアップグレードの前に実行される
postrm アンインストールの後やアップグレードの後に実行される

例えば、インストール時に www-data ユーザを作成する場合、以下の様なファイルを DEBIAN/postinst に配置すればよいです。 (chmod +x して実行可能にしておいてください)

#!/bin/sh -e
adduser --system --group --quiet --home /var/lib/www-data www-data

注意点として、スクリプトは何かエラーが起こった時には 0 以外の終了ステータスで終了すべきです。 また、エラーになったときに再実行できるように、スクリプトは冪等に書くべきです。

これらのスクリプトが呼ばれるタイミングや引数については Debian ポリシーマニュアル Chapter 6 に詳しく書いてあります。

他のパッケージへの依存を定義する

パッケージによっては他のパッケージがインストールされていないと動作しない場合があります。 そのような場合、control ファイルに Depends を書いておくことで、apt-get install したときに自動的に依存を解決してもらうことができます。

例えば、Java に依存したパッケージを作る場合、control に以下のような行を書くとよいです。

Depends: java7-runtime-headless

複数個の依存がある場合はカンマ区切りで指定します。 また、バージョンの制限がある場合はパッケージ名の後にカッコで指定します。

Depends: libjline-java, liblog4j1.2-java (>= 1.2), libnetty-java

パッケージをインストールする時点で既に他のパッケージが必要という場合もあります。 そのような場合、Depends ではなくて Pre-Depends に書いてください。

Pre-Depends: adduser

バーチャルパッケージを Provide する

同じ機能を提供するパッケージが複数個存在することがあります。 例えば、Java の場合、OpenJDK と OracleJDK が大体同じ機能を提供しています。 このような場合、「OpenJDK または OracleJDK に依存」という宣言が多発することになるのですが、これをいちいち OR で宣言するのは面倒です。 そこで、java-runtime という仮想的なパッケージを作り、Java ランタイムに依存したいパッケージはこれに Depends することにし、 OpenJDK や OracleJDK は java-runtime という機能を Provides することで、簡単に依存を記述できるようにしています。

ということで、OracleJDK とかを独自にパッケージングする場合、これらのバーチャルパッケージを Provides に宣言しておく必要があります。

Provides: java-compiler, java-sdk, java-runtime, java-runtime-headless

※ 実際に JDK をパッケージングする場合は java6-sdk, java7-runtime-headless などバージョンを含んだバーチャルパッケージも Provide する必要があります。詳しくは公式の JDK のパッケージを見てみてください。

公式パッケージの中身を見てみる

apt-get download して dpkg-deb --raw-extract すると公式パッケージの中身が見れます。 controlpostinst とかを参考にしたい場合に便利です。

例えば、mysql-server-5.5 の中身を見たい場合は以下のようにします。

$ apt-get download mysql-server-5.5
$ mkdir foo
$ dpkg-deb --raw-extract mysql-server-5.5_5.5.47-0ubuntu0.14.04.1_amd64.deb foo

apt サーバを立てる

HTTP サーバをインストール

@apt-server
$ sudo apt-get install nginx
server {
    listen 80;

    location / {
        root /srv/apt-repo;
        autoindex on;
    }
}

deb を配置

@apt-server
$ sudo mkdir -p /srv/apt-repo/pool
$ sudo cp direnv_2.8.0-1_amd64.deb /srv/apt-repo/pool

Packages.gz を作成

@apt-server
$ cd /srv/apt-repo
$ apt-ftparchive packages pool | gzip | sudo dd of=Packages.gz bs=1M

インストールしてみる

@local
$ cat <<EOF | sudo dd of=/etc/apt/sources.list.d/my-apt-repo.list
deb [trusted=yes] http://サーバ名/ ./
EOF
$ sudo apt-get update
$ sudo apt-get install direnv

設定ファイルを指定する

DEBIAN/conffiles に設定ファイルのリストを指定することができます。

deb パッケージをインストールするときに既にファイルが存在する場合、普通は問答無用で deb パッケージの中のファイルで上書きされます。 しかし、設定ファイルはインストール後にユーザが変更することがあるため、上書きしてよいかどうかがわかりません。

そこで、設定ファイルを DEBIAN/conffiles に指定しておくと、dpkg がある程度よしなに扱ってくれます。

conffiles の例:

/etc/logrotate.d/mysql-server
/etc/mysql/conf.d/mysqld_safe_syslog.cnf
/etc/mysql/debian-start
/etc/init.d/mysql
/etc/logcheck/ignore.d.paranoid/mysql-server-5_5
/etc/logcheck/ignore.d.workstation/mysql-server-5_5
/etc/logcheck/ignore.d.server/mysql-server-5_5
/etc/apparmor.d/usr.sbin.mysqld
/etc/init/mysql.conf

詳しくは Debian ポリシーマニュアル Appendix E を参照してください。

インストール時/アンインストール時の挙動

deb パッケージをインストールするときに、インストール対象のファイルが既にファイルシステムに存在していることがあります。 また、アンインストール時にアンインストール対象のファイルが既に存在しなかったり他のパッケージからまだ参照されていたりすることがあります。 このような場合、以下のようなルールで処理が行われます。

何かを配置しようとしたときのルール:

Install file/node Install symlink Install directory
Not exists Extract Extract Extract
File/node/symlink Atomic overwrite Atomic overwrite Non-atomic overwrite
Directory Non-atomic overwrite Do nothing Do nothing

何かを削除しようとしたときのルール:

  • 削除ファイルが conffile に含まれる場合、purge されるまでは削除せず残しておく。
  • 削除対象が存在しない場合、無視する。
  • 削除対象がディレクトリであるか、ディレクトリへのシンボリックリンクである場合、削除対象が他のパッケージから参照されていない場合のみ削除する。
  • 削除対象がディレクトリでないか、ディレクトリへのシンボリックリンクでない場合、削除する。

圧縮方法を指定する

dpkg-deb --build はデフォルトでデータを xz で圧縮します。 しかし、jar ファイルのように元々圧縮されているファイルを xz で再び圧縮するのは、時間のわりにサイズが減らないのでうれしくないです。 このような場合、オプションに -Z none を指定すると無圧縮にできます。

おわりに

このように deb パッケージを作成するのはとても簡単です。 tar.gz を心を込めて scp するような運用から卒業して、スマートに apt-get install する世界を目指していきましょう!!