読者です 読者をやめる 読者になる 読者になる

Mercari Engineering Blog

We're the software engineers behind Mercari. Check out our blog to see the tech that powers our marketplace.

DockerとMakeを利用したRPMパッケージのビルド環境

f:id:cubicdaiya:20160812131620p:plain

SREチームの@cubicdaiyaです。今回はDockerとMakeを利用したメルカリの自作RPMパッケージのビルド環境について紹介します。

メルカリの自作RPMパッケージ事情とVagrant、そしてDocker

メルカリの開発およびプロダクション環境では現在CentOS6と7を利用しており、随時CentOS7へ移行中です。そのため、自作RPMパッケージをビルドする際はCentOS6と7向けにそれぞれビルドしています。ビルドしたパッケージはyumリポジトリサーバにアップロードした後、必要に応じてyumでインストール、Ansibleのplaybook化を行います。

RPMパッケージの作成はSREチームのメンバーが行っており、各自のローカルマシン上において make {パッケージ名} を実行するだけでCentOS6と7向けのRPMパッケージをビルドできる環境をDockerで構築しています。

make nginx       # nginxのRPMパッケージをビルド
make nginx-build # nginx-buildのRPMパッケージをビルド
make gaurun      # gaurunのRPMパッケージをビルド
make slackboard  # slackboardのRPMパッケージをビルド
make cachectl    # cachectlのRPMパッケージをビルド
(etc)

これの良いところはMac上でもCentOS向けにRPMパッケージを作成できる点です。当初はVagrantを利用していましたが、最近Dockerを利用するように変更しました。Vagrant上でビルドするのと比べるとパフォーマンスが良く、特にコンテナの起動や破棄が高速なのが大きなメリットです。また、Cプログラムのコンパイルが以前よりも早く終わるようになりました。(もっとも最近はGoプログラムのRPMパッケージが多いのですが)

DockerとMakeを利用したRPMパッケージのビルドシステム

次にビルドシステムの中身を少しだけ紹介していきます。以下がそのレイアウトです。DockerfileとMakefile、各種ユーティリティを格納したscripts、そしてRPMパッケージをビルドする際に登場するおなじみのディレクトリ群(BUILD、SOURCES、SPECS、SRPMS)があります。

rpmbuild
├── BUILD
├── Dockerfile.centos6
├── Dockerfile.centos7
├── Makefile
├── README.md
├── RPMS
├── SOURCES
│   ├── gaurun-0.6.0.tar.gz
│   ├── nginx-1.11.3.tar.gz
│   (etc)
├── SPECS
│   ├── gaurun.spec
│   ├── nginx.spec
│   (etc)
├── SRPMS
└── scripts
    ├── build.sh
    (etc)

DockerfileにはDockerコンテナ内でRPMパッケージのビルドを実行するユーザの追加や各種パッケージをビルドするのに必要なツールのインストール処理を記述します。

RUN useradd -u ${RPMBUILD_UID} rpmbuild

RUN yum groupinstall -y "Development Tools" && \
    yum install -y wget gcc gcc-c++ rpm-build # etc...

# setup Go environment
ADD scripts /root/scripts
RUN /root/scripts/build-goruntime.sh

#
# etc...
#

リポジトリに含めるファイル、含めないファイル

各ソースコードのアーカイブやSPECファイルのほかにserviceやinit.dのスクリプトもまとめて同じGitリポジトリで管理しています。一方でビルド済みパッケージはサイズが大きいので、リポジトリにはコミットしないで定期的にyumリポジトリサーバ上のパッケージをS3のバケットと同期するようにしています。

また、Goプログラムをビルドする際はできるだけ最新のバージョンのGoを利用するようにしているのですが、配布されているGoのビルド済みアーカイブ(e.g. go1.6.3.linux-amd64.tar.gz)は非常にサイズが大きいのでこちらもリポジトリには含めずにDockerイメージをビルドするタイミングでダウンロードするようにしています。

make {パッケージ名} でRPMパッケージをビルド

ターゲットを指定せずに make を実行するとターゲット(パッケージ)の一覧が出力されます。

$ make

To make rpm package, type "make <TARGET>"

Available targets are 

cachectl 
gaurun 
nginx
nginx-build
(etc)

ターゲットを指定した場合はCentOS6と7向けにDockerを起動してビルドスクリプトを走らせます。(Dockerイメージのビルドやレジストリからのダウンロードは依存ターゲットの prepare-docker で行っています)

OSLIST = centos6 centos7

$(TARGET): prepare-docker
        for OS in $(OSLIST) ;                                                                           \
                do docker run -it --rm -v $(CURDIR):/workspace rpmbuild:$$OS scripts/build.sh $@ $$OS ; \    
                # 
                # processing miscellaneous tasks
                # 
        done

実際にRPMパッケージのビルドを実行する上記の scripts/build.sh では以下のようにrpmbuildコマンドを呼び出しています。

rpmbuild -bb SPECS/$TARGET.spec

ターゲット名とSPECファイルのサフィックスを一致させるのがポイントです。また、ファイルの存在確認をはじめ各種雑多な処理もここでラップしてMakefileの記述があまり複雑にならないようにしています。

まとめ

メルカリのRPM自作事情、DockerとMakeを利用したRPMのパッケージビルド環境について解説しました。

メルカリでのRPMパッケージ自作は2年ほど前にさくらのクラウド上のサーバで雑にrpmbuildをインストールするところから始まりましたが、その後まもなく怠惰な@shmorimo御大が「めんどくさい」と言ってVagrant上で make {パッケージ名} (以下省略)するスクリプトを書いたところからコマンド一発でRPMパッケージをビルドできるようになりました。

そしてCentOS7への移行が本格化したタイミングで複数の環境向けにより素早くパッケージをビルドしたい需要が出てきたことからDockerへと移行しました。Vagrantの頃も十分便利でしたが、Dockerになってビルドがより高速になったのが移行の最大のメリットでした。