まだまだつづくよPuppetアドベントカレンダー、18日目です。
今回はPuppetのプロジェクトのインフラCIの話。
いまやってる内容の概要
インフラCI向けのDockerイメージを作る
一般的にはDockerのイメージでは、init相当のプロセスは走っていません。ですが、インフラCIの場合、普通のサーバの状態をコンテナ内でできる限り再現する必要があるので、init相当のプロセスが必要となります。
以下が、ぼくがプロジェクトで使ってきたベースイメージたちです。
- CentOS 7
udzura/c7-systemd
- Ubuntu 14.04
ubuntu-upstart:trusty
- Ubuntu 15.10
tozd/ubuntu-systemd
ベースイメージの作り方は、以下の記事なども参考になるかと思います。
Dockerサーバを用意する
一方で、Dockerに関して、古いカーネルバージョン(といっても3.18より前なので、古いとは...)ではaufsドライバーの際にsystemdのアップグレードで問題が起こる現象を確認しています。また、devicemapperで立ち上げた際、パフォーマンスの問題が出がちであることも確認しています。
(試したことがないのですがoverlayfsなどで解決する可能性はあります)
以上の理由により、ぼくがいま運用しているDockerホストのOSのディストリは Ubuntu 15.10 となっております!! CoreOSでもいいと思います。
なお、特にディストリ固有の問題は起こっていません。。
CIのパイプラインを作る
以下のようなことをやっていく必要があります。
- ベースイメージのビルド
- イメージにpuppetのプロジェクトを送り込む、必要に応じてlibrarian-puppetなどを走らせる
- そのイメージを起動する
- 送り込んだファイルを元に puppet apply する
- Serverspec を流す
順に軽くブレイクダウンします。
ベースイメージのビルド〜プロジェクト送り込み
まず、CI用のベースイメージとして、上記紹介したinit入りのやつにpuppetなどを加えたイメージを作っときます。
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もここでできると思います。
FROM myci/puppet:onbuild
MAINTAINER udzura@udzura.jp
Drone.io でDocker in Dockerしている関係でこうしていますが、Jenkinsなどの場合は普通にマウントすればいいと思います。
ベースコンテナを起動、Puppetを適用する
イメージができたら、素直にrunで立ち上げます。潔く --privileged
でやりましょう! あと、encの対応のためにホスト名をやっていくのを忘れない。
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
はまあ、おまじないなんですが、日本語コメントのせいでウギャーってなる可能性が低くなります。
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モードになるようにしています。
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扱いです。雑なのは承知。
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 が残したビルドスクリプトをベースに、色々と試行錯誤した結果となっています。この場を借りて感謝の意を。
次回は @kijibato さんです!