Using SSL/TLS for a service hosted on a Kubernetes cluster

This article will dive into the necessary steps that you need to do in order to use SSL/TLS for a service of yours that is hosted on a Kubernetes cluster, making it accessible via https. We will use one Microsoft Bot Framework app to demonstrate this. This framework allows you to easily built chatbots that are hosted on the provider of your choice. Its Bot Connector service allows your bot to open “conversation channels” with Messenger, Skype, Slack and other providers. For this purpose, it requires the chatbot’s endpoint to be accessible via SSL/HTTPS, so that makes for a nice proof of concept apt for this article. So, how would you host a chatbot app on a Kubernetes cluster, taking into account the SSL requirement? One option, of course, would be to have the app itself handle the certificate process, like this example. The other option, which you’ll see in this article, is to use the Kubernetes ingress controller to handle all the SSL setup and usage. The only prerequisites from your side is to have a domain name that the certificate will be issued for and, of course, access to a Kubernetes cluster.

Writing the chatbot code

First, you will have to create a simple chatbot. You can use the below Node.js code for a simple ‘echo’ bot that also returns the hostname of the machine that responded (you’ll see later in this article how the hostname is used).


const restify = require('restify');
const builder = require('botbuilder');
const os = require('os');
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
server.post('/api/messages', connector.listen());
const bot = new builder.UniversalBot(connector, function (session) {
session.send("You said: %s, machine that responds is %s", session.message.text, os.hostname);
});

view raw

samplebot.js

hosted with ❤ by GitHub

In this code you can see that the bot will authenticate with Bot Framework connector using a Microsoft Application ID and its corresponding password. Both values come as environmental variables. To get these values, you need to register your bot in the Bot Framework portal (free, at the time of writing). Keep the ID/password combination handy because we will use them later.

Creating a Docker image from the bot code

Then, you need to ‘dockerize‘ your Node.js bot code. Here’s a Dockerfile you can use, but feel free to use your own.


FROM node:boron
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN npm install
COPY samplebot.js /usr/src/app
EXPOSE 3978
CMD [ "node", "samplebot" ]

view raw

Dockerfile

hosted with ❤ by GitHub

Next step would be to build the image and push it to Docker Hub. If you are using Azure, you could publish it to Azure Container Registry service.


docker build -t dgkanatsios/samplebot .

docker push dgkanatsios/samplebot

Deploying the bot Docker image to Kubernetes

I assume that you have already set up a Kubernetes cluster. If not, check here for instructions on how to set one up on Azure Container Service. Once you’ve done it, you can deploy your bot to the cluster. Here’s a Kubernetes deployment file that you can use.


apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: samplebot
labels:
name: samplebot
app: demo
spec:
replicas: 1
template:
metadata:
labels:
name: samplebot
app: demo
spec:
containers:
name: samplebot
image: dgkanatsios/samplebot:latest
ports:
containerPort: 3978
protocol: TCP
env:
name: MICROSOFT_APP_ID
value: "YOUR_MICROSOFT_APP_ID"
name: MICROSOFT_APP_PASSWORD
value: "YOUR_MICROSOFT_APP_PASSWORD"

view raw

deploy.yaml

hosted with ❤ by GitHub

With this deployment file, you’re instructing the Kubernetes cluster to perform the following things:

  • Create a Kubernetes deployment
  • This deployment contains 1 replica of the dgkanatsios/samplebot Docker image (which you pushed to the Docker Hub a while ago)
  • You are explicitly mentioning that your running Docker image will open and listen on port 3978/TCP
  • You are setting two environmental variables for your image with Application ID and password from the Bot Framework portal (don’t forget to insert your values). Best practice, though, would be to use Kubernetes secrets to pass these variables so they are not visible in the deployment file

Run this deployment file you created with kubectl command line tool.


kubectl create -f deploy.yaml

After a while, your new pod will be up and running.


kubectl get pods

NAME READY STATUS RESTARTS AGE
samplebot-3527369274-4sj0t 1/1 Running 0 1m

Now, let’s expose the 3978 port of your deployment by creating the relevant Kubernetes service.


kubectl expose deployments/samplebot --port=3978

You can check that it has been indeed exposed internally in the cluster using the ‘kubectl get svs’ command.


kubect get svc

NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
samplebot 10.0.82.233 <none> 3978/TCP 18s
kubernetes 10.0.0.1 <none> 443/TCP 8m

Installing NGINX Ingress Controller

Then, you need to install helm on your local computer. Helm is a package manager for Kubernetes, think of it like apt-get/yum/homebrew/chocolatey but built for Kubernetes. Once you install helm, you need to install tiller on your cluster.


helm init --upgrade

Feel free to check here for more details about helm and Azure Container Service.

Then, you will install the NGINX ingress controller chart.

helm install stable/nginx-ingress

Run helm list to see that the chart has been successfully installed.


helm list

NAME REVISION UPDATED STATUS CHART NAMESPACE
cantankerous-narwhal 1 Thu Jul 6 15:57:42 2017 DEPLOYED nginx-ingress-0.4.2 default

Configuring SSL

For the purposes of this article, you will use Let’s Encrypt to get a free certificate for your (sub)domain. To do this, you will use kube-lego chart which automatically requests certificates for Kubernetes ingress resources from Let’s Encrypt. Don’t forget to substitute ‘you@domain.tld; with your real e-mail address!

helm install --set config.LEGO_EMAIL=you@domain.tld --set config.LEGO_URL=https://acme-v01.api.letsencrypt.org/directory stable/kube-lego

Now you need to configure the Kubernetes service for the ingress. You can use this file as base:


apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
name: ingress
spec:
rules:
host: samplebot.dgkanatsios.com
http:
paths:
backend:
serviceName: samplebot
servicePort: 3978
path: /
tls:
hosts:
samplebot.dgkanatsios.com
secretName: example-tls

You should modify the host domain name (here I’m using a subdomain of my ‘dgkanatsios.com’ domain), the serviceName (use the name of your Kubernetes service where you exposed your chatbot’s container port) and the servicePort (if you’re using something other than 3978).

Once you finish creating the file, run kubectl with it.


kubectl create -f ingress-ssl.yaml

Once you do that, you should check if the ingress has been correctly deployed. Run ‘kubectl get ing’.


kubectl get ing

NAME HOSTS ADDRESS PORTS AGE
ingress samplebot.dgkanatsios.com 80, 443 5m
kube-lego-nginx samplebot.dgkanatsios.com 80 5m

Then, you should get the external IP of your NGINX ingress controller. If it comes as <pending>, this means that it is still being created in the Azure backend, so wait for a few minutes.


kubectl get svc

NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
samplebot 10.0.82.233 <none> 3978/TCP 23m
jaundiced-manatee-nginx-ingress-controller 10.0.143.24 <strong>13.95.230.243</strong> 80:31280/TCP,443:31501/TCP 16m
jaundiced-manatee-nginx-ingress-default-backend 10.0.97.68 <none> 80/TCP 16m
kube-lego-nginx 10.0.185.132 <none> 8080/TCP 44s
kubernetes 10.0.0.1 <none> 443/TCP 32m

and then configure your DNS server with an A record of your (sub)domain pointing to this external IP.

Testing that it actually works!

It make take some time for the DNS propagation to take place, so be patient. After a while, when this is up, you can use the Bot Framework emulator to test your bot code.

Capture.JPG

Optionally, you could return to the Bot Framework portal and configure your bot to be accessible at your new URL.

Capture

Next steps

  • If you want to enable TLS authentication through your NGINX controller, check here
  • Brendan Burns from Azure Container Service team demos the SSL steps we described in his BUILD talk here (on 47′ and onwards)

Bonus – scaling the bot

Remember that we returned the machine name from the bot’s response code? We could scale our bot easily and see this in action:


kubectl scale deploy samplebot --replicas=3

kubectl get pods

NAME READY STATUS RESTARTS AGE
samplebot-3527369274-4sj0t 1/1 Running 0 3h
samplebot-3527369274-8vjml 1/1 Running 0 18s
samplebot-3527369274-r4z0m 1/1 Running 0 18s
early-pig-kube-lego-610068836-psq8z 1/1 Running 0 1h
ironic-owl-nginx-ingress-controller-423399762-pz558 1/1 Running 0 1h
ironic-owl-nginx-ingress-default-backend-3925919273-qssbh 1/1 Running 0 1h

If you try talking to the bot using the emulator, you’ll see that each request will handled by a different pod based on the rules of the Replica Set.

Capture.JPG

One thought on “Using SSL/TLS for a service hosted on a Kubernetes cluster

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s