fpm を使って手軽に rpm パッケージを作ろう

f:id:aptpod_tech-writer:20201109181907p:plain

はじめに

こんにちは、SRE チームの柏崎です。

先日、intdash が AWS Marketplace にて提供開始されました。
これを期に、サーバサイドアプリをパッケージングするために、fpm というツールを使う機会がありました。

すっかりコンテナな世の中には地味めな話題ですが、今回は fpm について、rpm パッケージの作成例を交えながら紹介したいと思います。

経緯

弊社では、intdash を組み合わせた PoC プロジェクトが多くあり、製品である intdash 自体のカスタマイズがよく行われています。

その昔、intdash のサーバサイドアプリはモノリシックアーキテクチャだったため、カスタマイズのために製品本体に手を入れなければならず、派生製品が乱立していました。
故にデプロイにおいては、派生製品ごとの実行バイナリを管理する手間を減らすため、「都度サーバ上でビルドを行う」という、ちょっと斜め上の方式をとっていました。せっかく Go で作っているのにもったいないですね…。

現在ではマイクロサービスアーキテクチャへの移行が進み、製品に手を入れずに柔軟なカスタマイズが可能な構成になっていますが、デプロイは従来の方式を踏襲してしまっており、下記のようなたくさんの課題を抱えています。

  • Ansible タスクでビルドしているので、冪等性があいまい
  • Ansible の実行が長時間になりがち
  • サーバがそれぞれビルドのためのソースを持つので、ディスクスペースが無駄
  • お客様管理のサーバへのデプロイも、アプトポッドエンジニアがやらなければならない

そんなとき intdash の AMI 公開の話が持ち上がり、「こんなダサい方式でデプロイされた AMI を晒すのは恥ずかしい」と重い腰を上げたのでした。古いバージョンの利用者にアップデート手段を提供しないといけないですしね。

fpm とは?

目的

Linux (に限らずですが) のほとんどのディストリビューションはパッケージ管理システムを利用しています。
RHEL 系で rpm 形式を扱う yum、Debian 系で deb 形式を扱う apt がメジャーですね。

パッケージ管理システムによって、利用者はソフトウェア一式の追加削除や、依存関係の解決などを手軽に行うことができます。

便利なパッケージ管理システムですが、パッケージの作成者にとってはちょっと大変です。
様々な形式に対応するために、パッケージ作成手順を形式ごとに学習していかなければなりません。
rpm ひとつとっても、spec ファイルの複雑な書式や rpmbuild コマンドの使い方など、覚える事が多いです。

fpm は、覚えなければならない事を極力省き、シンプルに様々な形式のパッケージを作ることを目的として作られています。

どうやって動くのか

fpm は、様々な形式から入力と出力を指定すると、その間の変換を行ってくれます。

f:id:aptpod_tech-writer:20201106134238p:plain:w600

README にも書いてありますが、様々な入力形式・出力形式に対応しています。
npm モジュールから rpm を作ったり、deb から rpm を作ったり、色々な用途が思いつきますね。

使いかた

それでは、実際にパッケージの作成を行いながら、使い方を見ていきましょう。
入力形式として dir を、出力形式として rpm を使い、intdash の認証認可を担当するマイクロサービス「auth」の rpm パッケージを作ります。

構成ファイルの準備

入力形式 dir は、ディレクトリ配下に配置されたファイル一式をパッケージの構成ファイルとして扱ってくれます。
必要なファイルを用意し、ディレクトリ buildroot 配下に配置します。

buildroot/
  usr/
    bin/
      authd                              # 実行バイナリ
    lib/
      systemd/
        system/
          intdash-service-auth.service   # systemd ユニットファイル
    share/
      doc/
        intdash-service-auth-1.6.0/
          copyright                      # コピーライトファイルなど
          ...
  etc/
    intdash/
      authd.conf                         # 設定ファイル

また、出力形式 rpm は、ヘルパユーティリティとしてインストール時やアンインストール時に実行されるスクリプトを指定できます。
これらをディレクトリ rpm_helper 配下に配置します。

rpm_helper/
  pre.sh      # インストール前に実行される
  post.sh     # インストール後に実行される
  preun.sh    # アンインストール前に実行される
  postun.sh   # アンインストール後に実行される

例として、よくある rpm のヘルパユーティリティを以下に書いておきます。
サービスの動作に必要なユーザの作成や systemd 関連の操作などを行っています。

### rpm_helper/pre.sh

getent group intdash >/dev/null 2>&1 || \
  groupadd -r intdash
getent passwd intdash >/dev/null 2>&1 || \
  useradd -r -g intdash -d /var/lib/intdash -s /sbin/nologin intdash
exit 0
### rpm_helper/post.sh

if [ $1 -eq 1 ]; then
  systemctl daemon-reload >/dev/null 2>&1 ||:
fi
### rpm_helper/preun.sh

if [ $1 -eq 0 ]; then
  systemctl --no-reload disable intdash-service-auth.service >/dev/null 2>&1 ||:
  systemctl stop intdash-service-auth.service >/dev/null 2>&1 ||:
fi
### rpm_helper/postun.sh

if [ $1 -eq 0 ]; then
  systemctl daemon-reload >/dev/null 2>&1 ||:
fi

準備はこれだけです。
スクリプト周りで rpm のちょっとした知識は必要ですが、簡単ですね!

パッケージング

それでは、いざパッケージングをしていきましょう。
コマンド一発です。

$ fpm \
--output-type rpm \
--input-type dir \
--chdir ./buildroot \
--name intdash-service-auth \
--version 1.6.0 \
--iteration 1 \
--architecture x86_64 \
--license Unspecified \
--maintainer product-support@aptpod.co.jp \
--vendor "aptpod, Inc." \
--url https://www.aptpod.co.jp/ \
--rpm-summary "intdash Auth Service" \
--description "This package contains the intdash Auth Service." \
--rpm-os linux \
--depends shadow-utils \
--depends systemd \
--before-install ./rpm_helper/pre.sh \
--after-install ./rpm_helper/post.sh \
--before-remove ./rpm_helper/preun.sh \
--after-remove ./rpm_helper/postun.sh \
--directories /etc/intdash \
--directories /usr/share/doc/intdash-service-auth-1.6.0 \
--config-files /etc/intdash/authd.conf

{:timestamp=>"2020-11-05T08:27:27.428159+0000", :message=>"Created package", :path=>"intdash-service-auth-1.6.0-1.x86_64.rpm"}

ポイントをいくつか書いておきます。

  • アンインストール時にディレクトリが残ってしまわないように、 --directories オプションを忘れないようにしましょう。
  • --config-files を使って特定のファイルが設定ファイルであることを明示しておくと、編集済みの設定ファイルがパッケージアップデート時に上書きされたり、アンインストール時に削除されたりするのを防ぐことができます。
  • --iteration では、 1.6.0-11 ようなバージョンの後ろに付く文字列を指定できます。
    • パッケージ自体の更新で 2 3 と増やしたり、プレリリースバージョンにて 0.1.rc1 0.2.rc2 のように付与することで、パッケージ管理システムにより適切に新旧バージョンの比較が行われます。

以上で、rpm が出来上がりました。
お手軽ですね!

おわりに

fpm について、実例を交えながら紹介しました。

AMI は無事、恥ずかしくない状態で公開することができました。
引き続き、社内へのデプロイへの適用など、質の高い構築運用に活かしていくつもりです。

ここでは rpm の作成例を紹介しましたが、ほとんどの内容は deb など他の形式にも使い回せます。
パッケージングでお悩みの方の参考になれば幸いです。