とりあえず
サンプルはここにコミットしてあります。
https://github.com/TakiTake/example-kubernetes-jobs
背景
あまりChatbotに機能を盛り過ぎると、運用が辛くなりそう。
crontabは単一のサーバでしか使えず、運用コストが高い。
複雑な依存関係は無いので、jobschedulerはリッチ過ぎる。
社内の雑務なんでJenkinsでも良いんですが、せっかくKubernetesもあるので勉強がてらCronJobで実装してみたい。
ちなみに、JenkinsならPipeline Pluginのtriggersが使えそう。
Cron Jobとは?
Kubernetes版crontabと思っていただければ、良いかと。
定期的に実行したいタスクをKubernetesに面倒見てもらおう、と言うものです。
今回は、Kubernetes Server側がver1.8だったのでこちらのドキュメントを参考に実装しました。
https://v1-8.docs.kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
$ minikube version
minikube version: v0.24.1
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.1", GitCommit:"3a1c9449a956b6026f075fa3134ff92f7d55f812", GitTreeState:"clean", BuildDate:"2018-01-04T19:58:48Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.0", GitCommit:"0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4", GitTreeState:"clean", BuildDate:"2017-11-29T22:43:34Z", GoVersion:"go1.9.1", Compiler:"gc", Platform:"linux/amd64"}
何を自動化したいか?
- Openのまま放置されているチケットの一覧を通知
- MergeされていないPull Requestの一覧を通知
- Weekly Reportページの作成
この辺りが自動化したいタスクかなと。
今回は、最初の例を実装しました。
Openのまま放置されているチケットの一覧を通知
ツール群
go-jiraでチケット一覧を取得してSlackboard経由でSlackにコメントを投稿しています。
jira list | slackboard-cli ==> slackboard ==> Slack
チケットはJIRAで管理されているので、JIRA用のクエリJQLが使えるツールということで
go-jiraを採用。カスタムコマンドが作れるという点もポイント高いです。
通知先は、SlackでIncoming Webhookを利用。CURLは原始的過ぎるので、Slackboardを採用。
ディレクトリ構成
.
├── LICENSE
├── README.md
├── docker
│ ├── jira
│ │ └── Dockerfile
│ ├── slackboard
│ │ └── Dockerfile
│ └── slackboard-cli
│ └── Dockerfile
└── k8s
├── apps
│ └── slackboard
│ ├── conf
│ │ └── slackboard.toml
│ └── slackboard.yaml
└── cronjobs
├── hi-takky
│ └── hi-takky.yaml
└── list-unresolved
├── conf
│ ├── entrypoint.sh
│ └── jira.d
│ └── config.yml
└── list-unresolved.yaml
dockerディレクトリ以下ではDockerfileを管理しています。
k8sディレクトリ以下のappsでは常駐アプリのmanifestファイルと設定ファイルを、cronjobsでは定期的なタスクのmanifestファイルと設定ファイルを管理しています。
内容に関するポリシーは
- Dockerfileは実行環境の構築に専念して、なるべく汎用的に作る。
- Kubernetes Manifestファイルは、Kubernetesの設定に専念してアプリやジョブのロジックへの依存を少なくする。
- 変数や設定は、環境変数やConfigMapとして外出しする。
CronJob
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: list-unresolved
labels:
cronjob: list-unresolved
spec:
concurrencyPolicy: Replace
schedule: "50 8 * * 1-5"
jobTemplate:
spec:
template:
spec:
containers:
- name: list-unresolved
image: jira
imagePullPolicy: IfNotPresent
env:
- name: JIRA_USERNAME
value: takeshi.takizawa
- name: JIRA_PASSWORD
value: ***
args:
- "/bin/sh"
- "/root/entrypoint.sh"
volumeMounts:
- name: entrypoint
mountPath: /root/entrypoint.sh
subPath: entrypoint.sh
- name: jirad
mountPath: /root/.jira.d
restartPolicy: OnFailure
volumes:
- name: entrypoint
configMap:
name: list-unresolved-entrypoint
- name: jirad
configMap:
name: list-unresolved-jirad
Deploymentと結構似ているので、そんなに迷わずに書けました。
Concurrent Policyは以下の3つから選べます。
- Allow (default): 同時実行を許可する。
- Forbid: 一つ前のジョブが動いていた場合は、スキップする。
- Replace: 現在動いているジョブをキャンセルして、新しいジョブと入れ替える。
Scheduleは、crontabと同じフォーマットで書けます。
その他の項目は、Deploymentと同じなので割愛。
JIRAのパスワードがベタ書きなのは、おいおい直します。。
entrypoint.sh
echo ${JIRA_PASSWORD} | /root/jira login --user=${JIRA_USERNAME}
/root/jira list-unresolved | /root/slackboard-cli -t general -s ${SLACKBOARD_SERVICE_HOST}:${SLACKBOARD_SERVICE_PORT}
シェルの中身は、こんなものです。go-jiraのカスタムコマンドを定義しているのでかなりシンプルになりました。
.jira.d/config.yml
queries:
unresolved: |
project = "{{.project}}" AND labels = "External" AND resolution = Unresolved ORDER BY createdDate ASC
custom-commands:
- name: list-unresolved
help: display unresolved issues come from external
script: |-
{{jira}} list --named-query "unresolved" --project "DEVOPS"
カスタムコマンドの定義は、こんな感じです。JQLはJQLで別途定義できるので、横に長くなり過ぎないのが気に入っています。
まとめ
DockerfileやManifestファイルに起こすのは手間でしたが、docker buildしてkubectl createしたら、どこでも動くという手軽さは素晴らしいなと。
そして何より、何が必要か全部書いているので暗黙の前提ってやつを避けられるのは、新人の教育や、引き継ぎ時に大変役立ちます。