たのしい工学

プログラミングを学んで、モノをつくりたいひと、効率的に仕事をしたい人のための硬派なブログになりました

【LINE BOT】サーバレスでつくってみた

   

LINE BOTをサーバレスで作ってみました。

インフラはGoogle Cloud Platform(GCP)を利用しています。GCPの下記サービスを利用しました。
Cloud KMS(APIキー管理)
Clous Function(サーバレスファンクション。AWSでいうところのLambda)
Compute Engine(作業環境用VMインスタンス)

言語:Goを使っています。

作業の流れ
-LINE Messaging APIの利用設定
-Golangで実装
-Cloud KMS でAPI keyを暗号化
-Cloud Functionsにデプロイ

LINE Messaging APIの利用設定

LINEのAPIを利用するために開発者登録、アプリの登録、tokenの発行などを行います。
開発者登録
LINEのアカウントでログインすることで、LINE開発者サイトにアカウントが登録されます。
チャネル・プロバイダーの作成
公式のドキュメントに従ってチャンネル・プロバイダーの作成を行ってください。
作成が完了したらコンソールから作成したプロバイダー→チャネルを選択しチャネルの基本設定画面を開きます。

チャネルの基本設定画面
基本設定を開いたら「メッセージ送受信設定」のアクセストークン(ロングターム)の欄にある発行ボタンを押してアクセストークンを発行します。

このページの「Channel Secret」と「アクセストークン」をあとで利用します。

Ubuntu 18.04LTSに golang 1.11 をインストールする

Go1.11のインストール

sudo apt update
sudo apt upgrade -y
cd /tmp
wget https://dl.google.com/go/go1.11.linux-amd64.tar.gz
sudo tar -xvf go1.11.linux-amd64.tar.gz
sudo mv go /usr/local

参考:[Linux]ファイルの圧縮、解凍方法 - Qiita
Ubuntu 16.04に golang 1.11 をインストールする - IT系車中泊記録

GOROOTとGOPATHの設定

cd ~/
vim .bash_profile
//.bash_profile内に下記を設定
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

下記で、上記の設定を反映

$ source ~/.bash_profile

参考:Go Modules - Qiita

作業ディレクトリの作成

作業ディレクトリをGOPATH配下に作ります。($HOME/go配下)

mkdir gcf-linebot

webhook.go(エントリポイントを含むファイル)の作成

cd gcf-linebot
vim webhook.go

下記を実装します


package linebot

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"

cloudkms "cloud.google.com/go/kms/apiv1"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"

"github.com/line/line-bot-sdk-go/linebot"
)

var (
secrets Secrets
)

type Secrets struct {
LineChannelSecret      string json:"line_channel_secret"
LineChannelAccessToken string json:"line_channel_access_token"
}

func init() {
secretsJson, err := decryptLineSecrets()
if err != nil {
log.Fatal("failed decrypt secrets", err)
return
}
if err := json.Unmarshal(secretsJson, &secrets); err != nil {
log.Fatal("failed json unmarshal secrets", err)
return
}
}

func Webhook(w http.ResponseWriter, r *http.Request) {
client, err := linebot.New(secrets.LineChannelSecret, secrets.LineChannelAccessToken)
if err != nil {
http.Error(w, "Error init client", http.StatusBadRequest)
log.Fatal(err)
return
}
events, err := client.ParseRequest(r)
if err != nil {
http.Error(w, "Error parse request", http.StatusBadRequest)
log.Fatal(err)
return
}
for _, e := range events {
switch e.Type {
case linebot.EventTypeMessage:
message := linebot.NewTextMessage("Test")
_, err := client.ReplyMessage(e.ReplyToken, message).Do()
if err != nil {
log.Println(err)
continue
}
}
}
fmt.Fprint(w, "ok")
}

func lineSecretsKmsKeyName() string {
prjID := os.Getenv("GCP_PROJECT_ID")
keyRingName :=os.Getenv("KMS_KEY_RING_NAME")
keyName := os.Getenv("KMS_LINE_SECRETS_KEY_NAME")
return fmt.Sprintf("projects/%s/locations/global/keyRings/%s/cryptoKeys/%s", prjID, keyRingName, keyName)
}

func decryptLineSecrets() ([]byte, error) {
enc, err := ioutil.ReadFile("secrets.json.enc")
if err != nil {
return nil, err
}
return decryptSymmetric(lineSecretsKmsKeyName(), enc)
}

func decryptSymmetric(keyName string, ciphertext []byte) ([]byte, error) {
ctx := context.Background()
client, err := cloudkms.NewKeyManagementClient(ctx)
if err != nil {
return nil, err
}

// Build the request.
req := &kmspb.DecryptRequest{
Name:       keyName,
Ciphertext: ciphertext,
}
// Call the API.
resp, err := client.Decrypt(ctx, req)
if err != nil {
return nil, err
}
return resp.Plaintext, nil
}

Goのパッケージ管理関係の環境設定

pwd
YOURHOME/go/gcf-line
export GO111MODULE=on; go mod init go1.11

作業ディレクトリをGOPATHの中で作成し、go mod init go1.11で初期化しました。
ここで、webhook.goに含まれているmoduleを試しにinitしてしまうとこの後のgo build .
がうまく行われなかったので注意です。initの後ろに指定するのはmodule名です。(厳密にはモジュール・パスになるようです)

export GO111MODULE=on; go build .

を実行し、パッケージ管理ファイルのgo.mod、go.sumを作成します。

参考1:Go での依存関係の指定  |  Cloud Functions のドキュメント  |  Google Cloud
参考2:https://text.baldanders.info/golang/go-module-aware-mode/

gcloudコマンドの有効化

任意の場所で

gcloud init

を実行。

利用するGCPのプロジェクト名、インスタンス名を聞かれます。
それらを入力すれば、gcloudコマンドが現在作業しているインスタンス上で利用できるようになります。

認証関連

secrets.jsonの作成

LINEのチャネルの基本設定画面にあるChannel Secretと、先程発行したアクセストークン(ロングターム)を設定してください。

{
"line_channel_secret": "your_line_channel_secret",
"line_channel_access_token": "your_line_channel_access_token"
}

あとでこのファイルを暗号化します。

ignoreするファイルの設定

Cloud Functionsにデプロイする際にディレクトリのファイルすべてアップロードしてしまうので不要なものはここに設定します。
.gcloudignore
.git
.gitignore
node_modules
secrets.json

secrets.jsonの暗号化

secrets.jsonの内容を暗号化します。暗号化にはGCPのKMSを利用します。

API Keyの暗号化

キーリングを作成
$ gcloud kms keyrings create 【keyring_name】 \
--location global

【keyring_name】は任意のもので構いません。

キーリングに紐づく鍵を作成
$ gcloud kms keys create 【key_name】 \
--location global \
--keyring 【keyring_name】 \
--purpose encryption

【key_name】は任意のもので構いません。

これでsecret.jsonを暗号化するための鍵が作成されました。
では鍵を使ってsecret.jsonを暗号化します。

secret.jsonを暗号化

【keyring_name】【key_name】は先程と同じものを入力してください

$ gcloud kms encrypt \
--plaintext-file=secrets.json \
--ciphertext-file=secrets.json.enc \
--location=global \
--keyring=【keyring_name】 \
--key=【key_name】

成功すれば secrets.json.enc が作成され中に暗号化された文字列が入っています。
続いてCloud FunctionsでAPI Keyの復号を行うための権限設定を行います。
GCPコンソールから秘密鍵のページを開き、先程作成したキーリングを選択し、右側の欄にあるメンバーを追加を押し、新しいメンバーの欄に
【GCPのPROJECT_ID】@appspot.gserviceaccount.com
を入力し、役割から「クラウドKMSの暗号鍵の復号化」を選択して保存します。

デプロイ

$ gcloud functions deploy 【function】 \
--runtime=go111 \
--trigger-http \
--entry-point=Webhook \
--set-env-vars="GCP_PROJECT_ID"="【gcp_project_id】","KMS_KEY_RING_NAME"="【keyring_name】","KMS_LINE_SECRETS_KEY_NAME"="【key_name】"

【function_name】は任意の関数の名前、【gcp_project_id】はGCPのプロジェクトIDを、【keyring_name】【key_name】は先程と同じものを入力してください。
2分位でデプロイが完了します。以下のような表示がでたら成功です。
結果表示のhttpsTriggerの部分に書いてあるURLがAPIの接続先です。
また、serviceAccountEmailにでてくるメールアドレスも後ほど使用するのでメモしておいてください。

Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: Webhook
httpsTrigger:
url: 【url】
-- 【略】 --
timeout: 60s
updateTime: '2019-06-30T04:35:25Z'
versionId: '1'

このURLをWebhookのURLとして登録します。
再度LINEのチャネル基本設定ページを開き、「メッセージ送受信設定」にあるWebhook送信を「利用する」に変更します。そしてその下にあるWebhook URLに先程のURLを設定してください。その後「接続確認」のボタンが表示され、「成功しました。」と表示されれば完了です。

そして下記、webhookをonに設定することで、LINE Botが応答を返すようになります。

  • LINE OFFICIAL ACCOUNT MANAGERの応答設定の詳細設定で、webhookをオンに
  • line developerのwebhookをオンに

以上

 - Go