You can use your OpenFaaS functions to store and retrieve data to and from a persistent layer that sits outside the OpenFaaS framework. The database that we will use in this tutorial is Amazon’s DynamoDB.
If you are not familiar with the service, Amazon’s DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability.
At the end of this tutorial you will be able to invoke your functions to read and write items to DynamoDB with a dedicated IAM User that is only allowed to access DynamoDB, and secrets managed by your OpenFaaS framework.
What we will be doing in this Tutorial
In this tutorial we will cover a couple of things, and a summary on the to do list is:
Create a OpenFaaS IAM User, DynamoDB IAM Policy, associate the Policy to the User using the AWS CLI
Create a AWS Access Key, and save the Access Key and Secret key to file
Create OpenFaaS Secrets of the Access Key and Secret Key, remove the files from disk
Create 3 OpenFaaS Functions: write, lookup and get
Invoke the functions, read and write from DynamoDB
Our 3 functions will do very basic operations for this demonstration, but I believe this is a good starting point.
In this scenario we want to store user information into DynamoDB, we will use a hash that we will calculate using the users ID Number + Lastname. So when we have thousands or millions of items, we dont need to search through the entire table, but since we can re-calculate the sha hash, we can do a single GetItem operation to find the entry about the user in question.
Lookup Function:
The lookup function will calculate the hash by passing the users ID Number and Lastname, this will return a hash which will be teh primary key attribute of our table design. This hash value is required to do a GetItem on the user in question.
Get Function:
The Get function will interface with DynamoDB, it reads the AWS access key and secret key from the secrets path to authenticate with AWS and utilizes environment variables for the region and table name. This will do a GetItem on the DynamoDB Table and retrieve the Item. If the item is not found, it will return it in the response.
Write Function:
The write function will also interface with DynamoDB, the ID, Name and Payload will be included in the request body on our POST Request.
Note on Secrets and Environment Variables
I am treating my environment variables and secrets different from each other. The secrets such as my AWS access keys are stored on the cluster and the application reads them and stores the values in memory.
The environment variables such as non-secret information, such as my dynamodb table name and aws region, is defined in my environment variables.
This post and this post goes a bit more into detail on why you should not use environment variables for secret data, which I found from this link
I have a admin IAM account configured on my default profile, using the aws-cli tools generate the cli-skeleton that is required to provision a dynamodb table:
My table name will be lookup-table with the primary key hash_value and provisoned my throughput to 1 Read and Write Capacity Unit. Which will enable us 4KB/s for reads and 1KB/s for writes.
For demonstration purposes, I am sharing my altered ddb.json file:
Create the IAM Policy document which defines the access that we want to grant. You can see that we are only allowing Put and GetItem on the provisioned DynamoDB resource:
Create the Access Key, which will be our API keys for our application to authenticate requests. Save the AccessKeyId and SecretAccessKey temporarily to 2 seperate files, which we will delete after we create our secrets to our cluster:
We will create our first function to generate the yaml definition, then we will rename our generated filename to stack.yml then the next 2 functions, we will use the append flag to append the functions yaml to our stack.yml file, so that we can simply use faas-cli up
Create the Lookup Function:
Create a Python3 Function, and prefix it with your dockerhub user:
1234567
$ faas-cli new \--lang python3 fn-dynamodb-lookup \--prefix=ruanbekker \--gateway https://openfaas.domain.com
Function created in folder: fn-foo
Stack file written: fn-dynamodb-lookup.yml
As we will be using one stack file, rename the generated stack file:
1
$ mv fn-dynamodb-lookup.yml stack.yml
Open the stack file and set the environment variables:
Create a Python3 Function, and prefix it with your dockerhub user, and use the append flag to update our stack file:
12345678
$ faas-cli new \--lang python3 fn-dynamodb-write \--prefix=ruanbekker \--gateway https://openfaas.domain.com
--append stack.yml
Function created in folder: fn-dynamodb-write
Stack file updated: stack.yml
Open the stack file and set the environment variables and include the secrets that was created:
Create a Python3 Function, and prefix it with your dockerhub user, and use the append flag to specify the stack file:
12345678
$ faas-cli new \--lang python3 fn-dynamodb-get \--prefix=ruanbekker \--gateway https://openfaas.domain.com
--append stack.yml
Function created in folder: fn-dynamodb-get
Stack file updated: stack.yml
Open the stack file and set the environment variables and include the secrets that was created:
importboto3importosimportjsonaws_key=open('/var/openfaas/secrets/openfaas-aws-access-key','r').read()aws_secret=open('/var/openfaas/secrets/openfaas-aws-secret-key','r').read()dynamodb_region=os.environ['dynamodb_region']dynamodb_table=os.environ['dynamodb_table']client=boto3.Session(region_name=dynamodb_region).resource('dynamodb',aws_access_key_id=aws_key,aws_secret_access_key=aws_secret)table=client.Table(dynamodb_table)defhandle(req):event=json.loads(req)response=table.get_item(Key={'hash_value':event['hash_value']})if'Item'notinresponse:item_data='Item not found'else:item_data=response['Item']returnitem_data
Build, Push and Deploy:
It’s time to deploy our functions and since we have all our stack info in one file, we can use faas-cli up which will build, push and deploy our functions.
By default it expects the filename to be stack.yml therefore we don’t need to specify the filename, but if you had a different filename, you can overwrite the default behaviour with -f:
Note that the lookup function calculates a hash based on the input that you provide it, for example calculating a hash with userdata that does not exist in our table:
Using that hash value in our request body to read from dynamodb, will show us that the item has not been found:
12
$ curl -XPOST https://openfaas.domain.com/function/fn-dynamodb-get -d '{"hash_value": "c68dc272873140f4ae93bb3a3317772a6bdd9aa1"}'Item not found
You might want to change this behavior but this is just for the demonstration of this post.
When you head over to DynamoDB’s console you will see this in your table:
Thanks
This was a basic example using OpenFaaS with Amazon DynamoDB with Python and secrets managed with OpenFaas. I really like the way OpenFaaS let’s you work with secrets, it works great and don’t need an additional resource to manage your sensitive data.
Although this was basic usage with OpenFaaS and DynamoDB, the sky is the limit what you can do with it.