設定
各ソフトウェアの設定について説明していきます。
Docker
まず、DockerでSSH接続が可能なイメージを作成します。
今回、Dockerのイメージを作成するために、コンテナでの処理を記述することができるDockerfileを使用しました。
このDockerfileで、CentOSからSSH接続を行うために、CentOS側の公開鍵の設定も行っています。
$ cd docker #作業ディレクトリに移動します。
$ cp ~/id_rsa.pub . #CentOS側の公開鍵を作業ディレクトリにコピーします。
$ vi Dockfile
FROM ubuntu:12.04
MAINTAINER hidemium
RUN apt-get -y update
# install
RUN apt-get -y install openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:root' | chpasswd
# sshd config
ADD id_rsa.pub /root/id_rsa.pub #公開鍵をコンテナに追加します。
RUN mkdir /root/.ssh/
RUN mv /root/id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 700 /root/.ssh
RUN chmod 600 /root/.ssh/authorized_keys
RUN sed -i -e '/^UsePAM\s\+yes/d' /etc/ssh/sshd_config
EXPOSE 22
CMD /usr/sbin/sshd -D
docker buildコマンドにより、Dockerfileに記述された処理を実行後、commitをしてイメージの作成までを自動で行います。
実行後、ubuntu-sshdというイメージが作成されていることを確認します。
$ sudo docker build -t ubuntu-sshd .
$ sudo docker images
CentOS側からSSH接続が可能になったか確認するため、テストします。
先ほど作成したイメージから、22番ポートのポートフォワードを指定し、コンテナを起動します。
コンテナ起動後に、PORTSの列に「0.0.0.0:[ポート番号]」から、22番ポートにマッピングされたローカルのポートを確認します。
$ sudo docker run -d -p 22 ubuntu-sshd
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6e61ccbe71df ubuntu-sshd:latest /bin/sh -c '/usr/sbi 2 days ago Up 25 hours 0.0.0.0:[ポート番号]->22/tcp loving_almeida
CentOS側から、以下のコマンドを実行しログインできることを確認します。
$ ssh root@<Ubuntu側のIPアドレス> -p [ポート番号]
Chef
Chefのリポジトリを作成します。
$ knife solo init chef-repo
サーバ構築用のcookbookを作成します。
今回は、apache2をインストールするレシピを用意します。
$ cd chef-repo/
$ knife cookbook create apache2 -o site-cookbooks
$ vi site-cookbooks/apache2/recipes/default.rb
# Apacheをインストール
# sudo apt-get -y install apache2
package "apache2" do
action :install
end
JSONファイルが作成されるため、実行するapache2のcookbookを指定します。
$ vi nodes/<サーバのIPアドレス>.json
{
"run_list":[
"recipe[apache2]"
]
}
serverspec
Chefのrootディレクトリに移動し、serverspecの初期設定を行います。
$ cd chef-repo/
$ serverspec-init
Select OS type:
1) UN*X
2) Windows
Select number: 1 #UN*Xを選択
Select a backend type:
1) SSH
2) Exec (local)
Select number: 1 #SSHを選択
Vagrant instance y/n: n #nを選択
Input target host name: 192.168.xxx.xxx #テスト対象サーバのIPアドレスを指定
+ spec/
+ spec/192.168.xxx.xxx/
+ spec/192.168.xxx.xxx/httpd_spec.rb
+ spec/spec_helper.rb
+ Rakefile
apache2がインストールされているか確認するテストコードを書きます。
$ mv spec/192.168.xxx.xxx/httpd_spec.rb spec/192.168.xxx.xxx/apache2_spec.rb
$ vi spec/192.168.xxx.xxx/apache2_spec.rb
require 'spec_helper'
describe package('apache2') do
it { should be_installed }
end
serverspecは、SSH接続でテスト対象にアクセスしますが、テスト対象にログインするための情報は~/.ssh/configから取得しています。
serverspecが利用できるように、~/.ssh/configを修正します。
$ vi ~/.ssh/config
Host 192.168.xxx.xxx
HostName 192.168.xxx.xxx #テスト対象サーバのIPアドレスを指定
User root #SSH接続でログインするユーザを指定
Port 22
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile "/root/.ssh/id_rsa" #秘密鍵のパスを指定
IdentitiesOnly yes
LogLevel FATAL
Dockerは、新たなコンテナを起動するたびに、22番ポートにフォワードされるポートが毎回異なって生成される問題があります。
Chefの場合は、-pオプションを使用すれば、ポート番号を指定できますが、serverspecは~/.ssh/configからポート番号を取得しており、ポート番号を引数で与えることができません。
今回、~/.ssh/configからSSH接続の情報を取得している、spec/spec_helper.rbを修正し、外部ファイルからポート番号を設定できるようにしました。
$ vi spec/spec_helper.rb
require 'serverspec'
require 'pathname'
require 'net/ssh'
include SpecInfra::Helper::Ssh
include SpecInfra::Helper::DetectOS
RSpec.configure do |c|
if ENV['ASK_SUDO_PASSWORD']
require 'highline/import'
c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false }
else
c.sudo_password = ENV['SUDO_PASSWORD']
end
c.before :all do
block = self.class.metadata[:example_group_block]
if RUBY_VERSION.start_with?('1.8')
file = block.to_s.match(/.*@(.*):[0-9]+>/)[1]
else
file = block.source_location.first
end
host = File.basename(Pathname.new(file).dirname)
+ SSH_CONFIG_FILE = 'spec/.ssh-config'
+ config = File.open(SSH_CONFIG_FILE).read
+ port = ""
+ if config != ''
+ config.each_line do |line|
+ if match = /Port (.*)/.match(line)
+ port = match[1]
+ end
+ end
+ end
if c.host != host
c.ssh.close if c.ssh
c.host = host
options = Net::SSH::Config.for(c.host)
user = options[:user] || Etc.getlogin
+ options[:port] = port
c.ssh = Net::SSH.start(host, user, options)
end
end
end
ファイルは、spec/.ssh-configから取得しており、ポート番号が12345だった場合、以下のような内容になります。
$ echo Prot 12345 > spec/.ssh-config
$ cat spec/.ssh-config
Prot 12345
Git
Chef・serverspec用のリモートリポジトリを作成します。
$ mkdir /var/lib/git
$ mkdir /var/lib/git/chef-cookbooks.git
$ cd /var/lib/git/chef-cookbooks.git
$ git --bare init
Chef・serverspec用のローカルリポジトリを作成します。
chef-repoのディレクトリに移動し、リポジトリの初期化を行い、ローカルリポジトリを作成します。
リモートリポジトリには何も入っていないため、ローカルリポジトリの内容を追加します
$ cd chef-repo/
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin /var/lib/git/chef-cookbooks.git
$ git push origin master
Jenkins
Jenkinsでのジョブは、jenkinsユーザにより実行されるため、パスワードなしでsudoを実行する設定を入れておきます。
$ visudo
jenkins ALL=(ALL) NOPASSWD: ALL # ←最終行に追加
Jenkinsにログイン後、[新規ジョブの作成]をクリックし、ジョブ名とビルドを設定します。
以下のように設定を入れ、[OK]をクリックします。
- ジョブ名: infrastructure-ci
- フリースタイル・プロジェクトのビルド オン
上記で作成したリポジトリと連携するため、[ソースコード管理]を以下のように設定します。
- ソースコード管理
- Git オン
- Repositories
- Repository URL: /var/lib/git/chef-cookbooks.git
- Credentials: なし
- Branches to build
- Branch Specifier (blank for 'any'): */master
リポジトリのgit pushを検知するために、[ビルド・トリガ]を以下のように設定します。
- ビルド・トリガ
- SCMをポーリング チェック
- スケジュール 下記に記載
- post-commitフックを無視 チェックオフ
スケジュールはcronのように設定できます。
今回は、15分毎にチェックが走るように、以下のように設定します。
H/15 * * * *
Chefとserverspecのコマンドを実行するために、[ビルド手順を追加]>[シェルの実行]をクリックし、[シェルの実行]を以下のように設定します。
container_id=`sudo ssh chef@192.168.xxx.xxx sudo docker run -d -p 22 ubuntu-sshd`
container_port=`sudo ssh chef@192.168.xxx.xxx sudo docker inspect --format="'{{(index (index .NetworkSettings.Ports \"22/tcp\") 0).HostPort}}'" $container_id`
sudo /root/.rbenv/shims/knife solo bootstrap root@192.168.xxx.xxx -p $container_port
echo Port $container_port > spec/.ssh-config
sudo /root/.rbenv/shims/rake ci:setup:rspec spec
sudo ssh chef@192.168.xxx.xxx sudo docker stop $container_id
sudo ssh chef@192.168.xxx.xxx sudo docker rm $container_id
spec/reports/*.xml
上記の設定ができたら、[保存]をクリックします。
上記のシェルスクリプトの内容は、今回の環境構築でメインとなるところです。
各コマンドについて説明していきます。
1行目は、docker runで起動すると、コンテナIDが出力されるため、$container_idに格納しています。
すでにserverspecのところで説明しましたが、Dockerを使った構成で問題となるのは、22番ポートをフォワードしているポート番号の取得にあります。
Dockerのコンテナから情報を取得するには、Remote APIを使った方法やdocker-clientを使う方法がありましたが、シンプルにしたかったので、 docker inspectコマンドを使いました。
以下のコマンドで、22番ポートをフォワードしているポート番号を取得することができます。
docker inspect --format='{{(index (index .NetworkSettings.Ports "22/tcp") 0).HostPort}}' <コンテナID>
2行目は、コンテナIDからポート番号を取得し、$container_portに格納しています。
3行目は、knife solo bootstrapコマンドにより、prepareとcookを同時に実行しています。また、-pオプションによりコンテナのポート番号を指定しています。
4行目は、コンテナのポート番号をspec/.ssh-configに出力しています。
5行目は、serverspecを実行していますが、内部でspec/spec_helper.rbが呼ばれており、spec/.ssh-configの値を取得しています。このポート番号利用してSSH接続が行われます。
また、テスト結果をレポートするため、ci_reporterを使用しています。
ci_reporterの使用には、事前に以下の準備をしておきます。
$ gem install ci_reporter
$ vi Rakefile
require 'ci/reporter/rake/rspec'
6行目は、docker stopコマンドにより、コンテナの停止を行っています。docker rmコマンドでいきなり削除してもよかったのですが、時々削除に失敗して異常終了することがあったためです。他サイトの情報を確認したところ、一度停止するほうがいいようです。
7行目は、docker rmコマンドにより、コンテナの削除を行っています。
実行
以下のように、Chefのファイルを修正後、commitし、git pushを実行します。
$ cd chef-repo/
$ vi site-cookbooks/apache2/recipes/default.rb
$ git add .
$ git commit -m "first commit"
$ git push origin master
スケジュールのタイミングによりgit pushの通知が検知されます。
通知を検知すると、JenkinsのワークスペースにChefのリポジトリがコピーされます。
ジョブが実行されたら、[ジョブ名]>[ビルド履歴]>[ビルド番号]へ移動し、[コンソール出力]をクリックします。ジョブが正常終了すれば、コンソール出力の末尾に「Finished: SUCCESS」と表示されます。
以下は、Dockerの起動、Chefとserverspecの実行、Dockerの削除が正常終了していることを示しています。
ちなみに、Chefのレシピはapache2のインストールしか書いていませんが、仮想サーバの起動から廃棄まで2分程度で終了していました。
SCMのポーリングが実行
ビルドします。 ワークスペース: /var/lib/jenkins/workspace/infrastructure-ci
Fetching changes from the remote Git repository
Fetching upstream changes from /var/lib/git/chef-cookbooks.git
Checking out Revision 187f8b995de5ddc053a18a3d114f1aa5018f1979 (origin/master)
[infrastructure-ci] $ /bin/sh -xe /tmp/hudson7478251361203469042.sh
++ sudo ssh chef@192.168.xxx.xxx sudo docker run -d -p 22 ubuntu-sshd
+ container_id=e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
++ sudo ssh chef@192.168.xxx.xxx sudo docker inspect '--format='\''{{(index (index .NetworkSettings.Ports "22/tcp") 0).HostPort}}'\''' e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
+ container_port=49164
+ sudo /root/.rbenv/shims/knife solo bootstrap root@192.168.xxx.xxx -p 49164
Bootstrapping Chef...
:
hef Client finished, 1/1 resources updated in 44.868323127 secono Port 49164
+ sudo /root/.rbenv/shims/rake ci:setup:rspec spec
rm -rf spec/reports
/root/.rbenv/versions/2.0.0-p451/bin/ruby -S rspec spec/192.168.xxx.xxxx/apache2_spec.rb
.
Finished in 0.10092 seconds
1 example, 0 failures
+ sudo ssh chef@192.168.xxx.xxx sudo docker stop e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
+ sudo ssh chef@192.168.xxx.xxx sudo docker rm e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
e0a57b71d3e7352f7b405c5f9f74f746f3b3611107ea5766d894d319884e1998
Recording test results
Finished: SUCCESS
実行が失敗した場合は、以下のようにエラーが表示され、そこでJenkinsのジョブは停止します。
Build step 'シェルの実行' marked build as failure
Finished: FAILURE
ジョブの途中で失敗した場合は、後続の処理が実行されないため、正常終了すれば仮想サーバの起動から、サーバ構築、テスト、仮想サーバの廃棄まですべて終了したことになります。