PuppetのインフラCIのいま

まだまだつづくよPuppetアドベントカレンダー、18日目です。

今回はPuppetのプロジェクトのインフラCIの話。

## いまやってる内容の概要

### インフラCI向けのDockerイメージを作る

一般的にはDockerのイメージでは、init相当のプロセスは走っていません。ですが、インフラCIの場合、普通のサーバの状態をコンテナ内でできる限り再現する必要があるので、init相当のプロセスが必要となります。

以下が、ぼくがプロジェクトで使ってきたベースイメージたちです。

* CentOS 7 [`udzura/c7-systemd`](https://hub.docker.com/r/udzura/c7-systemd/)
* Ubuntu 14.04 [`ubuntu-upstart:trusty`](https://hub.docker.com/_/ubuntu-upstart/)
* Ubuntu 15.10 [`tozd/ubuntu-systemd`](https://hub.docker.com/r/tozd/ubuntu-systemd/)


ベースイメージの作り方は、以下の記事なども参考になるかと思います。

* http://qiita.com/yunano/items/9637ee21a71eba197345

### Dockerサーバを用意する

一方で、Dockerに関して、古いカーネルバージョン（といっても3.18より前なので、古いとは...）ではaufsドライバーの際にsystemdのアップグレードで問題が起こる現象を確認しています。また、devicemapperで立ち上げた際、パフォーマンスの問題が出がちであることも確認しています。
（試したことがないのですがoverlayfsなどで解決する可能性はあります）

* https://github.com/docker/docker/issues/6980
* https://github.com/boot2docker/boot2docker/pull/818

以上の理由により、ぼくがいま運用しているDockerホストのOSのディストリは **Ubuntu 15.10** となっております！！ CoreOSでもいいと思います。
なお、特にディストリ固有の問題は起こっていません。。

### CIのパイプラインを作る

以下のようなことをやっていく必要があります。

* ベースイメージのビルド
* イメージにpuppetのプロジェクトを送り込む、必要に応じてlibrarian-puppetなどを走らせる
* そのイメージを起動する
* 送り込んだファイルを元に puppet apply する
* Serverspec を流す

順に軽くブレイクダウンします。

#### ベースイメージのビルド〜プロジェクト送り込み

まず、CI用のベースイメージとして、上記紹介したinit入りのやつにpuppetなどを加えたイメージを作っときます。

```dockerfile:Dockerfile.base
FROM udzura/c7-systemd:7.1.1503

RUN yum -y update

# Install misc
RUN yum install -y \
    hostname \
    curl unzip tar cronie git diffutils sudo \
    gcc make rubygem-bundler ruby-devel
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm

RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm

RUN yum -y install puppet
RUN gem install librarian-puppet --no-ri --no-rdoc

RUN mkdir /var/puppet
ONBUILD ADD  ./ /var/puppet
ONBUILD WORKDIR /var/puppet
ONBUILD RUN bundle install --without development --path vendor/bundle
```

ポイントは `ONBUILD` で、CIで毎回以下の空っぽのDockerfileをビルドするだけで、puppetの状態が最新になるようにしています。書いてませんが、librarian-puppetもここでできると思います。

```dockerfile
FROM myci/puppet:onbuild
MAINTAINER udzura@udzura.jp
```

[Drone.io](https://drone.io/) でDocker in Dockerしている関係でこうしていますが、Jenkinsなどの場合は普通にマウントすればいいと思います。

#### ベースコンテナを起動、Puppetを適用する

イメージができたら、素直にrunで立ち上げます。潔く `--privileged` でやりましょう！ あと、[enc](http://qiita.com/udzura/items/63b22de6a97ce563f4e6)の対応のためにホスト名をやっていくのを忘れない。

```bash
role=$1
: ${IMAGE_NAME:=ci/puppet_${role}}
: ${CONTAINER_NAME:=${DRONE_BRANCH}_$(git rev-parse --short=8 HEAD)}

docker run -h ${role}.udzura.dev --name=$CONTAINER_NAME --privileged -d $IMAGE_NAME
```

立ち上げたら `exec` で適用できます。オプションはご自由に。 `RUBYOPT=-Eutf-8:utf-8` はまあ、おまじないなんですが、日本語コメントのせいでウギャーってなる可能性が低くなります。

```bash
docker exec $CONTAINER_NAME /bin/bash -c \
    'cd /var/puppet && \
     RUBYOPT=-Eutf-8:utf-8 \
     puppet apply --environment=development \
     --node_terminus=exec \
     --external_nodes=/var/puppet/enc.rb \
     --hiera_config=hiera/hiera.ci.yaml --vardir=./ \
     --detailed-exitcodes \
     --modulepath=modules:profiles:roles:vendor/modules manifests/site.pp'
```

`--test` や `--detailed-exitcodes` を指定して、ちゃんとexit codeが `0` もしくは `2` で成功、としとかないとハマるかもしれません。

#### Serverspecを流す

これも `docker exec` で、かつぼくはコンテナ内での実行時にはServerspecのExecモードになるようにしています。

```bash
docker exec $CONTAINER_NAME /bin/bash -c \
    "cd /var/puppet && \
     RUBYOPT=-Eutf-8:utf-8 bundle exec rake spec:ci:${role}"
```

ServerspecにはDocker Backendがあるので、そちらも検証してみたいですね。

### 上のパイプライン全体をシェルスクリプトにまとめる

まとめといて、ローカルではdocker-machineをつかって、CIではDockerサーバを立てといて、それぞれ同じように適用とテストが走るようにしておくと便利です。

### おまけ: Dockerならではの分岐

本番環境がDocker、というわけではないので、本番では動くのにどうしてもDockerで動かないマニフェストというものが出てきます。ネットワーク周りなど。まあ、正直そこまで多くはないんですが......。

こういうカスタム関数を書いて分岐しています。具体的にはルートファイルシステムがaufsならDocker扱いです。雑なのは承知。

```rb
module Puppet::Parser::Functions
  newfunction(:is_mounting_aufs_on_root, :type => :rvalue) do |args|
    mount = `mount`
    !!(mount =~ /^none +on +\/ +type +aufs/m)
  end
end

module Puppet::Parser::Functions
  newfunction(:in_docker, :type => :rvalue) do |args|
    Puppet::Parser::Functions.autoloader.loadall
    function_is_mounting_aufs_on_root([])
  end
end
```

----

最後になりますが、以上のパイプラインは、もともとmizzyさん [@gosukenator](https://twitter.com/gosukenator) が残したビルドスクリプトをベースに、色々と試行錯誤した結果となっています。この場を借りて感謝の意を。

次回は @kijibato さんです！