Docker + Serverspec + Infrataster を使ってCircleCI 上でインフラのテストを実行する
TL;DR
- Itamae レシピを Docker + Serverspec でテストする
- テストのためにDocker コンテナ上で
sshd
を実行したくない - CircleCI は
docker exec
使えないのでlxc-attach
を使う
おまけで Infrataster のテスト方法も記載しています。具体的な設定内容などはGitHub リポジトリ yulii/continuous-hardening #51e1618 を見てください。Itamae レシピやServerspec とInfrataster の内容自体は適当ですが・・・。
ディレクトリ構成
この記事の設定は下記のディレクトリ構成で実行しています。
% tree .
.
├── LICENSE.txt
├── README.md
├── circle.yml
├── cookbooks
│ └── nginx
│ ├── default.rb
│ ├── files
│ │ └── etc
│ │ └── nginx
│ │ └── conf.d
│ │ ├── default.conf
│ │ └── secure.conf
│ └── templates
│ └── etc
│ └── nginx
│ └── nginx.conf
├── infrataster
│ ├── Dockerfile
│ └── spec
│ ├── sample_spec.rb
│ └── spec_helper.rb
├── roles
│ └── ci.rb
└── serverspec
├── Dockerfile
├── Rakefile
└── spec
├── localhost
│ └── sample_spec.rb
├── matchers
│ └── have_http_header.rb
└── spec_helper.rb
ローカル環境 (OS X) からテストを実行する
必要なモノをインストールする
- docker
- docker-machine
- docker-machine-driver-xhyve
Homebrew などで適宜インストールしておいてください。 既にVirtualBox などを使っている場合は xhyve じゃなくても良いです。
ローカル環境 (OS X) は、下記のバージョンで実行しています。
% docker -v
Docker version 1.10.3, build 20f81dd
% docker-machine -v
docker-machine version 0.6.0, build e27fb87
CircleCI 上のホストでは、下記のバージョンで実行されています。
ubuntu@box723:~$ docker --version
Docker version 1.8.2-circleci-cp-workaround, build 4008b9c-dirty
cf. OS XのネイティブHypervisorを使うxhyveと、ネイティブDockerを立ち上げるdocker-machine-driver-xhyveを作った話
Docker コンテナを作成する
Docker Machine を作成して、コンテナを使う準備をします。スペックは適宜調整してください。
docker-machine create ci\
--driver xhyve\
--xhyve-memory-size 2048\
--xhyve-disk-size 5120\
--xhyve-cpu-count 1\
--xhyve-experimental-nfs-share;
Docker イメージをダウンロードする際にDNS が欲しいので適当に突っ込みます。
docker-machine ssh ci "sudo echo 'nameserver 8.8.8.8' > /etc/resolv.conf"
あとは、Docker コマンドを扱えるように docker-machine env ci
を確認して環境変数を設定してください。
コンテナを使ってテストを実行する
プロビジョニングのためにSSH サーバーを立てる場合、プロビジョニング用のSSH ユーザや鍵の追加でサーバー構成に差分が出るため、コンテナ内でローカルホストに向けて Itamae を実行しています。
SSH サーバーの構成差分が特に気にならなければ、SSH サーバーを立てる方法でも良いと思います。Docker コンテナ的にはSSH サーバー立てるなということもありますが、テストのためのコンテナなら良いと思います。
SSH 経由で動かすなら下記の記事などを参考にすると良いと思います。 CircleCI + Docker による Itamaeレシピの継続的インテグレーション
コンテナを定義する
コンテナ内で Itamae とServerspec を動かすため、Ruby と合わせてインストールします。Itamae のレシピは COPY
でコンテナ内に配置します。
FROM alpine
WORKDIR /usr/local/provisioning
RUN apk update && apk upgrade
RUN apk add --no-cache ruby ruby-io-console ruby-json
RUN gem install itamae rake serverspec --no-ri --no-rdoc
COPY cookbooks cookbooks
COPY roles roles
COPY serverspec/Rakefile Rakefile
COPY serverspec/.rspec .rspec
COPY serverspec/spec spec
CMD ["tail", "-f", "/dev/null"]
コンテナ自体は特にプロセスを実行する必要が無いので、代わりに tail -f /dev/null
を実行させてテスト実行まで待機させています。 RUN
はなるべく1行にまとめて実行したほうが良いですが、読みづらいので分けて書いておきます。
cf. Alpine Linux で軽量な Docker イメージを作る
Itamae + Serverspec を実行する
イメージをビルドして動かした後、 docker exec
を使ってコンテナ上で Itamae とServerspec を動かすだけです。
docker build -t serverspec -f serverspec/Dockerfile --no-cache .
docker run --name ci -it -d serverspec
docker exec -it ci /bin/sh -c 'itamae local roles/ci.rb'
docker exec -it ci /bin/sh -c 'rake spec'
CircleCI でテストを実行する
CircleCI は docker exec
に対応していないので、 lxc-attach
を使います。ローカルで構築した構成はそのまま使えるので、コマンドだけ置き換えればOK です。
machine:
services:
- docker
dependencies:
pre:
- docker build -t serverspec -f serverspec/Dockerfile --no-cache .
- docker run --name ci -it -d serverspec
test:
pre:
- sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' ci)" -- /bin/sh -c 'cd /usr/local/provisioning && itamae local roles/ci.rb'
override:
- sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' ci)" -- /bin/sh -c 'cd /usr/local/provisioning && rake spec'
よくわかっていないのですが、LXC (lxc-attach
) を使うとDockerfile に定義した WORKDIR
が反映されないので cd
しています。Docker と LXC は別物なのですが、詳しい説明は下記の記事を参考にすると良いと思います。
cf. Docker と LXC
Infrataster で振る舞いテストを実行する
Serverspec はサーバー内部のソフトウェアやファイルのテストですが、Infrataster は外部からサーバーの振る舞いを検証するツールです。詳しいことは公式サイト ryotarai/infrataster などを参照してください。
cf. Infratasterでリバースプロキシのテストをする
コンテナの構成図
- サーバー設定のテストを実行するコンテナ
- Itamae とServerspec が動作する環境
- サーバーの振る舞いテストを実行するコンテナ
- Infrataster が動作する環境
Infrataster 用のコンテナを定義する
Infrataster を実行できれば良いので、必要なソフトウェアをインストールして rspec
を実行します。
FROM alpine
WORKDIR /usr/local/provisioning
RUN apk update && apk upgrade
RUN apk add --no-cache g++ libxml2-dev make ruby ruby-dev
RUN gem install infrataster --no-ri --no-rdoc
COPY infrataster/.rspec .rspec
COPY infrataster/spec spec
CMD ["rspec"]
ローカル環境 (OS X) で確認する
docker inspect
でDocker コンテナのIP アドレスを取得してInfrataster を実行します。
docker build -t serverspec -f serverspec/Dockerfile --no-cache .
docker build -t infrataster -f infrataster/Dockerfile --no-cache .
docker run --name ci -it -d serverspec
docker exec -it ci /bin/sh -c 'itamae local roles/ci.rb'
docker exec -it ci /bin/sh -c 'rake spec'
docker run --add-host spechost:$(docker inspect --format '{{.NetworkSettings.IPAddress}}' ci) -it infrataster
Infrataster には spechost
という名称でテスト対象のホスト名を記述しています。docker run
する時にIP アドレスを --add-host
オプションで渡しています。
CircleCI でテストを実行する
CircleCI 上で試したところ、 docker inspect
から取得したIP アドレスでは繋がらなかったので circle.yml
には手を加えています。
machine:
services:
- docker
dependencies:
pre:
- docker build -t serverspec -f serverspec/Dockerfile --no-cache .
- docker build -t infrataster -f infrataster/Dockerfile --no-cache .
- docker run --name ci -it -d serverspec
test:
pre:
- sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' ci)" -- /bin/sh -c 'cd /usr/local/provisioning && itamae local roles/ci.rb'
override:
- sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' ci)" -- /bin/sh -c 'cd /usr/local/provisioning && rake spec'
- docker run --add-host spechost:$(sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' ci)" -- /bin/sh -c 'ip -f inet -o addr show eth0 | cut -d\ -f 7 | cut -d/ -f 1') -it infrataster
読みづらいですが、IP アドレスをテスト対象のホストにログインして ip
コマンドから取得します。
ip -f inet -o addr show eth0 | cut -d\ -f 7 | cut -d/ -f 1
docker exec
が使えないので、上記のコマンドを lxc-attach
経由で実行します(結果的にコマンドが長くなり読みづらい )。
Infrastructure as Code の構想
今回はCircleCI 上で継続的にテストを実行するため、Docker イメージには軽量なAlpine Linux を指定しています。実際の運用には違うOS を使う場合はなるべく合わせたほうが良いです。
AWS ベースを前提にしていますが、以下の様な形で軽量にCI を回しつつ、本番環境との差異をリスクヘッジしようとしています。
- Terraform でAWS リソース(インフラ)の定義する
- Itamae でサーバー設定の定義する
- CircleCI 上でServerspec を動かす (Alpine Linux)
- Terraform + Itamae で作成されたサーバーからAMI 作成する
- AMI 生成前に、Infrataster でサーバーとしての振る舞いを確認する
- AMI からサーバーを起動してアプリケーションをデプロイする
- 必要に応じて Infrataster で振る舞いテストや手動テストを実施する
AMI を作成する際にInfrataster でテストすれば本番環境と同じOS になるので、Serverspec で検知できなかった環境差異の問題が見つかるはず。たぶん。
というわけで、実際の運用ではCircleCI 上でInfrataster は動かさないです。