てくすた

ピクスタ株式会社のエンジニア・デザイナーがつづるよもやまテクニカルブログです

crontabをやめました

こんにちは、最近iphoneのバッテリーを交換したりしています、開発部でインフラを担当しているShimadaと申します。

今回は、Linuxを使ったシステムではとても便利なcrontabの使用をやめたお話をしたいと思います。

経緯

サーバの再起動が出来ない

PIXTAでは、サービスをAWS上で運用しています。
AWSでは、半年に一度OSの新バージョンがリリースされます。
セキュリティ対応や新しい機能の追加などOSはできるだけ新しいものにしておきたいものです。
OSの更新を行うと再起動が必要になりますが、バッチサーバのcrontabのスケジュールを確認するとサーバの再起動が不可能に近いものでした。

課題

Linuxを使ったシステムでは標準で使用できるcrontabは、定期処理を実行したい時にはとても便利な機能ですが以下の様な問題があります。

  • 冗長化が出来ない(cronieで可能なようです)
  • 負荷分散が出来ない

他にも使う側にも問題がありました。

  • 簡単で便利なため大量の処理を登録する
  • 負荷が増えた時にはスペックを上げるか、サーバを増やす
  • 処理の説明やスケジュールの資料を残さない
  • 処理エラー時はログを出力するのみ
  • 再実行を行う時にsshなどでつなぐのが面倒

上記などの問題を積み重ねcrontabに100もの処理を登録し、単一障害点となったサーバが複数台再起動もままならない状況に陥っていました。

課題の解決

何とかサーバ再起動は乗り越えて、課題解決を模索し始めました。

キーワード:

  • 冗長化
  • 負荷分散
  • スケールアップ/スケールダウン
  • エラー検出
  • 再実行
  • ジョブ管理
  • 管理画面
  • バージョン管理

移行先ツールの調査

crontabを移行できないかと調べてみました。

  • kuroko2 - rails5が必要
  • rundeck - 機能が多く登録項目が多い
  • zabbixの定期実行 - 最後の手段

いくつかのツールを調べてみましたが、アプリケーション稼働環境との差や機能が多く便利が故に登録作業が複雑になるなどなど今回の目的を満たせるものはなかなか見つかりませんでした。

Dkron

決め手は、ジョブの定義方法がcrontabとほぼ同じなこと、シンプルなことでした。

  • Dkron

http://dkron.io/

consulの機能を利用して、ジョブ管理、負荷分散を実現する。
設定ファイル、ジョブ定義ファイル共にjsonで記載し、ジョブ定義はcrontab形式に近い内容で記載できる、登録はHTTP経由のRESTful JSON APIで行う。

  • consul

www.consul.io

複数の機能を実装しているが、KVによるサーバ間データ同期とサーバの相互監視機能を利用する。
Dkron同様に設定ファイルはjsonで記載する。

なぜ、Dcronじゃないのか?という思い。

検証

まずは、”とりあえず動かしてみる”。

どちらも3台以上での稼働を推奨しているので、まずは検証用に3台用意しインストール設定を行います。 (もちろん単体での稼働も可能なようです)

インストール

どちらのツールもコンパイル済みの実行ファイルが提供されているのでダウンロード/解凍して設置するだけで実行可能です。

  • consul
wget https://releases.hashicorp.com/consul/<version>/consul_<version>_linux_amd64.zip
unzip consul_<version>_linux_amd64.zip
  • Dkron
wget https://github.com/victorcoder/dkron/releases/download/v<version>/dkron_<version>_linux_amd64.tar.gz
tar xzf dkron_<version>_linux_amd64.tar.gz

設定

サンプルを参考に.json ファイルを作成してみます。

consul

https://www.consul.io/docs/agent/options.html#configuration-files

  • 格納ディレクトリの作成
 mkdir -p /tmp/consul
 mkdir -p /etc/consul
  • /etc/consul/default.json
{
  "datacenter": "dc1", #グルーピングする文字列
  "server": true, #サーバとするかワーカとするか
  "bind_addr": "172.16.14.10", #ホストのアドレス
  "bootstrap_expect": 3, #クラスタの最小台数
  "rejoin_after_leave": true, #プロセス再起動時にクラスタに再接続をするかしないか
  "retry_join": ["172.16.14.10","172.16.14.11","172.16.14.12"], #クラスタメンバのアドレス
  "data_dir": "/tmp/consul", #データ格納場所
  "client_addr": "0.0.0.0", #WEBインターフェースの受信アドレス
  "ui": true, #WEBインターフェースの利用可否
  "enable_script_checks": true, #スクリプトによるサービス監視の許可を行うか
  "service":{ #監視するサービスの設定
    "name":"dkron", #Dkronの管理画面を監視
    "id":"bat",
    "tag":["bat"],
    "port":8080,
    "check":{
      "script":"curl localhost:8080 >/dev/null 2>&1",
      "interval":"10s"
    }
  }
}
Dkron

https://dkron.io/basics/configuration/

  • 格納ディレクトリの作成
 mkdir /etc/dkron
  • /etc/dkron/dkron.json
{
  "bind_addr": "172.16.14.10", #ホストのアドレス
  "backend": "consul", #データ管理ツールにconsulを指定
  "backend_machine": "127.0.0.1:8500", #データ管理ツールの稼働サーバ
  "server": true, #サーバとするかワーカとするか
  "debug": false, #ログレベルの指定
  "tags": { #サーバのグルーピング、この設定を指定して処理の実行が可能
    "role": "bat",
    "server": "bat1",
    "datacenter": "dc1"
  },
  "keyspace": "dkron", #KVで使用
  "encrypt": "testdkron", #通信暗号化キー
  "join": ["172.16.14.10","172.16.14.11","172.16.14.12"] #クラスタメンバのアドレス
}

起動

consul
  • 設定ファイルのチェック
 consul validate /etc/consul
  • プロセスの起動
 nohup consul agent -config-dir=/etc/consul -pid-file=/var/run/consul.pid >> /var/log/consul.log 2>&1 &
  • 稼働確認
 consul members #クラスタメンバの確認
 consul operator raft list-peers #クラスタメンバの状態と役割を確認
  • web管理画面

http://localhost:8500/ui/

consul管理画面
consul管理画面

  • バックアップとリストア
 consul snapshot save backup.snap #バックアップ
 consul snapshot restore backup.snap #リストア
Dkron
  • 設定ファイルのチェック
 dkron agent #エラーが出なければctrl+cで停止
  • プロセスの起動
 nohup dkron agent >> /var/log/dkron.log 2>&1 &
  • 稼働確認
 curl -s http://127.0.0.1:8080/v1/leader
 curl -s http://127.0.0.1:8080/v1/members
  • web管理画面

http://localhost:8080/

Dkron管理画面
Dkron管理画面

ここまででweb管理画面で稼働の確認はできました。
consulについては、これ以上の機能は使用しない為、設定は終了です。
Dkronは、ここからジョブを登録して動作検証を行います。

動作確認

下記の順にジョブを作成し確認を行います。

  • ジョブの定義
  • ジョブの登録
  • ジョブの確認
  • ジョブの実行
  • ジョブの実行結果確認
ジョブの定義

jsonで作成します。
https://dkron.io/usage/api/#job

{
  "name":"dkron-test-job1", #処理名称
  "schedule":"31 * * * * *", #実行スケジュール(秒+crontab形式
  "shell":true, #シェルを使用してコマンドを実行
  "command":"logger -i `hostname -f`; sleep 10;echo `whoami`@`hostname`;exit $(( `date +%s` % 2 ))", #実行コマンド
  "owner":"user", #処理担当者(実行ユーザではなく表示のみの模様
  "disabled":false, #処理の無効化
  "tags":{
    "role":"bat:1" #実行対象(batグループの1台で実行
  },
  "retries":3, #リトライ回数
  "concurrency":"forbid" #多重起動の可否
}
ジョブの登録

jsonもしくは.jsonファイルを指定し登録します。
https://dkron.io/usage/api/#post-jobs

curl -X POST -H "Content-Type: application/json" http://localhost:8080/v1/jobs -d @/tmp/dkron-test-job1.json
ジョブの確認

管理画面から登録したジョブの内容を確認します。

ジョブの確認
ジョブの確認

ジョブの実行

指定した実行時間を待つか管理画面から実行します。

ジョブの実行
ジョブの実行

ジョブの実行結果確認

管理画面で実行結果を確認します。
※Node項目を見ると実行ホストが変わっているのが分かります

ジョブの実行結果確認
ジョブの実行結果確認

本番導入

使用中のcrontabとDkronは同時稼働が可能なためジョブ移行は部分的に順次実施しました。
crontabでジョブを稼働したまま、Dkronにジョブを登録し無効化しておきます。
以下の順にcrontabのジョブを無効化、Dkronのジョブを有効化していきました。

  1. ジョブ停止による影響がない毎時処理
  2. ジョブ停止による影響がある毎時処理
  3. 日次処理
  4. 週次処理
  5. 月次処理

結果

移行作業自体は何の問題もなく完了しました。
ジョブ実行も各サーバに分散され負荷が集中することはなくなりました。
停止中のサーバがある場合は、稼働中のサーバにジョブが振り分けられるのでサーバの再起動も容易になりました。

移行後に表面化した問題

  • 常にエラーになっている処理があった
  • スクリプトファイルが無い処理がcrontabに残っていた
  • 1時間以上かかる毎時処理があった
  • ログの出力方法、出力先が統一されていなかった
  • 定期的にエラーログを確認していなかった

まとめ

今回は、”サーバの再起動が出来ない”というところから始まりましたが色々なメリットがありました。

  • 定期処理の負荷が分散出来たことでバッチサーバに過剰なリソースを割り当てる必要がなくなった
  • サーバの冗長化が出来たことで定期処理のスケジュールを気にすることなくサーバ再起動などのメンテナンス作業が出来るようになった
  • WEBの管理画面から処理結果が確認出来るようになった
  • WEBの管理画面から処理実行が出来るようになった
  • 潜在的な問題を発見する事が出来た

いかがでしたでしょうか、AWS上での稼働であれば他の方法もあるかとも思いますが、100にもなったジョブの移行はかなりの労力になります。
スモールスタートしたサービスでは、初めからジョブ管理用の仕組みを用意するのは難しく長くサービスの運用を続けていると出てくる問題かと思います。

もしも、同じような問題があれば一度お試し頂ければと思います。

* * * * *

ピクスタでは、明日にきらめく!!エンジニアを募集しています。

recruit.pixta.co.jp

recruit.pixta.co.jp