この変更は実は10月23日頃に実施していたのですが、なかなかまとめる機会もなく、そういえば、あれも、これも・・・といろいろ調整しているうちに年末になってしまったわけで。キリがないので、2015年の締めくくりとしてまとめます。dockerらしいdockerの使い方について。
このブログサーバーは2014/6頃から稼働しています。VPSサービスを使ってwordpressを動かしているのですが、それだけじゃ面白くないので、稼働当初から、さらに1層、仮想レイヤーとして「dockerコンテナ化」を実施しています。
その稼働開始当初からの構成が下図になります。
dockerコンテナは1つ。その中で、sshdからhttp、newrelicの監視エージェントまでを動かす構成で、イメージとしては、「VPS的なdockerコンテナの使い方」になります。
プロセスマネージャとしてmonitをforegroundで動かすのですが、sshd等、一部monitからの起動では遅いものや、不安なもの、そもそもなぜかうまく行かなかったものがあって、結局は初期シェルを1枚挟む構成にしています(追記:そういえばsyslogもcronも動かしていた記憶が)。
この構成のメリットは以下の通り。
- OS環境を親(VPS)の環境と切り離せます。これによって、親のパッケージアップデート等でサービスがおかしくなるリスクが低減されます。
- sshでログインしてオペレーションする形になるため、仮想サーバー的な操作が一通りできます。別のサーバー環境と考えて、違和感の無い構成です。
- dockerコンテナですので、コンテナリソースの管理はコンテナ単位でできます。
ただ、この構成で運用していて、ちょっと困ったことが出てきました。
- 依存性の問題。MySQLのバージョンを上げたいと思ったとき、phpのmysql-client等も一緒に上がってしまう。phpのバージョンを上げたいと思ったときも、結局mysql関連パッケージの依存関係の影響を受ける。つまり、dockerの中で動いているパッケージ同士の依存関係の問題は解決できない。
- sshでログインするにはssh用のtcpポートをexposeする必要がある。しないこともできるが、コンテナ側のアドレスが不定なので、実オペレーション上、exposeしたくなる。
- イメージが肥大化する。このコンテナ内にwwwコンテンツからmysqlデータ領域、アクセスログまですべて押し込んでいるため、imageが肥大化し、たまにcommitしたとき大変なことになる。
- つまり、全くimmutableではない。美しくない。
発端の動機はmysqlをMariaDBにしたいな、と思ったことです。mysqlは意外に厄介者で、rpmベースでインストールするとしても、OS標準とコミュニティ版のレポジトリ経由があり、特にコミュニティ版のレポジトリを使うと、後々mysql関連の依存関係でのアップデートが来た時、デッドロックを起こすことになります(経験アリ)。
こういった問題を解決するのは、やはり「dockerをdockerらしく使う」構成に変更しなければいけない、と思ったのでした。
「dockerらしく」とは何かといえば・・・
- 1プロセス1コンテナ
- 変更が入るファイルは共有ディレクトリにして外に出す
とか、まぁいろいろあるわけですが、今回の構成変更では、具体的には以下を心がけました。
- 変更されるファイルは外に出す。共有ディレクトリ設定で/var/logは一律で外部フォルダ化。wwwコンテンツ、mysqlデータ領域も外に出す。
- コンテナ間で発生する通信については、unix domain socket用の共有フォルダを作り、極力これを介するようにする(コンテナのIPアドレスが可変のため。linkが思った以上に不安定だったため)
- 1プロセス1コンテナで、サービスプロセスをフォアグラウンド化する。
- オペレーションはdocker exec /bin/bashに統一。ssh等は使わない。
- バックアップやログローテーションも親サーバー側から、cronでdocker execを実行する。
こんな形で、dockerを「完全なプロセスコンテナ化」しました。結果として、前述された問題はすべて回避できるようになりました。得られたメリットは以下のような感じ。
- mysql、php、apache等のモジュール依存性を気にすることなく、バージョン・アップや、プロダクトの変更が出来るようになった。
- バージョン・アップ作業の検証などに、別に自宅で動かしているcoreos環境が使えるようになった←意外にこれが一番、immutableっぽいポイントかなと思っています。DockerFileでbuildとかできる状態になった、ってことです。
- commitコスト低減。イメージが1GBを超えるようなことも無くなったし、速くなった。
- ファイルシステム速度が上がった(image外のファイルを使うため)
- 美しい
という訳で、図示するとこんな感じになりました。
いくつか妥協点とかポイントがありますので、1コンテナずつ説明します。
MariaDB
今回、コンテナ分離をしたかった動機の一つ、mariadbですが、mariadbのレポジトリからパッケージで10.1.8を入れています。ここはphp等の接続パッケージも一切要らないので、完全にmariadbのみの環境を作っています。
起動はmysql_safeです。mysqlには、mysqldを監視してサービスを維持するwrapperデーモンmysql_safeがあり、このプロセスはフォアグラウンドで動くようになっています。なので、このプロセスをdockerから実行することで、mairadbコンテナを稼働させることが可能です。
バックアップはdocker execでmysqldumpを用いて、外部ディレクトリに吐き出しています。
docker run -d -h mariadb.home --name mariadb.home -v /share/mariadb.home:/share -v /share/socks:/socks centos6/mariadb:20151024 /usr/bin/mysqld_safe
最終的な起動コマンドはこんな感じになります。TCP/3306も一応listenしていますが、基本は/socks/mysql.sock経由のunix domain接続です。
php-fpm
これまた動機の一つ。php5.6を使いたかったのです。レポジトリとしてremiを追加し、remi-php56からphp関連パッケージ一式をインストールします。mysql等の都合を考えずに済むので、これまた非常に気が楽です。
コンテンツディレクトリはwordpressでの更新がかかること、apache用コンテナとの共有が必要なので、共有ディレクトリにします。このコンテンツのバックアップは親側の定期処理に任せています。パーミッションだけ、apache側と合わせる必要がありますが、すべてコンテナのベースが同じ(例えばCentOS)であれば、uidのルールは共通なので大きな問題は無いはずです。
apacheのFastCGIからの接続はこれもunix domainソケットです。newrelicの監視エージェントは、「php-fpm起動時に勝手に起動される」動作になります(これがデフォルトのようですので)。
docker run -d -h php-fpm.home --name php-fpm.home -v /share/www/html:/var/www/html -v /share/php-fpm.home/var/log:/var/log -v /share/socks:/socks centos6/php-fpm:20151106 /usr/sbin/php-fpm --nodaemonize
こんな感じで、nodaemonizeを付けることで、php-fpmをフォアグラウンドで動かせます。
apache
本当はnginxにしたいなぁと思ってるんですが、未だにここだけは、OS標準のapache2.2です。apache2.4のeventスケジューラはなんだかあまり流行ってる気配も無いし、今乗り換えるならやはりnginxかな、と思いつつ、まだapache2.2なんですが・・・
ここはポイントとして、mod_pagespeedがあります。mod_pagespeedのファイルキャッシュとmemcachedをどこに配置するか、です。
意外に盲点なのがファイルキャッシュ領域で、これが/var/cache/mod_pagespeedというところにキャッシュフォルダを掘り始めます。そしてこれをdockerイメージに置きっぱなしにしておくと、忘れた頃に「遅い」ってエラーを吐き始めます。
[error] [mod_pagespeed 1.9.32.10-7423 @4242] Slow read operation on file /var/cache/mod_pagespeed/v2/ayurina.net/http,3A/,2Fblog.ayurina.net/wp-content/uploads/2014/10/2014-10-12-03.42.45.png, : 58.203ms; configure SlowFileLatencyUs to change threshold\n
なので、対策として共有ディレクトリにしてtmpfsにまでしてみたのですが、まだたまにエラー出ますね。頻度は下がったと思いますが・・・。
memcachedについては、これもunix domain socketで通信させたかったのですが、なんとmod_pagespeed側がunix domain socketでのmemcachedとの通信に対応していません(明確に対応していない、っていうメッセージが開発サイトにありました)。仕方がないのでmemcachedはtcpで上げるんですが、別コンテナだとアドレスが変わってしまう懸念があるため、親サーバーのdocker向けブリッジインターフェースのアドレスを固定して、こっちを向ける対応を行いました。
まぁ、一件落着、とは言いませんが、今はこれで動いていますので、とりあえずは・・・という感じ。
docker run -d -h apache.home --name apache.home -p 80:80 -v /share/www/html:/var/www/html -v /share/apache.home/var/log:/var/log -v /share/socks:/socks -v /share/apache.home/var/mod_pagespeed:/var/mod_pagespeed centos6/apache:20151221 /usr/sbin/httpd.worker -f /etc/httpd/conf/httpd.conf -DNO_DETACH -DFOREGROUND
起動はhttpd.worker自体を指定して、オプションデファインでフォアグラウンド化しています。
NewRelic関連
NewRelicの監視エージェント関連は、意外に構成が複雑で、いろいろ監視しようとすると結構環境汚染が激しいことが分かってきました。現状、2枚コンテナ上げつつ、php application用のエージェントはphp-fpmのコンテナで動くという状態で、若干散らかっています。それでも、親OS側には(親OS自体の、server agentは入れていますが)サービス監視関連のagentは動かしていないので、まぁ良く出来てるかなぁと。
最終的な起動コマンドだけ書いておきます。
NewRelic Plugin用 docker run -d -h nrmon.home --name nrmon.home -v /share/nrmon.home/var/log:/var/log \ --link apache.home:apache.home --link php-fpm.home:php-fpm.home \ centos6/nrmon:20151023 /usr/bin/newrelic-plugin-agent -c /etc/newrelic/newrelic-plugin-agent.cfg -f NewRelic NPI用(mysql監視) docker run -d -h mynrmon.home --name mynrmon.home -v /share/mynrmon.home/var/log:/var/log -w /root/newrelic-npi --link mariadb.home:mariadb.home centos6/mynrmon:20151023 /bin/sh npi start nrmysql --foreground
監視はどうしてもtcpソケット経由になるものがありここは「最後に起動する」ことを前提に、link設定しています。特にNPIはこの設定でフォアグラウンド起動できますので参考まで(これ分かる前は、起動シェル置いてました)。
という感じで、このブログサーバーは、「dockerらしいdockerの使い方」な構成に変更され、かれこれ2ヶ月ほど稼働中です。やってみて、dockerの良さや難しさ(ネットワーク、IPアドレス関連は、もうちょっとなんとかならないものか・・・)について、非常に勉強になったと思います。そして、immutableってのはこういうことか!って勝手に納得しました。
ご参考まで。
コメント