ちょっと話題の記事

Docker Meetup Tokyo #2でLTしてきた:「Docker+serverspecで作るconfigspec CI」 #dockerjp

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

2014年4月11日(金)に開催されたDocker Meetup Tokyo #2で、「Docker + serverspecで作る configspec CI」というタイトルでLTしてきました。

この内容について、少し詳細に落とし込んだのがこの記事です。拙いところも改善するべきところもいっぱいあるんですが、とりあえず動いたよ、というところで。

イベント中のtweetはTogetterにまとめました。

やりたいこと

結果としてやりたいこと、出来上がるものがこちらの図になります。

configspecとserverspecをRakefileとしてまとめておき、githubのリポジトリに配置しておきます。そのリポジトリにgit pushされると、webhookによってJenkinsに通知が入り、DockerでContainerを立ち上げ、rakeコマンドを実行し、configspecでプロビジョニングし、serverspecでテストします。

doc01

やったこと

Amazon Linux Docker Image

まず、Amazon Linux EC2を普通にLaunchし、そのままStopします。そのEC2のEBS VolumeをDettach後、DockerがセットアップされたEC2にAttachし、mountします。

Docker Imageにするには不要な情報がいくつかありますので、削除しました。

  • /var/log/* (全てのファイルを編集し0byteに)
  • /var/cache/yum/*
  • /home/ec2-user/.ssh/authorized_keys

その後、以下コマンドでimageとしてimportします。

# tar --numeric-owner -cjp . | docker import - local/amzn

AttachしたEBS Volumeはumountの上Dettachしておきます。

Amazon Linux Docker Imageをsshd接続可能なImageにする

上記で作成したImageは素の状態のAmazon Linuxなので、このImageを元にsshで接続可能なImageを作成します。

以下のようなDockerfileを作成します。またDockerfileと同じフォルダ内に、ssh接続で使用したい公開鍵をauthorized_keysという名前で配置しておきます。

FROM local/amzn

# PAM設定を変えておかないとsshがいきなり切れる
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config
RUN passwd -f -u ec2-user

# start-stopしてhost_keyを作る
RUN service sshd start
RUN service sshd stop

ADD ./authorized_keys /home/ec2-user/.ssh/authorized_keys

EXPOSE 22

このDockerfileを使って、Imageをbuildします。

docker build -t local/amznssh .

spec-ciが動くようにする

今回、configspecとserverspecをまとめて動かすようにしたものは、smokeymonkey/spec-ciに置いてあります。

これを動かすためには以下のgemを入れておく必要があります。

$ sudo gem install rspec rake configspec serverspec docker-api

また、Dockerの実行はroot権限が必要なため、spec-ciのrakeの実行もrootで行います。/root/.ssh/configにSSH接続のための設定を書いておきます。ポート番号は、本当はDockerが動的にマッピングしたポートフォワードされるポート番号を使えば良いんだと思うんですが、うまくいかずに固定化してしまいました。

# vi /root/.ssh/config
Host docker
  HostName      172.17.42.1
  Port          54322
  User          ec2-user
  IdentityFile  ~/.ssh/mykey.pem
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null

Rakefileの内容

Rakefileは以下のような内容になっています。

まず構成としてはserverspec のテストをホスト間で共有する方法 - Gosuke Miyashitaを参考にさせて頂いており、最初にhosts定義を作って、hosts毎にロールを指定する形にしています。そしてその中で使用するコンテナイメージも指定しています。

require 'rake'
require 'rspec/core/rake_task'
require 'docker'
 
hosts = [
  {
    :name  => 'docker',
    :roles => %w( web ),
    :image => 'local/amznssh'
  }
]

このhosts定義をeachでループしている部分で、まず最初にdocker-apiを使ってコンテナを起動しています。

namespace :spec do
  task :all => hosts.map {|h| 'spec:' + h[:short_name] }
  hosts.each do |host|
    container = Docker::Container.create(
      'Cmd'          => ['/usr/sbin/sshd', '-D'],
      'Image'        => host[:image],
      'PortSpecs'    => '22'
    )
    container.start(
      'PortBindings' => {
        '22/tcp' => [
          {'HostIp' => '0.0.0.0'},
          {'HostPort' => '54322'}
        ]
      }
    )
    sleep 1

その後にconfigspecを実行し、次にserverspecを実行し、最後にコンテナをstopしています。

    desc "Run spec to #{host[:name]}"
    ENV['TARGET_HOST'] = host[:name]

    # configspec
    RSpec::Core::RakeTask.new(host[:short_name].to_sym) do |t|
      t.pattern = 'spec/{' + host[:roles].join(',') + '}_config/*_spec.rb'
    end

    # serverspec
    RSpec::Core::RakeTask.new(host[:short_name].to_sym) do |t|
      t.pattern = 'spec/{' + host[:roles].join(',') + '}_test/*_spec.rb'
    end
    container.stop

rakeしてみる

Rakefileが置いてあるフォルダでrakeを実行すると以下のような出力がされます。

$ sudo /usr/local/bin/rake
/usr/bin/ruby2.0 -S rspec spec/web_config/nginx_spec.rb
.

Finished in 0.52104 seconds
1 example, 0 failures
/usr/bin/ruby2.0 -S rspec spec/web_test/nginx_spec.rb
.

Finished in 0.09493 seconds
1 example, 0 failures

Jenkinsのセットアップ

Jenkinsをインストールします。今回はパッケージとしてインストールしました。

$ sudo -s
# wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
# rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
# yum install jenkins
# chkconfig jenkins on
# service jenkins start

インストール後、http://EIP:8080/にアクセスしてJenkinsに接続します。このままだと何の認証も入らないので、[グローバルセキュリティの設定]でユーザ認証されるように設定変更します。

jen01

また、Jenkinsの実行ユーザ:jenkinsからgithubに接続出来るようにしておく必要があります。秘密鍵ファイルを/var/lib/jenkins/.ssh/に配置し、公開鍵をgithubリポジトリに登録し、git configを実行しました。

また、ユーザjenkinsからsudoを使えるようにしておく設定しておきます。

# visudo
jenkins ALL=(ALL) NOPASSWD:ALL
Defaults:jenkins !requiretty

Jenkinsとgithubの連携ではGit Pluginを使います。[プラグインの管理]から「Git Plugin」をインストールしておきます。

jen02

Githubのリポジトリを使ったジョブを作成します。git pluginを使って、ソースコード管理で[Git]を洗濯し、リポジトリのURLを登録します。

jen03

ビルド手順は[シェルの実行]として、以下1行を入れておくだけです。

jen04

github側の設定

github側のwebhooksの設定を行い、git pushがあった場合にJenkinsをキックするよう設定しておきます。設定場所は[Setting]-[Webhooks & Services]です。

jen05

実行

specファイルを追加したり編集したりして、git pushすると、GithubからJenkinsがキックされてビルドが実行されます。成功した場合はこんな感じです。

jen06

失敗した場合はこんな感じ。

jen07

最終的に完成したら、Amazon Linux EC2に実行してやれば、プロビジョニングとそのテストが完了です!

まとめ

今回やった事はただの実験ですが、Amazon Linux AMI 2014.03によってDockerが簡単に使えるようになった今、コンテナベースデプロイでアプリケーションを開発するというのが一般的になる未来はそう遠くないのかも知れません。とりあえずやってて楽しかったです。 僕の業務上ではDockerを触ることはほぼ無いのですが、今後も色々と試していきたいと思います!