python @ kubernetesでtwitterbotを作ってみた

# 前置き
先般の記事で紹介したようにRaspberryPIでkubernetesクラスタを構築したのでこれでせっかくだからなにがしかのアプリを動かしてみたいと思いました。

構築したてのkubernetesにとってはwebサーバー、webシステムの類を動かすにはingress Controllerをセットアップしたり、DNSのセットアップをしたりと素のままでは使いづらいのでリクエストを受けるものではなくて、リクエストを投げるものを作ろうと思いました。

そして思いついたのがtwitterbotでした。これでkubernetesのJob/CronJobを用いて定期もしくは不定期にpostしてやることができれば実現が可能なはず

# 構成
1. twitterにpostするためのアプリ（pythonで作成）
2. そのアプリを動かすためのdockerコンテナ
3. dockerコンテナを利用したkubernetesリソースは以下を作成
    - Job
    - Secret
    - ConfigMap

# 1.pythonアプリ
初めてのpython。先月にあったjetbrainsの半額セールでとりあえず[all products pack](https://samuraism.com/jetbrains/all-products-pack)を購入したので今まで書いたことのあるJavaやPHP以外で何かコード書いてみたいなあと思ってpythonで書いてみました。
取っ掛かりとしてwebでよく散見されるサンプルコード拝借して以下のようなコードを書いてみました。

```python:main.py
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](https://12factor.net/ja/)の考えを用いたつもりです。

# 2.Dockerコンテナを用意する
これが意外に厄介でした。
なぜならraspberryPI（raspibian）は32bitモードで動作するOSで、そのせいか世間で公開されているpython用のコンテナをベースに作成しようとするとかなりの確率で`standard_init_linux.go:190: exec user process caused "exec format error"`というようなエラーが出てしまいます。
なのでraspberryPI用に作られた（と、思われる）[イメージ](https://hub.docker.com/r/resin/raspberrypi3-python/)をベースにして以下のようなDockerfileからイメージをビルドしました。

```dockerfile: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の作成
以下のようなマニフェストファイルを作成してやります

```yaml:job.yaml
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を用いると良いでしょう。

```yaml
        - configMapRef:
            name: tweet
            prefix: PREFIX_
```
とすると`tweet`で定義している環境変数`EXAMPLE`が`PREFIX_EXAMPLE`で呼べるようになります。

### Secretの作成
SecretとConfigMapは基本的に使用方法は同じですが、Secretは設定後に`kubectl describe`などで設定値が確認できなくなる点が異なります。
その性質ゆえにパスワード等の秘密情報はSecretに定義するのがセオリーかと思います。（趣味で作るようなアプリはそんなにこだわらなくても良さそうだけど）
[twitterのデベロッパー向けページ](https://developer.twitter.com/en/apps)からappを作成して「Keys and tokens」必要なkey, tokenを取得して以下のようなマニフェストファイルを作成します。

```yaml:secret.yaml
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に定義してやります。以下のようなファイルを作成するだけです。

```yaml:configmap.yaml
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が作成可能です。

twitterにも反映されています。
![スクリーンショット 2018-09-23 12.06.53.png](https://qiita-image-store.s3.amazonaws.com/0/250225/66768e97-7746-e560-c9dd-64fb2daee5a9.png)