こんにちは。@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
すると公式パッケージの中身が見れます。
control
や postinst
とかを参考にしたい場合に便利です。
例えば、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
する世界を目指していきましょう!!