Ansible is a radically simple IT orchestration engine that makes your applications and systems easier to deploy. Avoid writing scripts or custom code to deploy and update your applications— automate in a language that approaches plain English, using SSH, with no agents to install on remote systems.
選んだ理由としては、ミドルウェアが増えない、サーバ/クライアントに特別なエージェント(デーモン)が不要という2点です。
ソフトウェアのインストールなどサーバ構築の手順は Playbook と呼ばれるYAMLファイルに記述します。スクリプト言語(DSL含む)と比較すると表現力は劣りますが、手軽に書けるところは魅力です。より複雑な処理が必要な場合はモジュール化してYAMLから呼び出してねっていうスタンスみたいです。
作業にあたって、公式リファレンス以外では以下のページを参考にさせていただきました。日本語で良くまとまっているので理解が進みました。
目次
- Ansibleのインストール
- FFmpegをインストールするPlaybook
- サポートされているCPU命令に応じて処理を変える
- ゲートウェイサーバを経由させる
- 感想など
環境
- CentOS 6.4 (x86_64/Intel Xeon E5-2690 2.90GHz 仮想32コア/96GB RAM)
- Ansible 1.4
- GCC 4.4.7
- Python 2.7.5
- リモートサーバ CentOS 6.4 (x86_64) 50台 (東日本DCに10台、西日本DCに40台)
リモートサーバ環境は、OSが同じでCPUなどのハードウェア構成はそれぞれ異なっているサーバ群を対象に検証しました。
Ansibleのインストール
Ansible本体は yum ではなく pip でインストールしました。依存モジュールとして paramiko/PyYAML/Jinja2 もいっしょにインストールされます。Ansible本体を動作させるにはPython 2.6以降が必要ですが、CentOS 6.x系であればシステムのPythonでその条件を満たしています。
1 |
$ sudo pip install ansible |
FFmpegをインストールするPlaybook
映像配信サービス向けのエンコードサーバクラスタを構築するという想定で、FFmpegをサーバにインストールするPlaybookを作ってみます。FFmpegは任意のメディアコーデック/コンテナライブラリを組み込んでビルドする大きなソフトウェアです。今回は以下の構成でFFmpegをビルド/インストールします。rpm/debパッケージで配布されているものはコーデック/コンテナ不足 & 最適化不足のため実務では使えないかと思います。
- Gitのインストール
- CMakeのインストール
- Yasm (x86アセンブラ)のソースコードのダウンロード
- Yasm をコンパイル
- Yasm のインストール
- x264 (H.264/AVCビデオコーデック)のソースコードのダウンロード
- x264 のコンパイル
- x264 のインストール
- libfdk-aac (AACオーディオコーデック)のソースコードのダウンロード
- libfdk-aac のコンパイル
- libfdk-aac のインストール
- libmp3lame (MP3オーディオコーデック)のソースコードのダウンロード
- libmp3lame のコンパイル
- libmp3lame のインストール
- libogg (ビットストリームライブラリ)のソースコードのダウンロード
- libogg のコンパイル
- libogg のインストール
- libopus (Opusオーディオコーデック)のソースコードのダウンロード
- libopus のコンパイル
- libopus のインストール
- libvorbis (Vorbisオーディオコーデック)のソースコードのダウンロード
- libvorbis のコンパイル
- libvorbis のインストール
- libvpx (VP8/VP9ビデオコーデック)のソースコードのダウンロード
- libvpx のコンパイル
- libvpx のインストール
- FFmpeg のソースコードのダウンロード
- FFmpeg のコンパイル
- FFmpeg のインストール
libvpx にはVP9コーデックが含まれているのでぜひ加えておきましょう。libtheora (Theoraコーデック) はこれまで組み込んでも実際は使う機会がほとんどなかったので外しました。あとは、ネットラジオのアーカイブとか作るような人なら libopus も入れておきましょう。Opusは今後普及が期待されるオーディオコーデックです。WebRTCのMTIコーデック(対応必須ですよという意味、ちなみにビデオコーデックはまだ決まっていない)にもなっているので、HTML5を勉強している人も知っておく必要があるかと思います。
また、上記の構成でビルドされたバイナリは libfdk-aac のライセンスがGPL非互換のため再配布不可なので注意してください。別のAACコーデックである libfaac を組み込んだ場合も同様です。ソフトウェアライセンス関連はまだ単純でいいのですが、ビジネスだとパテント絡みで利権やくざが怖いです。
参考:
2003年の記事ですけどこれはひどいですね。資本主義とはいったい。。
MPEG-4 backers protest Microsoft license
(要約:MPEG LAがMicrosoftに対して 「Windows Mediaのライセンスは安すぎる!不正競争だ!」)
話が逸れました。
まず、デプロイ対象ホストを Inventory host file(インベントリファイル) と呼ばれるINIファイルに書いておきます。
* servers.ini
1 2 |
[servers] enc[01:50].broadcast.yahoo.co.jp ansible_ssh_port=2222 |
インベントリファイルはYAMLではないんですね。なぜでしょう。
まずは疎通確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
## ansible コマンドで全てのホストに疎通確認(pingモジュール利用) ## Options: ## -i: specify inventory host file ## -m: module name to execute $ ansible all -i servers.ini -m ping enc01.broadcast.yahoo.co.jp | success >> { "changed": false, "ping": "pong" } enc02.broadcast.yahoo.co.jp | success >> { "changed": false, "ping": "pong" } ... 以下続く |
FFmpegの Playbook は以下のように作りました。さすがに150行を超えていると分割したくなりますけど、FFmpegの場合はコーデック/コンテナライブラリ毎に分割するとファイル数が増えて管理が面倒になる気がします。どれくらいの粒度で分割すれば良いかは調整していく必要がありそうです。
Playbookでは以下の標準モジュールを利用しています (モジュール一覧: Ansible Modules)
* command
* get_url
* git
* shell
* yum
* ffmpeg.yml
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
- hosts: servers user: ryo sudo: no vars: prefix: /usr/local/ffmpeg ffmpeg_version: 2.1.1 libmp3lame_version: 3.99 libmp3lame_patch_version: 5 libogg_version: 1.3.1 libopus_version: 1.0.3 libvorbis_version: 1.3.3 make_jobs: "{{ansible_processor_count * ansible_processor_cores}}" tasks: - name: install git sudo: yes yum: pkg=git.x86_64 state=installed - name: install cmake28 sudo: yes yum: pkg=cmake28.x86_64 state=installed - name: download yasm source code git: repo=git://github.com/yasm/yasm.git dest=/tmp/yasm - name: compile yasm shell: >- cmake28 -DCMAKE_INSTALL_PREFIX={{prefix}} . && make -j{{make_jobs}} chdir=/tmp/yasm creates=yasm - name: install yasm sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/yasm creates={{prefix}}/lib/libyasm.so - name: download x264 source code git: repo=git://git.videolan.org/x264.git dest=/tmp/x264 - name: compile x264 shell: >- PATH={{prefix}}/bin:$PATH && ./configure --prefix={{prefix}} --enable-shared --enable-pic --extra-cflags="-march=native -mfpmath=sse -msse2" && make -j{{make_jobs}} chdir=/tmp/x264 creates=x264 - name: install x264 sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/x264 creates={{prefix}}/bin/x264 - name: download libfdk_aac source code git: repo=git://git.code.sf.net/p/opencore-amr/fdk-aac dest=/tmp/libfdk_aac - name: compile libfdk_aac shell: >- ./autogen.sh && ./configure --prefix={{prefix}} --enable-shared && make -j{{make_jobs}} chdir=/tmp/libfdk_aac creates=libfdk-aac.la - name: install libfdk_aac sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/libfdk_aac creates={{prefix}}/lib/libfdk-aac.so - name: download libmp3lame source code get_url: url=http://sourceforge.net/projects/lame/files/lame/{{libmp3lame_version}}/lame-{{libmp3lame_version}}.{{libmp3lame_patch_version}}.tar.gz/download dest=/tmp/lame.tar.gz - name: extract libmp3lame gzip archive command: tar zxf lame.tar.gz chdir=/tmp creates=lame-{{libmp3lame_version}}.{{libmp3lame_patch_version}} - name: compile libmp3lame shell: >- ./configure --prefix={{prefix}} && make -j{{make_jobs}} chdir=/tmp/lame-{{libmp3lame_version}}.{{libmp3lame_patch_version}} creates=libmp3lame/libmp3lame.la - name: install libmp3lame sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/lame-{{libmp3lame_version}}.{{libmp3lame_patch_version}} creates={{prefix}}/lib/libmp3lame.so - name: download libogg source code get_url: url=http://downloads.xiph.org/releases/ogg/libogg-{{libogg_version}}.tar.gz dest=/tmp/libogg.tar.gz - name: extract libogg gzip archive command: tar zxf libogg.tar.gz chdir=/tmp creates=libogg-{{libogg_version}} - name: compile libogg shell: >- ./configure --prefix={{prefix}} && make -j{{make_jobs}} chdir=/tmp/libogg-{{libogg_version}} creates=src/libogg.la - name: install libogg sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/libogg-{{libogg_version}} creates={{prefix}}/lib/libogg.so - name: download libopus source code get_url: url=http://downloads.xiph.org/releases/opus/opus-{{libopus_version}}.tar.gz dest=/tmp/libopus.tar.gz - name: extract libopus gzip archive command: tar zxf libopus.tar.gz chdir=/tmp creates=opus-{{libopus_version}} - name: compile libopus shell: >- ./configure --prefix={{prefix}} && make -j{{make_jobs}} chdir=/tmp/opus-{{libopus_version}} creates=libopus.la - name: install libopus sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/opus-{{libopus_version}} creates={{prefix}}/lib/libopus.so - name: download libvorbis source code get_url: url=http://downloads.xiph.org/releases/vorbis/libvorbis-{{libvorbis_version}}.tar.gz dest=/tmp/libvorbis.tar.gz - name: extract libvorbis gzip archive command: tar zxf libvorbis.tar.gz chdir=/tmp creates=libvorbis-{{libvorbis_version}} - name: compile libvorbis shell: >- ./configure --prefix={{prefix}} && make -j{{make_jobs}} chdir=/tmp/libvorbis-{{libvorbis_version}} creates=lib/libvorbis.la - name: install libvorbis sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/libvorbis-{{libvorbis_version}} creates={{prefix}}/lib/libvorbis.so - name: download libvpx source code git: repo=http://git.chromium.org/webm/libvpx.git dest=/tmp/libvpx - name: compile libvpx shell: >- PATH={{prefix}}/bin:$PATH && ./configure --prefix={{prefix}} --enable-vp9 --enable-shared --enable-pic --disable-examples && make -j{{make_jobs}} chdir=/tmp/libvpx creates=libvpx.so - name: install libvpx sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/libvpx creates={{prefix}}/lib/libvpx.so - name: download ffmpeg source code get_url: url=http://www.ffmpeg.org/releases/ffmpeg-{{ffmpeg_version}}.tar.bz2 dest=/tmp/ffmpeg.tar.bz2 - name: extract ffmpeg bzip2 archive command: tar jxf ffmpeg.tar.bz2 chdir=/tmp creates=ffmpeg-{{ffmpeg_version}} - name: compile ffmpeg shell: >- PATH={{prefix}}/bin:$PATH && PKG_CONFIG_PATH={{prefix}}/lib/pkgconfig:$PKG_CONFIG_PATH && ./configure --prefix={{prefix}} --cc=gcc --extra-ldflags='-L{{prefix}}/lib' --extra-cflags='-I{{prefix}}/include -march=native -mfpmath=sse -msse2' --optflags='-O2 -ffast-math -fomit-frame-pointer -finline-functions' --disable-avisynth --enable-avfilter --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libvpx --enable-libx264 --enable-postproc --enable-pthreads --enable-shared --enable-swscale --enable-vdpau --enable-x11grab --enable-gpl --enable-nonfree --enable-version3 && make -j{{make_jobs}} chdir=/tmp/ffmpeg-{{ffmpeg_version}} creates=ffmpeg - name: install ffmpeg sudo: yes shell: >- make install && ldconfig {{prefix}}/lib chdir=/tmp/ffmpeg-{{ffmpeg_version}} creates={{prefix}}/bin/ffmpeg |
* 実行
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 |
## ansible-playbook コマンドでFFmpegをインストール ## Options: ## -i: specify inventory host file ## -f: specify number of parallel processes to use ## -k: ask for SSH password ## -K: ask for sudo password $ ansible-playbook -i servers.ini ffmpeg.yml -f 10 -k -K SSH password: sudo password [defaults to SSH password]: PLAY [servers] **************************************************************** GATHERING FACTS *************************************************************** ok: [enc01.broadcast.yahoo.co.jp] ok: [enc02.broadcast.yahoo.co.jp] ok: [enc03.broadcast.yahoo.co.jp] ... 省略 ok: [enc50.broadcast.yahoo.co.jp] TASK: [install git] *********************************************************** ok: [enc01.broadcast.yahoo.co.jp] ... 省略 TASK: [install ffmpeg] ******************************************************** changed: [enc01.broadcast.yahoo.co.jp] ... 省略 PLAY RECAP ******************************************************************** enc01.broadcast.yahoo.co.jp : ok=28 changed=16 unreachable=0 failed=0 enc02.broadcast.yahoo.co.jp : ok=25 changed=19 unreachable=0 failed=0 enc03.broadcast.yahoo.co.jp : ok=30 changed=14 unreachable=0 failed=0 ... 省略 enc50.broadcast.yahoo.co.jp : ok=30 changed=14 unreachable=0 failed=0 |
実際に50台のサーバを対象に実行してみると普通に何度か失敗しました。ネットワークや筐体障害というのは日常的にあることですので、リモートホストが生きていれば時間を置いて再度実行することになります。ただし今回の例では全てのソフトウェアをバージョン指定でインストールしているわけではないので、べき等性(何度実行しても状態は変わらない)は保証されません。
映像配信サービス用にエンコードサーバクラスタを構築することを考えた時に、全ホストのOSは統一はできてもCPUなどのハードウェア環境はそうもいかないことが多いと思います。よって、各環境に最適化されたバイナリをデプロイするには現地でコンパイルする必要があります。
サポートされているCPU命令に応じて処理を変える
リモートサーバのCPU/コア数、サポートされている命令を判別してコンパイル時に適切な CFLAGS を設定できるようにしたいのですけど、なにかスマートな方法はないものでしょうか。
リモートサーバのシステム情報(Ansibleの文脈では FACTS と呼ばれる)について確認するには setup モジュールを利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ ansible -m setup enc01.broadcast.yahoo.co.jp enc01.broadcast.yahoo.co.jp | success >> { "ansible_facts": { ... 省略 "ansible_processor": [ "Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz", "Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz", "Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz", ... 以下、仮想コアの数だけずらずら並ぶ ], "ansible_processor_cores": 8, "ansible_processor_count": 2, "ansible_processor_threads_per_core": 2, "ansible_processor_vcpus": 32 ... 省略 |
ansible_processor
はプロセッサ名の文字列が仮想コアの数だけ詰まってるリストになっています。上記例では物理CPU (ansible_processor_count
)が2つ、物理CPU内のコア(ansible_processor_cores
)が8つ、各コアがHTの2スレッドで仮想32コアの環境になります。コンパイル時のcc1の並列実行数には ansible_processor_count * ansible_processor_cores
の値を指定するのが効率良さそうです。スラッシングを避けるためにも並列数はあまり増やさない方がいいと思います。
1 2 |
## 上記FFmpegインストール用 Playbookの場合は以下のように修正 make_jobs: "{{ansible_processor_count * ansible_processor_cores}}" |
次に、サポートされているCPU命令に応じて処理を分ける場合です。こちらはAnsibleの setup モジュールだと flags の値は取れないようですので、setup モジュールを修正するか新しいモジュールを作る他なさそうです。一応 ohai モジュールを使えばできそうなんですが、Ruby処理系はともかく mixlib-* などたくさんの追加依存モジュールによって環境が汚れるのはツライところです。
setup モジュールには Hardware クラスというのがあって、LinuxHardware や SunOSHardware などOS毎にサブクラスが定義されています。Linux OSを対象とする場合は LinuxHardware クラスの get_cpu_facts
メソッドをいじります。/proc/cpuinfo
の内容をバラしてるだけの処理ですので修正は簡単です。
setup モジュールに以下のパッチを当てると、サポートされているCPU命令のリストとL2キャッシュサイズ(KB)がFACTSに追加されます。
- setup.patch (Ansible 1.4で動作確認済)
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 34 35 36 37 38 39 40 41 42 43 44 |
## 追加されるFACTSの一例 (ただしdistcc環境は考慮していない) "ansible_processor_cache_size": 20480, "ansible_processor_flags": [ "fpu", "de", "pse", "tsc", "msr", "pae", "mce", "cx8", "apic", "mtrr", "pge", "mca", "cmov", "pat", "pse36", "clflush", "mmx", "fxsr", "sse", "sse2", "ht", "syscall", "nx", "lm", "constant_tsc", "arch_perfmon", "unfair_spinlock", "pni", "pclmulqdq", "ssse3", "cx16", "sse4_1", "sse4_2", "x2apic", "popcnt", "aes", "xsave", "avx", "hypervisor", "xsaveopt" ], |
コンパイラがGCCの場合、-march=native
がサポートされていないプラットフォーム上でソフトウェアをコンパイルする際は上記情報を使って CFLAGS を組み立てます。
setup モジュールを修正するのではなく新規にモジュールを作る場合、出力結果のJSONに ansible_facts
というキーを追加して、そこに任意の情報を入れておけばAnsibleがFACTSに追加してくれるようです。サポートされているCPU命令のリストやL2キャッシュサイズ、コンパイラのバージョン(gccなら -dumpversion の値)を集めて、最終的に Autotools や CMake に渡す CFLAGS (あるいはCXXFLAGS)を組み立てるロジックを埋め込んでおくと便利そうです。
ここではマルチメディア処理や機械学習処理など、CPUバウンドな科学技術計算を扱うソフトウェアをインストールする場合に考えなければならない問題を取り上げてみました。
ゲートウェイサーバを経由させる
Ansible Advent Calender 2013に良いエントリーがありました。MSP屋さんの視点は貴重ですね。
ネットワークセグメントが異なる自社DC間の通信において、ゲートウェイサーバ(踏み台)経由でないとSSHセッションが貼れないケースが考えられます(コンプライアンスの対象範囲を狭めたり、セキュリティ上の理由から)。ゲートウェイサーバではソフトウェアの新規インストールが許可されておらず、なおかつ実行可能なコマンドも厳しく制限されていることが多いため、これを解決できないとAnsibleの導入は難しくなります。
そこでAnsibleではSSHに渡すオプションを ANSIBLE_SSH_ARGS
環境変数で指定できるようなのでこれを利用します。ここから先はAnsible自体とは関係ない話なので詳細は上記エントリー等を参照ください。ssh_config
で ProxyCommand
を使ってゲートウェイサーバを経由させるだけです。
ただし、ゲートウェイサーバのOpenSSHがv5.4未満かつ nc コマンドが入っていなくて新規インストールもできない環境だとお手上げですかね? あと、最近流行ってるワンタイムキーによる二段階認証が必要な場合も厳しいかなぁと思います。
感想など
Ansibleは導入コストの低さも魅力ですが、ミドルウェアが増えない点が特に良いです。
Playbook内でいっぱい変数を使ったりRole分割しすぎると訳が分からなくなってくるので、上手くバランスを取って整備する必要があると感じました。今回作成したFFmpegインストール用のPlaybookの場合は、コーデック/コンテナライブラリ毎に分割すると細かくなりすぎて整備が煩雑になる気がするので、例えば映像関連と音声関連とで分けるくらいの粒度なら整備しやすいかもしれません。
ところで、Slashdotで他のツールとの比較記事がありました。
言われてみればWeb UIについては考えていませんでした。確かにWebブラウザから構成管理情報が閲覧できるとシステム運用者は嬉しいと思います。
以前は映像配信サービスの開発/運用に携わっていましたが、配信系だけでも数百台規模でしたし、エンコーダやストレージサーバクラスタも全て構成管理するのはちょっと面倒でした。ソフトウェアも商用ライセンスでたくさん買ってたので、インストールされているサーバ台数は正確に計上しなければなりません。そういった面でノード情報をINIファイルで管理するAnsibleでは正直心配なので、結局は構成管理用サーバを別に立ち上げてデータベースにノード情報と併せて管理することになるかもしれません。それやったらChefでいいやろって話になりますけども。
使い始めてまだ2週間弱ですけど、覚えることが少なくて使いやすいなと思いました。でもきっと年明けにはぜんぶ忘れていることでしょう。
おまけ
システム運用自動化の記事に対する他業種/職種の方々の反応を見るのは面白いです。
運用を機械に任せることに不安を抱いているのではなく、資本であるところの人を減らしているという事自体が不安なんだと思います。外からは体のいい人件費削減と読み取られても仕方ないかと。こういうのって株主の方々はどう見てるんでしょうか。