CircleCI で docker + serverspecを実行する

前回はローカルで docker 上で serverspecを実行した。
今回は、CircleCI上で dockerを起動し、knife-soloで構築し、 serverspecでテストができるようにしようと思う。

全体のワークフローとしては、以下の順序となる。

  1. Githubにchefのコードをpushする
  2. CircleCI上でdockerを起動する
  3. dockerのimageを作成し、コンテナを起動。
  4. そのコンテナに対し、knife-soloを実行し、インフラを構築。
  5. 構築したコンテナに対して、serverspecを実行し、テストを行う

そうすると、Githubの Merge Pull Request ボタンにテストの結果が表示される。

CircleCIの始め方などは、割愛。

Dockerfileを使用して、dockerイメージを作成する

まず、以下のDockerfileを作成し、ssh可能なコンテナを作成できるようにする。

FROM centos:centos6

RUN yum install -y passwd
RUN yum install -y openssh
RUN yum install -y openssh-server
RUN yum install -y openssh-clients
RUN yum install -y sudo

RUN useradd moock
RUN passwd -f -u moock
RUN mkdir -p /home/moock/.ssh; chown moock /home/moock/.ssh; chmod 700 /home/moock/.ssh
ADD authorized_keys /home/moock/.ssh/
RUN chown moock /home/moock/.ssh/authorized_keys; chmod 600 /home/moock/.ssh/authorized_keys

RUN echo "moock    ALL=(ALL)       ALL" >> /etc/sudoers.d/moock

RUN sed -i -e "s:^UsePAM yes$:#UsePAM yes:" /etc/ssh/sshd_config
RUN /etc/init.d/sshd start
RUN /etc/init.d/sshd stop

注意点としては、sshd_configのファイルでPAMを使用する設定になっていたが、 こうすると、ログインした後すぐにログアウトしてしまったので、PAMを使用しないようにした。

また、authorized_keysはcircle.ymlで作成する。

CircleCIの設定

次にCircleCIの設定を行う。

circle.yml は以下のように設定した。

machine:
  ruby:
    version: 2.0.0
  timezone: Asia/Tokyo
  services:
    - docker

dependencies:
  post:
    - echo "Host circle" >> ~/.ssh/config
    - echo "  HostName 127.0.0.1" >> ~/.ssh/config
    - echo "  User moock" >> ~/.ssh/config
    - ssh-keygen -N "" -t rsa -f ~/.ssh/id_rsa
    - cp ~/.ssh/id_rsa.pub authorized_keys

test:
  pre:
    - docker build -t moock/sshd .
  override:
    - cd chef-repo && container_id=`docker run -d -p 22 moock/sshd /usr/sbin/sshd -D` && ssh_port=`docker inspect --format='{{ if index .NetworkSettings.Ports "22/tcp" }}{{ (index (index .NetworkSettings.Ports "22/tcp") 0).HostPort }}{{  end }}' $container_id` && bundle exec knife solo prepare circle -p $ssh_port && bundle exec knife solo cook circle -p $ssh_port && bundle exec rake serverspec:circle SSH_PORT=$ssh_port:
      timeout: 1200

testの項目をoverrideし、knife-soloの実行とserverspecの実行を行っている。

CircleCIでの流れとしては、

  1. dockerのサービスを起動
  2. dockerでログインするホストに対しcircleというHost名を設定する。
    (knife-soloでホスト名.jsonで使用するのと、serverspecで、テスト先のホスト名を使用するため)
  3. knife-soloでsshでログインするので、ログインするための秘密鍵と公開鍵を作成。
  4. Dockerfileでauthorized_keysを設定するため、公開鍵をauthorized_keysとしてコピー。

  5. Dockerfileを使用して、docker imageを作成し、コンテナ実行。

  6. 作成したコンテナにknife-soloを実行。
  7. serverspecでテスト。

という感じになる。

knife-solo周りのコマンドは、前回のを流用。

ハマった点としては、test項目で timeout の設定をしないと、標準出力に3分間出力がないと timeoutエラーになってしまうところ。
chefでrbenvなどを使って、rubyコンパイルしたりすると、確実にtimeoutエラーになってしまうので、 タイムアウトの時間を20分に設定した。

あと、なんか汚いからもう少しきれいにしたい。。

knife-soloの設定

CircleCIで実行するレシピを指定する。ファイル名は .ssh/config で指定したホスト名を使用するため、 ここでは、circle.jsonとなる。

例として、nginxのレシピを作成し、実行する。

{
  "run_list":[
    "recipe[nginx]"
  ]
}

serverspecの設定

serverspecの設定としては、例としてspec/web/nginx_spec.rb を作成し、 property.ymlにテストする項目として追加する。

circle:
  :roles:
    - web

これで、Githubにchefのコードをpushすれば、自動でテストを行うようになった。

改善点

一応、環境は構築できたのだが、改善点がある。

Dockerfileを使用してコンテナを作成しているが、ここの部分はそれなりに時間がかかるし、毎回作成するのは、やっぱり無駄っぽい。
たぶんこの辺は、DockerHubを使用すれば、解決するのではないかと思う。