This tutorial guides you to a working knowledge of how to build functions using Node.js on the open source Function as a Service (FaaS) platform OpenWhisk.
This tutorial will use Minishift to run an instance of OpenShift on your local PC in a Virtual Machine (VM). OpenWhisk is then installed and run inside of OpenShift.
Note: FaaS is also commonly referred to as “serverless computing” or simply “serverless”.
You will need the following to execute this tutorial on your local PC.
You haven’t been forgotten. Throughout this tutorial, when a command differs between Linux and Windows, the PowerShell commands are noted.
Minishift allows you to run OpenShift and the entire OpenWhisk environment inside a VM on your PC.
docker allows the Linux containers used by OpenWhisk to run.
kubectl allows you to run Kubernetes commands on your PC
oc allows you to run OpenShift commands on your PC
wsk allows you to run OpenWhisk commands on your PC
git allows you to work with Github repositories (repos) on your PC
npm allows you to manage Node.js packages on your PC
Only required for Windows/PowerShell
Supports recursive zip file creation.
(Can be installed using choco install 7zip
)
Only required for Windows/PowerShell
Allows you to run the curl command
Don’t foget to add the location of Minishift executable to your PATH
Run the following commands, in sequence, to start the Minishift instance needed to support OpenWhisk:
minishift profile set faas-tutorial-nodejs
minishift config set memory 8GB
minishift config set cpus 3
minishift config set image-caching true
minishift addon enable admin-user
minishift addon enable anyuid
minishift start
minishift ssh -- sudo ip link set docker0 promisc on
You may need to use the following command if you are using VirtualBox:
minishift start --vm-driver virtualbox
minishift ssh -- sudo ip link set docker0 promisc on
command needs to be executed each time Minishift is restarted.
The following commands will ensure that your OpenShift and docker commands will use the OpenShift host:
eval $(minishift oc-env)
eval $(minishift docker-env)
For PowerShell users, the commands are:
& minishift oc-env | Invoke-Expression
& minishift docker-env | Invoke-Expression
The project OpenWhisk on OpenShift provides the OpenShift templates required to deploy Apache OpenWhisk.
Log in as the admin
oc login $(minishift ip):8443 -u admin -p admin
For PowerShell users, use the following two commands:
$url = & minishift ip
oc login $url:8443 -u admin -p admin
It is always better to group related classes of applications. Create a new OpenShift project called “faas” to deploy all OpenWhisk applications.
oc new-project faas
Deploy OpenWhisk applications to “faas” project in OpenShift.
oc process -f https://git.io/openwhisk-template | oc create -f -
It will take a few minutes until all of the required OpenWhisk pods are running and the FaaS is ready for some load. If your Mac has the utility ‘watch’ installed, you can watch the status using the command:
watch -n 5 'oc logs -f controller-0 -n faas | grep "invoker status changed"'
Alternatively, you can run the following command until all the pods have a status of ‘Running’ or ‘Completed’:
oc get pods
You can start the OpenShift console in your default browser by using the following command:
minishift console
When the console login appears in your browser window, log in using admin/admin as the username and password combination. Select the project “faas” and you will then see a screen like the following:
Verify that the OpenWhisk CLI tool in your path by running the following command:
wsk --help
The OpenWhisk CLI needs to be configured to know where the OpenWhisk instance is located and the authorization that is to be used to invoke wsk
commands. Run the following two commands to have that setup:
AUTH_SECRET=$(oc get secret whisk.auth -o yaml | grep "system:" | awk '{print $2}' | base64 --decode)
wsk property set --auth $AUTH_SECRET --apihost $(oc get route/openwhisk --template="{{.spec.host}}")
PowerShell users should use the following commands:
$AUTH_SECRET=(oc get secret whisk.auth -o yaml).Split(":",1)[3].Split()[3]
$AUTH_SECRET=[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($AUTH_SECRET))
$APIHOST=oc get route/openwhisk --template="{{.spec.host}}"
wsk property set --auth $AUTH_SECRET --apihost $APIHOST
Successful setup of wsk will show output like this:
In this case the OpenWhisk API Host is pointing to the local Minishift nip.io address openwhisk-faas.192.168.99.100.nip.io
. To verify if wsk CLI is configured properly run wsk -i action list
. This will list some actions which are installed as part of the OpenWhisk setup.
The nginx
in OpenWhisk deployment uses a self-signed certificate. To avoid certificate errors when using wsk
, you need to add wsk -i
to each of your wsk
commands. For convenience, you can add an alias to your profile with alias wsk='wsk -i'
.
Clone the tutorial repo using the following command:
git clone https://github.com/redhat-developer-demos/faas-tutorial-nodejs.git
For the rest of the tutorial, the directory into which this repo is placed will be referred to as $PROJECT_HOME.
Actions are stateless code snippets that run on the OpenWhisk platform. OpenWhisk actions are thread-safe meaning at a given point of time only one invocation happens.
For more details refer the official documentation.
Move into the ./solutions/hello-openwhisk directory and view the file hello-openwhisk.js:
cd solutions/hello-openwhisk
cat hello-openwhisk.js
PowerShell:
cd solutions/hello-openwhisk
type .\hello-openwhisk.js
You will see the following Node.js code:
function main() {
return {payload: 'Welcome to OpenWhisk on OpenShift'};
}
This example illustrates that an OpenWhisk function will return a JSON document. In this case, it’s hard-coded.
Use the following command to create an OpenWhisk action by using the following command:
wsk --insecure action create hello-openwhisk hello-openwhisk.js
Some things to note:
Given the success of the command, we now have an action named ‘hello-openwhisk’. We can see this listed by using the following command:
wsk -i action list
The command will yield a result similar to the following (you should see ‘hello-openwhisk’ at or near the top of your list):
Now we can invoke the action.
Use the following command to invoke the OpenWhisk “hello-openwhisk” action:
wsk -i action invoke hello-openwhisk --result
You will see the following output:
The results displayed are as expected. If you repeat the command multiple times, you will notice an improvement in performance over the first invocation. That’s because OpenWhisk has started the action and is now keeping the Kubernetes pod available. This availability defaults to five minutes; if there is no activity for an action within five minutes, it “goes to sleep” until invoked again.
Note the ‘–result’ option of the command. This runs the action synchronously; that is, we will wait for the result before returning control to the caller – in this case, the ‘wsk’ command line tool.
An action can be called asynchronously, in which case control is immediately returned to the caller with an Activation ID. This Activation ID can then be used to get the results.
Use the following command to invoke the OpenWhisk “hello-openwhisk” action asynchronously, getting an Activation ID in return:
wsk -i action invoke hello-openwhisk
Your result will be much like the following but with a different Activation ID:
You can use that Activation ID to get the results.
Use the following command to retrieve the Activation associated with the action invocation, substituting your Activation ID:
wsk -i activation get {activation_id}
Your result will be similar to the following. Note that, in this case, instead of simply the ‘result’ value, the entire JSON object is available:
Hint: You can access the latest Activation by running wsk -i activation get --last
Updating an existing action is as simple as a code change and one command.
Change the contents of ‘hello-openwhisk,js’ to return a different message, such as the following:
function main() {
return {payload: 'Welcome to OpenWhisk on OpenShift, version 2.0!'
};
With the code changed, updating the action is one command.
Use the following command to update the action ‘hello-openwhisk’:
wsk -i action update hello-openwhisk hello-openwhisk.js
Use the following command to invoke the action (Note: ‘–result’ can be shortened to ‘-r’)
wsk -i action invoke hello-openwhisk -r
Use the following command to delete the action:
wsk -i action delete hello-openwhisk
Up to this point, you’ve learned how to create, update, and delete a simple action that returns a hard-coded string. In the next section, you’ll see how to handle input parameters to your action.
OpenWhisk actions receive JSON objects as input. In this example, the action ‘splitter’ will receive a JSON object with a comma-delimted string of words and will return a JSON object with an array of the words.
For example: An input of
{text: "cat,blurg,apple,toothbrush"}
will return the following:
{
"result": [
"cat",
"blurg",
"apple",
"toothbrush"
]
}
Use the following command to move into the directory containing the ‘splitter’ code:
cd ../sequence-demo/splitter
Optional: Examine the code for the function:
cat splitter.js
PowerShell:
type splitter.js
Notice that the input JSON is expected to contain the property ‘.text’, which will contain the string of comma-separated words.
Create the OpenWhisk action ‘splitter’ using the code in file ‘splitter.js’.
wsk -i action create splitter splitter.js
You can see the action’s parameters by using the following command:
wsk -i action get splitter parameters
Use the following command test invoke the action ‘splitter’ using the example, above:
wsk -i action invoke splitter -r --param text "cat,blurg,apple,toothbrush"
You should see the following results:
You can bind parameter values to an action, which will set default values for the action.
Use the following command to bind the default parameter value to the action ‘splitter’:
wsk -i action update splitter --param text "cat,blurg,apple,toothbrush"
If you now run the action ‘splitter’ with no parameters, it will use these default values.
Run the action “splitter” with a different string and get back all of the JSON associated with the action. Hint: You want to get an Activation.
For this section, we have three actions that will be chained together. The first action, “splitter” was created in the previous section. The other two actions, “sorter” and “uppercase” will be created. Then, we’ll ‘stitch’ them together to make a sequence. The sequence will look like the following:
input –> splitter –> uppercase –> sorter
The sequence will be called ‘splitUpperSort’ and will give the following results:
Given
text: "cat,blurg,apple,toothbrush"
We will return
{
"result": [
"APPLE",
"BLURG",
"CAT",
"TOOTHBRUSH"
]
}
Move into the ‘uppercase’ directory:
cd ../uppercase
Optional: View the source code
cat uppercase.js
PowerShell:
type uppercase.js
** This is an exercise left to the reader **
Invoke the action with the test data:
wsk -i action invoke -r uppercase --param result "[\"cat\",\"blurg\",\"apple\",\"toothbrush\"]"
The results should be:
Move into the ‘sorter’ directory:
cd ../sorter
Optional: View the source code:
cat sorter.js
PowerShell:
type sorter.js
** This is an exercise left to the reader **
Invoke the action with the test data:
wsk -i action invoke -r sorter --param result "[\"cat\",\"blurg\",\"apple\",\"toothbrush\"]"
The results should be:
OpenWhisk allows you to create a sequence of actions, where the output from one action becomes the input for the next. Now that we have the actions “splitter”, “uppercase” and “sorter”, we can create a sequence.
Use the following command to create the sequence called “splitUpperSort”:
wsk -i action create splitUpperSort --sequence splitter,uppercase,sorter
Invoke the sequence with the test data:
wsk -i action invoke -r splitUpperSort --param text "cat,blurg,apple,toothbrush"
The sequence is returning the expected results.
What happens if you invoke the sequence with no parameters? Why?
This sequence has an annoying bug: leading and trailing spaces are not being removed. Observe the following:
As an optional exercise, fix this defect. Hint: You need only change one of the actions in the sequence; the sequence will then exhibit the proper behavior. You do not need to update the sequence.
Up to this point, we’ve been using the command line ‘wsk’ tool to invoke actions. While this is useful for developing and testing actions, eventually you’ll most likely wish to invoke your actions as a RESTful service. In this section you’ll learn how to create a web action and you’ll use a simple web application, written in React, to invoke the action and display the results in a browser.
One of the benefits of using OpenWhisk to create RESTful web actions is that you do not need to include any special libraries or dependencies in your code to support HTTP; OpenWhisk will handle that for you.
The simple action ‘hello-web’ will receive a string and simply echo it back to the caller. For example, the following:
wsk -i action invoke hello-web -r --param text "Hello web"
Gives this response:
{
"response": {
"text": "Hello web"
}
}
Move into the hello-web directory:
cd ../hello-web
To create a web action you use the ‘–web=true’ command line option, as follows:
wsk -i action create hello-web hello-web.js --web=true
Note that, even though this is now a web action, you can still test it using the command line:
wsk -i action invoke hello-web -r --param text "Hello web"
You will see the following results:
Use the ‘–url’ option to get the URL for the web action:
WEB_URL=\`wsk -i action get hello-web --url | awk 'FNR==2{print $1".json"}'\`
PowerShell:
$WEB_URL=wsk -i action get hello-web --url
$WEB_URL="$WEB_URL.json"
View the URL:
echo $WEB_URL
Copy the URL to your PC’s clipboard, and then paste it into your default browser. You can then use the security-related warning to bypass the security check (which is being caused by a self-signed certificate in OpenShift). Without this step, the web app (below) will not be able to access this web action.
Move into the faas-web directory:
cd ../faas-web
Make sure all of the dependencies are installed:
npm install --production
Start the application, using the WEB_URL environment variable to locate the web action:
REACT_APP_WEB_URL=$WEB_URL npm start
Your default browser should open to the web app. If it does not, use the value of the WEB_URL variable to browse to the app. Execute the app and see the results.
Remember the sequence demo, earlier, ‘splitUpperSort’? Make that into a web action and modify the web app to access that web action.
There will be times when you need to include dependency libraries in your action. To this point, we’ve been using a single *.js file as the source for an action. But what if we have libraries (i.e. a node_modules directory) that needs to be included with the code?
In this example, we will create an action that reads from a MySQL database, using the ‘mysql’ library.
Move into the hellodb directory:
cd ../hellodb
Optional: View the source code:
cat index.js
PowerShell:
type index.js
This function relies on a MySQL database named ‘mydb’ on host ‘mysql’. The login credentials will be:
user: myuser
password: myuser
We can take advantage of OpenShift to create a MySQL database using one line:
oc new-app -e MYSQL_USER=myuser -e MYSQL_PASSWORD=myuser -e MYSQL_DATABASE=mydb openshift/mysql-56-centos7 --name=mysql
oc get pods -w
What just happened?
We (automatically) pulled a MySQL image from docker hub, then used the OpenShift command line tool (oc) to create a new application in our OpenShift (running in Minishift) instance. Within a few seconds, we have a MySQL database up and running. For the purposes of this simple demo, all of the credentials have been hard-coded – not a best practice in a production environment.
If you switch to your Minishift console in your browser, you can see the MySQL application:
Running a MySQL database in an emphemeral pod is not something you should do in production unless you are using the High Availability (HA) installation of MySQL. For demos and development, however, this is fine.
MySQL is running in a pod in OpenShift. You can see the pod name by using the following command:
oc get pods
Note that your pod name will be different. We can isolate the name of the pod, which we’ll need to do for our following commands, using the following command:
oc get pods --selector app=mysql --output name | awk -F/ '{print $NF}'
PowerShell:
$mpod=oc get pods --selector app=mysql --output name
$mpod=$mpod.Split("/")[1]
The following command will run a MySQL command inside the pod; it will create the database ‘mydb’:
oc exec $(oc get pods --selector app=mysql --output name | awk -F/ '{print $NF}') -- bash -c "mysql --user=root mydb -e 'use mydb; create table personal_greeting (first_name varchar(20) not null primary key, custom_greeting varchar(20));'"
PowerShell:
oc exec $mpod -- bash -c "mysql --user=root mydb -e 'use mydb; create table personal_greeting (first_name varchar(20) not null primary key, custom_greeting varchar(20));'"
This command will populate the database table:
oc exec $(oc get pods --selector app=mysql --output name | awk -F/ '{print $NF}') -- bash -c "mysql --user=root mydb -e 'use mydb; insert into personal_greeting (first_name,custom_greeting) values(\"Don\", \"Howdy\")';"
PowerShell:
oc exec $mpod -- bash -c "mysql --user=root mydb -e 'use mydb; insert into personal_greeting (first_name,custom_greeting) values(\""Don\"", \""Howdy\"")';"
Use this command to prove that everything is up and running as inspected:
oc exec $(oc get pods --selector app=mysql --output name | awk -F/ '{print $NF}') -- bash -c "mysql --user=root mydb -e 'use mydb; select * from personal_greeting';"
PowerShell:
oc exec $mpod -- bash -c "mysql --user=root mydb -e 'use mydb; select * from personal_greeting';"
At this point we have a database running in OpenShift with the table, ‘personal_greeting’, populated with one entry.
OpenWhisk allows *.zip files to be used a functions. This allows us to use Node.js with external dependencies; in this case, the MySQL client module. Our solution also uses the modules ‘request-promise’ and ‘string-format’. This is apparent by viewing the package.json file content:
{
"name": "getgreeting",
"version": "1.0.0",
"description": "Get custom greeting from database",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"mysql": "^2.16.0",
"request-promise": "^4.2.2",
"string-format": "^2.0.0"
}
}
npm install --production
zip -rq hellodb.zip * -x *.sh *.me
wsk -i action create hellodb --kind nodejs:8 hellodb.zip
PowerShell:
7-Zip must be installed
npm install --production
7z a hellodb.zip -r *.*
wsk -i action create hellodb --kind nodejs:8 hellodb.zip
Note that we must tell the ‘action create’ command what kind of function we are building; in this case, a nodejs:8 function.
wsk -i action invoke --result hellodb --param name Sally
{
"message": "Hello, Sally"
}
wsk -i action invoke --result hellodb --param name Don
{
"message": "Howdy, Don"
}
Create an OpenWhisk action that writes records to the database.
Triggers and Rules are combined to respond to Events. A trigger is a named channel that is associated with events via named rules.
When an event causes a trigger to fire, any rules associated with the trigger will create an action and return an activation ID. By itself, without any associated rules a trigger will not cause an activation. That is, a standalone trigger does nothing.
A trigger can fire one or more rules. The activation created by the trigger will return a result containing the input parameters to the trigger. The logs associated with the activation of a trigger will contain a JSON array of the rules invoked. Each invoked rule, in turn, has an associated activation ID.
The events that fire triggers can be either user-generated or from an external event source. An example of an external event source is a commit to a Git repository.
wsk -i trigger create myTrigger
wsk -i rule create myRule myTrigger hello-openwhisk
wsk -i trigger fire myTrigger
wsk -i activation get -l
A trigger can be fired via the HTTP Post command.
Run the command
wsk -i property get
and copy the “whisk auth” value to your computer clipboard. Also note the values of “whisk API host” and “whisk namespace” – you will need them to build the URL needed to fire a trigger via an HTTP POST message.
The URL follows this pattern:
https://{whisk auth value}@{whisk API host}/api/v1/namespaces/{whisk namespace}/triggers/{trigger name}
For example:
Https://789c46b1-71f6-4ed5-8c54-816aa4f8c502:XsDY2SjQCtXC7hgkBpMht28faPPaDK3FxN5KhUfCHCfUCrP66AFEIBAtk8t52BYk@openwhisk-faas.192.168.99.100.nip.io/api/v1/namespaces/_/triggers/myTrigger
You can fire a the trigger via HTTP; it will return an activation ID. The following curl
command is an example:
curl -k -X POST https://789c46b1-71f6-4ed5-8c54-816aa4f8c502:XsDY2SjQCtXC7hgkBpMht28faPPaDK3FxN5KhUfCHCfUCrP66AFEIBAtk8t52BYk@openwhisk-faas.192.168.99.100.nip.io/api/v1/namespaces/_/triggers/myTrigger
PowerShell:
It may be necessary to remove the PowerShell curl alias in order for the curl command to work:
Remove-Item alias:curl
curl -k -X POST https://789c46b1-71f6-4ed5-8c54-816aa4f8c502:XsDY2SjQCtXC7hgkBpMht28faPPaDK3FxN5KhUfCHCfUCrP66AFEIBAtk8t52BYk@openwhisk-faas.192.168.99.100.nip.io/api/v1/namespaces/_/triggers/myTrigger
Packages allow you to group actions together inside of a namespace. Much like a software project may contain many files, a package may contain multiple actions.
wsk -i package create myPackage
wsk -i package list
[As noted earlier in this tutorial, $PROJECT_HOME is the directory into which you cloned this repo.]
cd $PROJECT_HOME/solutions/hello-openwhisk
wsk -i action create myPackage/hello-openwhisk hello-openwhisk.js
wsk -i package get myPackage
If you have completed the above tutorial, including the optional exercise, you are well on your way to successfully using OpenWhisk in a production setting.