ChatOps的なことを試してみたかったのと、Pythonのお勉強のため、PythonでSlackのBotを作成し、Kubernetes上にデプロイして、Podの状況を確認できるようにしたメモ。BotといえばHubotらしいけれども、今回はPythonのslackbot
モジュールで作成。
参考リンク
以下の先人の情報を参考に作成。
準備
以下のリンクよりBotsアプリのSlackインテグレーションを追加する。APIトークンを確認し、Botの名前やアイコンなど設定する。

Botをチャンネルに招待して追加する。
Python
slackbot
モジュールの使い方は以下の公式リポジトリと上の参考リンクを参照。
今回Pythonからkube-apiserverにアクセスしてPodとNamespaceの情報を取得したいので、PythonのKuberentes Clientであるkubernetes
モジュールも使う。
コード作成
以下の3つのファイルを作成する。→GitHub
python_slackbot
├── mybot.py
├── run.py
└── slackbot_settings.py
slackbot_setting.py
にはデフォルトのリプライや読み込むプラグインなどの設定を記述する。
トークンはAPI_TOKEN
としてここで書いてもよいが、環境変数SLACKBOT_API_TOKEN
でも渡せるので今回は環境変数で渡す。
エラーの通知先のユーザーを指定する場合はERRORS_TO
を指定する。ユーザーがいない場合は起動エラーになるので注意。
DEFAULT_REPLY = "何言ってんだこいつ"
PLUGINS = ['mybot']
ERRORS_TO = 'sotoiwa'
run.py
は以下の通り作成。
from slackbot.bot import Bot
def main():
bot = Bot()
bot.run()
if __name__ == "__main__":
print('start slackbot')
main()
slackbot_settings.py
のPLUGINS
で指定したmybot.py
に自分の処理を書く。
@respond_to('反応する文字列')
でメンションされたときの反応を書き、@listen_to('反応する文字列')
でチャンネルの投稿への反応を書く。反応する文字列は正規表現で指定する。
import os
import re
from kubernetes import client, config
import prettytable
from slackbot.bot import respond_to
from slackbot.bot import listen_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')
# get pod
@respond_to(r'^get\s+(po|pod|pods)\s+(-n|--namespace)\s+(.*)$')
def mention_get_po(message, arg2, arg3, namespace):
# kubernetes上で動いているかを環境変数から判断する
if os.getenv('KUBERNETES_SERVICE_HOST'):
# ServiceAccountの権限で実行する
config.load_incluster_config()
else:
# $HOME/.kube/config から読み込む
config.load_kube_config()
v1 = client.CoreV1Api()
ret = v1.list_namespaced_pod(namespace=namespace, watch=False)
table = prettytable.PrettyTable()
table.field_names = ['name', 'phase']
table.align['name'] = 'l'
table.align['phase'] = 'l'
for i in ret.items:
table.add_row([i.metadata.name,
i.status.phase
])
msg = '```\n' + table.get_string() + '\n```'
message.reply(msg)
# get ns
@respond_to(r'^get\s+(ns|namespace|namespaces)$')
def mention_get_ns(message, arg2):
# kubernetes上で動いているかを環境変数から判断する
if os.getenv('KUBERNETES_SERVICE_HOST'):
# ServiceAccountの権限で実行する
config.load_incluster_config()
else:
# $HOME/.kube/config から読み込む
config.load_kube_config()
v1 = client.CoreV1Api()
ret = v1.list_namespace(watch=False)
table = prettytable.PrettyTable()
table.field_names = ['name']
table.align['name'] = 'l'
for i in ret.items:
table.add_row([i.metadata.name])
msg = '```\n' + table.get_string() + '\n```'
message.reply(msg)
ローカル実行
ローカルで実行する場合は、HOME/.kube/config
からkube-apiserverへの認証情報を読み込むので、kubectl config
コマンドで適切なコンテキストを設定しておく。
必要なモジュールをインストールする。
pip install kubernetes
pip install prettytable
pip install slackbot
APIトークンをexport
する。
export SLACKBOT_API_TOKEN=hogehoge
Botを起動する。
python run.py
稼働確認
こんにちは、こんにちは、helloというメンションに反応する。
kubernetes、helmに反応してリアクションする。
BotにメンションしてNamespaceを確認する。結果はprettytable
モジュールで整形している。
BotにメンションしてPodを確認する。Namespaceを指定する必要あり。以下の例ではPodが稼働していないので空。
Kubernetesへのデプロイ
Kubernetes上にBotをデプロイする。
イメージのビルド
requirements.txt
とDockerfile
を作成する。
kubernetes
prettytable
slackbot
FROM python:3
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY ./*.py ./
ENTRYPOINT [ "python", "./run.py" ]
ファイル配置は以下の通り。
python_slackbot
├── Dockerfile
├── mybot.py
├── requirements.txt
├── run.py
└── slackbot_settings.py
イメージをビルドする。
docker build -t sotoiwa540/slackbot:1.0 .
docker push sotoiwa540/slackbot:1.0
Kubernetesへのデプロイ
ローカルで実行する場合は、HOME/.kube/config
から認証情報を読み込むが、Kubernetes上でPodとして実行する場合はPodを実行するServiceAccountの権限で認証するので、ClusterRoleとClusterRoleBindingを作成する。
今回はPodとNamespaceをリストできる権限を持つClusterRoleを作成し、default
のNamespaceのdefault
のServiceAccountにClusterRoleをバインドする。
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: slackbot
rules:
- apiGroups: [""]
resources:
- pods
- namespaces
verbs:
- list
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: slackbot
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: slackbot
apiGroup: rbac.authorization.k8s.io
kubectl apply -f slackbot-clusterrole.yaml
kubectl apply -f slackbot-clusterrolebinding.yaml
APIトークンのSecretを作成する。
kubectl create secret generic slackbot-secret --from-literal=SLACKBOT_API_TOKEN=hogehoge
Deploymentを作成する。
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.0
imagePullPolicy: Always
env:
- name: SLACKBOT_API_TOKEN
valueFrom:
secretKeyRef:
key: SLACKBOT_API_TOKEN
name: slackbot-secret
kubectl apply -f slackbot-deployment.yaml
Podが稼働したことを確認する。
$ kubectl get po
NAME READY STATUS RESTARTS AGE
slackbot-5b68f5dddf-klmpw 1/1 Running 0 8s
$
稼働確認
BotにメンションしてPodを確認する。
なお、今回はあくまでPodのphaseを見ているだけなので、普通にkubectl
コマンドを実行したときのSTATUS
列とは異なる。また、Slackの投稿の文字数の制限で、Podの数が多いと表示が崩れる。
(追記)
PythonのKubernetes Clientライブラリを使った方法では、メンションに対応する操作をそれぞれ作る必要がある。複雑な処理をプログラミングしたい場合はこの方法がよいかもしれないが、単なる状況照会についてそれぞれの処理を作ったり結果を整形するのは不便なので、kubectl
コマンドをサブプロセスで実行するように変更した。