はじめに
ネットワークソリューション部の山田です。
インフラエンジニアとして日々業務に励んでおります。
今回は、自社で運用しているサーバを使用して、インフラ CI な環境を作って試してみた結果を本ブログにて公開いたします。
今更感は否めませんが、GitBucket から始まって、IaaS は DigitalOcean で プロビジョニングは Ansible で、という組み合わせは余り情報がなかったため、試してみた次第です。
インフラCIを試す際の参考となれば幸いです。
やりたいこと
GitBucket のリポジトリへ git push したら、インフラ構築/テストまで自動的に実行されるようにしたい。
登場人物と役割
GitBucket
GitHub クローンのバージョン管理ツール。
今回のインフラ CI では、各ソースコードをバージョン管理し、かつ Jenkins に対し API プッシュ(web hook)を行う。
Jenkins
CI ツール。今回のインフラ CI では、GitBucket の webhook を受けて、任意のシェルを叩く役割。
バージョン管理と仮想環境との仲介役、かつ Slack へジョブ結果の通知も行う。
Vagrant
仮想環境のフロントエンドツール。今回のインフラ CI では、DigitalOcean に対し仮想マシンを立ち上げたり削除したり、を担う役割。
DigitalOcean
海外製 IaaS。今回のインフラ CI では、仮想マシンを立ち上げる仮想基盤の役割。
Ansible
Python ベースの構成管理ツール。
今回のインフラ CI では、仮想マシン起動後のサーバプロビジョニングを行う役割。
Serverspec
サーバの状態をテストするフレームワーク。
今回のインフラ CI では、Ansible にてプロビジョニングされたサーバの Config 内容、プロセス起動状況などが正しい状態であるかどうかをテストするための役割。
Slack
Web コミュニケーションツール。
今回のインフラ CI では、Jenkins ジョブによるインフラ CI の開始、および Serverspec まで実行した一連のジョブの結果通知を受ける役割。
インフラ CI の流れ
大まかな一連の流れになります。
- GitBucket 上のリポジトリに、インフラ CI を構成する設定ファイル、およびソースコードをすべて commit する
- 1 のリポジトリに対し、git push
- git push を受けて GitBucket が Jenkins のジョブを webhook
- Jenkins ジョブが開始したことを Slack に通知
- Jenkins のジョブにて、Vagrant を通じて DigitalOcean の上で仮想マシンを新規に作成/起動するシェルスクリプトを実行
- DigitalOcean 上で仮想マシン起動後、Ansible を実行しサーバプロビジョニング
- プロビジョニング後、Serverspec にてサーバの状態をテスト
- ジョブ結果を Slack に通知
各バージョン等
- OS(CIサーバ): CentOS 6.6 32bit
- OS(仮想マシン): CentOS 6.5 64bit
- GitBucket: 3.1
- Jenkins: 1.613-1.1
- Vagrant: 1.7.2-1
- Ansible: 1.9.1-1
- Serverspec: 2.17.0
環境セットアップ
各コンポーネントごとに、環境のセットアップ内容を記載します。
なお、各コンポーネント(GitBucket、Jenkins 等)の導入方法については省略し、インフラ CI を行う上でのセットアップ内容に限定し記載します。
GitBucket
GitBucket のセットアップについて記載します。
なお、GitBucket は公式の war ファイルを展開し、すでに Web アクセスできている状態とします。
GitBucket – webhook
webhook を設定します。
任意のリポジトリにアクセスし、[Settings]→[Service Hooks]に Jenkins ジョブの URL を登録。
Jenkins
以下のプラグインを導入。
- GIT client plugin
- GIT plugin
- Git server plugin
- GitBucket Plugin
- Slack Notification Plugin
インフラ CI 用のジョブを作成し、以下の要領で設定。
GitBucket リポジトリ URL
Slack 通知
ソースコード管理
ビルド設定
キックスクリプト
FileName: jenkins_digitalocean-ci.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#!/bin/bash ## function of return checker. returnChecker() { if [[ $1 != 0 ]]; then vagrant destroy --force exit 1 fi } ## Vagrant up phase. vagrant_plugins="dotenv vagrant-digitalocean" for p in $vagrant_plugins; do vagrant plugin list |grep $p if [[ $? != 0 ]]; then vagrant plugin install $p fi done vagrant up --provider=digital_ocean; returnChecker $? vagrant ssh-config --host="do-ci.centos" > ssh-config ## Ansible pahse. ipaddr=`grep "HostName" ./ssh-config |awk '{print $2}'` sed -i "s/x.x.x.x/$ipaddr/" hosts ansible-playbook --private-key=id_rsa --inventory-file=hosts playbook.yaml; returnChecker $? ### Serverspec phase. . /etc/bashrc bundle install --path vendor/bundle; returnChecker $? bundle exec rake spec; returnChecker $? ## Vagrant destroy phase. vagrant destroy --force exit 0 |
スクリプトの補足。
- vagrant up 前に必要なプラグインがインストールされているかどうか確認、無ければインストール
- vagrant ssh-config で、仮想マシンへの ssh 情報を出力
- Ansible インベントリファイルである hosts に、ssh-config に記載されている DigitalOcean 仮想マシンのグローバルアドレスを挿入置換
- Serverspec の実行(rake spec)前に bundle install
- 最後に vagrant destroy で仮想マシン削除/破棄
- 途中の処理でリターンコードが 0 以外の場合には、その時点で vagrant destroy を実行
- しかしながら、予測しない箇所でスクリプトが終了し、仮想マシンを破棄しないまま課金され続ける可能性もあるため、注意が必要
Vagrant
Vagrant で DigitalOcean を操作するためには、以下プラグインを導入する。
- vagrant-digitalocean
また、Vagrant から DigitalOcean の API を叩く上で必要となる API-KEY/ACCESS-TOKEN などの情報を、Vagrantfile 内にハードコーディングすることを避けるため、dotenv も併せて導入する。
- dotenv
Vagrantfile の内容は以下。
FileName: Vagrantfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# -*- mode: ruby -*- # vi: set ft=ruby : Dotenv.load Vagrant.configure(2) do |config| config.vm.provider :digital_ocean do |provider, override| config.vm.hostname = "do-ci.centos" override.vm.box = "digital_ocean" override.vm.box_url = "https://github.com/smdahlen/vagrant-digitalocean/raw/master/box/digital_ocean.box" override.vm.hostname = "do-ci.centos" override.ssh.private_key_path = ENV['DIGITALOCEAN_SSH_KEY'] provider.token = ENV['DIGITALOCEAN_TOKEN'] provider.region = "sgp1" provider.image = "centos-6-5-x64" provider.size = "512mb" end end |
dotenv 定義ファイルの内容
※値は環境に応じて適宜変更
FileName: .env
1 2 3 4 |
DIGITALOCEAN_CLIENT_ID="xxxxx" DIGITALOCEAN_API_KEY="xxxxx" DIGITALOCEAN_SSH_KEY="./id_rsa" DIGITALOCEAN_TOKEN="xxxxx" |
DigitalOcean
DigitalOcean では、アカウントを作成し、前述に記載した .env に定義する各アクセス情報を取得する。
Ansible
今回はテストケースでの実施のため、Ansible で行うサーバプロビジョニングは最低限の内容としてます。
- httpd インストール(RPM)
- httpd 起動
- httpd 自動起動有効化
Ansible PlayBook の内容は以下。
FileName: playbook.yaml
1 2 3 4 5 6 7 8 9 |
- hosts: all user: root tasks: - name: httpd install. yum: name=httpd state=latest - name: httpd service start running. service: name=httpd state=started - name: httpd service startup register. service: name=httpd enabled=yes |
Ansible では、プロビジョニング対象サーバの ssh フィンガープリントが変更された場合、デフォルトでは PlayBook の実行が停止してしまう。
これを回避するため、Ansible の設定ファイルである ansible.cfg に設定を追加する。
FileName: ansible.cfg
1 2 |
[defaults] host_key_checking = False |
Ansible のインベントリファイルも用意する。
インベントリファイルにはプロビジョニング対象サーバの宛先を指定する必要があるが、なんらかのクラウドサービスを利用する場合、仮想マシン作成/起動のたびに新たなグローバルアドレスが割り当てられる可能性がある。
このため、インベントリファイルである hosts の宛先は x.x.x.x とし、前述した Jenkins ジョブでキックするスクリプトにてこれをその都度置換する。
FileName: hosts
1 2 |
[web] x.x.x.x |
Serverspec
Serverspec では、Ansible でのサーバプロビジョニング後、サーバが正しい状態であるかをテストする。
- httpd パッケージがインストールされているかどうか
- httpd サービス/プロセスが起動しているかどうか
- httpd サービス/プロセスの自動起動が有効となっているかどうか
- TCP ポートの 80 番が Listen しているかどうか
FileName: httpd_spec.rb
1 2 3 4 5 6 7 8 9 10 11 |
require 'spec_helper' describe package('httpd'), :if => os[:family] == 'redhat' do it { should be_installed } end describe service('httpd'), :if => os[:family] == 'redhat' do it { should be_enabled } it { should be_running } end describe port(80) do it { should be_listening } end |
本インフラ CI における Serverspec では、テストを行うサーバに対し ssh 経由でテストを実行する。
デフォルトでは Serverspec を実行するユーザの ~/.ssh/config にて秘密鍵等の情報を参照するが、クラウドサービスを利用する場合は都度情報が変わる可能性がある。(インスタンスを都度作成/破棄するため)
このため、上述したスクリプト内で vagrant ssh-config を出力した config を Serverspec から参照するよう、spec_helper.rb に手を加えている。
その一部を抜粋する。
FileName: spec_helper.rb
1 2 |
## options = Net::SSH::Config.for(host) options = Net::SSH::Config.for("do-ci.centos", files=["ssh-config"]) |
なお、Jenkins からキックするスクリプトでは、Serverspec 実行前に必要 gem をインストールするため bundle install を行っている。
FileName: Gemfile
1 2 3 |
source "https://rubygems.org" gem 'rake' gem 'serverspec' |
Slack
Slack では、Jenkins ジョブの開始/終了を通知するために利用する。
前述した Jenkins ジョブ設定箇所を参考とし、プラグインの設定を行う。
Build
環境がひと通り揃ったところで、ビルドを行ってみる。
ビルドは Jenkins ジョブメニューの “ビルド実行” にて手動で試せるが、git push してビルドが走る流れが CI たる動きである。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ ll 合計 48</p> -rw-rw-r-- 1 yamada yamada 59 5月 29 02:07 2015 Gemfile -rw-rw-r-- 1 yamada yamada 840 5月 29 02:29 2015 Gemfile.lock -rw-rw-r-- 1 yamada yamada 87 5月 29 01:58 2015 README.md -rw-rw-r-- 1 yamada yamada 685 5月 28 22:22 2015 Rakefile -rw-rw-r-- 1 yamada yamada 700 5月 29 01:59 2015 Vagrantfile -rw-rw-r-- 1 yamada yamada 37 5月 29 01:03 2015 ansible.cfg -rw-rw-r-- 1 yamada yamada 14 7月 4 00:00 2015 hosts -rw------- 1 yamada yamada 1675 5月 20 01:26 2015 id_rsa -rw------- 1 yamada yamada 395 5月 29 00:25 2015 id_rsa.pub -rwxr-xr-x 1 yamada root 830 7月 14 00:15 2015 jenkins_digitalocean-ci.sh -rw-rw-r-- 1 yamada yamada 262 5月 25 00:35 2015 playbook.yaml drwxrwxr-x 3 yamada yamada 4096 6月 3 00:11 2015 spec |
↑が今回のインフラ CI にて利用しているリポジトリの内容。
ここに git push を行う。
1 2 3 |
$ git add jenkins_digitalocean-ci.sh $ git commit -m "commit for ci build." $ git push |
git push を契機に、Jenkins ジョブが走る。
Jenkins ジョブが開始されたこと、Slack に通知が飛ぶ。
Jenkins ジョブのコンソール出力にて、スクリプトがキックされ Vagrant にて DigitalOcean の Droplet が作成される。
実際に作成されたことを DigitalOcean のコントロールパネルからも確認。
その後、Ansible の PlayBook が走っていることをコンソール出力にて確認。
failed なく完了した模様。
続いて、bundle install されたのち、Serverspec も正常終了していることを確認。
Slack へ Jenkins ジョブが Success の通知が飛ぶ。
念のため DigitalOcean の Droplet が破棄されている確認。
↑先ほど存在していた Droplet が消されている模様。
最後に
これまで全て手動で構築→テストをしていた身としては、大変新鮮な体験をした感覚でした。
1度インフラ CI の環境を作ってしまえば、構成変更に対して常に構築→テストを自動化できることになるため、”楽” で “確実” の一言に尽きます。
加えて、インフラ CI の環境づくりがそもそも楽しくて、すべてキレイに動いた時はさらに楽しかったです。
次なる個人研究の分野としては、何といっても “コンテナ”、そして Coreos を考えています。
コンテナ単位のサーバ実運用を見据え、アウトプットできればと考えてます。
以上です。