Installing and securing OpenFaaS on an AKS cluster

A few months back, I wrote a guide for installing and locking down OpenFaaS in a Docker Swarm running on Google Cloud Platform virtual machines. Today I want to share a step-by-step guide that shows how to install OpenFaaS on a new Azure Kubernetes Service (AKS) cluster using an Nginx ingress controller to lock it down with basic authentication and free Let's Encrypt TLS certificates.

Before we begin

If you just want to do a quick deployment of OpenFaaS on AKS, there's a guide in the official AKS documentation here, however it does not show how to implement TLS encryption or authentication.

All the Azure configuration I'll show today is done via the command line, so if you don't already have the Azure CLI installed on your local system, install it from here. You can do it through the Azure portal, but it's much faster to do with the CLI.

You will also need Git command-line tools installed so you can pull down the latest version of OpenFaaS from its repository.

Finally, in order to secure your OpenFaaS installation with TLS, you will need a domain and access to your DNS provider so you can point a hostname to your cluster's public IP address

Ready? Let's get started.

Basic Azure configuration

  1. From the command line, log in to Azure using az login. Follow the prompts to complete your authentication.
  2. If you don't have an existing resource group you want to use for your AKS cluster, create a new one with az group create -l REGIONNAME -n RESOURCEGROUP. Replace REGIONNAME and RESOURCEGROUP with appropriate values, but make sure you use a region where AKS is currently available.
  3. Create a new AKS cluster with az aks create -g RESOURCEGROUP -n CLUSTERNAME --generate-ssh-keys. The RESOURCEGROUP value is the same as before, and CLUSTERNAME is whatever you want it to be called. Note that the default virtual machine size for your cluster is Standard_DS1_v2. You can change this by setting the --node-vm-size, and I am personally using burstable Standard_B2s VMs for my AKS cluster.
  4. Once the AKS cluster creation completes, use this command to get the credentials you need to manage the cluster with the Kubernetes CLI az aks get-credentials --resource-group RESOURCEGROUP --name CLUSTERNAME.
  5. Install the Kubernetes CLI (kubectl) with az aks install-cli.
  6. Get the name of the node resource group that was created for your AKS cluster with this command az resource show --resource-group RESOURCEGROUP --name CLUSTERNAME --resource-type Microsoft.ContainerService/managedClusters --query properties.nodeResourceGroup -o tsv. You should get an output that looks like MC_resourcegroup_clustername_regionname. You will use this return value in the next step.
  7. Create a public IP address in the node resource groupe with this command az network public-ip create --resource-group MC_RESOURCEGROUP --name IPADDRESSNAME --allocation-method static. You will get a JSON response that contains a "publicIp" object. Copy its "ipAddress" value and save it for later.

    Note: you might be tempted to create a DNS name label for this IP address so you can avoid using a custom domain name, but *.cloudapp.azure.com host names don't work with Let's Encrypt.
  8. Go to your DNS provider and register a new A record for your a hostname that points to the external IP you reserved in the previous step (mine is akskube.alexanderdevelopment.net). This will be the hostname you use to access OpenFaaS. You may also need to create a new CAA record to explicitly allow Let's Encrypt to issue certificates for your domain.

Basic cluster configuration

Once the basic Azure configuration work is done, it's time to configure the AKS cluster.

  1. If you don't already have the Helm client installed on your local system, install it by following the directions here. I am using a Windows dev workstation, so I installed Chocolatey and then installed the Helm client with choco install kubernetes-helm.
  2. Install Helm components on your AKS cluster with helm init --upgrade --service-account default.
  3. Install the Nginx ingress controller on your AKS cluster with helm install stable/nginx-ingress --namespace kube-system --set rbac.create=false --set rbac.createRole=false --set rbac.createClusterRole=false --set controller.service.loadBalancerIP=STATICIPADDRESS. Replace STATICIPADDRESS with the public IP address you created previously.
  4. Install cert-manager to request and manage your TLS certificates helm install --name cert-manager --namespace kube-system stable/cert-manager --set rbac.create=false.

Installing OpenFaas

It's relatively easy to install OpenFaaS on your AKS cluster using Helm, and a detailed readme is available here. Basically you need to download the faas-netes Git repository to your local system, create a couple of namespaces on the AKS cluser and use the Helm chart in the repo you downloaded. Here's how I set it up on my AKS cluster.

kubectl create ns openfaas
kubectl create ns openfaas-fn

git clone https://github.com/openfaas/faas-netes
cd faas-netes

helm install --namespace openfaas -n openfaas --set functionNamespace=openfaas-fn --set ingress.enabled=true --set rbac=false chart/openfaas/

Once OpenFaaS is installed, you need to create ingress resources to make it available externally.

Creating the ingress resources

Before creating your ingress resources, you need to create certificate issuer resources to get TLS certificates. Here's the YAML for a Let's Encrypt staging issuer:

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: EMAILADDRESS
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    http01: {}

Copy it, replace EMAILADDRESS with your email address and save it as faas-staging-issuer.yml. Then run kubectl apply -f faas-staging-issuer.yml -n openfaas.

Here's a corresponding production issuer:

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: EMAILADDRESS
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-production
    # Enable the HTTP-01 challenge provider
    http01: {}

Copy it, replace EMAILADDRESS with your email address and save it as faas-production-issuer.yml. Then run kubectl apply -f faas-production-issuer.yml -n openfaas.

Next you need to create a password file to implement basic authentication. If you are working on a system with apache2-utils installed, you can just use the htpasswd command. Otherwise, you can use a tool like http://aspirine.org/htpasswd_en.html to generate your htpasswd content. Once you have your htpasswd content generated, save it in a file named "auth" and run the following command kubectl create secret generic basic-auth --from-file=auth -n openfaas

Now you can use the following YAML to create an ingress resource that exposes your OpenFaaS instance:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: faas-ingress
  annotations:
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-type: basic
    kubernetes.io/tls-acme: "true"
    certmanager.k8s.io/issuer: letsencrypt-staging
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    - HOSTNAME
    secretName: faas-letsencrypt-staging
  rules:
  - host: HOSTNAME
    http:
      paths:
      - path: /faas-admin
        backend:
          serviceName: gateway
          servicePort: 8080

Copy it, replace both instances of HOSTNAME with the hostname you created earlier and save it as faas-ingress.yml. Deploy it to your cluser with this command kubectl apply -f faas-ingress.yml -n openfaas.

As the ingress starts up, it will request a staging certificate from Let's Encrypt, and then it will start listening for requests. It may take a few minutes, so now might be a good time to take a short break. Once everything is complete, you will be able to access your OpenFaaS UI from https://HOSTNAME/faas-admin/ui/, and once you deploy functions, they will be available at https://HOSTNAME/faas-admin/functions/FUNCTIONNAME. You should get a browser warning about the certificate because it's using a Let's Encrypt production certificate, but that's OK for now. You should also be prompted for basic authentication credentials, which will be the username and password you created earlier.

If everything looks good, you can switch over to using a production TLS certificate. Take the faas-ingress YAML and replace the "letsencrypt-staging" in the secretname and certmanager.k8s.io issuer values with "letsencrypt-production" instead. Save it and deploy the update with kubectl apply -f faas-ingress.yml -n openfaas. Like before, the ingress will take a few minutes to restart and request a production TLS certificate from Let's Encrypt. Once that's done, you can access the your OpenFaaS UI via the same URL, but now you should not get a warning about an invalid certificate.

At this point you have a locked-down OpenFaaS installation, but you might not want to use basic authentication to restrict access to your OpenFaaS functions. If that's the case you can create another ingress resource that exposes them outside the "/faas-admin" path. Here's the YAML for that resource:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: faas-function-ingress
  annotations:
    kubernetes.io/tls-acme: "true"
    certmanager.k8s.io/issuer: letsencrypt-production
    nginx.ingress.kubernetes.io/rewrite-target: /functions
spec:
  tls:
  - hosts:
    - HOSTNAME
    secretName: faas-letsencrypt-production
  rules:
  - host: HOSTNAME
    http:
      paths:
      - path: /functions
        backend:
          serviceName: gateway
          servicePort: 8080

Copy it, replace both instances of HOSTNAME with your DNS hostname from earlier and save it as faas-function-ingress.yml. Deploy it to your cluser with this command kubectl apply -f faas-function-ingress.yml -n openfaas.

Once the ingress starts up and applies the TLS certificate, you will be able to access your functions at https://HOSTNAME/functions/FUNCTIONNAME without authenticating.

Wrapping up

A few closing thoughts:

  1. I am still extremely new to AKS and Kubernetes, and I've tried to simplify this guide as much as possible for other newbies. In figuring out how to set this up, I relied heavily on the official AKS docs, and I encourage you to take a look at them if you want to dig in deeper.
  2. This configuration does not expose the OpenFaaS Prometheus montitoring service. If you want to set that up, you will need to create a different DNS entry (either an A record or CNAME record) and create another ingress resource in the openfaas namespace for that host name that points to the "prometheus" service on service port 9090.
  3. The Nginx ingress controller configuration I showed here is extremely simple. If you want to use a more sophisticated configurations to enable advanced features like rate limiting, for example, take a look at https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress#configuration and https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/configmap.md.