BLOG

Ansibleに入門してRubyをビルドさせてみるの巻 前編

kuroda

ご挨拶

ご無沙汰してしまいました、kurodaです。

最近、仕事の準備としてコンテナやら仮想マシンやらを作ってその中であれこれ設定したりビルドしたりということが増えてきました。世間ではそういう作業を自動化するために構成管理ツールを使う事が何年も前から流行っていましたが、僕もようやく重い腰を上げてみたというのが今回のお話です。

構成管理ツールと一口に言っても色々ありますが、管理対象にソフトをインストールしなくても良いと言う点が気に入って、Ansibleを選びました。

シナリオ

さて、ここでは管理対象としてlxcのコンテナが2つあり、そいつらに最新版のRubyを野良ビルドしてインストールするというのを課題として、これをAnsibleで解決してみます。

前提として、管理対象のコンテナでは以下の準備が整っているとします。

  • Ubuntuがインストールされている。バージョンは、片方が14.04(最新LTS。通称trusty)で、もう片方が15.10(最新版。通称Wily)
  • それぞれ、ホスト名は trusty.l.syngram.co.jp, wily.l.syngram.co.jp とする
  • kurodaという名前のユーザーアカウントが登録されており、既定のパスワードでsudoもできる。パスワードは取りあえずどちらも同じ
  • ssh-serverが動いていて、公開鍵認証で接続できる
  • /etc/apt/sources.list には deb-src 行も入っている(ubuntuホストにlxc-createでubuntuを入れると入っていないので追加する)
  • Ansibleの動作に必要なので python2 をインストール済み(いまは python3 だと動きません)

この状況で、以下のような作業を自動化してみましょう。

  • rubyのビルドに必要なdebパッケージを apt-get install する
  • ダウンロード済みのruby-2.2.3.tar.gzを各コンテナで展開する
  • /opt/ruby/2.2.3 にインストールされるように configure して make して make install する

準備

最初に、作業をするマシンにAnsibleその物を用意します。今回は管理対象のコンテナを置いてあるホストマシン(charlieという名前で、Ubuntu 15.10が動いています)で作業しました。Ansibleには管理対象にsshで接続させるので、それが可能なら特にコンテナホストである必要もありません。

Ansibleは以下のように公式のパッケージを使ってインストールします。

kuroda@charlie:~$ sudo apt-get ansible

インベントリに管理対象を列挙する

つぎに、管理対象のホスト名やアドレスを列挙する インベントリ というものを用意します。名前は慣例的に hosts とします( /etc/hosts と紛らわしいので好きではないのですが)

[target]
trusty.l.syngram.co.jp
wily.l.syngram.co.jp

実際の作業手順はYAML形式で書きます。これはプレイブックと呼ばれます。
今回のプレイブック main.yml の冒頭部はこんな感じです。

---
- hosts: target                 # <- 操作対象のグループ名(インベントリに書いた物)
  tasks:                        # <- 作業手順は配列で書きます
    - name: apt-get update      # <- 各手順には任意で名前(タイトル)を付けられます
      apt:                      # <- apt-getの
        update_cache: yes       # <- updateと
        upgrade: dist           # <- dist-upgradeを実行します
        cache_valid_time: 3600  # <- 前回実行時から1時間経過していない場合は実行しません
      become: true              # <- 上記手順を sudo で実行します

これだけで、インベントリの target グループに列挙した2台のホスト上で、 sudo apt-get update; sudo apt-get dist-upgrade を実行してくれます。
プレイブックは以下のように実行します。

$ ansible-playbook -i hosts --ask-become-pass main.yml
SUDO password:

--ask-become-pass オプションを付けているため、targetでsudoするときのパスワードを聞いてくるので入力します。

kuroda@charlie:~/work/ansible/single$ ansible-playbook -i hosts --ask-become-pass main.yml 
SUDO password: 

PLAY [target] ***************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [trusty.l.syngram.co.jp]
ok: [wily.l.syngram.co.jp]

TASK: [apt-get update] ******************************************************** 
ok: [wily.l.syngram.co.jp]
ok: [trusty.l.syngram.co.jp]

PLAY RECAP ******************************************************************** 
trusty.l.syngram.co.jp     : ok=2    changed=0    unreachable=0    failed=0   
wily.l.syngram.co.jp       : ok=2    changed=0    unreachable=0    failed=0   

残りの手順も同じ要領で書いてしまいます。

# sudo apt-get build-dep ruby-full する
    - name: build-dep ruby-full  
      apt:
        name: ruby-full
        state: build-dep
      become: true

# build-depで入らないライブラリを、 sudo apt-get instal libXX-dev する
    - name: install libXX-dev   
      apt:
        name: "{{ item }}"      #  <- こうやって書いておくと、
        state: installed
      with_items:               #  <- 与えた配列でループしてくれます
        - zlib1g
        - libreadline-dev
        - libssl-dev
      become: true

# tar-ballの展開先にするビルド用ディレクトリを作って
    - name: mkdir for unarchive
      file:
        path: /tmp/ansible-build-ruby
        state: directory

# ローカルに置いたruby-2.2.3.tar.gz をリモートに展開します
    - unarchive:                
        src: ruby-2.2.3.tar.gz
        dest: /tmp/ansible-build-ruby/

# お馴染みの ./configure と、
    - command: ./configure --prefix=/opt/ruby/2.2.3
      args:
        chdir: /tmp/ansible-build-ruby/ruby-2.2.3

# make に、
    - command: make
      args:
        chdir: /tmp/ansible-build-ruby/ruby-2.2.3

# make install です。
    - command: make install
      become: true
      args:
        chdir: /tmp/ansible-build-ruby/ruby-2.2.3

# 使い終わったビルド用ディレクトリを削除
    - name: cleanup build directory
      file:
        path: /tmp/ansible-build-ruby
        state: absent

記述の具体的な意味については、 公式のドキュメント を参照してください。
手順の種類を表す apt, command, unarchive, file はそれぞれモジュールと呼ばれるもので、ドキュメントの Module Index からたどったりサイドバーの検索窓から検索したりすれば見つかります。例えば、apt モジュールと file モジュールのドキュメントは、それぞれ http://docs.ansible.com/ansible/apt_module.htmlhttp://docs.ansible.com/ansible/file_module.html にあります。

プレイブックの書式は純粋なYAMLではなく、Jinja2というテンプレートエンジンの文法で書けます(ライブラリのインストールで書いている {{ item }} がそれ)。

ansible-playbook コマンドに指定したプレイブックのあるディレクトリが、実行時の基準ディレクトリになります。なので、事前にダウンロードした ruby-2.2.3.tar.gz もそこに置いておきます。

作業ディレクトリには hosts main.yml ruby-2.2.3.tar.gz の3つを置くことになります。

kuroda@charlie:~/work/ansible/single$ ls
hosts                  main.yml         ruby-2.2.3.tar.gz

実行すると、こんな感じで進みます

kuroda@charlie:~/work/ansible/single$ ansible-playbook -i hosts --ask-become-pass main.yml 
SUDO password: 

PLAY [target] ***************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [trusty.l.syngram.co.jp]
ok: [wily.l.syngram.co.jp]

TASK: [apt-get update] ******************************************************** 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

TASK: [build-dep ruby-full] *************************************************** 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

TASK: [install libXX-dev] ***************************************************** 
changed: [trusty.l.syngram.co.jp] => (item=zlib1g,libreadline-dev,libssl-dev)
changed: [wily.l.syngram.co.jp] => (item=zlib1g,libreadline-dev,libssl-dev)

TASK: [mkdir for unarchive] *************************************************** 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

TASK: [unarchive ] ************************************************************ 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

TASK: [command ./configure --prefix=/opt/ruby/2.2.3] ************************** 
changed: [wily.l.syngram.co.jp]
changed: [trusty.l.syngram.co.jp]

TASK: [command make] ********************************************************** 
changed: [wily.l.syngram.co.jp]
changed: [trusty.l.syngram.co.jp]

TASK: [command make install] ************************************************** 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

TASK: [cleanup build directory] *********************************************** 
changed: [trusty.l.syngram.co.jp]
changed: [wily.l.syngram.co.jp]

PLAY RECAP ******************************************************************** 
trusty.l.syngram.co.jp     : ok=10   changed=9    unreachable=0    failed=0   
wily.l.syngram.co.jp       : ok=10   changed=9    unreachable=0    failed=0   

これで、ビルドしたRubyがコンテナの中で動くようになりました。

kuroda@trusty:~$ /opt/ruby/2.2.3/bin/ruby -v -e 'p(require("openssl"))'
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]
true

余談

実は、そもそもの前提にしているコンテナ2つ自体を用意するプレイブックも作ったのですが、手元の環境固有の条件もあり、今回は話が長くなってしまうので省略です。実際の作業ディレクトリは、実はこんな風になっています:

kuroda@charlie:~/work/ansible/single$ ls
destroy_containers.sh  init-containers  rollback_containers.sh
hosts                  main.yml         ruby-2.2.3.tar.gz

今回のサンプルを全部まとめてGitHubに用意しましたが、おおまかには、以下のような感じになっています

  • コンテナのファイルシステムは zfs で作った /zroot/lxc/ 以下に配置しています。デフォルトでこのパスを使うように設定していて、 lxc-create ではオプションに -B zfs を与えています。
  • init-containers のプレイブックの中では既定のパスワードを usermod で指定しています(GitHubに置いたリポジトリ中のファイルに書いてあるのはダミーなので実際には使えません)
  • destroy_containers.sh はコンテナ2つを破棄するための物です
  • rollback_containers.sh は、コンテナ2つを破棄する代わりに、init-containers/main.yml 実行直後の状態に戻す物で、zfsのsnapshotを使っています。コンテナを10回くらい作り直した後で用意しました(汗。
  • このサンプルを作った頃、 Ubuntuの日本サーバーが更新中だったのかハッシュエラーが発生して apt-get できなかったので、 /etc/apt/sources.list 自体も適当なミラーを指定したファイルを別途用意して設置しています
  • コンテナ2つに同じ作業手順をいくつか条件を変えて実行するため、別途タスクファイル setup-lxc-ubuntu.yml を作り、これをmain.ymlから変数の値を変えて include して呼び出しています
  • なぜか lxc_container モジュールがSEGVしてしまうので、command モジュールから lxc-attach を使ってます

次回予告

さてさて。今回用意したプレイブックでは、以下のような問題が残っています。

  • 1つのファイルに手順を全部書くと、整理が付かず再利用しづらい
  • --ask-become-pass ではパスワードを1つしか入力できないので管理対象のパスワードを統一する必要がある
  • Rubyのバージョンが変わったときに、プレイブックの中をあちこち直す必要があって大変

これを解決するために、Ansibleの公式ドキュメントがオススメしているベストプラクティスにのっとってプレイブックを分割して、色々工夫してみようというのが次回のお話です。
続きます!(なるべく早いうちに..)