前置き
先般の記事で紹介したようにRaspberryPIでkubernetesクラスタを構築したのでこれでせっかくだからなにがしかのアプリを動かしてみたいと思いました。
構築したてのkubernetesにとってはwebサーバー、webシステムの類を動かすにはingress Controllerをセットアップしたり、DNSのセットアップをしたりと素のままでは使いづらいのでリクエストを受けるものではなくて、リクエストを投げるものを作ろうと思いました。
そして思いついたのがtwitterbotでした。これでkubernetesのJob/CronJobを用いて定期もしくは不定期にpostしてやることができれば実現が可能なはず
構成
- twitterにpostするためのアプリ(pythonで作成)
- そのアプリを動かすためのdockerコンテナ
- dockerコンテナを利用したkubernetesリソースは以下を作成
- Job
- Secret
- ConfigMap
1.pythonアプリ
初めてのpython。先月にあったjetbrainsの半額セールでとりあえずall products packを購入したので今まで書いたことのあるJavaやPHP以外で何かコード書いてみたいなあと思ってpythonで書いてみました。
取っ掛かりとしてwebでよく散見されるサンプルコード拝借して以下のようなコードを書いてみました。
import os
import twitter
auth = twitter.OAuth(consumer_key=os.environ['TWITTER_AUTH'],
consumer_secret=os.environ['TWITTER_CONSUMER_SECRET'],
token=os.environ['TWITTER_TOKEN'],
token_secret=os.environ['TWITTER_TOKEN_SECRET'])
t = twitter.Twitter(auth=auth)
status = os.environ['TWEET']
t.statuses.update(status=status)
たった数行のクラスすら使わないしょーもないコードです。
ただ唯一気にしたのは認証情報やツイート内容を環境変数化してコンテナ作成後にも可変にできるようにしたということぐらいでしょうか。一応12FactorAppの考えを用いたつもりです。
2.Dockerコンテナを用意する
これが意外に厄介でした。
なぜならraspberryPI(raspibian)は32bitモードで動作するOSで、そのせいか世間で公開されているpython用のコンテナをベースに作成しようとするとかなりの確率でstandard_init_linux.go:190: exec user process caused "exec format error"
というようなエラーが出てしまいます。
なのでraspberryPI用に作られた(と、思われる)イメージをベースにして以下のようなDockerfileからイメージをビルドしました。
FROM resin/raspberrypi3-python:3.6-slim
WORKDIR /usr/local/src/twitter
USER root
RUN chmod -R 777 ./
RUN apt-get update && apt-get install -y git
RUN pip install setuptools wheel && \
pip install pipenv
RUN git clone <1で作成したコードのリポジトリ> ./
RUN pipenv install
RUN命令は1つにまとめてしまった方が良いのかもしれません(その方がイメージがコンパクトになるらしい・・・)
アプリの部分と分離させるために毎回最新のコードをcloneするようにします。必要に応じて--depth 1
のオプションをつけるといいでしょう。
3.kubernetesのリソースを用意する
今回はさっと動作を確認するためにJobを用いました。
CronJobを使う場合でもスケジュールの設定が増えるだけで他は同じです。24h/365days稼働しているkubernetesクラスタがあるならCronJobの方がいいでしょう。
我が家では今の所そこまでkubernetesクラスタを活用しているわけではないので基本電源は切っています。
Jobの作成
以下のようなマニフェストファイルを作成してやります
apiVersion: batch/v1
kind: Job
metadata:
name: twitter-bot
spec:
template:
spec:
containers:
- name: twitter-bot
image: <作成したdocker image>
command:
- sh
- -c
- pipenv run python <main.pyへのパス>
envFrom:
- secretRef:
name: twitter-token
- configMapRef:
name: tweet
restartPolicy: Never
backoffLimit: 4
Jobを作成すると指定したimageでpodが作成されてそのpod上で指定したコマンドが実行されます。
podが作成されるのでdeploymentと同様にボリュームをマウントさせることも可能です。
なので、コード上で引用していた環境変数をsecret, configmapに定義し、それをマウントさせます。
ファイルそのままでマウントさせたい場合はvolumeMounts
を使用しますが、環境変数として使用してやりたい場合はenvFrom
を使用します。
配列形式になっているのでサンプルのように幾つでもマウントさせることはできるのですが、キー名(フィールド名)の衝突が起きないように気をつけてください。不安な場合はprefixを用いると良いでしょう。
- configMapRef:
name: tweet
prefix: PREFIX_
とするとtweet
で定義している環境変数EXAMPLE
がPREFIX_EXAMPLE
で呼べるようになります。
Secretの作成
SecretとConfigMapは基本的に使用方法は同じですが、Secretは設定後にkubectl describe
などで設定値が確認できなくなる点が異なります。
その性質ゆえにパスワード等の秘密情報はSecretに定義するのがセオリーかと思います。(趣味で作るようなアプリはそんなにこだわらなくても良さそうだけど)
twitterのデベロッパー向けページからappを作成して「Keys and tokens」必要なkey, tokenを取得して以下のようなマニフェストファイルを作成します。
apiVersion: v1
kind: Secret
metadata:
name: twitter-token
data:
TWITTER_TOKEN_SECRET: <Access token secret>
TWITTER_TOKEN: <Access token>
TWITTER_CONSUMER_SECRET: <API secret key>
TWITTER_AUTH: <API key>
注意点としてkubernetesのSecretに設定される値はbase64エンコードされていないといけないので例えばAccess token secret
の値がhoge
だとしたら以下のようにエンコードしてaG9nZQ==
を設定してください。
$ echo -n "hoge" | base64
aG9nZQ==
ConfigMapの設定
SecretとConfigMapの違いは先ほど述べたとおりです。ここではツイートする内容をConfigMapに定義してやります。以下のようなファイルを作成するだけです。
apiVersion: v1
kind: ConfigMap
metadata:
name: tweet
data:
TWEET: "text of tweet"
Secretと異なり、値は平文のままでOKです。
デプロイ(Job)の実行
マニフェストファイルが全て同じディレクトリにあれば以下のコマンドを実行するだけです。
$ kubectl apply -f path/to/yamlfile
job.batch/twitter-bot created
secret/twitter-token created
configmap/tweet created
作成される(た)podの様子をモニタリングします。-w
オプションでリアルタイム監視できます。
$ kubectl get po -w
NAME READY STATUS RESTARTS AGE
twitter-bot-bmdq4 0/1 ContainerCreating 0 5s
twitter-bot-bmdq4 1/1 Running 0 1m
twitter-bot-bmdq4 0/1 Completed 0 1m
Completed
になっているので無事Jobが実行されたことがわかります。
CronJobを使えば定期的にpostする、まさにbotが作成可能です。