Deploy a Swarm Cluster with Alexa

Serverless and Containers changed the way we leverage public clouds and how we write, deploy and maintain applications. A great way to combine the two paradigms is to build a voice assistant with Alexa based on Lambda functions – written in Go – to deploy a Docker Swarm cluster on AWS.

The figure below shows all components needed to deploy a production-ready Swarm cluster on AWS with Alexa.



Note: Full code is available on my GitHub.

A user will ask Amazon Echo to deploy a Swarm Cluster:



Echo will intercept the user’s voice command with built-in natural language understanding and speech recognition. Convey them to the Alexa service. A custom Alexa skill will convert the voice commands to intents:



The Alexa skill will trigger a Lambda function for intent fulfilment:



The Lambda Function will use the AWS EC2 API to deploy a fleet of EC2 instances from an AMI with Docker CE preinstalled (I used Packer to bake the AMI to reduce the cold-start of the instances). Then, push the cluster IP addresses to a SQS:



Next, the function will insert a new item to a DynamoDB table with the current state of the cluster:



Once the SQS received the message, a CloudWatch alarm (it monitors the ApproximateNumberOfMessagesVisible parameter) will be triggered and as a result it will publish a message to an SNS topic:



The SNS topic triggers a subscribed Lambda function:



The Lambda function will pull the queue for a new cluster and use the AWS System Manager API to provision a Swarm cluster on the fleet of EC2 instances created earlier:



For debugging, the function will output the Swarm Token to CloudWatch:



Finally, it will update the DynamoDB item state from Pending to Done and delete the message from SQS.

You can test your skill on your Amazon Echo, Echo Dot, or any Alexa device by saying, “Alexa, open Docker



At the end of the workflow described above, a Swarm cluster will be created:



At this point you can see your Swarm status by firing the following command as shown below:





Improvements & Limitations:

  • Lambda execution timeout if the cluster size is huge. You can use a Master Lambda function to spawn child Lambda.
  • CloudWatch & SNS parts can be deleted if SQS is supported as Lambda event source (AWS PLEAAASE !). DynamoDB streams or Kinesis streams cannot be used to notify Lambda as I wanted to create some kind of delay for the instances to be fully created before setting up the Swarm cluster. (maybe Simple Workflow Service ?)
  • Inject SNS before SQS. SNS can add the message to SQS and trigger the Lambda function. We won’t need CloudWatch Alarm.
  • You can improve the Skill by adding new custom intents to deploy Docker containers on the cluster or ask Alexa to deploy the cluster on a VPC

In-depth details about the skill can be found on my GitHub. Make sure to drop your comments, feedback, or suggestions below — or connect with me directly on Twitter @mlabouardy.

Become AWS Certified Developer with Alexa

Being an AWS Certified can boost your career (increasing your salary, finding better job or getting a promotion) and make your expertise and skills relevant. Hence, there’s no better way to prepare for your AWS Certified Developer Associate exam than getting your hands dirty and build a Serverless Quiz game with Alexa Skill and AWS Lambda.



Note: all the code is available in my GitHub.

1 – DynamoDB

To get started, create a DynamoDB Table using the AWS CLI:

1
2
3
4
5
aws dynamodb create-table --table-name Questions
--attribute-definitions AttributeName=ID,AttributeType=S
--key-schema AttributeName=ID,KeyType=HASH
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
--region AWS_REGION

I prepared in advance a list of questions for the following AWS services:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[
{
"category" : "S3",
"questions" : [
{
"question": "In S3 what can be used to delete a large number of objects",
"answers" : {
"A" : "QuickDelete",
"B" : "Multi-Object Delete",
"C" : "Multi-S3 Delete",
"D" : "There is no such option available"
},
"correct" : "B"
},
{
"question": "S3 buckets can contain both encrypted and non-encrypted objects",
"answers" : {
"A" : "False",
"B" : "True"
},
"correct" : "B"
}
]
}
]

Next, import the JSON file to the DynamoDB table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
log.Fatal(err)
}

raw, err := ioutil.ReadFile("questions.json")
if err != nil {
log.Fatal(err)
}

services := make([]Service, 0)
json.Unmarshal(raw, &services)

for _, service := range services {
fmt.Printf("AWS Service: %s\n", service.Category)
fmt.Printf("\tQuestions: %d\n", len(service.Questions))
for _, question := range service.Questions {
err = insertToDynamoDB(cfg, service.Category, question)
if err != nil {
log.Fatal(err)
}
}
}

}

The insertToDynamoDB function uses the AWS DynamoDB SDK and PutItemRequest method to insert an item into the table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func insertToDynamoDB(cfg aws.Config, category string, question Question) error {
tableName := os.Getenv("TABLE_NAME")

item := DBItem{
ID: xid.New().String(),
Category: category,
Question: question.Question,
Answers: question.Answers,
Correct: question.Correct,
}

av, err := dynamodbattribute.MarshalMap(item)
if err != nil {
fmt.Println(err)
return err
}

svc := dynamodb.New(cfg)
req := svc.PutItemRequest(&dynamodb.PutItemInput{
TableName: &tableName,
Item: av,
})
_, err = req.Send()
if err != nil {
return err
}

return nil
}

Run the main.go file by issuing the following command:



If you navigate to DynamoDB Dashboard, you should see that the list of questions has been successfully inserted:



2 – Alexa Skill

This is what ties it all together, by linking the phrases the user says to interact with the quiz to the intents.

For people who are not familiar with NLP. Alexa is based on an NLP Engine which is a system that analyses phrases (users messages) and returns an intent. Intents describe what a user want or want to do. It is the intention behind his message. Alexa can learn new intents by attributing examples of messages to an intent. Behind the scenes, the Engine will be able to predict the intent even if he had never seen it before.

So, sign up to Amazon Developer Console, and create a new custom Alexa Skill. Set an invocation name as follows:



Create a new Intent for starting the Quiz:



Add a new type “Slot” to store user choice:



Then, create another intent for AWS service choice:



And for user’s answer choice:



Save your interaction model. Then, you’re ready to configure your Alexa Skill.

3 – Lambda Function

The Lambda handler function is self explanatory, it maps each intent to a code snippet. To keep track of the user’s score. We use Alexa sessionAttributes property of the JSON response. The session attributes will then be passed back to you with the next request JSON inside the session’s object. The list of questions is retrieved from DynamoDB using AWS SDK and SSML (Speech Synthesis Markup Language) is used to make Alexa speaks a sentence ending in a question mark as a question or add pause in the speech:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import (
"context"
"fmt"
"log"
"strings"

"github.com/aws/aws-lambda-go/lambda"
)

func HandleRequest(ctx context.Context, r AlexaRequest) (AlexaResponse, error) {
resp := CreateResponse()

switch r.Request.Intent.Name {
case "Begin":
resp.Say(`<speak>
Choose the AWS service you want to be tested on <break time="1s"/>
A <break time="1s"/> EC2 <break time="1s"/>
B <break time="1s"/> VPC <break time="1s"/>
C <break time="1s"/> DynamoDB <break time="1s"/>
D <break time="1s"/> S3 <break time="1s"/>
E <break time="1s"/> SQS
</speak>`, false, "SSML")
case "ServiceChoice":
number := strings.TrimSuffix(r.Request.Intent.Slots["choice"].Value, ".")

questions, _ := getQuestions(services[number])

resp.SessionAttributes = make(map[string]interface{}, 1)
resp.SessionAttributes["score"] = 0
resp.SessionAttributes["correct"] = questions[0].Correct

resp.Say(text, false, "SSML")
case "AnswerChoice":
resp.SessionAttributes = make(map[string]interface{}, 1)
score := int(r.Session.Attributes["score"].(float64))
correctChoice := r.Session.Attributes["correct"]
userChoice := strings.TrimSuffix(r.Request.Intent.Slots["choice"].Value, ".")

text := "<speak>"
if correctChoice == userChoice {
text += "Correct</speak>"
score++
} else {
text += "Incorrect</speak>"
}

resp.Say(text, false, "SSML")

case "AMAZON.HelpIntent":
resp.Say(welcomeMessage, false, "PlainText")
case "AMAZON.StopIntent":
resp.Say(exitMessage, true, "PlainText")
case "AMAZON.CancelIntent":
resp.Say(exitMessage, true, "PlainText")
default:
resp.Say(welcomeMessage, false, "PlainText")
}
return *resp, nil
}

func main() {
lambda.Start(HandleRequest)
}

Note: Full code is available in my GitHub.

Sign in to AWS Management Console, and create a new Lambda function in Go from scratch:



Assign the following IAM role to the function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Sid": "2",
"Effect": "Allow",
"Action": "dynamodb:Scan",
"Resource": [
"arn:aws:dynamodb:us-east-1:ACCOUNT_ID:table/Questions/index/ID",
"arn:aws:dynamodb:us-east-1:ACCOUNT_ID:table/Questions"
]
}
]
}

Generate the deployment package, and upload it to the Lambda Console and set the TABLE_NAME environment variable to the table name:



4 – Testing

Now that you have created the function and put its code in place, it’s time to specify how it gets called. We’ll do this by linking the Lambda ARN to Alexa Skill:



Once the information is in place, click Save Endpoints. You’re ready to start testing your new Alexa Skill !



To test, you need to login to Alexa Developer Console, and enable the “Test” switch on your skill from the “Test” Tab:



Or use an Alexa enabled device like Amazon Echo, by saying “Alexa, Open AWS Developer Quiz” :



Drop your comments, feedback, or suggestions below — or connect with me directly on Twitter @mlabouardy.

Real-Time Infrastructure Monitoring with Amazon Echo

Years ago, managing your infrastructure through voice was a science-fiction movie, but thanks to virtual assistants like Alexa it becomes a reality. In this post, I will show you how I was able to monitor my infrastructure on AWS using a simple Alexa Skill.



At a high level, the architecture of the skill is as follows:

I installed data collector agent (Telegraf) in each EC2 instance to collect metrics about system usage (disk, memory, cpu ..) and send them to a time-series database (InfluxDB)

Once my database is populated with metrics, Amazon echo will transform my voice commands to intents that will trigger a Lambda function, which will use the InfluxDB REST API to query the database.



Enough with talking, lets build this skill from scratch, clone the following GitHub repository:

1
git clone https://github.com/mlabouardy/alexa-monitor

Create a simple fleet of EC2 instances using Terraform. Install the AWS provider:

1
terraform init

Set your own AWS credentials in variables.tfvars. Create an execution plan:

1
terraform plan --var-file=variables.tfvars

Provision the infrastructure:

1
terraform apply --var-file=variables.tfvars

You should see the IP address for each machine:



Login to AWS Management Console, you should see your nodes has been created successfully:



To install Telegraf on each machine, I used Ansible, update the ansible/inventory with your nodes IP addresses as follows:

1
2
3
4
5
6
7
8
[nodes]
35.178.10.226 hostname=Rabat
35.177.164.157 hostname=Paris
52.56.126.138 hostname=London

[nodes:vars]
remote_user=ec2-user
influxdb_ip=35.177.123.180

Execute the playbook:

1
ansible-playbook -i inventory playbook.yml --private-key=key.pem


If you connect via SSH to one of the server, you should see the Telegraf agent is running as Docker container:



In few seconds the InfluxDB database will be populated with some metrics:



Sign in to the Amazon Developer Portal, create a new Alexa Skill:



Create an invocation name – aws – This is the word that will trigger the Skill.

In the Intent Schema box, paste the following JSON code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"intents" : [
{
"intent" : "GetSystemUsage",
"slots" : [
{
"name" : "Metric",
"type" : "TYPE_OF_METRICS"
},
{
"name" : "City",
"type" : "LIST_OF_CITIES"
}
]
}
]
}

Create a new slot types to store the type of metric and machine hostname:

1
2
3
4
5
6
7
8
9
10
11
TYPE_OF_METRICS:

CPU
DISK
MEMORY

LIST_OF_CITIES:

Paris
Rabat
London

Under Uterrances, enter all the phrases that you think you might say to interact with the skill

1
GetSystemUsage {Metric} usage of machine in {City}


Click on “Next” and you will move onto a page that allows us to use an ARN (Amazon Resource Name) to link to AWS Lambda.

Before that, let’s create our lambda function, go to AWS Management Console and create a new lambda function from scratch:



Note: Select US East (N.Virginia), which is a supported region for Alexa Skill Kit.

Make sure the trigger is set to Alexa Skills Kit, then select Next.

The code provided uses the InfluxDB client to fetch metrics from database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function MetricsDB(){
this.influx = new Influx.InfluxDB({
host: process.env.INFLUXDB_HOST,
database: process.env.INFLUXDB_DATABASE
})
}

MetricsDB.prototype.getCPU = function(machine, callback){
this.influx.query(`
SELECT last(usage_system) AS system, last(usage_user) AS "user"
FROM cpu
WHERE time > now() - 5m AND host='${machine}'
`).then(result => {
var system_usage = result[0].system.toFixed(2)
var user_usage = result[0].user.toFixed(2)
callback(`System usage is ${system_usage} percent & user usage is ${user_usage} percent`)
}).catch(err => {
callback(`Cannot get cpu usage values`)
})
}

Specify the .zip file name as your deployment package at the time you create the Lambda function. Don’t forget to set the InfluxDB Hostname & Database name as an environment variables:



Then go to the Configuration step of your Alexa Skill in the Amazon Developer Console and enter the Lambda Function ARN:



Click on “Next“. Under the “Service Simulator” section, you’ll be able to enter a sample utterance to trigger your skill:

Memory Usage



Disk usage:



CPU usage:



Test your skill on your Amazon Echo, Echo Dot, or any Alexa device by saying, “Alexa, ask AWS for disk usage of machine in Paris

Drop your comments, feedback, or suggestions below — or connect with me directly on Twitter @mlabouardy.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×