The purpose of this time is to explore OpenFaaS and to relate it to the team's existing skills around the Ruby eco-system. We will be using OpenFaaS with Kubernetes (k3s).
This workshop is available to the public under the MIT license. Anyone can follow along.
Agenda:
- brief introduction to OpenFaaS and concept of Serverless
- provision Kubernetes using a Civo VM
- try some of the labs
- work through the Civo extensions for writing code in Ruby
- consume secrets such as API tokens
- work with HTTP headers
- work with queued / asynchronous invocations
Throughout the exercises we'll tie back what we are learning to the team's existing skills and platform.
You can get 50 USD free credit via http://bit.ly/2Lx9d2o
- Setup a new Medium VM (20 USD / mo)
This comes with 2CPU 4GB RAM and 50GB disk. The Small (10 USD / mo) with 2GB RAM is also workable if you want to stretch your credit out.
-
For the Image, pick Ubuntu 18.04 LTS
-
Initial user: civo
-
Public IP Address: Create
-
Add your SSH Key
If you don't have one yet, run ssh-keygen
and accept all defaults.
- Once created, note down the Public IP:
export IP="185.136.234.31" # for example
ssh civo@$IP
- Install k3s, which provides Kubernetes
Replace 185.136.234.31
with the value in $IP
:
curl -sLS get.k3s.io | INSTALL_K3S_EXEC='server --tls-san 185.136.234.31' sh -
- Double check you used the correct IP, you should see it below:
sudo systemctl cat k3s
- Create a Civo firewall
By default k3s
is insecure because it exposes the flannel
networking ports.
- Create a firewall
Firewalls in Civo are whitelist-based, so allow:
22
(SSH)80
(HTTP)443
(HTTPS)6443
(Kubernetes API)31112
(OpenFaaS Gateway)
On the VM we need to make the k3s file readable:
mkdir ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown civo:civo .kube/config
Now on your laptop copy it down:
export IP="185.136.234.31" # for example
mkdir -p ~/dev/workshop/ && cd ~/dev/workshop
scp civo@$IP:~/.kube/config .
Update the localhost
reference to the remote address:
sed -ie s/localhost/$IP/g config
Now see if kubectl
is working:
export KUBECONFIG=`pwd`/config
kubectl get all --all-namespaces
If you open a new terminal later on, type in:
cd ~/dev/workshop
export KUBECONFIG=config
There are some pre-requisites to install, which you may already have.
When installing OpenFaaS from the helm chart pick "A) For local clusters", this is because we do not have a LoadBalancer
available when running k3s on a Civo VM.
Your gateway address will be: http://$IP:31112/
and the password is saved in gateway-password.txt
You can set it in OPENFAAS_URL
, i.e.:
export OPENFAAS_URL=http://$IP:31112
This will save on typing -g
or --gateway
when using the faas-cli
.
It's suggested that you run through at least the following before starting the extensions:
Deploy the curl
function from the store.
Go to the Civo API docs
Copy and paste the example for how to List Instances:
-H "Authorization: bearer (redacted)" https://api.civo.com/v2/instances
Change -H
to -s -H
Now copy and paste the whole command to the UI and execute it.
Did it work? What do you think?
There is a built-in ruby template in the templates repo.
-
Find its Dockerfile. How would you change the Ruby version?
-
Create a function and deploy it:
export HUB_USERNAME="" # Your docker hub name
faas-cli template pull
faas-cli new --lang ruby rss-tester --prefix $HUB_USERNAME
faas-cli up -f rss-tester.yml
Add a public, pure-Ruby gem
to your function and consume it in the handler.rb
file.
Let's use simple-rss found from awesome-ruby.
require 'rubygems'
require 'simple-rss'
require 'open-uri'
class Handler
def run(req)
uri = ENV["URI"]
rss = SimpleRSS.parse open(uri)
return rss.channel.title
end
end
handler.rb
Set an environment variable:
environment:
URI: http://rss.slashdot.org/Slashdot/slashdot/to
rss-tester.yml
Now configure the gem needed for the build:
source 'https://rubygems.org'
gem 'simple-rss'
Gemfile
Run the following to redeploy the function
faas-cli up -f rss-tester.yml
Get its URL and invoke it:
faas-cli describe -f rss-tester.yml rss-tester
Did it work? Can you try a different RSS feed?
- Task: take a sample from the Civo API developer guide and call one of the APIs from your
handler.rb
file
Note: make sure you do not hard-code any secret keys or API keys in the Ruby code, it will be uploaded to the Docker Hub
In 2.0 we'll see how to use a secure Kubernetes secret, for this exercise use an environment variable in your YAML file.
You can read environment variables in Ruby with ENV["name-of-variable"]
For example, how about listing all VM templates?
Create a new function:
export HUB_USERNAME="" # Your docker hub name
faas-cli template pull
faas-cli new --lang ruby civo-api-tester --prefix $HUB_USERNAME
Add an environment variable:
environment:
api-token: x-z-y
Deploy it, then iterate on the code:
faas-cli up -f civo-api-tester.yml
Remember to run
faas-cli up -f civo-api-tester.yml
after each change to the Ruby code orGemfile
.
Links: https://www.civo.com/api
There is another Ruby template that provides greater control over HTTP responses.
View the Dockerfile and how it's different from the original Ruby template:
https://github.com/openfaas-incubator/ruby-http
- What are the key differences?
Please read: Templates in OpenFaaS - classic vs. of-watchdog
- Create a new function using the
ruby-http
template:
export HUB_USERNAME="" # Your docker hub name
faas-cli template store list
faas-cli template store pull ruby-http
faas-cli new --lang ruby-http user-test --prefix $HUB_USERNAME
faas-cli up -f user-test.yml
Write code in handler.rb
to read the X-User-Id
header. Return a header "X-User-Status" with value of OK
or Not Found
.
curl -i http://$IP:31112/function/user-test -H "X-User-Id: mark`
X-User-Status: OK
curl -i http://$IP:31112/function/user-test -H "X-User-Id: john`
X-User-Status: Not Found
Try the following example:
https://github.com/alexellis/openfaas-nokogiri
Once you have it working, think of another public gem
which needs compilation and try that out with the same approach.
The Civo API publishes events in the form of webhooks every time something happens in your account.
- Create a new Ruby HTTP function:
export HUB_USERNAME="" # Your docker hub name
faas-cli template store list
faas-cli template store pull ruby-http
faas-cli new --lang ruby-http civo-hooks --prefix $HUB_USERNAME
faas-cli up -f civo-hooks.yml
- Now go to your Settings page and click Webhooks
Get the public endpoint for your new function:
faas-cli describe -f civo-hooks.yml civo-hooks
Let's use the async
route so that our webhook can return immediately, and our function will be invoked when capacity is available in the cluster.
-
Now click Create Webhook
-
Click All Events
-
Enter URL of the value received in
faas-cli describe
-
Click Create Webhook
Note down the Secret that will be used to generate a hash for each message you receive.
It's time to trigger the webhook
- Start tailing the logs of the queue-worker:
kubectl logs -n openfaas deploy/queue-worker -f
-
Create a extra-small instance
-
Check to see if your function is invoked
The queue-worker will show the body that was used to invoke the function including any headers.
Can you see the following?
-
X-Civo-Signature
-
X-Civo-Event
-
User-Agent
-
Check the logs of the function:
kubectl logs -n openfaas-fn deploy/civo-hooks
-
How do we know if the person calling our endpoint was really the Civo API and not a bad actor?
-
Create a secret for the webhoook secret
echo -n "webhook-secret-from-settings-page" | faas-cli secret create civo-webhook
- Bind the secret to the function and deploy it again
Under this section:
version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
civo-hooks:
Add:
secrets:
- civo-webhook
faas-cli up -f civo-hooks.yml
- Now that your function has the secret, you can use HMAC to validate the incoming body
This is a stretch goal and will require some coding.
Andy has written up a page on how to validate webhooks in Ruby, you may need some additional gems.
Try it below:
https://www.civo.com/learn/using-webhooks-to-receive-important-change-callbacks
Use this to get started:
require 'openssl'
class Handler
def run(body, headers)
response_headers = {"content-type": "text/plain"}
secret_value = "hard-coded"
signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha1'),
secret_value,
body
)
return signature, response_headers
end
end
handler.rb
source 'https://rubygems.org'
gem 'openssl'
Gemfile
- Can you now read the secret file we created called
civo-webhook
?
Find out how in the docs: secrets
- Finally use the secret to validate the payload and return the appropriate HTTP code.
For further information see: the OpenFaaS blog and Slack community