Docker ＋ Itamae でレシピ作成の試行錯誤を 高速化 #itamae #docker

# Docker ＋ Itamae でレシピ作成の試行錯誤を 高速化 :rocket: #itamae #docker
## <i class="fa fa-cubes" style="font-size:1em;"></i> 概要
:whale:Docker ＋ :sushi:Itamae でレシピ作成の試行錯誤を 高速化 :rocket: します

## <i class="fa fa-flag-checkered" style="font-size:1em;"></i> 目的
* Itamae のレシピ作成時間を短縮
    * 特に工夫をしないと、レシピのプロビジョニングリトライは非常に時間がかかる
        * 例えば、Ubuntu 環境に rbenv + ruby(2.1.3) + rails(4.1.8) 環境をインストールしたところ 15 分かかりました
          何も工夫をしなければ再試行に + 15 分かかります
    * 普段のプログラミング同様、何事も一発で成功することは少ない。試行錯誤は発生する

※ Itamae に限った話ではないので、プロビジョニングツール全般に応用可能

## 前提
* Vagrant で構築した CoreOS 環境 の上に Docker で作成した Ubuntu 14.04 のコンテナを作成し、その環境を利用します [^1]
* Docker の基本操作の知識が必要です [^2]
* Immutable なレシピ（冪等性を気にしない）

## <i class="fa fa-flask" style="font-size:1em;"></i> 試行
### <i class="fa fa-book" style="font-size:1em;"></i> 要件
* OS: Ubuntu 14.04
* rbenv + ruby 2.1.3
* rails 4.1.8
* WebServer: WEBric
* DB: sqlite
* Todo アプリケーションを scaffold して rails 起動

### <i class="fa fa-files-o" style="font-size:1em;"></i> 手順
* 下記記事の手順に従い、 Ubuntu のコンテナを立ち上げる
    * <i class="fa fa-external-link" style="font-size:1em;"></i> [Vagrant で Windows 上の VirtualBox に CoreOS のインスタンスを作成し、 Ubuntu 14.04 LTS のコンテナを作成し、 SSH で接続可能にする](http://qiita.com/tbpgr/items/800ad117e234b38f0a80)
        * docker run 時に `-p 80:3000 ` のオプションも追加して実行しておく ( WEBric のポートフォワード )

* イメージの確認

~~~bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              trusty              04c5d3b7b065        8 days ago          192.7 MB
~~~

* 作成した Ubuntu 14.04 コンテナを確認

~~~bash
$ docker ps
CONTAINER ID        IMAGE                  COMMAND               CREATED             STATUS              PORTS                                        NAMES
0e50ba901b58        sample/ubuntu:trusty   "/usr/sbin/sshd -D"   4 seconds ago       Up 3 seconds        0.0.0.0:80->3000/tcp, 0.0.0.0:2222->22/tcp   berserk_ardinghelli
~~~

* こちらの Itamae recipe を利用して Ruby 2.1.3 + Rails 4.1.8 環境を構築します
    * <i class="fa fa-external-link-square" style="font-size:1em;"></i> [Itamae | Itamae を使って Ubuntu 14.04 LTS 環境へ Ruby 2.1.3 , Rails 4.1.8 のインストール](http://qiita.com/tbpgr/items/1674798179ff223b1d86)

~~~bash
sudo docker run -d -p 2222:22 -p 80:3000 sample/ubuntu:trusty /usr/sbin/sshd -D
~~~

* Rails 環境の構築まで成功したら、 Docker の commit を実行してイメージを作成します

~~~bash
$ docker commit <container name | id> rails_sample
~~~

~~~bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
rails_sample        latest              777715acc917        26 seconds ago      546.9 MB
sample/ubuntu       trusty              544a982d72bf        51 minutes ago      255.3 MB
ubuntu              trusty              04c5d3b7b065        8 days ago          192.7 MB
~~~

* コンテナを停止・削除して、 commit した新規イメージでコンテナを再作成します

~~~bash
$ docker stop <container name | id>
$ docker rm <container name | id>
$ sudo docker run -d -p 2222:22 -p 80:3000 rails_sample /usr/sbin/sshd -D
$ docker ps
CONTAINER ID        IMAGE                 COMMAND               CREATED             STATUS              PORTS                                        NAMES
3d013b1fe510        rails_sample:latest   "/usr/sbin/sshd -D"   4 seconds ago       Up 3 seconds        0.0.0.0:80->3000/tcp, 0.0.0.0:2222->22/tcp   elegant_colden
~~~

* rails 環境構築用のレシピに Todo アプリケーションを scaffold して、 Rails を起動するまでの記述を追加します

変更前レシピ

~~~bash
execute 'apt-get update' do
  command 'apt-get update -y'
end

package "build-essential" do
  action :install
end

include_recipe 'rtn_rbenv::user'
~~~

変更後レシピ

~~~bash:recipe.rb
execute 'apt-get update' do
  command 'apt-get update -y'
end

package "build-essential" do
  action :install
end

include_recipe 'rtn_rbenv::user'
# rails setup の include を追加
include_recipe './rails_setup.rb'
~~~

~~~bash:rails_setup.rb
package "libsqlite3-dev" do
  action :install
end

execute "setup rails" do
  cwd "/home/core"
  user "core"
  command <<-EOS
. /home/core/.bash_profile
rbenv rehash
rails new todo --skip-bundle
cd todo
cat << EOT >> Gemfile
gem 'execjs'
gem 'therubyracer'
EOT
bundle install
rails generate scaffold Tudu name:string estimate:integer results:integer complete:boolean
bundle exec rake db:migrate
bundle exec rails server -d
  EOS
end
~~~

※ model 名 todo は rails に予約されているので、 tudu にしています

* itamae 実行

~~~bash
$ bundle exec itamae ssh -p 2222 -j node.json -i /path/to/your/key/key_name -h 172.17.8.101 -u core rails_setup.rb
~~~

* 起動確認

![rails1.png](https://qiita-image-store.s3.amazonaws.com/0/43480/64614c0b-3b39-e3ea-a462-32ab3698dd03.png)

データを2件登録してみました

![rails2.png](https://qiita-image-store.s3.amazonaws.com/0/43480/0d33727d-7049-e427-3c29-2a34ed8f9697.png)

## :hourglass_flowing_sand: 実行時間の比較 (:chicken: + :chicken: => :chicken::chicken:) <i class="fa fa-arrows-h" style="font-size:1em;"></i> (:baby_chick: + :baby_chick: => :chicken:)
### :chicken: + :chicken: => :chicken::chicken:
* 途中で Docker のイメージを作成せずに、 itamae のレシピを全て再実行した場合

~~~
(15 分 × 2) + 2 分 = 32 分
~~~

### :baby_chick: + :baby_chick: => :chicken:
* 途中で Docker のイメージを作成した場合（今回紹介した手順）

~~~
15 分 + 2 分 + N 分 = 17 + N 分
~~~

N 分 は新規イメージの作成にかかる時間。
今回の例だと docker commit の実行は 10 秒程度で終わりました。
その他オペレーション込みで 1 分程度。

たった 1 回の試行でここまで差がつくので、
試行回数が多ければもっと大きな差がつくでしょう。
普段のプログラミングと同様、レシピの作成が一発で成功する方が少ないと思うのですが、
皆さんいかがでしょうか？

効率的にレシピを作成するノウハウを余り見かけないので、今回の記事を作成しました。
**私は基本的に（リアルワールドで）誰とも情報交換していないこともあり、情報弱者の可能性は高く、
今回紹介した方法より優れた方法はたくさんあるかもしれません。
この記事への突込みが入って、より優れた方法を引き出すきっかけになれば、
それはそれでいいかな、と思います。**

## :runner: 作業フロー

1. レシピ内の最小ステップ [^3] をインクリメンタルに作成
    1. dry-run
        1. 成功
            1. 次のステップへ
        1. 失敗
            1. レシピ修正
    1. プロビジョニング実行
        1. 成功
            1. コンテナをイメージ化
            1. 新規イメージで新規コンテナ作成
        1. 失敗
            1. レシピ修正
            1. プロビジョニングに失敗したらイメージからコンテナを再作成してリトライ
1. 新規コンテナで作業再開
1. 全てのレシピが完成するまで 1-2 を繰り返す
1. レシピ全体を通して実行
1. レシピが完成したら作業用のイメージ、コンテナを破棄

## :memo: 補足
* Vagrant + sahara などでもスナップショットを活用して作業用の中間イメージを作成できるが、 docker ほど早くない。
  vagrant + sahara のみを使っていて、 Docker を利用したことがない方は是非試してみてください。

* レシピ試行の際の1回あたりの実行時間が長いと、単純に時間がかかるという以外に下記の弊害がある
    * :dizzy_face: 思考が途切れる
    * :sleepy: 眠くなる

試行錯誤を繰り返すたびに、作業のパフォーマンスが下がっていく。

## :smiling_imp: 戯れ言
* :whale: Docker を利用した記事で Rocket ( :rocket: ) のアイコンを使うブラックジョーク [^4]

## <i class="fa fa-arrow-right" style="font-size:1em;"></i> 外部資料
* <i class="fa fa-github-square" style="font-size:1em;"></i> [itamae | GitHub](https://github.com/ryotarai/itamae)
* <i class="fa fa-external-link-square" style="font-size:1em;"></i> [Docker](https://www.docker.com/)

## :books: 脚注
[^1]: <i class="fa fa-external-link" style="font-size:1em;"></i> [Vagrant で Windows 上の VirtualBox に CoreOS のインスタンスを作成し、 Ubuntu 14.04 LTS のコンテナを作成し、 SSH で接続可能にする](http://qiita.com/tbpgr/items/800ad117e234b38f0a80)
[^2]: <i class="fa fa-external-link" style="font-size:1em;"></i> [Docker | 基本操作コマンドについて](http://qiita.com/tbpgr/items/5370bf6bc945c3cf3ec0)
[^3]: recipe の resource block 1 つごとがよいか？
[^4]: <i class="fa fa-external-link-square" style="font-size:1em;"></i> [CoreOS is building a container runtime, Rocket](https://coreos.com/blog/rocket/)