ククログ

株式会社クリアコード > ククログ > TravisCI上で複数のLinuxディストリビューションのテストをする方法

TravisCI上で複数のLinuxディストリビューションのテストをする方法

クリアコードでは、独立行政法人情報処理推進機構(IPA)平成20年度オープンソフトウェア利用促進事業迷惑メール対策を柔軟に実現するためのmilterの開発」の一環としてmilter managerの開発を行なって以来、milter managerの開発を継続しています。 最初のコミットを見ると2008年9月1日なので、約9年開発を続けています。

milter managerは、記事執筆時点では以下のLinuxディストリビューション向けにパッケージを作成しています。

  • CentOS 6

  • CentOS 7

  • Debian Jessie 1

  • Debian Stretch

  • Debian Buster

  • Debian Sid

  • Ubuntu 14.04 LTS

  • Ubuntu 16.04 LTS

  • Ubuntu 16.10

  • Ubuntu 17.04

CentOS 7以外はi386とx86_64(amd64)向けのパッケージをビルドして提供しています。2 以前はTravisCIのrvmを使ってRubyのバージョンだけを切り替えてテストしていましたが、パッケージを提供するという観点で考えるとTravisCIの動かしているUbuntu 14.04やUbuntu 12.04だとRubyだけ切り替えてもmilter managerが依存しているGLib等のライブラリのバージョンの違いについてはテストできていおらず不十分でした。開発を続けていくためには、サポートしているディストリビューションできちんと動作することを確認することが重要です。提供しているパッケージでは、それぞれのディストリビューションごとに1つのRubyバージョンをサポートしているので、CIでもそのようなテストを実行するべきです。

そのようなテストを実行するためには、TravisCI上で複数のディストリビューションを切り替えてテストを実行する必要があります。調べてみたところ、TravisCI上ではDocker Compose | Docker Documentationを使えることがわかりました。またDocker Hubを確認すると、それぞれのディストリビューションのOfficialイメージがありました。

これで材料は揃ったので、あとはどうやって.travis.ymldocker-compose.ymlDockerfileを書いていくか考えるだけです。

Dockerfileはビルド環境を整えるところまでやることにしました。実際のビルドまでDockerfileでやってしまうと、ビルド環境が整っていなかった場合の修正に時間がかかるためです。 また、milter managerのソースコードはTravisCIのホスト上にあるのでそれをそのまま使うことにしました。 ローカル環境でイメージを作るときは、カレントディレクトリ以下のファイルを全てCOPYするので新規にgit cloneしてやった方がよいです。

FROM debian:sid

ENV CODE_NAME=unstable

RUN apt-get update && \
    apt-get install -qq -y \
      debhelper dh-systemd autotools-dev \
      libglib2.0-dev libev-dev ruby ruby-dev ruby-gnome2-dev ruby-test-unit-rr \
      intltool lcov git libtool sudo lsb-release apt-transport-https \
      rrdtool rsyslog && \
    curl -L https://raw.github.com/clear-code/cutter/master/data/travis/setup.sh | env CUTTER_MASTER=yes sh && \
    gem install --no-rdoc --no-ri coveralls-lcov && \
    gem install --no-rdoc --no-ri pkg-config && \
    useradd -m --user-group --shell /bin/bash milter-manager

WORKDIR /home/milter-manager/milter-manager
COPY . .
RUN chown -R milter-manager:milter-manager .
USER milter-manager

docker-compose.ymlは特に工夫したところはありません。無限に待ち続けるためにコマンドをtail -f /dev/nullにしたくらいです。 Dockerfile-*と連携してビルド環境を整えるところまでを意識しています。また、対応するディストリビューションが増えたときもコピペで簡単に増やせるようにしました。

version: "2"
services:
  ubuntu-trusty:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile-ubuntu-trusty
    command: tail -f /dev/null

  ubuntu-xenial:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile-ubuntu-xenial
    command: tail -f /dev/null
# ... 以下同じような内容が続くので略

.travis.yamlではmatrixを使って環境変数でテストを実行するコンテナを制御しました。 matrixのTARGET_DISTRIBUTIONの行を増やせば、対応するディストリビューションを増やせるようにbefore_script:以降を調整しました。 ディストリビューションごとのパッケージの依存関係はDockerfileで解決し、Rubyのバージョンの違いや依存ライブラリのバージョン違いはautotoolsなどを使って解決しています。 そのため.travis.ymlの内容はとてもシンプルになっています。

ただし、CentOS6だけは利用しているRubyも自分でビルドしたsuffix付きのものを使っているのでconfigureのオプションが異なっています。3

dist: trusty
sudo: required
notifications:
  email:
    recipients:
      - milter-manager@ml.commit-email.info

services:
  - docker

env:
  global:
    DOCKER_COMPOSE_VERSION: 1.8.1
  matrix:
    - TARGET_DISTRIBUTION=ubuntu-trusty
    - TARGET_DISTRIBUTION=ubuntu-xenial
    - TARGET_DISTRIBUTION=ubuntu-zesty
    - TARGET_DISTRIBUTION=debian-stretch
    - TARGET_DISTRIBUTION=debian-buster
    - TARGET_DISTRIBUTION=debian-sid
    - TARGET_DISTRIBUTION=centos6 EXTRA_CONFIGURE_OPTIONS="--with-ruby=/usr/bin/ruby2.2 --with-bundled-ruby-glib2"
    - TARGET_DISTRIBUTION=centos7

before_install:
  - sudo rm /usr/local/bin/docker-compose
  - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) > docker-compose
  - chmod +x docker-compose
  - sudo mv docker-compose /usr/local/bin/

before_script:
  - docker-compose build ${TARGET_DISTRIBUTION}
  - docker-compose ps
  - docker-compose up -d ${TARGET_DISTRIBUTION}
  - docker-compose exec --user root ${TARGET_DISTRIBUTION} rsyslogd -f /etc/rsyslog.conf
  - docker-compose exec ${TARGET_DISTRIBUTION} ./autogen.sh
  - docker-compose exec ${TARGET_DISTRIBUTION} ./configure --enable-coverage --with-default-connection-spec="inet:10025@[127.0.0.1]" ${EXTRA_CONFIGURE_OPTIONS}
  - docker-compose exec ${TARGET_DISTRIBUTION} make

script:
  - docker-compose exec ${TARGET_DISTRIBUTION} ./binding/ruby/test/run-test.sh
  - docker-compose exec ${TARGET_DISTRIBUTION} ./test/run-test.sh

after_success:
  - docker-compose exec ${TARGET_DISTRIBUTION} lcov --compat-libtool --directory . --capture --output-file coverage.info
  - docker-compose exec ${TARGET_DISTRIBUTION} coveralls-lcov -v coverage.info

これでTravisCI上では一つのディストリビューションあたり7分から10分程度でテストが実行できるようになりました。トータルでは40分から60分程度かかります。 TravisCI上の実行時間の制限は、1ジョブあたり450分なので特に問題ありませんでした。

導入してよかったこと

実際に、UbuntuやCentOSの新しいバージョンをサポートするときに、依存するライブラリのバージョンが開発環境であるDebian Sidと異なっていることが原因で修正が必要になることがありました。 直近では、Ubuntu 17.04に対応するためにGLib2.52.0以降に対応しました。この対応をした時点では、Debian SidのGLibは2.50.xでした5

Ubuntu 17.04対応をしようとして、CIに新しいディストリビューションを追加したところ、Ubuntu 17.04だけCIに失敗したので対応が必要なことにすぐに気付くことができてとても便利でした。 デバッグするときも、ローカルでdocker-compose up -d ubuntu-zesty && docker-compose exec ubuntu-zesty bash -iとしてほぼコンテナ内で作業を完結させることができました。

まとめ

実際にパッケージを配布する環境でCIを実行することで、パッケージ作成を始める前に問題に気付くことができる可能性が上がります。これによって、パッケージ作成及びリリース作業にかかる時間を抑えることができそうです。

またUbuntuなどの新しいバージョンに対応するための最初の一手がとても簡単になりました。

  1. old stableになったのでmilter managerの次回リリースではパッケージの作成をやめます

  2. CentOS 7はi386をサポートしていないため

  3. milter managerのビルド環境を整えるという観点から、CentOS6用のDockerfileではRubyをビルドしています。

  4. matrixの1行あたり

  5. 記事執筆時点で確認するとGLibは2.52.3-1に上がっていました