こんにちは。 インフラエンジニアの光野です。
先日のブログ記事でご紹介したとおり、弊社のクローラーはDockerコンテナ化されています。このコンテナはApache MesosとMarathonのクラスタ上で動いています。
先日の記事はクローラーシステム全体を取り扱いましたが、本記事ではMesos/Marathonを導入するにあたって必要だった設定について「〜したい」という形で紹介いたします。 Tips集として導入や検討の参考にしていただければ何よりです。
記事中の用語については先頭の前提知識・用語まとめにまとめています。また、Tipsは各見出しごとに独立させていますので、お好きな部分を参照ください。
シリーズ一覧
- 新クローラーシステムの全体観
- クラスタへのデプロイについて
- クラスタ構築時のTips
- 本記事
Tips一覧
- MesosのTips
- MarathonのTips
前提知識・用語まとめ
本記事で使う用語や、コマンドを簡単にまとめます。 なお、Apache MesosとMarathon自体については先日のブログ記事で触れておりますので説明を省略します。
登場する用語
名称 | 概要 |
---|---|
Mesosクラスタ | Apache Mesosのマスタとスレーブから成る。zookeeperによって管理される |
Mesosマスタ | Apache Mesosのマスタノード。スレーブに対してタスクを投入する。Web UIもここにある。 |
Mesosスレーブ | Apache Mesosのスレーブノード。クラスタのリソースを担う。 |
Mesosエージェント | Mesosスレーブの各ノードで動くデーモン。起動オプションによってMesosでできることが変わる。 |
タスク | Apache Mesosで実行する処理。シェルコマンドからコンテナまでなんでもよい。本記事中ではdocker run されるもの。 |
ソフトウェアのバージョン
- Ubuntu 16.04.1 LTS
- Apache Mesos 1.1.0
- Marathon 1.4.1
Mesos / Marathonの起動
Ubuntuの場合は、Mesosphare社がパッケージ化してくれており、
リポジトリを追加することでapt-get
を使ってインストールが可能です。
# master sudo apt-get install mesos marathon sudo systemctl start mesos-master sudo systemctl start marathon # slave sudo apt-get install mesos sudo systemctl start mesos-slave
初期設定については以下の記事がとても参考になります。リポジトリについても記載されています。 なおOSバージョン毎に存在するパッケージが若干異なるためご注意下さい。
How To Configure a Production-Ready Mesosphere Cluster on Ubuntu 14.04 | DigitalOcean
Mesosエージェントの設定方法
- 起動時にオプションとして与える
mesos-agent --resources=ports:[80-80, 31000-32000]
- 設定ファイルに記述する
- オプション名 = ファイル名
- ファイルの内容 = 引数
echo 'ports:[80-80, 31000-32000]' > /etc/mesos-slave/resources
本記事では2の方法を使っています。
ref. Apache Mesos - Configuration
sudo systemctl restart mesos-slave
なお、リスタートに失敗するようであればlatestディレクトリを削除して下さい。
sudo rm -rf /var/lib/mesos/meta/slaves/latest
Mesosマスタはzookeeperを介して各スレーブを識別しており、その情報がlatestディレクトリに記録されています。 記録されている情報と新しい設定が食い違うとリスタートに失敗するため、latestを削除し新しいスレーブとして認識させます。
ref. Apache Mesos - Slave Recovery in Apache Mesos
Marathonのタスク宣言
Marathonには大きく3つのタスク宣言方法がありますが、編集の手段が異なるだけで最終的なリクエストは同じJSONです。
- Web UIで宣言する
- Web UIのJSONモードで宣言する
- Web APIで宣言する
本記事でも文中にMarathon用のJSONを記述します。 ただ、説明に必要な部分だけを抜粋しているため、コピー&ペーストでは動作しません。
MesosのTips
Tips 1. ホストポートをコンテナに割り当てたい(Mesos編)
実行するタスクによってはホストのポートを専有したいことがあるかもしれません。
その場合、予めMesosエージェントに起動オプションを与えておく必要があります。
デフォルトで[31000-32000]
が専有可能ですが、これに80番を追加する場合は次のように指定します。
echo 'ports:[80-80, 31000-32000]' > /etc/mesos-slave/resources sudo systemctl restart mesos-slave
Tips 2. プライベートサブネット環境下でMesos UIを使いたい
MesosはWeb UIを持っており、ここからクラスタやタスクの状況、またタスクごとのサンドボックスを確認することができます。
サンドボックス内には、fetch済みのファイルやログファイルがあるためデバッグ時に大変便利です。
この情報は、Web UIがMesosスレーブに対して直接リクエストを行い収集しています。
一方、AWSのベストプラクティスに従うとMesosクラスタはプライベートサブネットに構築されることが多いと思います。 実際に、弊社のMesosクラスタは次の構成になっています。
MesosのWeb UIでサンドボックスの中を確認するためには、手元からプライベートサブネットにあるMesosスレーブへアクセスする必要があります。 間にELBとnginxによるプロキシを挟みこれを解決します。
Mesos Config
Web UIがスレーブにアクセスする場合、その問い合わせはMesosエージェントに起動オプションとして与えられたホストネームに対して行われます。
まず、nginxで扱いやすいユニークな名前を設定してください。
echo "$(hostname).mesos-slave.xxxxx.yyyyy" > /etc/mesos-slave/hostname sudo systemctl restart mesos-slave
xxxxx / yyyyyは、適宜ご自身で所有されているドメインへ読み替えてください。
DNS Record
ELBに対するAliasレコードとして*.mesos-slave.xxxxx.yyyyy
を設定してください。
nginx
nginxを使って、特定のルールに基づくホストネームから名前解決を行い、 プライベートサブネットに存在する各スレーブへプロキシします。 なお、コメントにもありますが、5051ポートはMesosエージェントが利用するためnginxは別ポートでListenしています。
server { set_real_ip_from 10.0.0.0/8; real_ip_header X-Forwarded-For; # [NOTE] 5051はMesosエージェントがbindする # ELBでポートを変えて送信。 # Web UI -> 5051 ELB -> 15051 nginx -> 5051 Mesosスレーブ listen 15051; server_name .mesos-slave.xxxxx.yyyyy; location / { # [NOTE] Route53 resolver 10.0.0.2; # [NOTE] 定期的に名前解決を行えるようにsetする # 直接書くとnginx restartでしか名前解決が行われない if ($host ~* (.*)\.mesos-slave\.xxxxx\.yyyyy) { set $mesos_slave_server "$1.YOUR_AWS_REGION.compute.internal"; } proxy_pass http://${mesos_slave_server}:5051; } }
ここではRoute53をVPC内のprivate DNSとして使っています。
MarathonのTips
Tips 3. ホストポートをコンテナに割り当てたい(Marathon編)
Marathonにおいて特定のホストポートを専有するタスクはスケジューリングに制約を与えることから非推奨になっています。 とはいえ実行するタスクによってはホストのポートを専有したいことがあるかもしれません。 この場合、Marathonでタスクを宣言する際にオプションを与える必要があります。
ポートを割り当てる(入門編)
まずは公式ドキュメントに従って単純に設定します。
{ "container": { "type": "DOCKER", "docker": { "network": "BRIDGE", "requirePorts": true, "portMappings": [ { "containerPort": 3000, "hostPort": 80, "protocol": "tcp"} ] } } }
- requirePortsをtrueに設定
- portMappingsでhostPortを0ではない値に設定
hostPortで指定するポート番号は、予めMesosエージェントに起動オプションで許可されている必要があります。 これらは、公式ドキュメントのトラブルシューティングにわかりやすくまとめられています。
ポートを割り当てる(実践編)
入門編の内容で、ポートを割り当てる事自体は完了です。実践編ではデプロイ時の問題を解決します。
{ "container": { "type": "DOCKER", "docker": { "network": "BRIDGE", "requirePorts": true, "portMappings": [ { "containerPort": 3000, "hostPort": 80, "protocol": "tcp"} ] } }, "constraints": [ ["hostname", "UNIQUE"], // あるタスクがMesosスレーブあたり高々1コンテナになるようにする ], "upgradeStrategy": { "minimumHealthCapacity": 0, // デプロイ時、旧コンテナ数が0になることを許容する "maximumOverCapacity": 0 // デプロイ時、旧タスクをkillしてから新タスクをrunする } }
requirePorts
とhostPort
に加えて、constraints
とupgradeStrategy
を設定しています。
Marathonはタスクを更新する際、upgradeStrategy
とに基づいてタスクをローリングリスタートしてくれます。
新しいタスクが何らかのバグで起動しない場合も、古いタスクがそのまま動き続けるため安全です。
しかし、既存のタスクがホストポートを専有するタスクの場合、新しいタスクはポートをバインドできずデプロイが必ず失敗するという状況に陥ります。
その為、constraints
を使ってタスクのスケジューリングに制約を与えた上で、upgradeStrategy
を変更して旧タスクが無い状況を作り出すことで問題を回避します。
幸いにも弊社で運用されているポートを専有するタスクは、最悪瞬断しても良いという類のものでした。
もし、瞬断が許されない条件で動かす場合は、別の工夫が必要になります。
Tips 4. タスクでUserDefinedNetworkを使いたい
DockerのUDNを使いたい場合は、3箇所の宣言が必要です。
{ "container": { "type": "DOCKER", "docker": { "network": "USER" } }, "ipAddress": { "networkName": "mesos_slave_host_nw" }, "ports": [] }
network
にUSERを指定ipAddress
にUDN名を指定docker network create
したときの名前
ports
に空配列を指定
空配列を明示的に指定しないと、エラーになります。 portsはオプショナルな項目のため、ついつい忘れがちです。ご注意ください。
Tips 5. タスクの宣言をWeb APIで行いたい
Marathonは整理されたWeb APIをもっています。 Web APIにはコンソールも用意され、各パラメータの詳細を確認することが可能です。
Web APIはRESTfulに設計されており、状況に応じてPOST/PUT/PATCH/DELETEを使い分けます。
- POST: 新タスクの宣言
- PUT: 既存タスクの更新 / もし既存タスクがなければ作成される
- PATCH: 既存タスクの更新(1.4.1時点で
/v2/apps
以下のエントリポイントのみ) - DELETE: タスクの削除
操作したいリソースに対するエントリポイントさえ分かれば自然に利用できるかと思いますが、
その中で/v2/apps
に対するPUTについては注意が必要です。
curl -XPUT -H "Accept: application/json" -H "Content-type: application/json" <Mesosマスター>/v2/apps/ -d@app.json
PUTはとても便利でPOST/PATCHの両方を兼ねてくれるのですが、 Marathon 1.4系からPATCHのように振る舞うPUTについてDeprecatedになりました。
後方互換性を守るため、1.4.1時点ではPUTとPATCHに挙動の差はありません。ただし、次のバージョン(おそらく1.5.0)で変更されるという宣言がされています。
For backward compatibility, we will not change this behaviour, but let users opt in for a proper PUT. The next version of Marathon will use PATCH and PUT as two separate actions.
将来のバージョンアップを考えると、PATCHを積極的に利用するのが望ましいです。
余談ですが、1.4.0には「PATCHのように振る舞うPUTがすべてエラーになる」という不具合がありUIも一部動作しません。そのためアップデートの際には1.4.1以降を選択下さい。
おわりに
Apache MesosとMarathonを本番運用するにあたって必要になるであろう内容をTipsの形でご紹介いたしました。
Apache MesosとMarathonは実際に運用している情報が少なく、発生する問題に対しては自力で解決する必要があります。 とはいえ、両者とも機能そのものは豊富ですし、なにより公式の情報がとても丁寧に整備されています。 そのため、ドキュメントさえ読み込めば大抵のことはフレームワーク上で解決できるというのが、実際に運用してみての感想です。
今後もまた問題が発生するとは思いますが、都度ドキュメントとにらめっこして解決して解決していこうと考えています。
最後に
VASILYにはこんなトライ&エラーを繰り返しながら成長できる環境があります。皆様の応募をお待ちしております。