Skip to content

TLS on Kubernetes

You can obtain TLS certificates for the OpenFaaS API Gateway and for your functions using cert-manager from JetStack.

We will use the following components:

We will split this tutorial into two parts:

  • 1.0 TLS for the Gateway
  • 2.0 TLS and custom domains for your functions
  • 3.0 REST-style API mapping for your functions

1.0 TLS for the Gateway

This part guides you through setting up all the pre-requisite components to enable TLS for your gateway. You can then access your gateway via a URL such as https://gw.example.com and each function such as: https://gw.example.com/function/nodeinfo.

Configure Helm

First install Helm v3 following the instructions provided by Helm

Install nginx-ingress

This example will use a Kubernetes IngressController.

Add Nginx using the helm chart-ingress:

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm install nginxingress ingress-nginx/ingress-nginx

The full configuration options for nginx can be found here.

You will see a service created in the default namespace with an EXTERNAL-IP of Pending, after a few moments it should reveal the public IP allocated by your cloud provider.

$ kubectl get svc
nginxingress-nginx-ingress-controller        LoadBalancer   192.168.137.172   134.209.179.1

Caveats:

  • Alternatively, use arkade to install nginx-ingress.
arkade install ingress-nginx
  • If you do not have a cloud provider for your Kubernetes cluster, but have a public IP, then you can install Nginx in "host-mode" and use the IP of one or more of your nodes for the DNS record.
$ helm install nginxingress ingress-nginx/ingress-nginx \
  --set rbac.create=true,controller.hostNetwork=true \ 
  --set controller.daemonset.useHostPort=true \
  --set dnsPolicy=ClusterFirstWithHostNet \
  --set controller.kind=DaemonSet

Taken from tutorial: Setup a private Docker registry with TLS on Kubernetes

If you do not have a public IP for your Kubernetes cluster, then you can use the inlets-operator to get a LoadBalancer for your local or private cluster, even behind NAT or a firewall.

Install OpenFaaS

Follow the instructions found in the OpenFaaS Helm Chart. As part of these instructions you will create a basic-auth password to secure the Gateway's API and UI.

Alternatively, use arkade to install openfaas:

arkade install openfaas

Create a DNS record

Determine the public IP address which can be used to connect to Nginx:

  • If you are using a managed cloud provider, you will receive an IP address in EXTERNAL-IP
  • If you are using AWS EKS, Nginx will receive a DNS A record in EXTERNAL-IP
  • If you are using Host Mode for Nginx, then use the IP address of your node

For most people you can create a domain such as gw.example.com using a DNS A record, for those using AWS EKS, you will have to create a DNS CNAME entry instead.

The required steps will vary depending on your domain provider and your cluster provider. For example; on Google Cloud DNS or with Route53 using AWS.

Once created, verify that what you entered into your DNS control-panel worked with ping:

ping gw.example.com

You should now see the value you entered. Sometimes DNS can take 1-5 minutes to propagate.

Install cert-manager

Following the recommended default installation for cert-manager, we install it into a new cert-manager namespace using the following commands:

# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io

# Update your local Helm chart repository cache
helm repo update

# Install the cert-manager Helm chart (including cert-manager CRDs)
helm install \
  cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true \
  --version v1.13.4 \
  jetstack/cert-manager

This configuration will work for most deployments, but you can also see https://cert-manager.io/docs/installation/helm/ for additional instructions and options for installing cert-manager.

Configure cert-manager

In additional to the controller installed in the previous step, we must also configure an "Issuer" before cert-manager can create certificates for our services. For convenience we will create an Issuer for both Let's Encrypt's production API and their staging API. The staging API has much higher rate limits. We will use it to issue a test certificate before switching over to a production certificate if everything works as expected.

Replace <your-email-here> with the contact email that will be shown with the TLS certificate.

# letsencrypt-issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-staging
  namespace: openfaas
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: <your-email-here>
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: staging-issuer-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-prod
  namespace: openfaas
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: <your-email-here>
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: prod-issuer-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx
$ kubectl apply -f letsencrypt-issuer.yaml

This will allow cert-manager to automatically provision Certificates just in the openfaas namespace.

Add TLS to openfaas

The OpenFaaS Helm Chart already supports the nginx-ingress, but we want to customize it further. This is easiest with a custom values file. Below, we enable and configure the ingress object to use our certificate and expose just the gateway

# tls.yaml
ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: "nginx"    
    cert-manager.io/issuer: letsencrypt-staging
  tls:
    - hosts:
        - gw.example.com
      secretName: openfaas-crt
  hosts:
    - host: gw.example.com
      serviceName: gateway
      servicePort: 8080
      path: /
$ helm upgrade openfaas \
    --namespace openfaas \
    --reuse-values \
    --values tls.yaml \
    openfaas/openfaas

Check the certificate

A certificate will be created automatically through cert-manager's ingress-shim component. The ingress-shim reads annotations to decide which certificates to provision for us.

You can validate that certificate has been obtained successfully using:

$ kubectl describe certificate \
  -n openfaas \
  openfaas-crt

Switch over to the production issuer

If it was successful, then you can change to the production Let's Encrypt issuer.

  • Replace letsencrypt-staging with letsencrypt-prod in tls.yaml
  • Run the helm command again
$ helm upgrade openfaas \
    --namespace openfaas \
    --reuse-values \
    --values tls.yaml \
    openfaas/openfaas

Deploy and Invoke a function

In your projects containing OpenFaaS functions, you can now deploy using your domain as the gateway, replace gw.example.com with your domain as well as adding the username and password you created when you deployed OpenFaaS.

faas-cli login --gateway https://gw.example.com --username <username> --password <password>
faas-cli deploy --gateway https://gw.example.com

Alternatives to using the helm chart

An arkade app also exists which makes the above much easier by automating the Issuer and Ingress records for you:

  arkade install openfaas-ingress \
  --domain openfaas.example.com \
  --email openfaas@example.com

Verify and Debug

There are several commands we can use to verify that the required kubernetes objects have been created.

  • To check that the cert-manager issuers were created:

    $ kubectl -n openfaas get issuer letsencrypt-prod letsencrypt-staging
    

  • To check that your certificate was created and that cert-manager created the required secret with the actual TLS certificate:

    $ kubectl -n openfaas get certificate,secret openfaas-crt
    

  • If you want to tail the Nginx logs, you can use

    $ kubectl logs -f $(kubectl get po -l "app.kubernetes.io/instance=nginxingress,app.kubernetes.io/component=controller" -o jsonpath="{.items[0].metadata.name}")
    

2.0 TLS and custom domains for functions

It's important that you never expose a function to the Internet directly, but only via the OpenFaaS gateway. The gateway is required in the invocation path to provide metrics, auto-scaling and asynchronous invocations etc.

This part builds on part 1.0 and now enables custom domains for any of your functions. You will need to have installed OpenFaaS and an IngressController. For TLS, which is optional you need to have cert-manager and at least one Issuer.

For example, rather than accessing a function nodeinfo via https://gw.example.com/function/nodeinfo, you can now use a custom URL such as: https://nodeinfo.example.com.

The IngressOperator introduces a new CRD (Custom Resource Definition) called FunctionIngress. The role of FunctionIngress is to create an Ingress Kubernetes object to map a function to a domain-name, and optionally to also provision a TLS certificate using cert-manager.

How to enable the OpenFaaS IngressOperator

You can install the ingress-operator by passing --set ingressOperator.create=true to the Helm chart at the installation time of OpenFaaS. Or by adding the following to your values.yaml file:

ingressOperator:
  create: true

If you use arkade, add the --ingress-operator flag to arkade install openfaas.

Check that the operator was deployed and has started:

kubectl get -n openfaas deploy/ingress-operator \
  -o wide

kubectl logs -n openfaas deploy/ingress-operator -f

If it's working, you will see AVAILABLE showing 1.

Deploy a function

Let's deploy a function from the store:

faas-cli store deploy nodeinfo

If you're using ingress-nginx, then check the public IP with kubectl get svc/nginxingress-nginx-ingress-controller, note down the EXTERNAL-IP.

Create a DNS A record or CNAME nodeinfo.example.com pointing to the EXTERNAL-IP

Create a FunctionIngress Custom Resource (without TLS)

Now create a FunctionIngress custom resource:

apiVersion: openfaas.com/v1
kind: FunctionIngress
metadata:
  name: nodeinfo-tls
  namespace: openfaas
spec:
  domain: "nodeinfo-tls.myfaas.club"
  function: "nodeinfo"
  ingressType: "nginx"

Change ingressType if you're using a different Ingress like traefik.

Verify that the Ingress record was created:

kubectl get ingress -n openfaas

Ingress records are always created in the same namespace as the OpenFaaS Gateway.

Create a FunctionIngress with TLS certificate

To enable TLS, we just need to add the tls section and the following fields:

  • tls.enabled - whether to create the certificate
  • issuerRef.name - as per the Issuer name created above
  • issuerRef.kind - optional: either Issuer or ClusterIssuer

Note: The FunctionIngress currently makes use of the HTTP01 challenge.

apiVersion: openfaas.com/v1
kind: FunctionIngress
metadata:
  name: nodeinfo-tls
  namespace: openfaas
spec:
  domain: "nodeinfo-tls.myfaas.club"
  function: "nodeinfo"
  ingressType: "nginx"
  tls:
    enabled: true
    issuerRef:
      name: "letsencrypt-staging"
      kind: "Issuer"

Verify that the Certificate record was created:

kubectl get cert -n openfaas

Appendix

Custom annotations

Any annotations that you add to the FunctionIngress will be added to the Ingress record. This is useful for adding custom annotations that your IngressController supports such as timeouts, rate-limiting or authorization settings.

Deleting FunctionIngress records

You can see the FunctionIngress records via:

kubectl get fni -n openfaas

Then delete one if you need to via: kubectl delete fni/name -n openfaas.

Use Zalando's skipper IngressController

Zalando's skipper IngressController is also supported. To switch over simply add the following to your YAML definition:

spec:
  ingressType: "skipper"

3.0 REST-style API mapping for your functions

The FunctionIngress discussed above provides a simple way to create a custom URL mapping scheme for your functions. This is a common request from users, and means that you can map your functions into a REST-style API.

Here we map three functions to a REST-style API:

https://gateway.example.com/function/env -> https://api.example.com/v1/env/
https://gateway.example.com/nodeinfo -> https://api.example.com/v1/nodeinfo/
https://gateway.example.com/certinfo -> https://api.example.com/v1/certinfo/
faas-cli deploy --image functions/alpine:latest --name env --fprocess env
faas-cli store deploy nodeinfo
faas-cli store deploy certinfo

Save the following in a file "fni.yaml" and customise it as required.

Then run kubectl apply -f fni.yaml.

To create the TLS Issuer, see the steps above.

apiVersion: openfaas.com/v1
kind: FunctionIngress
metadata:
  name: nodeinfo
  namespace: openfaas
spec:
  domain: "api.example.com"
  function: "nodeinfo"
  ingressType: "nginx"
  path: "/v1/nodeinfo/(.*)"
  tls:
    enabled: true
    issuerRef:
      name: "letsencrypt-staging"
      kind: "Issuer"
---
apiVersion: openfaas.com/v1
kind: FunctionIngress
metadata:
  name: env
  namespace: openfaas
spec:
  domain: "api.example.com"
  function: "env"
  ingressType: "nginx"
  path: "/v1/env/(.*)"
  tls:
    enabled: true
    issuerRef:
      name: "letsencrypt-staging"
      kind: "Issuer"
---
apiVersion: openfaas.com/v1
kind: FunctionIngress
metadata:
  name: certinfo
  namespace: openfaas
spec:
  domain: "api.example.com"
  function: "certinfo"
  ingressType: "nginx"
  path: "/v1/certinfo/(.*)"
  tls:
    enabled: true
    issuerRef:
      name: "letsencrypt-staging"
      kind: "Issuer"

What about IngressController X?

Feel free to raise a feature request for your IngressController on the GitHub repo.