PythonのslackbotのPodからkubectlコマンドを実行させる

- [PythonのslackbotをKubernetesにデプロイしてPodの状況を確認する](https://qiita.com/sotoiwa/items/a82c49e4d5031aa271fa)

という記事を書いたものの、[PythonのKubernetes Clientライブラリ](https://github.com/kubernetes-client/python)を使ったため、`get pod`したいなど、それぞれの操作毎にBotの動作を記載する必要があり不便。Python ClientではなくBotのコンテナ内で直接`kubectl`コマンドを実行するようにする。

Podから`kubectl`コマンドを実行するには、以下の記事に書いた通り、`kubectl`のバイナリをコンテナイメージ内に配置し、RBACでPodを実行するServiceAccountに適切な権限を与える必要がある。

- [Kubernetesクラスター内のPodからkubectlを実行する](https://qiita.com/sotoiwa/items/aff12291957d85069a76)

## 準備

Slack側の準備については[前回の記事](https://qiita.com/sotoiwa/items/a82c49e4d5031aa271fa#%E6%BA%96%E5%82%99)を参照。

## Python

`slackbot`モジュールの使い方は以下の公式リポジトリを参照。

- https://github.com/lins05/slackbot

### コード作成

以下の3つのファイルを作成する。→[GitHub](https://github.com/sotoiwa/python_slackbot)
`slacbot_settings.py`と`run.py`は[前回](https://qiita.com/sotoiwa/items/a82c49e4d5031aa271fa#%E3%82%B3%E3%83%BC%E3%83%89%E4%BD%9C%E6%88%90)と同じ。

```
python_slackbot
├── mybot.py
├── run.py
└── slackbot_settings.py
```

`mybot.py`は以下の通り作成。`kubernetes`モジュールや出力の整形に使っていた`prettytable`モジュールは削除し、`kubectl`コマンドをサブプロセスで実行する関数を追加した。

```python
import re
import subprocess

from slackbot.bot import listen_to
from slackbot.bot import respond_to


# こんにちはに応答する
@respond_to('hello', re.IGNORECASE)
@respond_to('こんにちは|こんにちわ')
def mention_hello(message):
    message.reply('こんにちは！')


# kubernetesに反応する
@listen_to('kubernetes', re.IGNORECASE)
def listen_kubernetes(message):
    message.reply('kubernetesかっこいい！')
    message.react('+1')


# helmに反応する
@listen_to('helm', re.IGNORECASE)
def listen_helm(message):
    message.reply('helmかっこいい！')
    message.react('+1')


# kubectl
@respond_to(r'^kubectl (.*)')
def mention_kubectl(message, kubectl_args):

    try:
        cmd = 'kubectl {}'.format(kubectl_args)
        completed_process = subprocess.run(cmd.split(),
                                           check=True,
                                           capture_output=True)
        result_str = completed_process.stdout.decode('utf-8')

    except subprocess.CalledProcessError as e:
        result_str = e.stderr.decode('utf-8')

    msg = '```\n' + result_str + '```'
    message.reply(msg)
```

### ローカル実行

ローカルで実行する場合は、`HOME/.kube/config`からkube-apiserverへの認証情報を読み込むので、`kubectl config`コマンドで適切なコンテキストを設定しておく。

必要なモジュールをインストールする。

```shell
pip install slackbot
```

APIトークンを`export`する。

```
export SLACKBOT_API_TOKEN=hogehoge
```

Botを起動する。

```shell
python run.py
```

### 稼働確認

Botにメンションして任意のkubectlコマンドを実行する。

<img width="709" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/86449/a285c693-25a9-9ebc-4f65-16e8338e78a1.png">

## Kubernetesへのデプロイ

Kubernetes上にBotをデプロイする。

### イメージのビルド

`requirements.txt`と`Dockerfile`を作成する。

```:requirements.txt
slackbot
```

```dockerfile:Dockerfile
FROM python:3-alpine

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY ./*.py ./

RUN wget https://storage.googleapis.com/kubernetes-release/release/$(wget -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \
  && mv kubectl /usr/local/bin/kubectl \
  && chmod +x /usr/local/bin/kubectl

ENTRYPOINT [ "python", "./run.py" ]
```

ファイル配置は以下の通り。

```
python_slackbot
├── Dockerfile
├── mybot.py
├── requirements.txt
├── run.py
└── slackbot_settings.py
```

イメージをビルドする。

```shell
docker build -t sotoiwa540/slackbot:1.1 .
docker push sotoiwa540/slackbot:1.1
```

### Kubernetesへのデプロイ

今回はSlackbotをデプロイする専用のNamespaceを作る。

```
kubectl create ns slackbot
```

ローカルで実行する場合は、kubectlは`HOME/.kube/config`から認証情報を読み込むが、Kubernetes上でPodとして実行する場合はPodを実行するServiceAccountの権限で実行されるので、適切な権限を与える必要がある。

Podに明示的にServiceAccountを指定しない場合は、Podは稼働するNamespaceの`default`のServiceAccountの権限で実行される。

今回は、デフォルトで存在する`view`という読み取りだけでるっぽいClusterRoleを、`default`のServiceAccountに割り当てるClusterRoleBindingを作成する。何でも実行できるようにしたかったら`cluster-admin`のClusterRoleとかを割り当てればよい。

```yaml:slackbot-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: slackbot
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
- kind: ServiceAccount
  name: default
  namespace: slackbot
```

```shell
kubectl apply -f slackbot-clusterrolebinding.yaml -n slackbot
```

SlackのAPIトークンを格納したSecretを作成する。

```shell
kubectl create secret generic slackbot-secret -n slackbot \
  --from-literal=SLACKBOT_API_TOKEN=hogehoge
```

Deploymentを作成する。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
  labels:
    app: slackbot
  name: slackbot
spec:
  replicas: 1
  selector:
    matchLabels:
      app: slackbot
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: slackbot
    spec:
      containers:
      - name: slackbot
        image: sotoiwa540/slackbot:1.1
        imagePullPolicy: Always
        env:
        - name: SLACKBOT_API_TOKEN
          valueFrom:
            secretKeyRef:
              key: SLACKBOT_API_TOKEN
              name: slackbot-secret
```

```shell
kubectl apply -f slackbot-deployment.yaml -n slackbot
```

Podが稼働したことを確認する。

```shell-session
$ kubectl get po -n slackbot
NAME                       READY   STATUS    RESTARTS   AGE
slackbot-8544c9454-q5jtc   1/1     Running   0          19s
$
```

### 稼働確認

Botにメンションして`kubectl`コマンドを実行できることを確認。

<img width="707" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/86449/120a9edc-53da-ce19-e61e-cde03ae502eb.png">