Skip to content

Commit 0389631

Browse files
amygdalajmdobry
authored andcommitted
support for optionally using a PD for the db; better error handling (#276)
* support for optionally using a PD for the db; better error handling * readme and code cleanup
1 parent 8c649c4 commit 0389631

File tree

3 files changed

+193
-10
lines changed

3 files changed

+193
-10
lines changed

language/slackbot/README.md

+80-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11

22
# Building a Botkit-based Slack Bot that uses the GCP NL API and runs on Google Container Engine
33

4+
- [Setting up your environment](#setting-up-your-environment)
5+
- [Container Engine prerequisites](#container-engine-prerequisites)
6+
- [NL API prerequisites](#nl-api-prerequisites)
7+
- [Create a cluster](#create-a-cluster)
8+
- [Install Docker](#install-docker)
9+
- [Get a Slack token and invite the bot to a Slack channel](#get-a-slack-token-and-invite-the-bot-to-a-slack-channel)
10+
- [Running the slackbot on Kubernetes](#running-the-slackbot-on-kubernetes)
11+
- [Upload the slackbot token to Kubernetes](#upload-the-slackbot-token-to-kubernetes)
12+
- [Build the bot's container](#build-the-bots-container)
13+
- [Running the container](#running-the-container)
14+
- [Running the bot locally](#running-the-bot-locally)
15+
- [Using the Bot](#using-the-bot)
16+
- [Sentiment Analysis](#sentiment-analysis)
17+
- [Entity Analysis](#entity-analysis)
18+
- [Optional: Create a slackbot app that uses persistent storage](#optional-create-a-slackbot-app-that-uses-persistent-storage)
19+
- [Shutting down](#shutting-down)
20+
- [Cleanup](#cleanup)
21+
422

523
This example shows a Slack bot built using the [Botkit](https://github.com/howdyai/botkit) library.
624
It runs on a Google Container Engine (Kubernetes) cluster, and uses one of the Google Cloud Platform's ML
@@ -131,8 +149,8 @@ Then, set GCLOUD_PROJECT to your project id:
131149
export GCLOUD_PROJECT=my-cloud-project-id
132150
```
133151

134-
Then, create a file containing your Slack token, and point 'SLACK_TOKEN_PATH' to that file when you run the script
135-
(substitute 'my-slack-token with your actual token):
152+
Then, create a file containing your Slack token, and point `SLACK_TOKEN_PATH` to that file when you run the script
153+
(substitute `my-slack-token` with your actual token):
136154

137155
echo my-slack-token > slack-token
138156
SLACK_TOKEN_PATH=./slack-token node demo_bot.js
@@ -180,14 +198,72 @@ To see the top entities, send it this message:
180198
```
181199

182200

201+
## Optional: Create a slackbot app that uses persistent storage
202+
203+
Kubernetes will keep your slackbot running — we have specified that we want one pod replica, and so if this pod goes down for some reason, Kubernetes will restart it.
204+
You might be pondering what happens to your sqlite3 database if this happens.
205+
With the configuration above, you will lose your data if the pod needs to be restarted.
206+
207+
One way to address that would be to use a more persistent database service instead of sqlite3, and configure your bot to connect to that instead. ([Cloud SQL](https://cloud.google.com/sql/) would be an option for such a service.)
208+
209+
Alternatively (for this simple scenario), we can just create a persistent disk on which to store our sqlite3 database, and configure our pod replica to access it. That way, if the slackbot pod needs to be restarted, the database file won't be lost. We'll do that for this example.
210+
211+
We'll accomplish this by defining a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) resource, and then creating a [Persistent Volume Claim](http://kubernetes.io/docs/user-guide/persistent-volumes/#persistentvolumeclaims) on that resource which will be used by the slackbot app.
212+
213+
First, create a persistent disk to use with this app, as follows. Name it `slackbotstore`. You can adjust its size as you like.
214+
215+
```bash
216+
gcloud compute disks create --size=20GB --zone=<your-cluster-zone> slackbotstore
217+
```
218+
219+
Then, edit `demo_bot.js` to use `/var/sqlite3/slackDB.db` as its sqlite3 db file:
220+
221+
```javascript
222+
// create our database if it does not already exist.
223+
// const db = new sqlite3.cached.Database(path.join(__dirname, './slackDB.db'));
224+
const db = new sqlite3.cached.Database('/var/sqlite3/slackDB.db');
225+
```
226+
227+
Once you've done that, rebuild your docker image to capture that code change:
228+
229+
```bash
230+
export PROJECT_ID=my-cloud-project-id
231+
docker build -t gcr.io/${PROJECT_ID}/slack-bot .
232+
```
233+
234+
Generate a different .yaml file to use for this configuration:
235+
236+
```bash
237+
./generate-dep.sh $PROJECT_ID
238+
```
239+
240+
If you take a look at the result, in `slack-bot-dep.yaml`, you will see that it contains the specification of the persistent volume as well as a persistent volume claim on that volume. Then, you'll see that the slackbot mounts a volume matching that claim at `/var/sqlite3`.
241+
(In this config, the slackbot is also specified as a [Deployment](http://kubernetes.io/docs/user-guide/deployments/) rather than a Replication Controller. For the purposes of this example, the difference is not important.)
242+
243+
Note: when you created your disk, if you named it something other than `slackbotstore`, you will need to edit this configuration to specify your disk name instead.
244+
245+
If you already have a slackbot running, you will probably want to shut it down before you start up this new version, so that there are not two separate bots monitoring your channel. See the "Shutting down" section below. Then, start up the persistent-storage version of the bot like this:
246+
247+
```bash
248+
kubectl create -f slack-bot-dep.yaml
249+
```
250+
251+
183252
## Shutting down
184253

185-
To shutdown your bot, we tell Kubernetes to delete the replication controller.
254+
To shut down your bot, we tell Kubernetes to delete the Replication Controller:
186255

187256
```bash
188257
kubectl delete -f slack-bot-rc.yaml
189258
```
190259

260+
Or, if you are running the variant that uses a persistent disk, shut it down with:
261+
262+
```bash
263+
kubectl delete -f slack-bot-dep.yaml
264+
```
265+
266+
This will delete the persistent disk resource and claim as well as the Deployment, but does *not* delete the disk itself, which remains part of your GCP project. If you restart later using the same config file, your existing sqlite3 db will be preserved.
191267

192268
## Cleanup
193269

@@ -202,3 +278,4 @@ gcloud container clusters delete slackbot-cluster
202278
(If you used a different name for your cluster, substitute that name for `slackbot-cluster`.)
203279
This deletes the Google Compute Engine instances that are running the cluster.
204280

281+
If you created a persistent disk for your db, you may want to [delete that as well](https://cloud.google.com/sdk/gcloud/reference/compute/disks/delete).

language/slackbot/demo_bot.js

+31-7
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,21 @@ const controller = Botkit.slackbot({ debug: false });
5252

5353
// create our database if it does not already exist.
5454
const db = new sqlite3.cached.Database(path.join(__dirname, './slackDB.db'));
55+
// comment out the line above, and instead uncomment the following, to store
56+
// the db on a persistent disk mounted at /var/sqlite3. See the README
57+
// section on 'using a persistent disk' for this config.
58+
// const db = new sqlite3.cached.Database('/var/sqlite3/slackDB.db');
5559

5660
// the number of most frequent entities to retrieve from the db on request.
5761
const NUM_ENTITIES = 20;
5862
// The magnitude of sentiment of a posted text above which the bot will respond.
5963
const SENTIMENT_THRESHOLD = 30;
64+
const SEVEN_DAYS_AGO = 60 * 60 * 24 * 7;
6065

61-
const ENTITIES_SQL = `SELECT name, type, count(name) as wc
62-
FROM entities
63-
GROUP BY name
64-
ORDER BY wc DESC
66+
const ENTITIES_BASE_SQL = `SELECT name, type, count(name) as wc
67+
FROM entities`;
68+
69+
const ENTITIES_SQL = ` GROUP BY name ORDER BY wc DESC
6570
LIMIT ${NUM_ENTITIES};`;
6671

6772
const TABLE_SQL = `CREATE TABLE if not exists entities (
@@ -112,7 +117,24 @@ function startController () {
112117
)
113118
// For any posted message, the bot will send the text to the NL API for
114119
// analysis.
115-
.on('ambient', handleAmbientMessage);
120+
.on('ambient', handleAmbientMessage)
121+
.on('rtm_close', startBot);
122+
}
123+
124+
function startBot (bot, cerr) {
125+
console.error('RTM closed');
126+
let token = fs.readFileSync(process.env.SLACK_TOKEN_PATH, { encoding: 'utf8' });
127+
token = token.replace(/\s/g, '');
128+
129+
bot
130+
.spawn({ token: token })
131+
.startRTM((err) => {
132+
if (err) {
133+
console.error('Failed to start controller!');
134+
console.error(err);
135+
process.exit(1);
136+
}
137+
});
116138
}
117139

118140
function handleSimpleReply (bot, message) {
@@ -122,8 +144,10 @@ function handleSimpleReply (bot, message) {
122144
function handleEntitiesReply (bot, message) {
123145
bot.reply(message, 'Top entities: ');
124146

125-
// Query the database for the top N entities
126-
db.all(ENTITIES_SQL, (err, topEntities) => {
147+
// Query the database for the top N entities in the past week
148+
const queryTs = Math.floor(Date.now() / 1000) - SEVEN_DAYS_AGO;
149+
const entitiesWeekSql = `${ENTITIES_BASE_SQL} WHERE ts > ${queryTs}${ENTITIES_SQL}`;
150+
db.all(entitiesWeekSql, (err, topEntities) => {
127151
if (err) {
128152
throw err;
129153
}

language/slackbot/generate-dep.sh

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/bash
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
if [[ $# -ne 1 ]] ; then
17+
echo "Your project ID must be specified."
18+
echo "Usage:" >&2
19+
echo " ${0} my-cloud-project" >&2
20+
exit 1
21+
fi
22+
cloud_project=$1
23+
24+
cat <<END > slack-bot-dep.yaml
25+
apiVersion: v1
26+
kind: PersistentVolume
27+
metadata:
28+
name: slackbotstore
29+
spec:
30+
capacity:
31+
storage: 20Gi
32+
accessModes:
33+
- ReadWriteOnce
34+
gcePersistentDisk:
35+
pdName: slackbotstore
36+
fsType: ext4
37+
---
38+
apiVersion: v1
39+
kind: PersistentVolumeClaim
40+
metadata:
41+
name: sb-pv-claim
42+
labels:
43+
name: slack-bot
44+
spec:
45+
accessModes:
46+
- ReadWriteOnce
47+
resources:
48+
requests:
49+
storage: 20Gi
50+
---
51+
apiVersion: extensions/v1beta1
52+
kind: Deployment
53+
metadata:
54+
name: slack-bot
55+
spec:
56+
replicas: 1
57+
template:
58+
metadata:
59+
labels:
60+
name: slack-bot
61+
spec:
62+
containers:
63+
- name: master
64+
image: gcr.io/${cloud_project}/slack-bot
65+
volumeMounts:
66+
- name: slack-token
67+
mountPath: /etc/slack-token
68+
- name: slackbot-persistent-storage
69+
mountPath: /var/sqlite3
70+
env:
71+
- name: SLACK_TOKEN_PATH
72+
value: /etc/slack-token/slack-token
73+
- name: GCLOUD_PROJECT
74+
value: ${cloud_project}
75+
volumes:
76+
- name: slack-token
77+
secret:
78+
secretName: slack-token
79+
- name: slackbot-persistent-storage
80+
persistentVolumeClaim:
81+
claimName: sb-pv-claim
82+
END

0 commit comments

Comments
 (0)