最近のジョブスケジューラは何がいいのか
仕事でJenkinsを使ってるのですが、Jenkinsをビルドツール以上に、「定期実行してくれるもの全て」を管理するジョブ管理ツールとして使うことに疑問を覚えたので、メモします。
Jenkinsは継続的インテグレーションツールであって、ジョブ管理ツールではないと思うのです。
巷の記事を見ると、大抵「Jenkinsはジョブ管理の代替になる」という論調ばかりなのですが、Jenkinsをジョブ管理ツールとして使うには、かなり無理があるのではないかと思います。
というのも、ジョブ管理ツールに求めるのは
- どのサーバで動いているか
- 同一時刻にどんな処理が動いてるか
- どのジョブとどのジョブが関連しているか
といった情報を設定でき、管理できることなのですが、1つ目はそもそもJenkins単体だとローカルでしか動かないので把握できない(sshなどでリモート実行は当然可能だが、それは処理を見ないと分からない)、2つ目は過去の実行履歴としては見られるが、定義としては俯瞰できない、3つ目はBuild Flow pluginやBuild Pipeline pluginとしては実現可能ですが、細かいところで微妙に使いづらいのです。(Build Flowは、ジョブ間の繋がりを1つの定義ビューからしか追えないことや、実行後でないとgraphとして見ることができない点。Build Pipelineは、同一ジョブを複数のpipelineで使いまわせない点や、ジョブの"合流"ができない点)
確かにcronよりは遥かにマシですが、ジョブ管理ツールとして比べるにはかなり無理があるような気がしてなりません。そもそもJenkinsは"Build"というように、ビルドに特化したツールだと思うので、上記のような機能が充実してなくても違和感はないのですが、むしろ世間の「Jenkinsはジョブ管理として使える」という風潮は本当にそうなのだろうか?と思ったりします…。
一応ジョブ管理のツールとしては以下のようなものがあるようなのですが、とはいえこのへんのツールが最近の勉強会で取りざたされることはないような気がします(知らないだけかもしれません)。
世の中の企業はどうやってジョブ管理してるんでしょうか?
consul watchの変な挙動
watchとは
consulのwatch機能は、サービスなどを監視し、状態が変化したときに指定した処理を実行する機能です。
公式によると、以下の条件でwatchは発動するようです。 https://www.consul.io/docs/agent/watches.html
- Key/Valueが変化したとき
- Key Prefix配下が変化したとき
- Serviceの数が変化したとき
- nodeの数が変化したとき
- 特定のServiceの状態が変化したとき
- checkの状態が変化したとき
- ユーザの指定したeventを発行したとき
ここではwatch機能の挙動を確認してみます。
自分の理解が足りないのか、ちょっと今のままでは使いづらいなという印象です…。
なお、検証したのは上記のうち「特定のServiceの状態が変化したとき」についてです。
使いづらいポイント
検証で後述しますが、まとめると以下です。
- consulノードが起動/停止しても発動(チェック対象のサービスの状態にかかわらず)
- consul reloadしても発動
- しかも発動する回数が1回とは限らない
- Serviceが停止/起動しても発動
- しかも停止時と起動時でアクションを変更できない(どちらも同じアクションになる)
設定
今回はserver :1 , agent :2の構成で確認します。
基本設定
dc1_server
- /etc/consul/conf.d/config.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1 }
consul自体の起動は、後でserviceとwatchのconfigを配置してから一緒に行います。
dc1_client[12]
2台登録します。(あまり今回の検証と関係ありませんが)
- /etc/consul/conf.d/config.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": false, "retry_join": ["dc1server"] }
Serviceの登録
dc1_server, dc1_client[12]
-config-dir
配下にファイルを配置します。
- /etc/consul/conf.d/service-filecheck.json
{ "service": { "id": "file-check", "name": "file-check", "tags": ["master"], "checks": [ { "script": "/tmp/filecheck.sh", "interval": "10s" } ] } }
service監視用のスクリプトを配置します。
- /tmp/filecheck.sh
#!/bin/bash if [ -f "/tmp/testfile" ] ; then exit 0 fi exit 2
単純にファイルの存在をチェックし、結果を返すだけのスクリプトです。
/tmp/testfile
自体は空です。
watchの登録
dc1_server
/etc/consul/conf.d/watch-filecheck.json
{ "watches": [ { "type": "service", "service": "file-check", "handler": "/tmp/filecheck_handler.sh" } ] }
起動
dc1_server, dc1_client[12]
consul agent -config-dir=/etc/consul/conf.d
検証
起動時にまず1回以上実行されます。
これは、3台を同時に起動しており、consul起動とService登録が順次行われたためと思われます。
起動順によるのか、1回だったり以下のとおり2回だったり、回数は不定です。
発動自体は想定通りなのですが、回数が不定なのは仕方ないとはいえちょっと気になります。
[root@dc1server /]# cat /tmp/consul.log executed script: Sat Jun 27 06:14:37 UTC 2015 executed script: Sat Jun 27 06:14:42 UTC 2015
reloadしても1回以上実行されます。
[root@dc1server /]# consul reload Configuration reload triggered [root@dc1server /]# cat /tmp/consul.log executed script: Sat Jun 27 06:14:37 UTC 2015 executed script: Sat Jun 27 06:14:42 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 # 増えた executed script: Sat Jun 27 06:15:23 UTC 2015 # 増えた
同じことを実行しても、発動が1回だけのときもあります。
これがよく分からない…。
[root@dc1server /]# consul reload Configuration reload triggered [root@dc1server /]# cat /tmp/consul.log executed script: Sat Jun 27 06:14:37 UTC 2015 executed script: Sat Jun 27 06:14:42 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:48 UTC 2015 # 増えた
これは本来の動作ですが、サービスをダウンさせても発動します。
(ここではサービス監視対象のファイルを削除しています)
[root@dc1server /]# consul exec -node dc1client1 "rm -f /tmp/testfile" ==> dc1client1: finished with exit code 0 1 / 1 node(s) completed / acknowledged [root@dc1server /]# consul exec "ls -l /tmp/testfile" dc1server: -rw-r--r-- 1 root root 0 Jun 27 04:34 /tmp/testfile dc1server: ==> dc1server: finished with exit code 0 dc1client1: ls: cannot access /tmp/testfile: No such file or directory dc1client1: dc1client2: -rw-r--r-- 1 root root 0 Jun 27 04:34 /tmp/testfile dc1client2: ==> dc1client1: finished with exit code 2 ==> dc1client2: finished with exit code 0 3 / 3 node(s) completed / acknowledged [root@dc1server /]# cat /tmp/consul.log executed script: Sat Jun 27 06:14:37 UTC 2015 executed script: Sat Jun 27 06:14:42 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:48 UTC 2015 executed script: Sat Jun 27 06:23:42 UTC 2015 # 増えた
サービスを復旧させても発動します。
(ここではサービス監視対象のファイルを作っています)
[root@dc1server /]# consul exec -node dc1client1 "touch /tmp/testfile" ==> dc1client1: finished with exit code 0 1 / 1 node(s) completed / acknowledged [root@dc1server /]# consul exec "ls -l /tmp/testfile" dc1server: -rw-r--r-- 1 root root 0 Jun 27 04:34 /tmp/testfile dc1server: ==> dc1server: finished with exit code 0 dc1client1: -rw-r--r-- 1 root root 0 Jun 27 06:25 /tmp/testfile dc1client1: dc1client2: -rw-r--r-- 1 root root 0 Jun 27 04:34 /tmp/testfile dc1client2: ==> dc1client2: finished with exit code 0 ==> dc1client1: finished with exit code 0 3 / 3 node(s) completed / acknowledged [root@dc1server /]# cat /tmp/consul.log executed script: Sat Jun 27 06:14:37 UTC 2015 executed script: Sat Jun 27 06:14:42 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:23 UTC 2015 executed script: Sat Jun 27 06:15:48 UTC 2015 executed script: Sat Jun 27 06:23:42 UTC 2015 executed script: Sat Jun 27 06:25:32 UTC 2015 # 増えた
Service機能の1つであるDNSは、想定通り3つ返しています。
[root@dc1server /]# dig @127.0.0.1 -p 8600 file-check.service.consul. ANY (snip) ;; QUESTION SECTION: ;file-check.service.consul. IN ANY ;; ANSWER SECTION: file-check.service.consul. 0 IN A 172.17.0.34 file-check.service.consul. 0 IN A 172.17.0.36 file-check.service.consul. 0 IN A 172.17.0.32 (snip)
まとめ
consulのservice機能自体はかなり便利です。特に生きているノードに対してのみレコードを返すようなDNSは、いろいろと応用が効きそうです。
一方、Serviceと組み合わせたwatch機能はかなり使いづらい印象を受けました。
- Serviceのdown/upに関係なく発動する
- configのreload時にも発動する
- 1回発動するとは限らない。何回発動するかも分からない(自分だけかも)
watch機能は、何回実行しても問題ないような処理に限ったほうがよさそうだという印象です。configの入れ替えなどでしょうか。ただ、サービスのダウン時、アップ時に処理を分けたいと思うことは多いと思われますので、今のままだとスクリプトで処理を分けなければならず、ちょっと使いづらいです。特にreload時に勝手に発動するのは、できればコントロールさせて欲しい…。
以下でも話題になっています。
Reloading Consul re-runs all watch commands every time. · Issue #571 · hashicorp/consul · GitHub
あと今回は触れませんでしたが、checkと連動するときも注意が必要です。 「このcheck」という指定ができないので、checkを複数種類登録しており、かついずれかのcheckの状態が変更されると、どのcheckだったとしてもwatchが発動します。 基本的に、watchはserviceに紐づくものなので、checkもserviceに紐付けることが前提になりそうです。
consul : aclを利用して特定のdcからのみconsul execを実行可能なようにアクセス制限をかける
consul exec
consulにはconsul exec
という便利なコマンドがあります。
これは、consulクラスタに属しているメンバに対して一斉にコマンドを発行できるというものです。
しかも、発行先を選択することができ、データセンタ単位やノード単位だったり、それを正規表現で更に絞ったりと柔軟な発行ができます。
このように、consul exec
は非常に便利なコマンドである一方、権限によっては何でもできてしまうので、限られた条件でしか発行できないように
制限をかけることも考えたくなります。
一方、consulにはacl機能もあり、tokenを発行してこのような権限管理を行うことができます。
aclを利用した制限のかけ方(範囲)には様々なケースが考えられます。
またconsulにはdatacenterという考え方がありますが、dcを管理エリア単位と捉えて、以下のようなイメージで設定を行ってみます。
- dc1 : マネジメントdc。このdcからは他のdcも含めてどこにでもexecを発行できる。
- dc2 : 非マネジメントdc。このdcからはconsul execを発行した場合、dc1にもdc2にも発行できない。
イメージとしては以下のとおりです。
この後に設定を行っていきますが、「擬似的に」その状態にすることは可能です。(どう「擬似的」なのかは後述します)
consul execの仕組み
https://www.consul.io/docs/commands/exec.html https://www.consul.io/docs/internals/acl.html
公式のページにもありますが、consul execは以下のような仕組みで実行されています。
- consulのkey/valueストア機能を利用
- デフォルトで
_rexec
prefixに対してread/writeを行い、この値をノード間で伝播させてそれぞれのノードでコマンドを発行している
したがって、_rexec
prefixに対して読み書きできないとconsul execは実行できない。
prefixである_rexec
という値はコマンド発行時に変更可能。
- デフォルトで
- acl的には、デフォルトではanonymous tokenを利用してkey/valueの読み書きを行う。
consul exec
も同様。
利用するtokenのデフォルトはanonymousだが、acl_token
の設定により変更することが可能。
基本的な動きは前回のエントリで紹介したので、ここでは設定だけ。
設定
dc1 server
consul agent -config-file=/etc/consul/conf.d/dc1_server.json
/etc/consul/conf.d/dc1_server.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1, "acl_datacenter": "dc1", "acl_default_policy": "deny", "acl_master_token": "master_token", "acl_token": "anonymous" }
{ "ID": "anonymous", "Type": "client", "Rules": "{ \"key\": { \"_somerandomstring/\": { \"policy\":\"write\" } } }" }
設定
[root@dc1server /]# curl -X PUT http://localhost:8500/v1/acl/update?token=master_token -d @/tmp/acl.json {"ID":"anonymous"}
dc1 client
consul agent -config-file=/etc/consul/conf.d/dc1_client.json -join=dc1server
/etc/consul/conf.d/dc1_client.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": false, "acl_token": "anonymous" }
dc2 server
consul agent -config-file=/etc/consul/conf.d/dc2_server.json -join-wan=dc1server
/etc/consul/conf.d/dc2_server.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1, "acl_datacenter": "dc1", "acl_token": "anonymous" }
dc2 client
consul agent -config-file=/etc/consul/conf.d/dc2_client.json -join=dc2server
/etc/consul/conf.d/dc2_client.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": false, "acl_token": "anonymous" }
結果
consul exec
実行時は、実行オプションに-prefix="_somerandomstring"
を指定しないとエラーになります。
[root@dc1server /]# consul exec hostname Failed to create job file: Unexpected response code: 403 (Permission denied) [root@dc1server /]# consul exec -prefix="_somerandomstring" hostname dc1server: dc1server dc1server: ==> dc1server: finished with exit code 0 dc1client: dc1client dc1client: ==> dc1client: finished with exit code 0 2 / 2 node(s) completed / acknowledged [root@dc1server /]# consul exec -prefix="_somerandomstring" -datacenter="dc2" hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 dc2client: dc2client dc2client: ==> dc2client: finished with exit code 0 2 / 2 node(s) completed / acknowledged
仕組み
仕組みは単純で、consul exec
で指定するk/vのprefixを明示的に指定し、そのprefixに対してのみwrite権限を付与するというものです。
そのprefix自体を秘匿してしまうことで、"結果的に"dc1でしかconsul execを発行できなくなります。
_somerandomstring
という文字列のみwriteを許可しているので、この文字列を推測不能にしておく必要があります。_somerandomstring
という文字列を見るためには、dc1のmaster tokenを利用してaclの中を覗き見るしかありません。
が、dc2から見るとacl_datacenter
はdc1
となっているため、dc2内のサーバにはmaster tokenの設定はありません。- 逆に言うと、
_somerandomstring
を知られてしまい、それを使えば、dc2からであっても実行は可能です。
最後に
必要なのでこういう検証をしたわけですが、そもそもこんな使い方をするところはないような気がします。
あと、結構無理やり設定を活用した気がしてならないので、もっと簡単にできればいいのですが…。
consulのacl管理とconsul execの関係
consulのacl
consulは、aclによって実行制限を行うことができます。
ここでは、multi datacenterでacl管理を行うための、設定と効果を見てみます。
きっかけは、consulの設定の中にacl_datacenter
というものがあるのですが、ネット上のいろいろなページを見てもいまいち意味がわからなかったためです。
datacenter
と acl_datacenter
を同じ値にしていたり、では別の値だとどうなるのか?など…。
consul接続イメージ
今回構築しているのは以下のようなイメージです。(前回と同じイメージです)
- dc1,dc2の2つのデータセンタがあり、それぞれにconsulサーバ1台、consulクライアント1台が属する
- dc1とdc2間は接続されている
構築
前回の設定に加えて、aclの設定を入れてみます。
dc1 server
consul agent -config-file=/etc/consul/conf.d/dc1_server.json
/etc/consul/conf.d/dc1_server.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1 "acl_datacenter": "dc1", "acl_default_policy": "deny", "acl_master_token": "master_token", "acl_token": "anonymous" }
acl_default_policy
をdeny
としています。
dc1 client
consul agent -config-file=/etc/consul/conf.d/dc1_client.json -join=dc1server
/etc/consul/conf.d/dc1_client.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": false, "acl_token": "anonymous" }
dc2 server
consul agent -config-file=/etc/consul/conf.d/dc2_server.json -join-wan=dc1server
/etc/consul/conf.d/dc2_server.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1, "acl_datacenter": "dc1", "acl_default_policy": "allow", "acl_master_token": "master_token", "acl_token": "anonymous" }
ここでは、datacenter
はdc2
ですが、acl_datacenter
はdc1
としています。
また、acl_default_policy
をallow
としています。
dc2 client
consul agent -config-file=/etc/consul/conf.d/dc2_client.json -join=dc2server
/etc/consul/conf.d/dc2_client.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": false, "acl_token": "z" }
他のノードと異なり、acl_token
名を異なるものにしています。
(anonymous
ではなくsome_token
としています)
各設定の意味
acl_datacenter
:
自dcが従いたいdc名を指定します。ここで設定したdcでのacl設定の影響を受けます。
他dcを指定した場合、自dcでのacl設定は意味を成さなくなり、指定したdcのacl設定に従います。acl_default_policy
:
acl設定が、blacklistモードかwhitelistモードかを指定します。
allow
と指定した場合、基本的にあらゆる操作を許容し、別途拒否したい操作を追加で設定していきます。
deny
と指定した場合、逆に基本的にあらゆる操作を拒否し、別途許容したい操作を追加で設定していきます。acl_master_token
:
key/valueに対して、read/write(=あらゆる操作)が可能になるtoken名を指定します。acl_token
:
tokenを指定しない場合に、デフォルトで(暗黙的に)指定するtokenを記載します。
consul exec実行によるaclの影響確認
consul exec
コマンドも、暗黙的にk/vで_rexec
prefixを利用しているため、aclの影響を受けます。
上記設定の場合、どのような振る舞いになるのか確認してみます。
dc1 server
[root@dc1server /]# consul exec hostname Failed to create job file: Unexpected response code: 403 (Permission denied)
dc2 server
[root@dc2server /]# consul exec hostname Failed to create job file: Unexpected response code: 403 (Permission denied)
ポイントは、dc2 serverではconfigで"acl_default_policy": "allow"
と設定しているにも関わらず、実行に失敗している点です。
これは、acl_datacenter
がdc1
であるために、dc1
側の設定である"acl_default_policy": "deny"
が有効になっているためと思われます。
aclの設定
ここから、acl設定を追加することでどのような挙動になるのかを確認してみます。
最終的には、consul exec
が実行でき、結果が返ってくるようにします。
aclの登録
まずはpolicy設定を作成します。
公式(https://www.consul.io/docs/internals/acl.html)によるとjson形式で記述できるとあるのですが、少しだけ注意が必要です。
下記を見るとわかる通り、jsonはjsonなのですが、jsonの中にjsonを書くという、特殊な記載になります…。
(全体がjson形式で、さらにRules
の中が更にjsonになっていることがわかると思います)
{ "ID": "anonymous", "Type": "client", "Rules": "{ \"key\": { \"_rexec/\": { \"policy\":\"write\" } } }" }
このjsonファイルを登録します。登録時はacl_master_token
で指定したtokenを指定します。
[root@dc1server /]# curl -X PUT http://localhost:8500/v1/acl/update?token=master_token -d @/tmp/acl.json {"ID":"anonymous"}
効果の確認
先ほど、anonymous tokenの_rexec
prefixに対してwrite権を付与したので、consul exec
が実行できるはずです。
まずはdc1 serverから。
[root@dc1server /]# consul exec hostname dc1server: dc1server dc1server: ==> dc1server: finished with exit code 0 dc1client: dc1client dc1client: ==> dc1client: finished with exit code 0 2 / 2 node(s) completed / acknowledged [root@dc1server /]# consul exec -datacenter="dc2" hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 1 / 1 node(s) completed / acknowledged [root@dc1server /]#
実行できています。ただし、dc2 clientの結果は返ってきていません。
次にdc2 serverから。
[root@dc2server /]# consul exec hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 1 / 1 node(s) completed / acknowledged [root@dc2server /]# consul exec -datacenter="dc1" hostname dc1server: dc1server dc1server: ==> dc1server: finished with exit code 0 dc1client: dc1client dc1client: ==> dc1client: finished with exit code 0 2 / 2 node(s) completed / acknowledged
上記の通り、実行できました。ただし、こちらもdc2 clientだけ結果が返って来ません。
これは、dc2 clientのacl_token
をanonymous
ではなく別の名前(ここではsome_token
)にしているためです。
先ほどaclの設定でwrite権を設定した対象は、anonymousでした。
dc2 clientでも以下のログが出力されています。
2015/06/20 15:09:24 [ERR] agent: failed to get remote exec job: rpc error: ACL not found
aclの登録(2回目)
そこで、some_token
に対しても_rexec
に対してwrite権を付与してみます。
{ "ID": "some_token", "Type": "client", "Rules": "{ \"key\": { \"_rexec/\": { \"policy\":\"write\" } } }" }
登録します。
[root@dc1server /]# curl -X PUT http://localhost:8500/v1/acl/create?token=master_token -d @/tmp/acl_2.json {"ID":"some_token"}
最初と異なり、今回のhttp apiはupdate
ではなくcreate
を指定します。これは、anonymousはデフォルトで既存の設定が付与されているためです。
aclのcreate
は、通常はID
を指定せずに実行することで、ランダム文字列となるtokenが返ってきます。
通常、httpのapiを利用するときには、ここで返却されるID
を利用してtokenに指定することで権限を利用して操作を行いますが、
今回はacl_token
で既にtoken名を指定してしまっているので、決め打ちでaclを設定しています。
効果の確認(2回目)
これで、dc2 clientでもconsul exec
が実行できているはずです。
[root@dc1server /]# consul exec -datacenter="dc2" hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 dc2client: dc2client dc2client: ==> dc2client: finished with exit code 0 2 / 2 node(s) completed / acknowledged
無事に実行できました。
これで、aclの効果と設定方法が少しだけ分かった気がします。
consulのdatacenter間接続
consulをいろいろと検証してみているので、分かったことをまとめます。
consulのversionは0.5.2です。
consulとは
consulとはservice検出、healthcheck機能やkey/value store機能を持ったクラスタリングソフトです。
公式によると以下。
- サービス検出
- ヘルスチェック
- Key/Valueストア
- マルチデータセンタ
consul接続イメージ
今回構築しているのは以下のようなイメージです。
- dc1,dc2の2つのデータセンタがあり、それぞれにconsulサーバ1台、consulクライアント1台が属する
- dc1とdc2間は接続されている
このようなときに、どのような見え方になるのか?を確認してみます。
構築
dc1 server
consul agent -config-file=/etc/consul/conf.d/dc1_server.json
/etc/consul/conf.d/dc1_server.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1 }
dc1 client
consul agent -config-file=/etc/consul/conf.d/dc1_client.json -join=dc1server
/etc/consul/conf.d/dc1_client.json
{ "datacenter": "dc1", "data_dir": "/tmp/consul", "server": false }
dc2 server
consul agent -config-file=/etc/consul/conf.d/dc2_server.json -join-wan=dc1server
/etc/consul/conf.d/dc2_server.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": true, "bootstrap_expect": 1 }
dc2 client
consul agent -config-file=/etc/consul/conf.d/dc2_client.json -join=dc2server
/etc/consul/conf.d/dc2_client.json
{ "datacenter": "dc2", "data_dir": "/tmp/consul", "server": false }
consul membersでの見え方
dc1 serverから見た場合
[root@dc1server /]# consul members Node Address Status Type Build Protocol DC dc1client 172.17.0.58:8301 alive client 0.5.2 2 dc1 dc1server 172.17.0.57:8301 alive server 0.5.2 2 dc1 [root@dc1server /]# consul members -wan Node Address Status Type Build Protocol DC dc1server.dc1 172.17.0.57:8302 alive server 0.5.2 2 dc1 dc2server.dc2 172.17.0.59:8302 alive server 0.5.2 2 dc2
dc1 clientから見た場合
[root@dc1client /]# consul members Node Address Status Type Build Protocol DC dc1server 172.17.0.57:8301 alive server 0.5.2 2 dc1 dc1client 172.17.0.58:8301 alive client 0.5.2 2 dc1 [root@dc1client /]# consul members -wan
このことから、以下のことが分かります。
consul members
では、自dc内のメンバを確認できる-wan
オプションを付与することで、dc同士のサーバ間接続については確認可能。clientからは確認不可。
consul execでの発行先
dc1 serverから実行した場合
[root@dc1server /]# consul exec hostname dc1server: dc1server dc1server: ==> dc1server: finished with exit code 0 dc1client: dc1client dc1client: ==> dc1client: finished with exit code 0 2 / 2 node(s) completed / acknowledged [root@dc1server /]# consul exec -datacenter="dc2" hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 dc2client: dc2client dc2client: ==> dc2client: finished with exit code 0 2 / 2 node(s) completed / acknowledged
dc1 clientから実行した場合
[root@dc1client /]# consul exec hostname dc1client: dc1client dc1client: ==> dc1client: finished with exit code 0 dc1server: dc1server dc1server: ==> dc1server: finished with exit code 0 2 / 2 node(s) completed / acknowledged [root@dc1client /]# consul exec -datacenter="dc2" hostname dc2server: dc2server dc2server: ==> dc2server: finished with exit code 0 dc2client: dc2client dc2client: ==> dc2client: finished with exit code 0 2 / 2 node(s) completed / acknowledged
このことから、以下のことが分かります。
- serverでもclientでも
exec
コマンドは実行可能。 - clientから見て
members -wan
コマンドで他dcが見えなかったとしても、コマンド自体は発行できる。
logrotateでcreate時にパーミッションが効かない
/etc/logrotate.d/
配下でログローテートの設定を行ったときに、ローテートが実行されてもどうしてもパーミッション設定が変わらないことがありました。以下のような設定をしていたときです。
/etc/logrotate.d/sample
/var/log/hoge.log { daily rotate 7 missingok copytruncate create 644 root root }
もともと、対象のログのパーミッションが640
だったものを、ログローテート時に644
にうまく変更してくれることを期待していました。
しかし、実際にはローテートしたとしても、パーミッションは変わりません。640
で作成されたなら、640
のままです。どうしても設定が効かずに、ならなぜcreate
みたいな設定があるのだろうか?と疑問に思いました。
原因は、設定の競合でした。
copytruncate
: ファイル自体を消さずに、中身をコピーして元ファイルの中身を消すcreate
: ファイルが存在しなければ作る
ということで、そもそもcreate
は、ファイルをなければ作るのであって、既に存在するファイルのパーミッションを変更するものではないのでした。一方でcopytruncate
は、ファイルの存在自体を消さないので、いつまでも当初のパーミッションのままファイル自体は存在し続けます。
従って、ログローテート対象のログのパーミッションを変更するには、以下のようにするしかなさそうです。
- logrotateのオプションで
copytruncate
をやめる - そもそもchmodなどでログのパーミッションを変更する
そもそも、ミドルウェアがローテート後のログを見続ける問題があるので、copytruncate
を入れているのであれば、「あえて」入れている場合がほとんどだと思います。
ということで、素直に後者で対応しましょう、という話でした。
chefの依存定義(depends)を勘違いしていた話
chefでいうmetadata.rb
中のdepends
が保証する依存性がどこまでなのかを勘違いしていたのでメモしておきます。
結論からいうと、 depends指定はattributeの参照先として読み込むだけ の機能でした。
- dependsにより保証されること
- depends参照先cookbookのattributeを流用できること
- dependsで保証されないこと
- 必ずしもdepends参照先cookbookを実行する必要がないこと
- ましてや、depends参照先が、記載元cookbookより「先に」実行される必要はないこと
この、「保証されないこと」ができるとばかり勘違いしていたので、dependsに頼った開発をしていたらエラーにならずに、あれ…?となってしまったのでした。
公式の記述
以下のように記載があります。
Use to show that a cookbook has a dependency on another cookbook.
これだけだと分からないですね。
テスト
事前準備
cookbook aとcookbook bを作っておきます。
概ね以下のような構造です。(検証で無関係なところは省略しています)
chef-repo ├cookbooks │├a ││├attributes/default.rb ││├recipes/default.rb ││└metadata.rb │└b │ ├attributes/default.rb │ ├recipes/default.rb │ └metadata.rb └localhost.json
cookbook bのmetadata.rb
中でa
への依存性を定義します。
cookbooks/b/metadata.rb
<略> depends 'a'
cookbook a,b内ではそれぞれattributeを定義しておきます。
cookbooks/a/attributes/default/rb
default['a']['value'] = "attribute a"
cookbooks/b/attributes/default/rb
default['b']['value'] = "attribute b"
cookbook bのrecipeでは、cookbook a,bのattributeを参照するようにします。
cookbooks/b/recipes/default.rb
log "this is cookbook b" log "attribute b: #{node['b']['value']}" log "attribute a: #{node['a']['value']}"
chef-solo
実行時は、cookbook bのみを実行するようにします。
{ "run_list":[ "recipe[b]" ] }
このまま実行する場合
depends指定をすれば、たとえ他cookbookを実行しなかったとしても、attributeは利用できます。
$ sudo chef-solo -c solo.rb -j ./localhost.json Starting Chef Client, version 12.0.3 Compiling Cookbooks... Converging 3 resources Recipe: b::default * log[this is cookbook b] action write * log[attribute b: attribute b] action write * log[attribute a: attribute a] action write Running handlers: Running handlers complete Chef Client finished, 3/3 resources updated in 1.688893613 seconds
depends指定をせず、他cookbookのattributeを参照する場合
attributeが参照できないので、当然エラーになります。
cookbooks/b/metadata.rb
<略> # depends 'a' # コメントアウト
実行時エラー。
$ sudo chef-solo -c solo.rb -j ./localhost.json Starting Chef Client, version 12.0.3 Compiling Cookbooks... ================================================================================ Recipe Compile Error in /home/vagrant/chef-repo/cookbooks/b/recipes/default.rb ================================================================================ NoMethodError ------------- undefined method `[]' for nil:NilClass Cookbook Trace: --------------- /home/vagrant/chef-repo/cookbooks/b/recipes/default.rb:12:in `from_file' Relevant File Content: ---------------------- /home/vagrant/chef-repo/cookbooks/b/recipes/default.rb: 5: # Copyright 2015, YOUR_COMPANY_NAME 6: # 7: # All rights reserved - Do Not Redistribute 8: # 9: 10: log "this is cookbook b" 11: log "attribute b: #{node['b']['value']}" 12>> log "attribute a: #{node['a']['value']}" 13: <略>
attribute上の依存関係をなくして、依存先cookbookを利用しない場合
cookbook aのattributeを参照しないようにします。
cookbooks/b/recipes/default.rb
log "this is cookbook b" log "attribute b: #{node['b']['value']}" # log "attribute a: #{node['a']['value']}" # コメントアウト
cookbook bのaへの依存性も元に戻しておきます。
cookbooks/b/metadata.rb
<略> depends 'a'
この時点で実行すると、エラーは発生せず正常終了します。
$ sudo chef-solo -c solo.rb -j ./localhost.json Starting Chef Client, version 12.0.3 Compiling Cookbooks... Converging 2 resources Recipe: b::default * log[this is cookbook b] action write * log[attribute b: attribute b] action write Running handlers: Running handlers complete Chef Client finished, 2/2 resources updated in 41.895869267 seconds
ポイントは、 dependsによりcookbook aを指定しているにも関わらず、aを実行しなくても問題がない ことです。
依存先cookbookを配置しない場合
では、cookbook aをそもそも配置しなかったら…エラーになります。
chef-repo ├cookbooks ││# aディレクトリを消す │└b │ ├attributes/default.rb │ ├recipes/default.rb │ └metadata.rb └localhost.json
実行時エラー。
$ sudo chef-solo -c solo.rb -j ./localhost.json Starting Chef Client, version 12.0.3 Compiling Cookbooks... Running handlers: [2015-03-29T00:11:22+00:00] ERROR: Running exception handlers Running handlers complete [2015-03-29T00:11:22+00:00] ERROR: Exception handlers complete [2015-03-29T00:11:22+00:00] FATAL: Stacktrace dumped to /tmp/chef-solo/chef-stacktrace.out Chef Client failed. 0 resources updated in 41.570472421 seconds [2015-03-29T00:11:22+00:00] ERROR: Cookbook a not found. If you're loading a from another cookbook, make sure you configure the dependency in your metadata [2015-03-29T00:11:23+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
まとめ
metadata.rb
中のdepends指定により分かったことは以下です。
- depends参照先のcookbookはcookbook_pathに配置している必要がある
- ただし、配置するだけでよく、実行される必要はない
「cookbook aが実行されてからでないとcookbook bが実行されてはならない」というシチュエーション時に、depends
を記載すれば依存関係や順序性が(実行はされずとも)チェックされると思ったのですが、全然そんなことはなかったです…。例えば、「jdkをインストールしてからelasticsearchをインストール」とか、「rubyをインストールしてから各種gemをインストール」とか、そういったケースはあり得ると思うのですが、それはdepends
でチェックするべきではなかったんですね…。
期待していたのは、
"recipe[gem]", "recipe[ruby]"
みたいな書き方をlocalhost.json
中で書いたとし、「rubyを先に実行する必要があるよ」とエラーを出してくれるとか、
"recipe[gem]"
と書いたときに「rubyが実行されてないよ」とエラーを出してくれることだったのですが、そうはならず…。
おそらく、ひとつのcookbookにinclude_recipe
を記載することで順序性や依存性を定義するのが理想なのかと思いますが、それを汲み取ることができずに苦労した話でした。