最近、仕事やら趣味やらで AWS / OpenStack / GCE その他のいわゆるIaaSなプラットフォームを調査したり、いじったり、そのAPIをいじったりする機会が多かった。
この辺の運用を考えていて試したこと、ぶつかったことなどをまとめたい。
実際やったこととしては、特定のプラットフォームに依存している訳ではないが、たいていの人はAWSのアレね、みたいなイメージを持った方が読み進めやすいんじゃないかと思う。
ゴールデンイメージを雑に走らせること
この手のIaaSの基本であるが、サーバーはインスタンスと呼ばれ、イメージを指定して起動する。ec2 aws run-instanceとかnova bootとかその類いである。
イメージからの起動のためには雑には大きく2要素があって、一つはイメージ自体の構築、もう一つは起動直後のそれぞれのインスタンスの特性に合わせた初期化処理のフェーズである。
この「イメージ自体の構築」と「初期化処理」の両方が完全にかっちり嵌ることで、サーバの起動は高速になり、サーバの追加削除は圧倒的に簡単になる。
この辺を突き詰めて雑にコマンド一発でサーバ追加ができるようになると、「あ〜俺クラウドだわ」感が出てくる。いや雑すぎた。
イメージのビルドパイプライン
「イメージ自体の構築」について考えると、色々と試行錯誤した結果、以下のようなパイプラインがしっくり来たので晒してみる。
イメージの種類として、
- minimalイメージ
- アプリケーション系のイメージ
- 頻繁に更新される/インスタンス数が動的 と言う特徴があるもの。頻繁に変更するので、差分で構築するようにしておく
- むろん、一から構築し直す手段も確保しておくべき
- その他のロールのイメージ
- これはminimalからクリーンに作れるようにプロビジョニング設定をメンテしておく。
この3種類がほとんどのサービスにぴたっと嵌るんじゃないかと思う。
アプリケーションの配備が厄介だが、Railsならあらかじめ同じアーキテクチャで「bundle install + asset precompile済のアプリケーション」をtgzに詰めて用意し、それをs3的なものに置いておいて(後述のプロビジョナのフェーズで)取ってくる、ぐらいの雑さが良いかもしれない。
ただ、サーバ構成の変更とアプリケーションの変更は頻度が全然違うので、アプリケーションサーバ系のビルドはもう一フェーズ置いても良いかもと言う思いはある。
packer
ビルドパイプラインを構築する上で、EC2などでは独自のツールもある。とはいえ、packerが優れていると思う。
- 基本的にはプラットフォームに依存しない(完成度の違いはある...)
- プロバイダ(AWSだOpenStackだ、あるいはVirtualBoxだ)とプロビジョニング(shellだのpuppetだの)とが分かれているのは、開発の上で扱いやすい
- OSSなのでどうしても困ったらソースを追いかけられる
などがメリット。
コツとしては以下のような点があった。
- 基本、userdataで複雑なことはやらない。後述するユーザの作成や追加、gitみたいな自分で入れるのがめんどくさいツールのインストールにとどめるべき。Packerではruncmdを使ったら負けと思うこと。
- 複雑なことはshellのプロビジョナーで行う。プロビジョナーでの実行結果はPackerがうまいことロギングしてくれるので分かりやすい。
- 逆に、cloud-initのログは基本 /var/log/messages に吐かれるので、イメージ構築においては確認やデバッグが難しい。
- Puppet/Chefのマスターサーバを別途用意した方が便利になる。ファイルアップロードの機能はPackerにはあるとは言え、puppet agent 一発で済む方が良い。
あとおすすめのtipsとして、Packerによる構築直後(AMI化の直前)の状態をServerspecで検査できるの。これは不安が減るので良い。
cloud-init
「初期化処理」を司るのがcloud-init。
cloud-initはほとんど日本語の情報がなく、AWSに関するブログで有名なクラスメソッド社の記事が目立つ。オンプレで言うcobblerみたいなもんと考えれば良いかもしれない。いやこの説明もめっちゃ雑だな......。
cloud-initの仕組みで起動時に流し込まれる初期化データを「userdata」と呼んだりする。このuserdataでよく使う形式は二種類あって、一つはcloud-config形式とも呼ばれるYAMLフォーマット。
これは先述のPackerで使うと良いと思う。中身はこんなイメージで、ユーザ作成と設定(これが意外とpuppetでやるのはめんどくさい)やロケール、タイムゾーンなどをよしなにしている。
#cloud-config repo_update: true repo_upgrade: none packages: - git - curl - unzip users: - default - name: app lock-passwd: true sudo: ALL=(ALL) NOPASSWD:ALL locale: ja_JP.UTF-8 timezone: Asia/Tokyo
もう一つは普通にシェルスクリプトで、userdataとしてシェルスクリプトを流し込むと普通に実行してくれる。
こちらは、run-instanceのタイミングで、下記のような処理を実行させるのに向いている。
個人的には、run-instanceのタイミングでデプロイ、puppetの適用みたいな時間のかかる、かつファイルシステムに保存できる系の操作はやらない方が良いと思う。さっさと起動してくれる方が色々と見積もりやすいので。
また、run-instanceのタイミングでも /etc/cloud/cloud.cfg に書かれているcloud-initの処理は走ってしまうので、例えば意図せずhostnameが置き換わってしまうかもしれない。適宜コンフリクトしないように意識した方が良いと思う。
そしてcloud-initはかなり多機能で、実はぼくも使うとこの機能をざっくりしか把握していないのであった...。
APIをラップする簡単なツール
以上のようなツールは基本的にコマンドラインで操作できるので、その操作のひとまとめをThorを用いてラップしたものを作ると圧倒的に便利になる。手順書よりコードである。
上記の「操作のひとまとめ」には往々にしてuserdataの動的な生成が含まれるので、なおさらコマンドラインツールでラップすべきだと思う。
Thorは割と柔軟に書けるのもあるが、ぼくがそもそもRubyに慣れていたので選んだ。今日日ならGo言語で作成するのも良いかもしれない。この手のツールはおおむねXMLまたはJSONをHTTPで叩くだけなのでWindowsでも動作するツールになる可能性は高い(どこまで動くか、ぼくはWindowsを持っていないので実際のところは試していない!)(最初はもっと雑で嫌みな表現だったので直した)。
追記
ここまでのパイプラインをさらに上から管轄するものとしてTerraformが存在することは言及したい。
とはいえ、試した構成ではThorベースのツールで用に足りてしまった(と判断した)のでTerraformの検証までは至っていない。独自のツールよりコミュニティやHashicorp社のサポートのあるツールの方が有利な面も多いだろうから、どこかで試したいところではある。
[PR]まとめ
そんなこんなをここ数ヶ月ぐらい考えているのだが、minneと言うサービスが熱く人材を募集していて、こう言う感じのことを沢山考えたり試せたりするので、マジで一緒にやりませんか!!1