Docker と継続的インテグレーション
14/02/12 Docker Meetup in Tokyo #1
Kazuki Suda
! @superbrothers
" github.com/superbrothers
# 本日のお話

Docker での
ビルド -> テスト -> プッシュの自動化
# アジェンダ
-

CI ワークフロー 構成編

-

Dockerfile のテスト
-

ベースとなるイメージの実装

-

テストの準備と実行

-

CI ワークフロー ジョブスクリプト編

-

デモ

-

まとめ
# デモコードあります

https://github.com/ydnjp/
docker-continuous-integration-workflow
# CI ワークフロー 登場人物編
!

ビルド -> テスト -> プッシュの自動化
## 登場人物
Jenkins

Docker レジストリ

#

$
Git リポジトリ

Docker 入りスレーブ
## ワークフロー

ビルド

テスト

プッシュ

docker build

serverspec

docker push
# Dockerfile のテスト
## ディレクトリ構造
├──Gemfile
├──Gemfile.lock
├──Rakefile
├──dockerfiles
│
├──base
│
│
├──Dockerfile
│
│
└──keys
│
│
├──id_rsa
│
│
└──id_rsa.pub
│
└──jenkins
│
├──Dockerfile
│
└──start-jenkins.sh
└──spec
├──base
│
└──sshd_spec.rb
├──jenkins
│
└──jenkins_spec.rb
└──spec_helper.rb
## Dockerfile の配置
├──Gemfile
dockerfiles/<image-name> で
├──Gemfile.lock
Dockerfile を配置する
├──Rakefile
├──dockerfiles
│
├──base
│
│
├──Dockerfile
│
│
└──keys
│
│
├──id_rsa
│
│
└──id_rsa.pub
base イメージ
│
└──jenkins
│
├──Dockerfile
│
└──start-jenkins.sh
└──spec
├──base
jenkins イメージ
│
└──sshd_spec.rb
├──jenkins
│
└──jenkins_spec.rb
└──spec_helper.rb
## spec の配置
├──Gemfile
spec/<image-name> で
├──Gemfile.lock
├──Rakefile
スペックを配置する
├──dockerfiles
│
├──base
│
│
├──Dockerfile
│
│
└──keys
│
│
├──id_rsa
│
│
└──id_rsa.pub
│
└──jenkins
│
├──Dockerfile
│
└──start-jenkins.sh
dockerfiles 以下に
└──spec
├──base
対応する形です
│
└──sshd_spec.rb
├──jenkins
│
└──jenkins_spec.rb
└──spec_helper.rb
serverspec は ssh を通して
コンテナとやりとりするので
sshd 入りのベースとする
イメージを用意します
(base イメージ)

├──Gemfile
├──Gemfile.lock
├──Rakefile
├──dockerfiles
│
├──base
│
│
├──Dockerfile
│
│
└──keys
│
│
├──id_rsa
│
│
└──id_rsa.pub
│
└──jenkins
│
├──Dockerfile
│
└──start-jenkins.sh
└──spec
├──base
│
└──sshd_spec.rb
├──jenkins
│
└──jenkins_spec.rb
└──spec_helper.rb

ssh ログインに使う
ノーパスキーです
dockerfiles/base/Dockerfile
FROM ubuntu:13.10
!
ENV DEBIAN_FRONTEND noninteractive
!
RUN apt-get -q update && apt-get -y upgrade
!
# Install openssh-server for serverspec
sshd をインストール
RUN apt-get -q -y install openssh-server && apt-get clean
RUN mkdir /var/run/sshd
RUN mkdir /root/.ssh && chmod 600 /root/.ssh
ノーパスの
ADD keys/id_rsa.pub /root/.ssh/authorized_keys
公開鍵を追加
RUN chown root:root /root/.ssh/authorized_keys
!
# Ubuntu 13.10 additional steps for SSHD Service
RUN sed -i 's/.*session.*required.*pam_loginuid.so.*/session optional
pam_loginuid.so/g' /etc/pam.d/sshd
RUN echo LANG="en_US.UTF-8" > /etc/default/locale
## ベースイメージのビルドとコンテナの起動

% docker build -t base dockerfiles/base
!

% docker run -d -p 22 base /usr/sbin/sshd -D

22番ポートが bind されたポートを指定して
ノーパスキーの秘密鍵を使うとログインできる
## ベースイメージのポイント

-

ノーパスキーを利用しよう

-

root にパスワードを設定しちゃダメ!
# serverspec を使ったテストの準備と実行
## serverspec を使ったテスト実行の流れ

1. イメージからコンテナを起動する
-

CMD /usr/sbin/sshd -D

-

22番ポートを bind する

2. bind されたポートに対して SSH を使って spec を流し込む
-

ノーパスの秘密鍵を使います

-

bind されたポートは inspect で取得

-

1 -> 2 を最後まで繰り返す

3. 利用したコンテナを殺して削除する
spec/spec_helper.rb (前編)
c.before :all do
(略)…
host = File.basename(Pathname.new(file).dirname)
!

if c.host != host
Docker イメージごとにコンテナを起動
## Start container and retrieve port number of sshd
container = Docker::Container.create(
docker-api 使ってます
:Image => "#{host}",
:Entrypoint => ['/usr/sbin/sshd'],
:Cmd => ['-D'],
sshd を起動させる
:ExposedPorts => {'22/tcp' => {}},
:User => 'root'
).start(
:PortBindings => {
コンテナの22番ポートを
親のホストに bind
'22/tcp' => [{:HostIp =>‘127.0.0.1’}]
}
)
使ったコンテナはあとで
sleep 1
停止、削除するのでとっておく
containers << container
spec/spec_helper.rb (後編)
c.ssh.close if c.ssh
base イメージに追加した公開鍵と
c.host = host
対の秘密鍵を使う
options = {
:keys => [File.expand_path('../../dockerfiles/base/keys/id_rsa',
__FILE__)],
:port => container.json['HostConfig']['PortBindings']['22/tcp'][0]
['HostPort']
HostConfig から bind されたポートを
}
取得する
c.ssh = Net::SSH.start('0.0.0.0', 'root', options)
end
接続先はローカルホストで root ユーザ
end
!
c.after(:suite) do
利用したコンテナを
## Kill and delete containers
殺して削除する
containers.each {|container| container.kill.delete }
end
spec/base/sshd_spec.rb
require 'spec_helper'
!
describe package('openssh-server') do
it { should be_installed }
end
!
describe file('/var/run/sshd') do
it { should be_directory }
end
!
describe file('/root/.ssh') do
it { should be_directory }
it { should be_mode 600 }
end

パッケージが正しく
インストールされているか

ディレクトリが存在するか

ディレクトリが存在するか

パーミッションが正しいか

spec の記述については serverspec 公式の
ドキュメントを確認してください
## テストの実行

$ bundle exec rake spec
/usr/bin/ruby1.9.1 -S rspec spec/base/sshd_spec.rb
.........
!
Finished in 2.21 seconds
9 examples, 0 failures
ベースとなるイメージの実装が終わったので
本来必要なイメージを
実装する準備が整いました(長い)

例として jenkins イメージを作ります

(ここからは早送り
## jenkins イメージ
├──Gemfile
├──Gemfile.lock
├──Rakefile
├──dockerfiles
│
├──base
│
│
├──Dockerfile
│
│
└──keys
│
│
├──id_rsa
│
│
└──id_rsa.pub
│
└──jenkins
│
├──Dockerfile
│
└──start-jenkins.sh
└──spec
├──base
│
└──sshd_spec.rb
├──jenkins
│
└──jenkins_spec.rb
└──spec_helper.rb

Dockerfile はここ

spec はこちら
dockerfiles/jenkins/Dockerfile

FROM base
base をベースとするので、sshd 入り
!
RUN apt-get -q -y install openjdk-7-jre-headless && apt-get clean
!
# Install Jenkins
jenkins のセットアップ(略)
RUN mkdir /opt/jenkins
…

FROM base としているので、そのままで
sshd を起動したコンテナを起動させることができます
!

あとは spec を実装したら完了です
(spec を先に実装することで TDD もできちゃう)
spec の実装は省略します
# docker build, push の Rake タスク化
!

繰り返し行うことはタスクにまとめましょう
## docker build
イメージが base に依存しているので、
base から順番にビルドする
% bundle exec rake docker:build

docker build -t base dockerfiles/base
(略)...

## docker push
docker tag でプライベートレジストリの情報を付けたのち、
順番に push する
% bundle exec rake docker:push
docker tag base 0.0.0.0:5000/base
docker push 0.0.0.0:5000/base
(略)...

実装はデモコードの Rakefile を参照してください!
# CI ワークフロー ジョブスクリプト編
!

ビルド -> テスト -> プッシュの自動化
## ジョブスクリプト

ベースは出来ているのでこれだけです。
デモ
## まとめ

-

ビルド、テスト、プッシュの自動化ができました

-

毎日自動でまわすことで常に最新のセキュリティ
アップデートを当て続けることもできます

!

次は継続的デリバリーに向けて?
# デモコードあります (再掲)

https://github.com/ydnjp/
docker-continuous-integration-workflow
ありがとうございました

Dockerと継続的インテグレーション