ローカル（オンプレミス）のKubernetes（minikube）上で動く Slash Command 用 Golang Bot

# Golang Bot for Slash Command running on local(on-premises) Kubernetes(minikube)
## 0. はじめに
今回GolangとKubernetes/Dockerの勉強のためにMattermost（Slack Clone）のSlash Commandに応答するGolangの簡単なBotサンプルを作成しましたので、自己整理も兼ねて以下一通りを記事にします。

1. GolangでSlash Commandのメッセージに対して分岐応答する簡易Bot作成
2. Botを含んだDockerイメージのビルド
3. Kubernetes（minikube）上へk8sマニフェストファイルを利用してBot及びBot確認用Mattermostのサービス及びデプロイメントの設定
4. Mattermost上でSlash Commandを設定し、Botの応答を確認

まだまだ勉強中のため、至らないところや冗長な部分などがあると思いますので何かありましたらコメントいただければと思います。

なお、Botの動作環境は、Kubernetes/Docker/Golangを選択可能です。
またMattermost（Slack Clone）に対応していますのでローカルやオンプレミス環境で動作可能です。

Botについて今回ローカルやオンプレミスでの動作検証を想定しています。
外部へ公開などをする場合は、セキュリティ対策を含めたデータのチェック機能などを別途実装して下さい。
また、k8s上のMattermost（Slack Clone）については、Botの動作確認用途のためデータの永続化などは行っていません。

検証環境等でのChatOpsデモ、素振り用などの参考にして頂ければと思います。

その他、本記事で利用するソースコードはgithubでも公開しています。
https://github.com/tbuchi888/golang-slashcommand-k8s.git

## 1. 動作環境等
### 1.1. 環境
以下環境を前提としています。
- minikube version: v0.17.1
- VirtualBox: バージョン 5.0.16 r105871
- mac OSX Yosemite

Kubernetesはminikubeを利用しています。
インストールについては[Kubernetes公式サイト](https://kubernetes.io/docs/getting-started-guides/minikube/)や[「minikubeでローカルKubernetesクラスタを5分でつくる方法」](http://qiita.com/mumoshu/items/8f55ee830d8e5c172dd4)を参考にしてください。

### 1.2.構成
以下構成で確認ました。

![k8s.png](https://qiita-image-store.s3.amazonaws.com/0/117986/03d36a16-0738-71a9-0c0f-5cff027c3338.png)

- Mattermostは動作確認用途なので、データの永続化などは行わずに、単純に`replicas: 1`としてhttps://hub.docker.com/r/mattermost/platform/ を動かします。

- tbuchi888/gobotblue:v1は後述しますローカルでビルドしたDockerイメージで、ここでGolangのBotを動かします。


## 2. コード
### 2.1. Golang Bot
こちら[「SlackのSlash CommandsをApp Engineで稼働させる」](https://developers.eure.jp/tech/gae-slashcommands-example/)を参考にSlash Commandのメッセージをswitch文で分岐処理して応答するようにしています。利用用途に合わせて適宜修正をしてください。
またTokenの環境変数からの取得と、MethodやTokenの確認、リッスンするport番号（3000番）の指定など行っています。

``` main.go
package main

import (
        "bytes"
        "encoding/json"
        "net/http"
        "os"

        "github.com/favclip/ucon"
)

func main() {
        ucon.Orthodox()

        ucon.HandleFunc("POST", "/", func(w http.ResponseWriter, r *http.Request) {
                if err := r.ParseForm(); err != nil {
                        w.Write([]byte(err.Error()))
                        return
                }

                w.Header().Set("Content-Type", "application/json")

                // Check method and token
                if r.Method != "POST" {
                    w.WriteHeader(http.StatusBadRequest)
                    return
                }
                if r.PostFormValue("token") != os.Getenv("GOBOTTOKEN") {
                    w.WriteHeader(http.StatusUnauthorized)
                    return
                }
                w.WriteHeader(http.StatusOK)

                data := map[string]string{
                        "response_type": "in_channel",
                        "username": "gobot",
                        "icon_url": "https://golang.org/doc/gopher/gophercolor.png",
                }

                // Switch by text
                t := r.PostFormValue("text")
                switch t {
/*--------------------------------------------------------------------------------
                        ここへ追記
                        case "条件となる文字列を指定" :
　　　　　　　　　　　　　　　　やらせたい処理などを記載しMD記法で応答メッセージをセット。
                                data["text"] = "## 応答メッセージ"
--------------------------------------------------------------------------------*/
                        case "" :
                                data["text"] = "## メッセージが設定されていません。"
                        case "今日の天気は？" :
                                data["text"] = "## わかりません。。。"
                        default:
                                data["text"] = "## " + t + "gobotより自動返信しています。"
                }

                var buf bytes.Buffer
                json.NewEncoder(&buf).Encode(data)
                w.Write(buf.Bytes())
        })

        ucon.ListenAndServe(":3000")
        ucon.DefaultMux.Prepare()
        http.Handle("/", ucon.DefaultMux)
}
```

### 2.2. Dockerfile
Dockerファイルは`golang：1.8`をベースに追加モジュール`github.com/favclip/ucon`のインストールと今回作成するBot `main.go`のコピー及びコンパイル等をしています。

Dockerfile

``` Dockerfile
FROM golang:1.8

RUN go get -u github.com/favclip/ucon

COPY ./main.go /go/src/main/
RUN go install main

ENTRYPOINT /go/bin/main
```

### 2.3. Kubernetes
GolangBot用のService定義k8sマニフェストファイル

``` gobotsv.yml
apiVersion: v1
kind: Service
metadata:
  name: gobotsv
  labels:
    name: app
    app: gobot
spec:
  ports:
    - name: gobot
      port: 3000
      targetPort: 3000
  selector:
    name: app
    app: gobot
    color: blue
  type: LoadBalancer
```

GolangBot用のDeployment定義k8sマニフェストファイル
blue/greenデプロイメントなど可能なようにdeploymentの定義を別にしています。
イメージは上述の`Dokcerfile`からビルドしたものを指定しています。
環境変数`GOBOTOTOKEN`の`value: Your_slashcommand_token`についてはMattermost（Slack）側のSlashCommand設定時に払出されたTokenへ置き換えるか、デプロイメント作成後に`kubectl apply -f`または`kubectl edit deployment`コマンドで別途変更してください。
これを変更しないとTokenのチェックで401エラーとなります。

``` gobotblue.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: gobotblue
  labels:
    name: app
    app: gobot
    color: blue
spec:
  minReadySeconds: 30
  strategy:
    type: RollingUpdate
  replicas: 4
  template:
    metadata:
      name: gobotblue
      labels:
        name: app
        app: gobot
        color: blue
    spec:
      containers:
      - image: tbuchi888/gobotblue:v1
        name: gobot
        ports:
          - containerPort: 3000
        env:
          - name: GOBOTTOKEN
            value: Your_slashcommand_token
```

Mattermost用のService及びDeployment定義k8sマニフェストファイル
Botの動作確認用途なので、データの永続化などは行わずに、単純に`replicas: 1`としてk8s上で動かします。
なお、利用しているコンテナイメージは以下となります。
https://hub.docker.com/r/mattermost/platform/

``` mattermost.yml
apiVersion: v1
kind: Service
metadata:
  name: mattermostsv
  labels:
    name: app
    app: mattermost
spec:
  ports:
    - name: mattermost
      port: 8065
      targetPort: 8065
  selector:
    name: app
    app: mattermost
    color: blue
  type: LoadBalancer
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mattermostdep
  labels:
    name: app
    app: mattermost
    color: blue
spec:
  minReadySeconds: 30
  strategy:
    type: RollingUpdate
  replicas: 1
  template:
    metadata:
      name: mattermostdep
      labels:
        name: app
        app: mattermost
        color: blue
    spec:
      containers:
      - image: mattermost/mattermost-preview:latest 
        name: mattermost
        ports:
          - containerPort: 8065
```

## 3.使い方

git clone または、こちらの記事から参考にソースファイルを用意します。

```
Git clone https://github.com/tbuchi888/golang-slashcommand-k8s.git
cd golang-slashcommand-k8s.git
```

ディレクトリ構成は以下となります。（git　clone時）

``` 
golang-slashcommand-k8s
    ├── Dockerfile
    ├── LICENSE
    ├── README.md
    ├── k8s
    │   ├── gobotblue.yml
    │   ├── gobotsv.yml
    │   ├── mattermost.yml
    │   └── readme.md
    └── main.go
```

なお、Kubernetes/Docker/golang　いずれの場合も
環境変数`GOBOTOTOKEN`の値`Your_slashcommand_token`についてはMattermost（Slack）側のSlash Command設定時に払出されたTokenへ置き換えが必要です。

### 3.1. on kubernetes(minikube)
以下kubernetes(minikube)上での使用例です。

```
#　use　Docker　on　Kubernetes（minikube）
#　macからminikube上のdockerにアクセスするために以下コマンドを実行します。
eval $(minikube docker-env)

# create docker image
docker build -t tbuchi888/gobotblue:v1 ./

# check image
docker images

# create k8s service and deploymnet
kubectl create -f k8s/gobotsv.yml --record
kubectl create -f k8s/gobotblue.yml --record

# get url of service on minikube 
# Mattermost側Slash Command登録時にRequestURLとして利用するので控えておきます。
minikube service gobotsv --url

# below mattermost is for testing
# create k8s service and deploymnet
kubectl create -f k8s/mattermost.yml --record

# get url of service on minikube 
# MattermostのURLのため控えておきます。
minikube service mattermostsv --url
```

`minikube service gobotsv --url`の結果を
後述のMattermost（Slack）側のSlashCommand登録時にRequestURLとして設定します。
また、SlashCommand設定時に払い出されたTokenの値を使って　k8s側gobotblue deploymentのTokenの値を変更します。
 
```
kubectl apply -f k8s/gobotblue.yml --record
#️ または以下で直接編集
kubectl edit deployment gobotblue

# 新しいpodsに切り替わることを確認（少し時間がかかります）
kubectl get pods
```

### 3.2. on Docker　（参考）
以下は`Dokcerfile`及び`main.go`があるディレクトリで`tbuchi888/gobotblue:v1`としてビルドしたイメージを、`golang-bot`というコンテナ名で3000番portを待ち受けとして動作させる例です。
＊なお、事前にDokcerのインストールが必要です。

```
docker build -t tbuchi888/gobotblue:v1 ./
docker run --name golang-bot -d -p 3000:3000 -e GOBOTTOKEN=Your_slashcommand_token tbuchi888/gobotblue:v1
```

### 3.3. Golang　（参考）
以下は`main.go`があるディレクトリで3000番portを待ち受けとして動作させる例です。
＊なお、事前にGolangのインストールやパスの設定等が必要です。


```
export GOBOTTOKEN="Your_slashcommand_token"
go get -u github.com/favclip/ucon
go install main
./main
```

## 4.（参考）Mattermostの設定　及び　実行結果
以下参考までにMattermost側でのSlash　Commandの設定及び実行結果の例となります。

3.1の`minikube service mattermostsv --url`実行結果のURLへブラウザからアクセスし、初期アカウントの登録を行います。
![matt01.png](https://qiita-image-store.s3.amazonaws.com/0/117986/a44d4fb7-3fbd-dcb8-414e-3c9ee4d95105.png)

Create　New tearmを行います。
![matt02.png](https://qiita-image-store.s3.amazonaws.com/0/117986/fe8daf2a-e762-3019-26bd-c90e9106078f.png)

![matt03.png](https://qiita-image-store.s3.amazonaws.com/0/117986/f4e669dd-cc26-e873-dbd9-f0b8eb18bb71.png)

![matt04.png](https://qiita-image-store.s3.amazonaws.com/0/117986/9b81838d-1b19-eeed-c867-736772919bef.png)

左上チーム名横のメニュー（...）から`SystemConsole`を選択
![matt05.png](https://qiita-image-store.s3.amazonaws.com/0/117986/f296f6fb-e582-2ea0-7e5e-f8e73eae4f69.png)

`INTEGRATIONS`-`Custom Integrations`の設定で以下を`true`へ変更します。

- `Enable Custom Slash Commands:`
- `Enable integrations to override usernames:`
- `Enable integrations to override profile picture icons:`

左上チーム名横のメニュー（...）から`Switch to チーム名`で`SystemConsole`を抜けます。

![matt06.png](https://qiita-image-store.s3.amazonaws.com/0/117986/f7d95c49-3126-a0c1-33be-b1a845159e54.png)

![matt07.png](https://qiita-image-store.s3.amazonaws.com/0/117986/4d896ef7-6e1f-b18d-13d1-6ab9fd640dac.png)

左上チーム名横のメニュー（...）から`Integrations`を選択します。

![matt09.png](https://qiita-image-store.s3.amazonaws.com/0/117986/c42955bc-b304-89ba-5f23-238cd0b1b437.png)

`Slash Command`を選択
![matt10.png](https://qiita-image-store.s3.amazonaws.com/0/117986/714da18a-2bb0-c360-306c-e36c7002a7eb.png)

右上の`Add Slash Command`を選択
![matt11.png](https://qiita-image-store.s3.amazonaws.com/0/117986/4e65f5ee-e4d2-2009-21ee-473af81aa8a5.png)

`Slash Command`を登録します。なお、`Request URL`へ3.1.で確認した`minikube service gobotsv --url`の実行結果を設定します。
![matt12.png](https://qiita-image-store.s3.amazonaws.com/0/117986/b9e37245-3789-18e2-8752-9538e6c2da16.png)

表示される`Token`を控えておきます。
![matt13.png](https://qiita-image-store.s3.amazonaws.com/0/117986/04841a42-5463-2c5e-e576-5e3108ef3ec1.png)

Kubernetesの`gobotblue`デプロイメントの環境変数GOBOTTOKENの値(Your_slashcommand_token)を先ほど控えた`Token`に変更します。
![matt14.png](https://qiita-image-store.s3.amazonaws.com/0/117986/15d99045-14ea-8db6-87c2-e9dfdb624073.png)

Mattermostの適当なチャンネルを選択します。
![matt15.png](https://qiita-image-store.s3.amazonaws.com/0/117986/f84a093e-a7dc-ac9d-42e0-88ac1b51f9c8.png)

先ほど登録したSlashComannd（今回の例では`/gobot`）を呼び出して作成したGobotが応答することを確認します。
![matt16.png](https://qiita-image-store.s3.amazonaws.com/0/117986/1b8d36ac-fd7f-d761-9bea-ebf911ace8d9.png)

## 5.その他
文中での引用以外に以下を参考にさせて頂きました。

-　https://docs.mattermost.com/developer/slash-commands.html
-　https://github.com/favclip/ucon
- [Kubernetes 速習会](http://qiita.com/koudaiii/items/d0b3b0b78dc44d97232a)　
- [Kubernetes: Deployment の仕組み](http://qiita.com/tkusumi/items/01cd18c59b742eebdc6a)