Koyomi を CentOS 7 にインストールし、Systemd で動かす

筆者が開発している [Koyomi](https://github.com/key-amb/perl5-App-Koyomi) という、分散ジョブスケジューラのインストール、使用方法について解説するチュートリアル・ドキュメントです。

今回は Vagrant を使ってまっさらな CentOS 7 の環境に Koyomi をインストールし、systemd[^1]の service として起動します。
その上で、ジョブを登録して実行するところまでやってみます。

[^1]: http://www.freedesktop.org/wiki/Software/systemd/

このチュートリアルを通して、Koyomi が cron のように動作することがわかると思います。

本稿執筆時点の Koyomi のバージョンは 0.5.1 です。

## Vagrant による CentOS 7 環境構築

http://www.vagrantbox.es/ で配布されている Vagrantbox を使います。

以下のコマンドは MacOS X 上で実行しました。環境に応じて、適宜読み替えて下さい。

```
vagrant box add centos7 https://github.com/holms/vagrant-centos7-box/releases/download/7.1.1503.001/CentOS-7.1.1503-x86_64-netboot.box
mkdir ~/Vagrant/centos7
cd ~/Vagrant/centos7
vagrant init centos7
vagrant up
```

`vagrant ssh` で VM にログインできます。

## CentOS 7 VM Setup

ここからは上で作成した Vagrant による CentOS 7 の環境で作業していきます。
vagrant ユーザで作業します。

### plenv と Perl 5.22.0 インストール

VM に plenv[^2] と Perl 5.22.0 をインストールします。

[^2]: https://github.com/tokuhirom/plenv

```
git clone git://github.com/tokuhirom/plenv.git ~/.plenv

echo 'export PATH="$HOME/.plenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(plenv init -)"' >> ~/.bash_profile
exec $SHELL -l

git clone git://github.com/tokuhirom/Perl-Build.git ~/.plenv/plugins/perl-build/
plenv install 5.22.0
```

## Koyomi インストール & セットアップ

[インストールガイド](http://key-amb.github.io/App-Koyomi-Doc/bootstrap/)では、GitHub または CPAN からインストールする方法を紹介しています。

今回は GitHub 上の master ブランチの HEAD を用いますが、通常は最新の安定版を取得することを推奨します。

```
git clone git@github.com:key-amb/perl5-App-Koyomi.git ~/koyomi
```

### cpanm, Carton を Perl 5.22.0 にインストールしておく

plenv を使って、`~/koyomi` 以下では Perl 5.22.0 を使うようにします。
また、下準備として cpanm[^3] と `Carton`[^4] を Perl 5.22.0 にインストールしておきます。

[^3]: https://github.com/miyagawa/cpanminus
[^4]: https://github.com/perl-carton/carton

```
cd ~/koyomi
plenv local 5.22.0
plenv install-cpanm
cpanm -v Carton
plenv rehash
```

### 依存モジュールのインストール

Carton を使って `local/` に Koyomi の依存モジュールをインストールします。
依存モジュールが多いので、初回は少々時間が掛かるかと思います。

```
cd ~/koyomi
carton install
```

### Configure

今回はデフォルトの `config/default.toml` を使います。
設定ファイルを次のように編集します。

```
$ cd ~/koyomi
$ git diff config/default.toml
diff --git a/config/default.toml b/config/default.toml
index 1ef5b29..58c2ec0 100644
--- a/config/default.toml
+++ b/config/default.toml
@@ -4,7 +4,7 @@
 #debug_mode = true
 
 [log]
-#debug   = true
+debug   = true
 console = true
 file    = true
 # file_path = /path/to/log
@@ -27,13 +27,13 @@ lock_ttl_seconds = 25
 
 [datasource.module]
 job       = "Teng"
-semaphore = "Teng"
-#semaphore = "None"
+#semaphore = "Teng"
+semaphore = "None"
 
 [datasource.connector]
-dsn      = "dbi:mysql:database=koyomi;host=127.0.0.1;port=3306"
-#dsn      = "dbi:SQLite:tmp/koyomi.sqlite"
-user     = "root"
+#dsn      = "dbi:mysql:database=koyomi;host=127.0.0.1;port=3306"
+dsn      = "dbi:SQLite:tmp/koyomi.sqlite"
+user     = "" # "root"
 password = ""
 
 # Can override for each entity
```

設定ファイルの詳細については、[ドキュメント](http://key-amb.github.io/App-Koyomi-Doc/bootstrap/)を参照して下さい。

### SQLite に DB 作成

今回は簡単のため、ジョブのデータストアとして SQLite を使います。
リポジトリに同梱している DDL を発行して、DB を作成します。

```
cat schema/sqlite/koyomi.ddl | sqlite3 tmp/koyomi.sqlite
```

`sqlite3` コマンドが見つからない場合、`yum install sqlite` などとしてインストールして下さい。

以上で、`koyomi` を動かすための準備は完了です。

### koyomi を動かす

ここでは `Makefile` に定義したタスクを使って手動で worker を実行します。

```
$ make worker
carton exec perl -Ilib script/koyomi
2015-06-20T22:20:53 [DEBUG] bless( {'datasource' => {'connector' => {'dsn' => 'dbi:SQLite:tmp/koyomi.sqlite','password' => '','user' => ''},'module' => {'job' => 'Teng','semaphore' => 'None'}},'debug' => {'worker' => {'sleep_seconds' => 5}},'job' => {'lock_ttl_seconds' => 25},'log' => {'console' => 'true','debug' => 'true','file' => 'true'},'schedule' => {'update_interval_seconds' => 120},'worker' => {'interval_minutes' => 1,'minimum_interval_seconds' => 30}}, 'App::Koyomi::Config' ) at lib/App/Koyomi/Config.pm line 30
2015-06-20T22:20:53 [DEBUG] update schedule at lib/App/Koyomi/Schedule.pm line 46
2015-06-20T22:20:53 [DEBUG] 2015-06-20T22:20:53 Sat at lib/App/Koyomi/Schedule.pm line 67
```

無事に起動しました。
デバッグログを有効にしているので、出力が多めになっています。

この時点では、まだ1つもジョブを登録していません。

一旦、Ctrl-C で worker プロセスを中断しておいて下さい。

## Koyomi を Systemd の Service として動かす

`koyomi` は daemon として使われることを想定していますが、 `koyomi` 単体では自らを daemon 化する方法を提供していません。
daemontools など外部のツールを用いる必要があります。

今回は `koyomi` を systemd[^1] の service として動かしてみます。

### Unit 定義ファイル作成

次のような Unit 定義ファイルを作成しました。

```
$ cat /etc/systemd/system/koyomi.service 
[Unit]
Description=koyomi daemon

[Service]
User=vagrant
Group=vagrant
ExecStart=/home/vagrant/koyomi/tmp/koyomi.sh

[Install]
WantedBy=multi-user.target
```

### 起動スクリプト作成

`koyomi` を起動するため、次のようなシェルスクリプトを作成しました。

```bash
$ cat /home/vagrant/koyomi/tmp/koyomi.sh
#!/bin/sh

set -e

HOME=/home/vagrant
KOYOMI=${HOME}/koyomi
CARTON=${HOME}/.plenv/versions/5.22.0/bin/carton

eval "$(${HOME}/.plenv/bin/plenv init -)"

cd $KOYOMI && \
  $CARTON exec perl -Ilib script/koyomi
```

以上で、`koyomi` を systemd で動かすための準備は完了です。

### Service の起動

systemctl コマンドを使って service を有効化し、起動します。

```
sudo systemctl enable koyomi
sudo systemctl start koyomi
```

`systemctl status` で起動したことを確認できます。

```
$ sudo systemctl status koyomi 
koyomi.service - koyomi daemon
   Loaded: loaded (/etc/systemd/system/koyomi.service; enabled)
   Active: active (running) since 土 2015-06-20 23:23:51 JST; 2min 55s ago
 Main PID: 5129 (koyomi.sh)
   CGroup: /system.slice/koyomi.service
           ├─5129 /bin/sh /home/vagrant/koyomi/tmp/koyomi.sh
           └─5173 perl -I lib script/koyomi

 6月 20 23:23:51 localhost.localdomain systemd[1]: Started koyomi daemon.
 6月 20 23:23:52 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:23:52 [DEBUG] bless( {'datasource' => {'connector' => {'dsn' => 'dbi:SQLite:tm... {'conso
 6月 20 23:23:52 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:23:52 [DEBUG] update schedule at lib/App/Koyomi/Schedule.pm line 46
 6月 20 23:23:52 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:23:52 [DEBUG] 2015-06-20T23:23:52 Sat at lib/App/Koyomi/Schedule.pm line 67
 6月 20 23:24:22 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:24:22 [DEBUG] no need to update schedule at lib/App/Koyomi/Schedule.pm line 42
 6月 20 23:24:22 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:24:22 [DEBUG] 2015-06-20T23:24:22 Sat at lib/App/Koyomi/Schedule.pm line 67
 6月 20 23:25:00 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:25:00 [DEBUG] no need to update schedule at lib/App/Koyomi/Schedule.pm line 42
 6月 20 23:25:00 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:25:00 [DEBUG] 2015-06-20T23:25:00 Sat at lib/App/Koyomi/Schedule.pm line 67
 6月 20 23:26:00 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:26:00 [DEBUG] update schedule at lib/App/Koyomi/Schedule.pm line 46
 6月 20 23:26:00 localhost.localdomain koyomi.sh[5129]: 2015-06-20T23:26:00 [DEBUG] 2015-06-20T23:26:00 Sat at lib/App/Koyomi/Schedule.pm line 67
Hint: Some lines were ellipsized, use -l to show in full.
```

## koyomi-cli を用いたジョブの登録

せっかくなので、1つジョブを登録することにします。

`koyomi-cli add` を実行するとエディタ(`vi`)が起動し、予めフォーマットされた YAML を編集する画面になります。
YAML を編集して保存することで、ジョブを登録します。

今回は `echo $(date) alive >> /tmp/tick.log` というコマンドを毎分実行するジョブを登録してみます。

```yaml
$ cd ~/koyomi
$ carton exec perl -Ilib script/koyomi-cli add
:
# (エディタ起動)
---
command: 'echo $(date) alive >> /tmp/tick.log'
memo: ''
times:
- '*/*/* *:* (*)'
user: ''

# __EOF__
# Format and Description:
#   "command": String. Job as shell command to execute.
#   "memo":    String. You can add comment about the job on this field.
#   "times":   Array. Each entry follows this format:
#     - 'YYYY/mm/dd HH:MM (number of day in week)'
#     Examples:
#       - '2015/*/* 0:0 (7)' ... At 0:00 am every sunday in 2015
#       - '*/*/* *:30 (*)'   ... At 30 minutes after every o'clock
#   "user": String. OS user to execute the command. Leave it blank to execute by the user of worker
:
"/tmp/Ekdf40ta3e" 17L, 612C written
---
command: echo $(date) alive >> /tmp/tick.log
memo: ''
times:
- '*/*/* *:* (*)'
user: ''

Add this job. OK? (y/n) [n] y
2015-06-20T23:55:48 [INFO] [add] Finished. at lib/App/Koyomi/CLI.pm line 103
```

`koyomi-cli list` で登録されたジョブを確認できます。

```
$ koyomi-cli list
.-------------------------------------------------------------------------------.
| id | user | command                             | Y | m | d | H | M | weekday |
+----+------+-------------------------------------+---+---+---+---+---+---------+
|  1 |      | echo $(date) alive >> /tmp/tick.log | * | * | * | * | * | *       |
'----+------+-------------------------------------+---+---+---+---+---+---------'

```

デフォルトの設定ではジョブの登録・削除・変更は約2分後に daemon に反映されます。
数分後に `journalctl` コマンドでログを確認してみます。

```
$ sudo journalctl -n 100
6月 20 23:55:16 localhost.localdomain sudo[5410]: vagrant : TTY=pts/1 ; PWD=/home/vagrant/koyomi ; USER=root ; COMMAND=/bin/systemctl restart koyomi
6月 20 23:55:16 localhost.localdomain systemd[1]: Stopping koyomi daemon...
6月 20 23:55:16 localhost.localdomain systemd[1]: Starting koyomi daemon...
6月 20 23:55:16 localhost.localdomain systemd[1]: Started koyomi daemon.
6月 20 23:55:17 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:55:17 [DEBUG] bless( {'datasource' => {'connector' => {'dsn' => 'dbi:SQLite:tmp/koyomi.sql
6月 20 23:55:17 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:55:17 [DEBUG] update schedule at lib/App/Koyomi/Schedule.pm line 46
6月 20 23:55:17 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:55:17 [DEBUG] 2015-06-20T23:55:17 Sat at lib/App/Koyomi/Schedule.pm line 67
6月 20 23:56:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:56:00 [DEBUG] no need to update schedule at lib/App/Koyomi/Schedule.pm line 42
6月 20 23:56:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:56:00 [DEBUG] 2015-06-20T23:56:00 Sat at lib/App/Koyomi/Schedule.pm line 67
6月 20 23:57:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:57:00 [DEBUG] no need to update schedule at lib/App/Koyomi/Schedule.pm line 42
6月 20 23:57:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:57:00 [DEBUG] 2015-06-20T23:57:00 Sat at lib/App/Koyomi/Schedule.pm line 67
6月 20 23:58:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:58:00 [DEBUG] update schedule at lib/App/Koyomi/Schedule.pm line 46
6月 20 23:58:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:58:00 [DEBUG] (id,user,command) = (1,<NULL>,"echo $(date) alive >> /tmp/tick.log") at lib/
6月 20 23:58:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:58:00 [DEBUG] 2015-06-20T23:58:00 Sat at lib/App/Koyomi/Schedule.pm line 67
6月 20 23:58:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:58:00 [INFO] 5589 1 USER=vagrant COMMAND="echo $(date) alive >> /tmp/tick.log" at lib/App/
6月 20 23:58:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:58:00 [INFO] 5589 1 Succeeded. at lib/App/Koyomi/Job.pm line 69
6月 20 23:59:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:59:00 [DEBUG] no need to update schedule at lib/App/Koyomi/Schedule.pm line 42
6月 20 23:59:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:59:00 [DEBUG] 2015-06-20T23:59:00 Sat at lib/App/Koyomi/Schedule.pm line 67
6月 20 23:59:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:59:00 [INFO] 5595 1 USER=vagrant COMMAND="echo $(date) alive >> /tmp/tick.log" at lib/App/
6月 20 23:59:00 localhost.localdomain koyomi.sh[5414]: 2015-06-20T23:59:00 [INFO] 5595 1 Succeeded. at lib/App/Koyomi/Job.pm line 69
```

登録されたジョブが読み込まれ、実行されている様子がわかります。
`/tmp/tick.log` も見てみましょう。

```
$ cat /tmp/tick.log 
Sat Jun 20 23:58:00 JST 2015 alive
Sat Jun 20 23:59:00 JST 2015 alive
Sun Jun 21 00:00:00 JST 2015 alive
Sun Jun 21 00:01:00 JST 2015 alive
Sun Jun 21 00:02:00 JST 2015 alive
Sun Jun 21 00:03:00 JST 2015 alive
:
```

## まとめ

CentOS 7 の環境に Koyomi をインストールし、systemd の service として動かしてみました。

Koyomi が動くこと、cron のように使えることがわかった(わかる)のではないかと思います。

ここでは SQLite の例を示しましたが、ジョブのデータストアとしてリモートサーバの MySQL を用いれば、ジョブの実行プロセスを冗長化することも出来ます。

Koyomi はまだ生まれたばかりのプロダクトなので、機能の不足などあるかと思います。
引き続き開発していくつもりですが、何かフィードバックがあれば是非お寄せ下さい。

Have fun!!

## 参考

- [Koyomi - Documentation](http://key-amb.github.io/App-Koyomi-Doc/)
- [シンプルな分散ジョブスケジューラを作ってみた #appkoyomi - weblog of key_amb](http://keyamb.hatenablog.com/entry/koyomi-release-v0.1)