無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

AnsibleのGroupごと&状態ごとにServerspecのテストを書く

最近開発に参加したサービスで、サーバの構築はAnsibleのPlaybookで自動化されているのですが、構築後のテストは手作業で行っている環境がありました。それだけなら、正常にサービスが動作している状態をServerspecで定義すればテストできそうですが、このサービスはアップデートの際にいくつかのプロセスを停止する必要がありました。この停止後のチェックも手作業でした。

              一部プロセスの停止
    動作中 -----------------------> 更新可能
  (Running)                       (Updatable)
      ^                                |
      |                                |
      +--------------------------------+
              一部プロセスの再起動

このような状況で、Serverspecのテストをうまく書く方法について考えてみました。

要件

今回の事例について、以下のように要件を定義しました。ちなみに、片方だけ実現できれば良いという場合は、今回のサンプルコードを多少削れば実現できます。

  1. Ansibleのグループごとに、Serverspecでテストを書ける。また、一部のホストについて、そのホスト特有のテストを書くこともできる(Master-Slave構成の、Masterのみに実施したいテストなど)。
  2. 「動作中(Running)」、「更新可能(Updatable)」など、ホストの状態に応じた複数のテストを書ける。

最終的に構築される環境

Rakefileの書き方を工夫して、以下のいずれかのコマンドでServerspecのテストを実行できるようにします。

% bundle exec rake inventory=<inventory file> status=<status name> spec:all
% bundle exec rake inventory=<inventory file> status=<status name> spec:<group name>
% bundle exec rake inventory=<inventory file> status=<status name> spec:<host name>

テストの内容を記載した*_spec.rbファイルは、以下のディレクトリに配置します。ちなみに、Ansibleのインベントリファイルは、Serverspecのディレクトリの外にあっても問題ありません。

serverspec/
  ├ Gemfile
  ├ Gemfile.lock
  ├ Rakefile
  ├ <inventory file>
  ├ spec/
  │   ├ spec_helper.rb
  │   ├ <group name>/
  │   │   └ <status name>/
  │   │        └ *_spec.rb
  │   └ hosts/
  │        └ <host name>/
  │             └ <status name>/
  │                  └ *_spec.rb
  └ vendor/

構築手順

以下の例では、~/serverspec ディレクトリ以下に、必要なすべてのファイルを配置するものとします。bundler を使わない場合は bundle exec を省いてください。

1. ~/serverspec ディレクトリの作成
2. ~/serverspec/Gemfile を作成し、以下の内容を記載
source 'https://rubygems.org'
gem 'serverspec'
gem 'rake'
3. bundle installコマンドの実行
% bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/.......
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Installing rake 10.4.2
Installing diff-lcs 1.2.5
Installing multi_json 1.11.2
Installing net-ssh 2.9.2
Installing net-scp 1.2.1
Installing net-telnet 0.1.1
Installing rspec-support 3.3.0
Installing rspec-core 3.3.2
Installing rspec-expectations 3.3.1
Installing rspec-mocks 3.3.2
Installing rspec 3.3.0
Installing rspec-its 1.2.0
Installing sfl 2.2
Installing specinfra 2.43.4
Installing serverspec 2.23.1
Using bundler 1.10.4
Bundle complete! 2 Gemfile dependencies, 16 gems now installed.
Bundled gems are installed into ./vendor/bundle.
4. serverspec-initコマンドで大枠を作成(サンプルが不要ならパスして良い)
% bundle exec serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: n
Input target host name: sample.example.com
 + spec/
 + spec/sample.example.com/
 + spec/sample.example.com/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec
5. AnsibleのインベントリファイルからRSpecのタスクを自動生成できるように、Rakefileを編集

RakefileはGistにアップロードしたので、こちらをご参考ください。

Serverspec Rakefile for creating tasks from Ansible inventory file with server status

6. 上記のディレクトリ構成に従って、Ansibleのグループごと、またはホストごとの設定を *_spec.rb ファイルに記載

テストの作成方法は、普通にServerspecを使う場合と特に変わりないと思います。

7. rake -T コマンドで動作確認

インベントリファイルとしては、今のところ以下のような簡単なものだけをサポートしています。以下は Inventory — Ansible Documentation から抜粋した例です。

mail.example.com

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
three.example.com

この状態で rake -T コマンドを実行すると、正しくタスクを生成できているか確認できます。設定に成功した場合は、以下のように出力されるはずです。

% bundle exec rake inventory=./hosts status=running -T
rake spec:bar.example.com              # Run tests for host 'bar.example.com'
rake spec:dbservers:one.example.com    # Run tests for group 'dbservers'
rake spec:dbservers:three.example.com  # Run tests for group 'dbservers'
rake spec:dbservers:two.example.com    # Run tests for group 'dbservers'
rake spec:foo.example.com              # Run tests for host 'foo.example.com'
rake spec:mail.example.com             # Run tests for host 'mail.example.com'
rake spec:one.example.com              # Run tests for host 'one.example.com'
rake spec:three.example.com            # Run tests for host 'three.example.com'
rake spec:two.example.com              # Run tests for host 'two.example.com'
rake spec:webservers:bar.example.com   # Run tests for group 'webservers'
rake spec:webservers:foo.example.com   # Run tests for group 'webservers'

Rakefileの解説

AnsibleとServerspecを組み合わせて使うための既存ツール

先行事例としては、@さんが作成されている「Ansibleの設定ファイルを使ってServerspecを実行するテンプレート作成用Gem(ansible_spec)」があります。できることは、リンク先のタイトルがほぼ完全な説明になってます。

今回はサーバの状態ごとにテストを分けたかった、すでに存在するAnsible Playbookと密結合にしたくなかった、などの理由で ansible_spec は使いませんでした。

Rakeに引数を渡す方法

Rakeに引数を渡す方法については、rake でのコマンドライン引数の扱い の説明がとても参考になりました。主に以下の2つの方法があるということで、今回は後者を採用しました。

  1. タスク毎に引数を定義し、受け取る。
  2. 環境変数経由で受け取る。

記法としては、(rake spec:all[running]みたいに)鍵括弧内に引数を記載する前者の方が簡潔なのですが、この方法は「直接実行されるタスクしか引数を受け取れない」という欠点があります。今回は、タスク名でグループを指定したら、そのグループに属するホストすべてにテストを実行する、という動作を実現したかったので、この方式は採用できませんでした。

インベントリファイルの読み込み

インベントリファイルはINIファイルに似た形式なので、INIファイル用のパーサで読み込めるかと思い、PythonやRubyでのINIファイルの参照 - Qiita を読んで inifile を試してみました。

で、結果としては、key=value形式になっていない行(つまりkeyだけの行)を読み込もうとすると、以下のようなエラーが出て駄目でした。

irb(main):004:0> hosts = IniFile.load('./hosts')
IniFile::Error: Could not parse line: "web01"
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:578:in `error'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:532:in `block in parse'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:515:in `each_line'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:515:in `parse'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:400:in `parse'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:128:in `block in read'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:128:in `open'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:128:in `read'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:80:in `initialize'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:31:in `new'
    from /Library/Ruby/Gems/2.0.0/gems/inifile-3.0.0/lib/inifile.rb:31:in `load'
    from (irb):4
    from /usr/bin/irb:12:in `<main>'

結局、今回のRakefileではインベントリファイルのパース処理を自前で作成しました。ただ、これは単純な構造のインベントリファイルしかサポートしていないので、導入先の環境によっては色々修正する必要がありそうです。ここの汎用性は今後の課題ですね。

参考文献

Ansible Meetup in Tokyo 2015.09レポート 〜Ansible 2.0の機能紹介、Ansible 2.0で組むKubernetesクラスタなど

f:id:muziyoshiz:20150914234606p:plain
  • イベント名: Ansible Meetup in Tokyo 2015.09
  • 開催日時: 2015-09-14(月)
  • 会場: 日経ビル6F 日経カンファレンスルーム(大手町)
  • Webサイト: Ansible Meetup in Tokyo 2015.09 - connpass

最近、Ansibleを業務で使い始めて色々調べていたところに、ちょうどAnsible Meetupが開催されたので参加してきました。去年も9月に開催されていて、2回目みたいですね。

今回の目玉は、僕も参考にさせて頂いた「入門Ansible」著者の若山史郎さん(@r_rudi)による、Ansible 2.0の新機能・変更点紹介でした。Ansible 2.0についての情報をコンパクトにまとめて紹介してくれて勉強になったので、僕のように2.0系の予備知識ゼロの人にはスライド資料(と以下のメモ)をおすすめしたいと思います。あと、個人的には、Ansible+Kubernetesの無理矢理感や、LTでの「Vagrant環境のAnsibleを速くしたい」という話が面白かったです。

他は事例紹介が多かったのですが、Ansibleの使い方は現場によって結構マチマチな感じですね。昨日の記事で書いたような、AnsibleとServerspecを組み合わせて使う方法に関する講演もいくつかあったのですが、この組合せに関する、誰もが納得するベストプラクティスは無さそうですね……。日経の梅崎さん(@bungoume)の講演で出てきたディレクトリ構成は、ansible_specに似てそうでしたが、質疑応答で質問してみたところ、ansible_specのことはご存じなく、自前でhelperを作っているとのことでした。

※2015-09-15追記
ansible_spec作者の@さんのtweetを見て気付いたのですが、日経とansible_specのディレクトリ構成とは違っていたみたいです。volanja/ansible_spec の方は、GitHubに載っている例を見ると、/spec以下にはspec_helper.rbを置いているだけですね。すみません、勘違いでした……。

考えてみれば、Ansibleを使ってシステムを構築している時点で、Playbookの実行がエラー無く終了していれば構築は成功しているはず(またはそうあるべき)で、Serverspecで検証するニーズはあまり無いのかもしれません。Ansibleと組み合わせてServerspecを使う理由としては、

  1. Ansibleを実行してから時間が経っても同じ状態が保たれていることを定期的にテストするために使う
  2. Ansibleをジョブ実行のツールとして使う場合に、実行前に事前条件を満たすこと、および実行後に事後条件を満たすことをテストするために使う

など、Ansibleの実行直後以外のタイミングでのテストが重要な場合なのかな、と思いました。Serverspecでのテストは、あまりやり過ぎるとモジュールのテストにどんどん近づいてしまいそうで、線引きが難しそうです。

以下、僕が興味をもった部分を中心にまとめた、講演内容のメモです。

講演内容

Ansibleを結構使ってみた (@bungoume, 日経電子版)

  • 自己紹介

    • デジタル編成局 梅崎裕利
    • 2014年入社(2年目)
    • 社内では基盤チーム、サーバ管理からアプリ開発まで
    • PythonとElasticsearch検索API開発を担当
  • Ansibleを使った目的

    • Excelからの脱却、AMIコピーだけでは駄目な細かい設定の違いがある、など
  • Ansibleを選んだ理由

    • Python, エージェントレス, Chefは過去に挫折
    • @r_rudiにサポートをお願いできた
  • 電子版のWebサーバ群

    • 約300台、30種類以上
    • AWS上に構築
    • サーバリプレースのタイミングでAnsibleを採用
  • 運用までのステップ

    • 2014/9-12:実験期間(@r_rudiさんに教えてもらいながら)
    • クラウドベンダと共同での設計、関係者へのAnsible教育などを経て、2015/8から運用開始
  • ディレクトリ構造

    • specディレクトリ内に入れたServerspecで、リリース結果をテストする
    • 共通設定のディレクトリを作成
  • 構成をクラウド向けに見直し

    • 1機能1サーバ
    • サーバに状態を持たない(ログの転送など)
    • サーバのrole名の統一(関係者間での呼び名の統一)
  • CI環境はCircleCI

    • 初期はansible-lintでチェック
    • Serverspecでテスト
    • Jenkinsで自動デプロイ
  • 運用してみての感想

    • Chefと比べて運用コストが高いと言われているが、今回やると決めたことの範囲では破綻していない
    • 設定変更が容易になった
    • サーバが状態を持たず、AMIバックアップの必要性がなくなったので、コスト削減につながった
    • 開発環境の追加が容易になった
  • 共同開発で困ったこと・事故

    • Ansibleを初期構築にしか使わない、Task化されていない変更 → 変更はすべてAnsibleで、と認識合わせ
    • 設定ファイルだけ違うコピペroles
    • Gitへの不慣れ
  • 苦労、工夫した点など

    • Windowsで、期待していたtemplateモジュールが使えなかった(入ったのが8月。構築に間に合わず)
    • サーバの夜間停止〜自動起動をAnsibleのEC2モジュールで実行している
    • HAProxyの振り分け設定をプラグイン(今回開発した)で自動生成
  • 監視

    • Zabbix-agentをAnsibleでインストール
    • FluentdでログをSentryに飛ばす
    • アクセスログ(Varnish、Apache、Nginx)をElasticsearchで可視化
  • Q: Ansibleで構築したサーバをServerspecでテストするのには、ansible_specを使っている?

    • A: 使っていない(知らない)。自分たちでhelperを作って、それを使っている。

Ansible 2.0 (@r_rudi, ツキノワ)

  • 自己紹介

  • Ansible 2.0とは

    • 現在の1系(最新は1.9.3)と平行して開発中
    • 少なくとも今年2月以降から開発されている
    • 内部構造をほぼ位置から書き直した
  • Ansible 2.0についてもっとも重要な点

    • 従来のPlaybookと100%互換性がある(を目指している)
  • 追加機能

    • block
      • タスクをまとめることができる。3つのタスクに対してwhenを指定、など
      • blockに対して例外処理を定義できる(rescue, always)
      • block内に限定して設定を上書きできる(ブロック内だけrootになる、など)
    • strategy plugin
      • Ansibleはホストごとに並列でタスクが動く。このタスク実行戦略を変えられる
      • プラグインなので、追加も可能
      • 全ホストの実行終了を待つ(linear, v1と同じ)、待たない(free)など
    • include に with が使える(引数を渡せる)
    • include内で変数展開ができる
      • include: included_{{ inventory_hostname }}.yml
    • meta: refresh_inventory が追加される
      • 従来は、inventoryファイルは最初の1回しか読み込まれなかった。dynamic inventoryなどで問題になることがあった
    • taskレベルで変数の定義、上書きが可能になる
      • やり過ぎると、どこで変数が定義されているのかわからなくなる
    • 140以上の新規モジュールの追加
      • openstack, docker, zabbix, vmwareなど
    • inventory, connection pluginも追加
      • serf, consul, dockerなどからインベントリ情報を取ってこれる
    • callbackプラグインの同梱
      • e.g. profile_tasks plugin 各タスクの開始・終了時刻を計測して、最後に集計
  • 変更部分

    • 内部構造の整理
      • 変数管理、変数展開の順序などに課題があった。分かりづらかった → VariableManagerで一括管理
      • plugin構造の整理 → 継承構造を整理
    • 内部APIの変更
      • 自作pluginには多少の変更が必要
    • Python 3対応は入らなかった(準備は進めている様子)
  • 宣伝

    • Pythonエンジニア養成読本のなかでAnsibleの話を書いた
    • 17日にAnsible章の読書会をやる http://pymook.connpass.com/
  • Q: Windows対応は?

    • A: すでに1系で着々と進んでいる。2系で入るモジュールにもWindows対応のものが6個くらいある。

Ansible 2.0を使って組むkubernetesクラスタ vol.1 (h-hirokawa, 株式会社リアルグローブ)

  • タイトル

    • 今回はKubernetesの話はせず、Ansibleの話に特化したので、vol.1とした
  • 自己紹介

    • 廣川英寿
    • Webエンジニア。メイン言語はPython、とりわけDjango
    • Ansibleは2012年から利用中:NiftyCloud C4SA, Deplow
    • 毎月の無料Ansible勉強会開催、CI導入支援、Playbook代書なども
  • 今回やりたいこと

    • Ansible v2でk8sクラスタを自動構築
    • CoreOSで作る
    • 複数IaaS対応(GCE & IDCF)
      • 1クラスタを複数基盤に載せるのは、非推奨構成だったためやめた
  • CoreOSではPythonが使えない

    • モジュールがどんな言語でも書けるというメリットがあるので、Ansible使いたい
    • Ansibleにはrawがある。かといって、全部scriptでやるのはつらい
  • PyPy

    • RPythonで書かれたPythonの実装系
    • CoreOSにPyPyを入れれば、いつもどおりにAnsibleが使えるようになる
    • ただし、どのAnsibleモジュールも使えるかは要確認
    • 最新のPyPy 2.6.1は現段階では動いてくれないので、PyPy 2.4.0を使う
  • Step 1. IaaS上にCoreOSノードを作成

    • IaaS操作はlocalhostからAPIを呼び出す → 普通にやると並列化が効かない
    • ほとんどのクラウド操作モジュールには待機しないためのパラメータがあるので、それを設定する
    • GCEモジュールがそういうパラメータがない → モジュール作る? → asyncで非同期処理できる
    • v2ではasyncでループが使える
      • インスタンスを作成し、全インスタンスの作成を待って次のタスクを実行、といったことができる
  • Step 2. CoreOSのセットアップ(1)

    • Ansibleでは、shebangがあって、JSONで入出力すればプラグインとして動く
    • raw_spec
    • Ansible v2では、モジュールへの引数を hoge=fuga foo=bar のような形式で渡せなくなっている
    • <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>というリプレーサを使う
    • v1では、モジュール内にWANT_JSONというコメントを入れると、引数ファイルの形式がJSONに変わる → v2での引数処理と同様になり、汎用的になる
  • Step 3. CoreOSのセットアップ(2)

    • Cloud-Config: CoreOSで使われている設定の仕組み。yamlで書く
    • yamlの渡し方がクラウドベンダごとに異なる
      • idcfは2KB制限あり
      • cloudconfigをAnsibleで書き換え → 10個以上のPlaybookに分かれてしまった
    • 解決策:ブート完了後にAnsibleでログインして、Config用のyamlを設置し、coreos-cloudinitを実行
    • どんなツールや手法ともうまく連携できるのがAnsibleの長所
  • 今後やりたいこと

    • Ansibleからオートスケール
      • オートスケール操作はgce系モジュールでは未提供。やるなら自作モジュールが必要
    • Ansibleからk8sの操作
    • Ansibleからk8s内のコンテナの操作
  • 宣伝

    • Ansible 2.0に対応した「Ansible完全読本」を年内発売予定

LT

Dynamic Inventoryを使ってみよう! (@saito_hideki)

スライド: Ansible meetuptokyo 2015 Dynamic Inventory

  • IIJの方
  • OpenStack環境上のVM管理にもAnsibleを使っている
  • Dynamic Inventory
  • ansibleコマンド実行時に-iオプションでプログラム名を指定。通常はクラウドコントローラに丸投げ
  • 作り方を間違えると、APIサービスに過剰なAPIコールをして、レートリミットに到達してしまう
  • クラウドAPIは、リスト取得時にホストの詳細も返してくれるものが多い
  • これを--listの結果に返すと、その次の、各ホストの詳細問合せAPIコールを省略できる

Ansibleを使ってみよう~Windowsターゲット編~ (@tsarah0822)

スライド: Ansibleを使ってみよう ~Windowsターゲット編~

  • TIS OSS推進室 倉持健史
  • Windowsモジュールの紹介
    • win_get_url: インターネットからファイルをダウンロード
    • win_msi: msiの実行
    • win_file: ファイル、ディレクトリの作成
  • 実際の案件での使用頻度上位3件:raw, script, setup
  • 要注意モジュール: win_copy
    • 1MB近くのファイルでもやたら時間がかかる
    • win_get_urlでの代替を推奨
  • v2.0の期待モジュール: win_regedit, win_unzip, win_package
  • 人柱としてWindowsのモジュールを一通り試してQiitaに投稿している

Ansibleのベストプラクティス構成に従ったplaybook開発を一工夫する (ynn, Qiitaではyunano)

スライド: Ansibleのベストプラクティス構成に従ったplaybook開発を一工夫する // Speaker Deck

Serverspecを導入したものの放置気味な人へ (@ks888sk)

スライド: Serverspecを導入したものの放置気味な人へ

  • 自己紹介
    • メーカー系企業のインフラエンジニア
    • Ansibleは1年ほど実サービスで使ってる。Ansibleのデバッガも作ってる
  • Serverspecのテストを書き続ける方法 → ツールでテスト不足をチェックする
  • Kirby: Ansibleのコードカバレッジツール(自作)
    • Ansibleのタスクのうち、Serverspecでテストされていないものの一覧を表示
  • Ansible向けのコードカバレッジツールKirbyを作りました - ks888の日記

Ansible の CI を drone/Docker で試してみた (@grenteeea)

スライド: Ansible の CI を drone/Docker で試してみた

  • 自己紹介
    • 西村健太@NTTコミュニケーションズ
    • ビッグデータ系のプロジェクトでAnsibleを使っている
    • そろそろServerspec使わないと、と思っている
  • ソースコードを社外に出せないため、CIサービスは使えない。
  • 代替として、dockerでCI環境を構築する。
  • Serverspecでコケると、droneで表示される。
  • GitLabへのpush時にWebHook経由でdrone起動。サーバ構築してテスト実行。
  • 試してみての雑感:単体テストなら使える。複数のコンテナを動かす必要があるなら、もっと手を加える必要あり

Vagrant環境のAnsibleを速くしたい (@oinume)

スライド: Vagrant環境のAnsibleを速くしたい

  • 自己紹介
    • CyberAgent, Inc.
    • amebaownd.com のバックエンドエンジニア、Go, Pythonメイン
  • Ansibleの実行遅い
  • Profiling Ansible Tasksでプロファイリング
  • 遅い作業
    • パッケージのダウンロード
      • 海外からダウンロード → 国内に変更
      • vagrant-cachierプラグインで、パッケージをホストOSにキャッシュ
    • VirtualBoxのNIC変更
      • VirtualBoxではNICをエミュレートしないように変更 → virtio-netを使う

SIerでもAnsibleを導入したい! (@kk_Ataka)

スライド: slideshare/20150914_ansible_for_sier_digest_for_reveal.js.pdf at master · gosyujin/slideshare

  • SIerの社内で、Ansibleの紹介をして反応をヒアリング
  • Ansible導入に向けて押すと良いポイント
    • 秘伝のタレを再利用できます!
    • サーバ側は、とりあえずSSHで入れるだけでOK!

Ansibleモジュール作成・配布・貢献 (@hogegashi)

スライド: Ansible モジュール 作成・配布・貢献 // Speaker Deck

  • 自作モジュール紹介:blockinfileモジュール
  • モジュール配布のノウハウ
    • roleの形にしてGitHubで公開
    • Ansible Galaxyにロールを登録
  • アップストリームへの貢献のノウハウ
    • ansible-modules-extrasにpull requestを出す
    • Module Checklistをよく読む
    • 既存モジュール作者によるレビュー・承認が必要
    • 投稿しても放置されてしまった事例の紹介
    • Ansible, Inc.は課題を認識しており、プロセスの見直しを考えている