はじめに
Kubernetes(以下、k8s)と言えばマイクロサービスとかのWebアプリとかをコンテナ化する話しの方が一般的な気はしますが、個人的にはDバッチを移行する方にも興味があります。
というのも、仕事柄同時に大量のバッチを流す事が多く、そういう処理はバッチ自体がマルチスレッドのものもあれば、そうじゃないものも混在していてリソース管理がややこしので、そのあたりのリソース管理が出来ると良いなぁ、と。
というわけで、k8sのマネージド環境であるGoogle Kubernetes Engineを使って、バッチを組んでみました。k8sを利用するのは初めてなのでおかしな所があればご指摘ください。
準備
バッチ向けのDokcer
Dockerfileの時点では特に変わったことはありません。
FROM google/cloud-sdk
RUN echo '\n\
curl "https://api.coindesk.com/v1/bpi/historical/close.json?start=2018-01-01&end=$(date +%Y-%m-%d)" > btc_history.json \n\
gsutil cp btc_history.json gs://cn_orz_pascal-bitcoin_prediction/ \n\
gsutil ls -l gs://cn_orz_pascal-bitcoin_prediction/ \n\
' > run.sh
CMD sh -x run.sh%
こんな感じでシンプルにCMDで実行します。もちろん、pythonとかgoで書いたコードもCOPYしてCMDで実行してすれば良いだけですね。
FROM jupyter/scipy-notebook
RUN pip install --upgrade pip && pip install google-cloud
COPY predictor.py predictor.py
CMD python predictor.py
Build & Push
ビルドも普通に実質すれば良いのですが、Google Container Registryを使う時はgcr.io/{project name}/{container name}
というフォーマットにしてやる必要があります。
docker build -t gcr.io/koduki-docker-test-001-1083/collector .
gcloud docker -- push gcr.io/koduki-docker-test-001-1083/collector
鍵などの秘密情報
Cloud Storageなどのサービスを利用するためには鍵情報が必要です。コンテナ内に含むわけには当然行かないので以下のように実施します。
kubectl create secret generic btc-prediction-key --from-file=key.json=~/koduki-docker-test-001-1083-xxxxxxx.json
これでbtc-prediction-key
というsecretにkey.json
という名前でkoduki-docker-test-001-1083-xxxxxxx.json
が配置されたのでデプロイ時に指定することで利用できます。
デプロイ時に指定する項目を抜粋すると以下の通り
spec:
volumes:
-
name: google-cloud-key
secret:
secretName: btc-prediction-key
.
.
.
volumeMounts:
-
name: google-cloud-key
mountPath: /var/secrets/google
env:
-
name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
Cloud StorageなどのGCPのAPIは環境変数のGOOGLE_APPLICATION_CREDENTIALS
を確認して鍵ファイルをみる仕様になっているので、mountしたsecretの値を格納します。
また、k8sのクラスタを作るときにデフォルトは他のGCPサービスを利用できない設定になっているので、クラスタ構築時に忘れずに動くようにしておく必要があります。こちらのStack Overflowが参考になります。
k8sでのバッチの実行
手動で単発のバッチとして実行する
まずは手動でk8s上で実行するには以下のようにkubectl run
を使います。
kubectl run "btcollector" --restart=OnFailure --image="gcr.io/koduki-docker-test-001-1083/collector"
特に標準出力に情報が返って来なければ正解です。
get job
でジョブの状態を確認してみます。試したのは短いジョブなので既に終わってますが、SUCCESSFULになってる事が確認できます。
% kubectl get job
NAME DESIRED SUCCESSFUL AGE
btcollector 1 1 1m
もう少しジョブの詳細が見たいときはkubectl describe
を使います。どのPodで実行されたのか、などより詳細な内容が分かります。
% kubectl describe jobs/btcollector
Name: btcollector
Namespace: default
Selector: controller-uid=74f18356-ac64-11e8-a825-42010a800172
Labels: run=btcollector
.
.
.
ログを確認するにはkubectl log
を利用します。こちらで実行したコンテナの標準出力が取得出来ます。
% kubectl log jobs/btcollector
W0830 08:02:36.908945 33937 cmd.go:353] log is DEPRECATED and will be removed in a future version. Use logs instead.
+ date +%Y-%m-%d
+ curl https://api.coindesk.com/v1/bpi/historical/close.json?start=2018-01-01&end=2018-08-30
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 5698 0 5698 0 0 10602 0 --:--:-- --:--:-- --:--:-- 10610
+ gsutil cp btc_history.json gs://cn_orz_pascal-bitcoin_prediction/
Copying file://btc_history.json [Content-Type=application/json]...
/ [1 files][ 5.6 KiB/ 5.6 KiB]
Operation completed over 1 objects/5.6 KiB.
+ gsutil ls -l gs://cn_orz_pascal-bitcoin_prediction/
5698 2018-08-30T14:53:40Z gs://cn_orz_pascal-bitcoin_prediction/btc_history.json
10060 2018-08-29T23:58:36Z gs://cn_orz_pascal-bitcoin_prediction/btc_prediction.json
TOTAL: 2 objects, 15758 bytes (15.39 KiB)
ちなみに実行時の名前は一意にする必要があるようなので2回同じ名前で実行するとエラーになります。Jenkinsなどで外部のジョブスケジューラから叩くときはタイムスタンプ入れるとか注意が必要そうです。
% kubectl run "btcollector" --restart=OnFailure --image="gcr.io/koduki-docker-test-001-1083/collector"
Error from server (AlreadyExists): jobs.batch "btcollector" already exists
スケジューリング実行
先程は手で実行する例を出しましたが、やはり特定の時間になったらスケジューリング実行されるcronのような機能は必要ですよね?
k8sにはその機能はちゃんと用意されています。
今回は記述も長くなるのでrunではなくyamlにジョブの情報を記載してdeployをして見ます。
まずは、ジョブの情報を記載したyamlは下記のようになります。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: btcpredictor
spec:
concurrencyPolicy: Replace
schedule: "55 23 * * * "
jobTemplate:
spec:
template:
spec:
volumes:
-
name: google-cloud-key
secret:
secretName: btc-prediction-key
containers:
-
name: btc-predictor-container-01
image: gcr.io/koduki-docker-test-001-1083/predictor
volumeMounts:
-
name: google-cloud-key
mountPath: /var/secrets/google
env:
-
name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
restartPolicy: Never
backoffLimit: 4
yamlを使ったdeployはkubectl create
を利用します。
% kubectl create -f my-job.yaml
これでgcr.io/koduki-docker-test-001-1083/predictor
が登録されました。毎日23:55に実行される事になります。
cronに登録してもjobである事には変わらないのでkubectl jobs
など手動実行の際と同様のコマンドで管理できます。
まとめ
基本的なジョブ実行をk8sで試して見ました。今回はしてませんがコンテナ毎でのCPUやメモリの設定をすればよりアプリ側は環境のリソースをMAX使うようにしておいても上手く運用時に制御が出来るようになるので、なかなか良さそうです。
また、本来であれば単なる時間実行だけでは無く待合せとか後続ジョブとか複雑なジョブ管理が必要になってきます。
ArgoとかのWorkflowエンジンを使えば出来るようなので、今度こちらも試して見たいと考えています。
現時点で気になる点としては、まだ私がツールに慣れてないだけかもしれないですが、SparkのCloudera Managerなどのようにジョブ単位でどのくらい今リソースを使ってるとかは分かりづらい感じがしました。
GKEのモニタリング画面だけだと不足しそうなのでこの辺は何らかの追加のDashBoardの作成が必要になりそうです。
それではHappy Hacking!