将Hubot部署到Kubernetes上,并通过Slack检查Pod的状态
-
- PythonのslackbotをKubernetesにデプロイしてPodの状況を確認する
- PythonのslackbotのPodからkubectlコマンドを実行させる
ということをやってみたが、Pythonのslackbotモジュールではなく、BotとしてはよりメジャーであるらしいHubotを試す。
Slackの準備
在下面添加Hubut的整合。
- https://slack.com/apps/A0F7XDU93-hubot

请确保之后能使用该令牌。
邀请机器人加入频道。

准备本地执行
ローカルでHubotを実行するためにNode.jsの環境を整える。
nodenvのインストール
有很多用于管理Node.js版本的工具,例如nvm、nodebrew等。我原本想要安装ndenv,但它已经被标注为[Deprecated],所以我决定安装推荐的nodenv来替代它。
brew install nodenv
将以下内容添加到.bash_profile文件中。
eval "$(nodenv init -)"
重新启动终端。
安装 Node.js
检查可安装的Node.js版本。
nodenv install -l
安装LTS版本的最新发行版。
nodenv install 10.15.0
指定要在全球使用的版本。
nodenv global 10.15.0
请确保更新npm。
npm install -g npm
查看版本。
$ node -v
v10.15.0
$ npm -v
6.7.0
安装Hubot
以下に従いyoとgenerator-hubotをインストールする。
- https://hubot.github.com/docs/
npm install -g yo generator-hubot
重新启动终端。
在本地测试Hubot
首先,在本地尝试运行Hubot。
创建聊天机器人
创建一个目录并创建模板。
mkdir myhubot-shell
cd myhubot-shell
yo hubot
为了在本地运行,首先需要指定一个用于适配的shell。
- https://hubot.github.com/docs/adapters/shell/
平均
まずはHubotを起動してPingしてみる。起動時に警告などでているがとりあえず無視する。
$ bin/hubot
audited 179 packages in 0.765s
found 0 vulnerabilities
body-parser deprecated undefined extended: provide extended option node_modules/hubot/src/robot.js:446:21
No history available
myhubot-shell> [Fri Feb 01 2019 11:31:01 GMT+0900 (GMT+09:00)] WARNING Loading scripts from hubot-scripts.json is deprecated and will be removed in 3.0 (https://github.com/github/hubot-scripts/issues/1113) in favor of packages for each script.
Your hubot-scripts.json is empty, so you just need to remove it.
[Fri Feb 01 2019 11:31:01 GMT+0900 (GMT+09:00)] INFO hubot-redis-brain: Using default redis on localhost:6379
[Fri Feb 01 2019 11:31:01 GMT+0900 (GMT+09:00)] ERROR hubot-heroku-keepalive included, but missing HUBOT_HEROKU_KEEPALIVE_URL. `heroku config:set HUBOT_HEROKU_KEEPALIVE_URL=$(heroku apps:info -s | grep web.url | cut -d= -f2)`
myhubot-shell> myhubot-shell ping
myhubot-shell> PONG
myhubot-shell>
スクリプトの作成
scriptsディレクトリにサンプルがある。とりあえずスクリプトを作ってみる。以下のURLにも解説がある。
- https://hubot.github.com/docs/scripting/
module.exports = (robot) ->
robot.hear /hello/i, (res) ->
res.send "Hello World!"
robot.respond /how are you/i, (res) ->
res.reply "I am fine!"
投稿をウォッチするhearと、呼びかけに応答するrespondがある。Pythonのslackbotとよく似ている。
myhubot-shellと呼びかけるのはやや長いので、引数で名前をhubotと指定して起動する。
bin/hubot --name hubot
尝试提交投稿和发起呼吁。
hubot> hello
hubot> Hello World!
hubot> hubot How are you?
hubot> Shell: I am fine!
hubot>
我要試試社區腳本。
Hubotはコミュニティースクリプトがたくさんあるようなので、Kubernetes関連のパッケージを探してみる。
$ npm search hubot-scripts kubernetes
NAME | DESCRIPTION | AUTHOR | DATE | VERSION | KEYWORDS
rk295-hubot-kubernetes | Hubot Kubernetes… | =rk295 | 2018-08-10 | 0.0.4 | hubot hubot-scripts kuberne
hubot-k8s | A hubot script for… | =kevin_schmidt | 2018-12-25 | 0.1.0 | hubot hubot-scripts k8s kub
hubot-sbueringer-kubernet | Hubot Kubernetes… | =sbueringer | 2017-06-02 | 0.4.2 | hubot hubot-scripts kuberne
es | | | | |
hubot-kubernetes | Hubot Kubernetes… | =canthefason | 2016-01-18 | 0.3.0 | hubot hubot-scripts kuberne
$ npm home hubot-k8s
$
有很多选项,但我想试试相对维护良好的hubot-k8s。
可以使用npm home hubot-k8s命令打开该软件包的主页。
- https://github.com/kevinschmidt/hubot-k8s
安装软件包。
npm install --save hubot-k8s
インストールしたパッケージをexternal-scripts.jsonに追記する。
[
"hubot-diagnostics",
"hubot-help",
"hubot-heroku-keepalive",
"hubot-google-images",
"hubot-google-translate",
"hubot-pugme",
"hubot-maps",
"hubot-redis-brain",
"hubot-rules",
"hubot-shipit",
"hubot-k8s"
]
hubot-k8sからの接続を試すため、Minikubeを起動する。
minikube start
在hubot-k8s中,需要使用下面的环境变量来传递与kube-apiserver的连接信息。
-
- HUBOT_K8S_CONTEXTS
-
- HUBOT_K8S_DEFAULT_CONTEXT
- HUBOT_K8S_DEFAULT_NAMESPACE
HUBOT_K8S_CONTEXTS 的json如下所示。
{
"prod": {
"server": "https://kubernetes.cluster.io",
"ca": "./ca.crt",
"dashboardPrefix": "https://kubernetes.cluster.io",
"token": "<kubernetes token>"
}
}
确认连接到Minikube所需的信息。
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority: /Users/sotoiwa/.minikube/ca.crt
server: https://192.168.99.104:8443
name: minikube
contexts:
- context:
cluster: minikube
user: minikube
name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
client-certificate: /Users/sotoiwa/.minikube/client.crt
client-key: /Users/sotoiwa/.minikube/client.key
$
Minikubeは鍵で認証しているが、hubot-k8sでは鍵は指定できず、トークンが必要なようなので、ServiceAccountを作成してそのトークンを使うことにする。
あとでHubotをコンテナ化してPodとしてMinikubeにデプロイしたいので、そのときにもServiceAccountを流用できるように、デプロイ先のNamespaceを作成し、自動的に作成されるそのNamespaceのdefaultのServiceAccountを使う。このServiceAccountに強い権限をもつcluster-adminのClusterRoleをバインドする。
kubectl create ns hubot
kubectl create clusterrolebinding hubot \
--clusterrole=cluster-admin \
--serviceaccount=hubot:default
获取此ServiceAccount的令牌。
SECRET_NAME=$(kubectl get sa default -n hubot -o jsonpath='{.secrets[].name}')
echo $SECRET_NAME
TOKEN=$(kubectl get secret $SECRET_NAME -n hubot -o jsonpath={.data.token} | base64 --decode)
echo $TOKEN
HUBOT_K8S_CONTEXTS将会是以下的json格式。
{
"minikube": {
"server": "https://192.168.99.104:8443",
"ca": "/Users/sotoiwa/.minikube/ca.crt",
"dashboardPrefix": "https://kubernetes.cluster.io",
"token": "$TOKEN"
}
}
将其放在一行内,对双引号进行转义,并将其作为环境变量导出,然后启动Hubot。
export HUBOT_K8S_CONTEXTS="{\"minikube\":{\"server\":\"https://192.168.99.104:8443\",\"ca\":\"/Users/sotoiwa/.minikube/ca.crt\",\"dashboardPrefix\":\"https://kubernetes.cluster.io\",\"token\":\"$TOKEN\"}}"
export HUBOT_K8S_DEFAULT_CONTEXT=minikube
export HUBOT_K8S_DEFAULT_NAMESPACE=default
bin/hubot --name hubot
确认在hubot-k8s包的命令中能够获取到Pod和Deployment的信息。
hubot> hubot k8s ns
hubot> Shell: Your current kubernetes namespace is: `default`
hubot> hubot k8s ns kube-system
hubot> Shell: Your current kubernetes namespace is changed to `kube-system`
hubot> hubot k8s po
hubot> Shell: Here is the list of *pods* running in namespace *kube-system* with context *minikube*
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/coredns-86c58d9df4-htjgd?namespace=kube-system|coredns-86c58d9df4-htjgd>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/coredns-86c58d9df4-znckf?namespace=kube-system|coredns-86c58d9df4-znckf>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/etcd-minikube?namespace=kube-system|etcd-minikube>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kube-addon-manager-minikube?namespace=kube-system|kube-addon-manager-minikube>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kube-apiserver-minikube?namespace=kube-system|kube-apiserver-minikube>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kube-controller-manager-minikube?namespace=kube-system|kube-controller-manager-minikube>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kube-proxy-jrjsx?namespace=kube-system|kube-proxy-jrjsx>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kube-scheduler-minikube?namespace=kube-system|kube-scheduler-minikube>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/kubernetes-dashboard-ccc79bfc9-vc6r9?namespace=kube-system|kubernetes-dashboard-ccc79bfc9-vc6r9>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/pod/kube-system/storage-provisioner?namespace=kube-system|storage-provisioner>* - pods `1/1` with status `Running` and restart count `0` since `12 minutes ago`
hubot> hubot k8s deploy
hubot> Shell: Here is the list of *deployments* running in namespace *kube-system* with context *minikube*
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/deployment/kube-system/coredns?namespace=kube-system|coredns>* - desired `2`, current `2`, updated `2`, available `2`
>*<https://kubernetes.cluster.io/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/deployment/kube-system/kubernetes-dashboard?namespace=kube-system|kubernetes-dashboard>* - desired `1`, current `1`, updated `1`, available `1`
hubot>
直接执行 kubectl
hubot-k8sパッケージではできることが限られているので、kubectlコマンドを直接実行できるようにしてみる。以下を参考にmybot.coffeeスクリプトにkubectlというメンションへの反応を追記する。kubectl以下を引数としてkubectlコマンドを実行する。このときexecだとシェルを介して任意のコマンドを実行されてしまうリスクがあるので、execFileを使うようにした。
-
- SlackからHubot経由で、サーバのシェルを実行してみよう!
-
- Child Process
- node.jsのchild_processのexecとexecFileとspawnの違い
module.exports = (robot) ->
robot.hear /hello/i, (res) ->
res.send "Hello World!"
robot.respond /how are you/i, (res) ->
res.reply "I am fine!"
robot.respond /kubectl (.*)/, (msg) ->
args = msg.match[1]
@exec = require("child_process").execFile
command = "kubectl"
msg.send "Command: #{command} #{args}"
@exec command, args.split(" "), (error, stdout, stderr) ->
msg.send error if error?
msg.send stdout if stdout?
msg.send stderr if stderr?
确认启动Hubot并确保能够执行kubectl。
hubot> hubot kubectl get ns
hubot> Command: kubectl get ns
NAME STATUS AGE
default Active 169m
hubot Active 165m
kube-public Active 169m
kube-system Active 169m
hubot>
將Slack與其他系統進行整合或協同工作
接下来尝试Slack适配器而不是Shell适配器。
创建Hubot
因为不知道如何更改适配器,所以我决定新建一个Bot模板。
mkdir myhubot-slack
cd myhubot-slack
yo hubot
请按照之前的步骤将之前创建的mybot.coffee脚本和hubot-k8s包进行安装(略)。
确认运作
导出Slack的令牌并指定适配器启动机器人(如果重新启动终端,需要导出所需的环境变量以将hubot-k8s连接到Minikube)。
export HUBOT_SLACK_TOKEN=hogehoge
bin/hubot --name hubot --adapter slack
确认可以通过hubot-k8s来检查Pod的状态。

确认能够直接执行kubectl命令。

部署到 Kubernetes
一般来说,通常会选择在Heroku上运行,但我们尝试将其容器化并部署到Minikube中。
构建图像
请根据以下内容创建Dockerfile。
- https://nodejs.org/en/docs/guides/nodejs-docker-webapp/
可以查阅以下内容以了解通过Pod执行kubectl的方法。只需在容器内部放置kubectl二进制文件,并为执行Pod的ServiceAccount授予适当的权限。
- Kubernetesクラスター内のPodからkubectlを実行する
FROM node:10.15.0-alpine
WORKDIR /usr/src/app
RUN wget https://storage.googleapis.com/kubernetes-release/release/v1.13.0/bin/linux/amd64/kubectl \
&& mv kubectl /usr/local/bin/kubectl \
&& chmod +x /usr/local/bin/kubectl
COPY bin/ bin/
COPY scripts/ scripts/
COPY *.json ./
RUN npm install
CMD [ "./bin/hubot", "--name", "hubot", "--adapter", "slack" ]
目录结构如下。
myhubot-slack
├── Dockerfile
├── Procfile
├── README.md
├── bin
│ ├── hubot
│ └── hubot.cmd
├── external-scripts.json
├── hubot-scripts.json
├── node_modules
├── package-lock.json
├── package.json
└── scripts
├── example.coffee
└── mybot.coffee
构建并推送镜像。
docker build -t sotoiwa540/myhubot-slack:1.0 .
docker push sotoiwa540/myhubot-slack:1.0
部署至Kubernetes
要在Pod中运行kubectl命令,需要将适当的权限分配给运行Pod的ServiceAccount。然而,由于在hubot命名空间中的默认ServiceAccount已经与cluster-admin的ClusterRole绑定,因此只需在hubot命名空间部署Pod即可执行kubectl命令。
对于hubot-k8s包,需要通过环境变量HUBOT_K8S_CONTEXTS传递给kube-apiserver的身份验证信息,而且需要以JSON格式传递。
将连接到Slack所需的令牌以及除hubot-k8s的HUBOT_K8S_CONTEXTS之外的环境变量放入Secret中,并传递给Pod。
kubectl create secret generic hubot-secret -n hubot \
--from-literal=HUBOT_SLACK_TOKEN=${HUBOT_SLACK_TOKEN} \
--from-literal=HUBOT_K8S_DEFAULT_CONTEXT=${HUBOT_K8S_DEFAULT_CONTEXT} \
--from-literal=HUBOT_K8S_DEFAULT_NAMESPACE=${HUBOT_K8S_DEFAULT_NAMESPACE}
关于HUBOT_K8S_CONTEXTS,可以利用Kubernetes自动将证书和ServiceAccount的令牌挂载到容器的/var/run/secrets/kubernetes.io/serviceaccount目录下。因此,在启动之前,需要先导出环境变量,然后再启动Hubot。这个过程需要直接在清单文件中编写。另外,需要注意的是,在使用hubot命令启动Node.js进程时,如果不使用exec,将导致sh进程和其子进程的node进程都会增加两个。
部署的清单应该按照以下方式进行。
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
labels:
app: hubot
name: hubot
spec:
replicas: 1
selector:
matchLabels:
app: hubot
strategy:
type: Recreate
template:
metadata:
labels:
app: hubot
spec:
containers:
- name: hubot
image: sotoiwa540/myhubot-slack:1.0
imagePullPolicy: Always
envFrom:
- secretRef:
name: hubot-secret
command:
- sh
- -c
- |
export CA=$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export HUBOT_K8S_CONTEXTS=$(echo "{\"minikube\":{\"server\":\"https://kubernetes.default:443\",\"ca\":\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\",\"dashboardPrefix\":\"https://kubernetes.cluster.io\",\"token\":\"$TOKEN\"}}")
exec /usr/src/app/bin/hubot --name hubot --adapter slack
部署Hubot。
kubectl apply -f hubot-deployment.yaml -n hubot
查看Pod。
$ kubectl get po
NAME READY STATUS RESTARTS AGE
hubot-68cc597477-8kt4n 1/1 Running 0 22s
$
确认运行
确认运行。首先确认Hubot-k8s软件包能够运行。

确认能够直接执行kubectl命令。

与之相同。感觉上与Python的slackbot模块几乎一致。