このエントリは、ドワンゴ Advent Calendar 2014 – Qiita の15日目のエントリです。昨日はkokuyouさん、明日はsaitenさんです。
要約
Ansibleを使って、Chinachuがインストールされた録画サーバーを自動でセットアップするPlaybook作りました。
安心してアニメが観たい。
世の中の大抵の人は、安心してアニメが観たいはずです。そして世の中の大抵の人は、普通のテレビと普通のBDレコーダーを使い、安心してテレビを観たり録画しているはずです。よっぽど変なテレビやレコーダーを買わない限り、普通にアニメを観てアニメを録画できる機能が備わっています。安心ですね。
しかし世の中には、宗教的な理由や金銭的な理由で、テレビやレコーダーを買うことを拒む人が居ます。私もその1人です。このエントリでは、訳があってテレビやレコーダーを買えないけれど、クリスマスはどうせ暇だから一日中アニメを観たいという人のために、クリスマスまで安心してアニメを録画し続けることが出来る、録画サーバーの作り方を解説します。
安心とは
難しいテーマだ……安全とは一体なんなのか。
高可用? 耐障害? 冗長性……とかそういうのが安全な気がしますが、このエントリで言う安全とは、「壊れてもすぐに治せる」ことです。普通のテレビと普通のレコーダーは、仮に物理的に壊れても、同じものを買えばすぐに治ったと同じ状態です(HDDとかが壊れたら別ですが)。では、自分で作った録画サーバーはどうでしょう……?
つらさ
これまで、自宅サーバーやVPSを含めて、いくつものサーバー的なモノを運用してきましたが、大抵は「その場で対応した再現法のわからない謎のレシピの積み重ね」がさらに積み重なり、確実に再現できない上に、この設定を作った時の俺は何を考えていたのかすら理解できない。明日の他人は今日の自分、お前は俺で俺はお前状態のモノを大量に作ってきました。嫌いな言葉は環境依存です。生きててすみません。
再現可能な環境を作る
環境に依存したつらい環境はなく、壊れてもすぐに同じ状態を再現するために、構成管理ツールを使いましょう。代表的なものは、ChefとかPuppetなどがありますが、あれらは何百台ものサーバーを常に管理し続けるためのツールです。一年に一回、壊れるかどうかもわからないような録画サーバーを管理するために存在しているわけではありません。
そこでおすすめの構成管理ツールが、Ansibleです。Python製ですが、設定の記述にYAMLを使い、特定の言語に依存しない簡単な記述で構成管理が可能ということで、今年ブレイクした(多分)ツールです。テンプレートエンジンにはPythonのJinja2というライブラリを使っているため、ほんの少しだけPythonの記述に依存する場所がありますが、学習コストは非常に低いです。似たような哲学を共有する構成管理ツールとしてFabricというものもありますが、少なくとも国内ではFabricよりもAnsibleの方が流行っていると思います。
また、年に一回壊れるかどうか、と言いましたが、サーバーを一回クリアにして再度セットアップしたくなる状況は、年に何度か出てくると思います。例えば私の場合、録画サーバー上で複数の仮想サーバーを運用しているため、物理メモリを増やしたくなったとか、USB3.0のカードを追加したくなったなどという事態が年に数度起きます。そんな場合でも、構成管理を使えば、物理環境を変えた後にでも、以前と全く同じ設定を自動で作り出すことが可能です。
やってみよう
私が作った録画サーバーの環境を、そのまま紹介します。作りたかった録画サーバーの条件として、
- ファンレス
- ゼロスピンドル
- 多めのメモリ
- 3番組以上の同時録画
という要求が自分の中でありました。自室で24h動くサーバーなので、うるさいと気になって眠れないし、仮想マシンをいくつか動かすので、メモリは多めに欲しいという判断からです。3番組以上の録画に関しては、実際に今年の春頃に、3番組が重なって絶望感を覚えたからです。
そこで、実際にとった構成は次のようになりました
- CPU&MB ASRock Q1900M
- 2.5インチHDD (妥協した)
- PT3 x 2
- ACアダプタのATX電源
まず、ASRockのQ1900は、CeleronのJ1900をオンボードで載せたSoCで、ファンレスのボードです。クアッドコア搭載で、ePCIが3枚刺せ、何より同じJ1900の中でもメモリも最大16Gと十分な拡張性を持っているため、選択しました。PT3の二枚刺しは、3番組同時録画の要件を満たすためです。また、電源もファンレスのためにACアダプタタイプを選択しました。J1900のTDPは10Wで、PT3を二枚刺してもまだ電力的な余裕は十分です。残念ながら、テレビを録画するという事自体が非常に容量を食う問題であったため、サーバー自身にもある程度のストレージは欲しいと思い、SSDは一旦見送り2.5インチの静音HDDを選択しました。
これらのパーツは、決して特殊なものではありません。もし物理的に壊れても、すぐに電気屋に買いに行くことで、構成管理を使い自動的に復帰が可能です。安心ですね。
サーバーに何を選択するか
録画サーバー機能そのものには、Chinachuというソフトを使います。Linux用の録画サーバーを構築するソフトとしては、メジャーなものにfolitaやEPGrecなどがあります。他のソフトと比較した時のChinachuの特徴は、インストールの容易さ、癖のあるUI、そしてWebAPIのエンドポイントが用意されており、ほぼすべてをAPI経由で操作できるなどの設計もさておき、「世界で一番キュートな録画システム」という可愛らしく魅力的な謳い文句です。かわいい。
他には、これはとても重要なことですが、作者の方が積極的にコミケでChinachuの設計方針や扱い方などを書いた同人誌を売っていたり、あとはDBなどのミドルウェアに依存が無くNode.jsだけ有ればいいなど、とにかく扱いやすく解決策を探しやすいという点が選択した理由です。ただし、Chinachu自身が生成する設定なども含めて、ほぼすべてがJSONで 記述されており、一回壊れると手作業での修復はほぼ不可能です。とはいえ、壊れたら再構成すればいいのです。安心ですね。
録画サーバーを構成管理する
AnsibleのPlaybookをGithubにあげてあります。すべてのコードはこっちを見てもらうとして、重要な部分を解説します
YasuhiroKinoshita/chinachu_ansible
ディレクトリ構成
ペストプラクティスを採っています。ロールは6つに分解してあり、それぞれ
- 共通設定
- PT3のドライバと録画コマンドのインストール
- Chinachuのインストール前確認
- Chinachuのインストール
- Sambaのインストール
- VagrantとVirtualBoxのインストール
に分かれています。Chinachuのサーバー構築に必要なのは1から4までで、5と6は自分が家のサーバーに欲しかった機能を入れているものです。また、1、5、6は、他のプロジェクトにも使えそうなので、Gitのサブモジュール化を行っています。このレポジトリを作った当初は、サブモジュール化を進めて色々応用させることに意欲を持っていたのでしょうが、これはこれで管理がつらいです。
Chinachuのロールが前後に分解されているのは、ユーザーの切り替えが面倒だったからです。事前準備はrootで行い、インストールはchinachuユーザーで行うためです。
設定(変数)
基本的に、次の2つのファイルの変数を書き換えるだけです
roles/chinachu/vars/main.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 |
--- chinachu_dir: "/home/chinachu" # chinachuを実行するユーザーのホームディレクトリを設定します。基本的に、勝手に作られるのでこのまま chinachu_root_dir_name: "chinachu" # ChinachuをCloneするディレクトリ名 chinachu_symlink: "chinachu_cmd" # Chinachuの実行ファイルへのシンボリックリンク名 chinachu_video_dir: "video" # 録画したファイルを保存する場所のディレクトリ名 chinachu_user: "chinachu" # WebUIを使う際の、ログインユーザー名 chinachu_password: "chinachu" # WebUIを使う際の、パスワード # Twitterの通知を使用する場合に使うトークン。この行を削除すると、Twitterの設定は作成されません ceinachu_twitter_consumer_key: "your_consumer_key" chinachu_twitter_consumer_secret: "your_consumer_secret" chinachu_twitter_access_token: "your_access_token" chinachu_twitter_access_token_secret: "your_access_token_secret" # 録画対象の物理チャンネルIDのリスト chinachu_channel_list: - 18 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 30 |
roles/pre-chinachu/vars/main.yml
1 |
pre_chinachu_private_key: "~/.ssh/id_rsa.pub" # chinachuユーザーにログインする際に使用する公開鍵<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px;"> </span> |
また、SambaやVagrantのインストールが必要ない場合は、次の行をコメントアウトしてください
chinachu.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
--- - hosts: chinachu-server user: root roles: - common - pt - pre_chinachu #- vm # この二行をコメントアウトする #- samba - hosts: chinachu-server user: chinachu roles: - chinachu<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px;"> </span> |
マシンの再起動
PT3のドライバを入れた後など、どうしても物理的にマシンを再起動する必要が出てきますが、せっかく自動化しているので手動でやるのはアホらしいです。なので、次のような記述でマシンを再起動し、再びsshで接続できるよう待機します。去年のAdvent Calendarを参考にしました。
roles/pt/tasks/main.yml
143 144 145 146 147 148 149 150 |
- name: reboot shell: sleep 2s && /sbin/reboot & - name: wait for the server to go down (reboot) local_action: wait_for host={{ inventory_hostname }} port=22 state=stopped - name: wait for the server to come up local_action: wait_for host={{ inventory_hostname }} port=22 delay=60 |
ただし、この設定では、仮想環境での実行がうまくいきません。基本的に録画サーバーは物理サーバーだと思うので、特に配慮はしませんが、仮想環境でPlaybookの実行テストを行う場合には注意が必要です。
Chinachuユーザーの作成
ユーザーの作成と鍵の登録は、userモジュールとauthorized_keyモジュールを使用しています
roles/pre_chinachu/tasks/main.yml
39 40 41 42 43 44 45 46 47 48 49 50 51 |
- name: create chinachu user user: name=chinachu password='$6$rounds=100000$t9cFLWAcHkPD2awG$alBfg4PJPCwARrxceQRB5rANzq8QvZwzdCyANDfa5SNTgruKIvhwXGziVopDHU64R7Zl7Fsf44ZEiN56H4fyj/' home=/home/chinachu shell=/bin/bash groups=sudo tags: chinachu_user - name: add authorized keys authorized_key: user=chinachu key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}" tags: chinachu_user |
userモジュールのpasswordオプションは、SHAでハッシュ化した値を渡さなくてはなりません。上のコードでは、’chinachu’という文字列をハッシュ化しています。詳細は公式FAQのここを参照。
Chinachuのインストーラー実行
chinachuは、git cloneをしてきた後に、ディレクトリルートに居るchinachuコマンドを実行するだけで、自動的にインストールされます。ですが、何故かそのコマンドが対話的で、引数を使った動作の制御が出来ないので、expectスクリプトを使って自動でインストールします。
roles/chinachu/files/chinachu_installer.sh
1 2 3 4 5 |
#!/usr/bin/expect spawn ./chinachu_cmd installer # シンボリックリンクを貼ったchinachuコマンド expect "what do you install? >" send "1\n" interact |
このファイルをリモートにコピーし、chinachuコマンドへのシンボリックリンク経由で実行します。chinachuは、シンボリックリンクからでも正しくパスを認識して実行することが可能です。できれば、インストールも引数で制御出来るようになればいいのですが……
roles/chinachu/tasks/main.yml
11 12 13 14 15 16 17 18 19 20 21 22 23 |
- name: create symlink file: src={{ chinachu_dir }}/{{ chinachu_root_dir_name}}/chinachu dest={{ chinachu_symlink }} state=link tags: chinachu - name: copy file copy: src=chinachu_installer.sh dest={{ chinachu_dir }} mode=0744 tags: chinachu - name: install chinachu command: ./chinachu_installer.sh chdir={{ chinachu_dir }} tags: chinachu |
PT3のドライバに関して
怖いのであまり触れません
実行する
Chinachuは、Debian上で動作させることを推奨されています。なので、まずはDebian Wheezyをサーバーにインストールします。このとき、root以外にユーザーを作製することを求められますが、chinachuというユーザーは作らないでください。Ansibleによって、自動的に作成されます。このDebianのインストール自体も自動化したかったのですが、手順的に特に難しいものでも無いことと、メンテのコストを考えると、手動でもいいかと思い特に何もしていません。開発用の最小構成でインストールしてください。また、Ansibleを実行するマシンと、Debianをインストールした録画サーバーのrootが、sshで公開鍵認証を行えるように、サーバー側に公開鍵の追加も行います(これも自動化したかったけどできなかった)
あとは、次のコマンドで修了です
1 |
./init.sh |
このスクリプトは、単に次のコマンドを実行しているだけです
1 |
ansible-playbook -i production site.yml |
毎回タイプするのが面倒なので、作っただけです。Vagrantなどで作成したDebianのマシンで、試してみると何が起こるのか解ると思います。(ただし、VagrantやVirtualBoxでの起動では、PT3用のドライバ等をインストールしている最後のRoleで行う再起動のタスクがうまく行きません。このPlaybookは実機を前提としているので、そこの部分をコメントアウトして自分で再起動を試してください)
録画サーバーとの通信さえ問題なければ、これであとは勝手にchinachuサーバーが作成されるはずです。指定した公開鍵を使って、chinachuユーザーとしての公開鍵を使ったログインも可能です。また、chinachuユーザーのデフォルトのパスワードはchinachuなので、sudoなどの際に必要であれば指定してください。
まとめ
以上の手順で、Ansibleを使った録画サーバーのプロビジョニングが完了します。あとは、Chinachuのドキュメントを参照して、初回の番組取得や起動スクリプトの作成など、自動化できないChinachuのオペレーションを開始してください。
物理マシンへのOSのインストールと、セットアップ後のChinachuの運用を除いて、ほぼすべて自動化しました。これで仮にどこかで故障が起きても、すぐに同じ環境を再度作製することが出来ます。安心してアニメが見れます。
このプロビジョニングで作成した録画サーバーを使って、私は2014年冬のアニメをほぼ無事にすべて録画しています。最近引っ越したので、正確なアップタイムは忘れましたが、少なくとも三ヶ月近くは無停止で稼働し続けました。
いくつか観れていないものがあるので、クリスマスに安心して観ようと思っています。
みんなで安心して、クリスマスはずっとアニメを観よう。
トラックバック: 1件
[…] この場合は、命令的な状態書き換えがなく、関数型プログラミングの考え方に近い実装であると思います。一方、関数型言語でも、例えばパフォーマンスのために命令的な記述を交えることがあります。つまるところ、「命令型プログラミング」「関数型プログラミング」とは書き方のスタイルの問題であって、言語はそのサポートに過ぎない、というわけです。そういったわけで、もし「関数型言語で書けばそれは関数型プログラミングなんだ!」と思っている人がいたら、そうではなく「関数型プログラミングをどこで活かせるか」を考えてもらえると嬉しいです。関数型プログラミングのメリットさて、関数型プログラミングがなんぞや、の部分を分析しましたが、それで結局、関数型プログラミングで書くとなにが良くなるんでしょうか。「副作用のない関数」として問題を記述する、という特性についてだけ考えれば、メリットは以下のような点になりそうです。宣言的な記述のため、簡潔に書けることが多く関数の仕様を理解しやすい副作用がないため、形式的な証明がしやすい関数の評価結果がコンテキストに依存しないため、テストがしやすいコンテキストに影響を与えないため、関数を組み合わせて利用しても相互に影響しない関数が相互に影響しないため、並列・並行処理が書きやすい(?)並列・並行性に関しては、メリットとして取り沙汰されることが多いものの、関数型の利点かというと微妙なところがある気はします。この手の話題になると必ずErlangの話が出てきますが、それはErlang自体が並列処理を行うためのマイクロプロセスとメッセージパッシングの機構を持っているためであって、関数型の恩恵というのは語弊があるでしょう。むしろ、メッセージパッシングはオブジェクト指向から派生した概念なので、(Alan Kayのいう意味での)オブジェクト指向の恩恵といえるかもしれません。Erlangがオブジェクト指向的であるというのは開発者のJoe ArmstrongもRalph Johnsonのインタビューの中で認めているところです。一方、純粋関数型のHaskellでは並行・並列処理で一冊本が出るレベルなので、「関数型を採用すれば並列処理が簡単になる」というのはやや誇張表現と言わざるを得ないでしょう。まぁそれでもJavaのThreadクラスを用いた並列処理に比べるとマシなのかもしれませんが… Javaでは命令的でない並列処理の実装(Future, Streamなど)の話も混じってきてしまうため、例えとしてあまり適切ではありませんでした。古典的な「複数のスレッドが共有リソースを書き換えながら動作するようなプログラミングスタイルと比べて」と解釈していただければと思います。一方、関数型とセットで取り沙汰されることの多い高階関数のメリットは、「関数自体の表現力が上がる」の一点だと思います。とはいえ関数自体の表現力が上がるのは非常にわかりやすいメリットです。また命令型のパラダイムと相反するわけではないため、高階関数の機能だけを独立して導入している言語も多く見られます。例えばrubyのブロックを扱うメソッドやC#のLINQなどは実質的に高階関数になっています。(もっとも、オブジェクト指向でも処理のまとまりを渡すブロックという仕組みがSmalltalkにあるため、そちらから影響を受けている部分もあるでしょう)他に強力な静的型検査システム、型推論による型注釈の省略、などが挙げられることもありますが、この辺は関数型言語の中でも一部の言語(主にOCamlとHaskell)に特有のもので、関数型プログラミングのメリットかというと微妙な気がするため今回は外します。関数型プログラミングのデメリットデメリットは、一部のメリットの裏返しが多いです。宣言的な記述のため、知識がないと実際にどう動くのかを追いづらく、パフォーマンスチューニングが難しい状況に応じて別のことをさせる、という処理はやや書き難いコンパイルが重くなりがちIOなど、本質的に副作用のある処理の記述が難しいとはいえ、そもそも命令型がコンピュータがわかりやすいものであるのに対して、宣言型はより人間の考え方に近いもののはずなので、個人的には(少なくとも命令型と比べての)デメリットは少ないのではないかと思います。またIOなどに関しては、無理に関数型プログラミングにこだわらず、そこだけ命令的に記述する、というのが最も簡単な解決策でしょう。個人的には、本質的なドメインロジックを副作用を排して書き、IOに関わる部分は多少の副作用を許容する、というのがバランスが良い気がします。関数型プログラミングと関係ないトピックメリットのところにも書きましたが、強力な静的型検査システム、型推論による型注釈の省略などは関数型プログラミング自体とは関連性が薄い気がします。そもそもErlang、Lispなどは動的型言語なので、「関数型プログラミングだから」という話ではなく、静的型付け言語だから、というべきでしょう。また、これは割と重要だと思うのですが、「どのような単位で処理を切り分けるのか」について、関数型プログラミングというパラダイムは特段示唆を与えていません。オブジェクト指向と関数型を比べて「関数型では処理を適切な単位に切り分ける基準がない」「設計論が確立されていない」みたいな論調を見たことがありますが、これについてはそもそも関数型プログラミングという枠で議論するべきではないでしょう。例えば(オブジェクト指向設計レイヤーでの)クラスと同レイヤーで使われるだろう機能として、OCamlにはファンクターの機能が、Haskellには型クラスの機能があります。これらは言語特有の機能のため、個別に使われ方や設計論を分析したうえで比較する必要があるはずです。オブジェクト指向という言葉がモジュール分割的な使われ方も兼ねているため、この辺は大変分かりづらい状況になっています。(今回その辺も絡めて記事にしたかったのですが、時間的にも文字数的にもボリュームが多すぎるため断念しました…)まとめアドベントカレンダーなので日付が変わる頃には投稿したかったのですが、既に1時をがっつり回ってしまっています…というわけで、今回「関数型プログラミングとはなんぞや」というのを自分なりにまとめてみました。非常に長文になりましたが、これで少しでも関数型プログラミングに対する理解が広まればと思います。ドワンゴアドベントカレンダー、明日15日は@Kinoppyさんです!お楽しみに!Check […]