Run Nameko Services on Kubernetes

In this article I’ll demonstrate how easy it is to run Nameko Microservices on Kubernetes Cluster.

We’ll be using local Minikube cluster hosted on VirtualBox along with community maintained Helm Charts to deploy 3rd party services. We will also create a set of custom Helm Charts for Nameko Example Services found in Nameko Examples repository.

Tested with Kubernetes v1.8.

GitHub repository for this article:
https://github.com/kooba/nameko-examples-k8s/tree/master/k8s

Prerequisites

Please make sure these are installed and working

Creating Kubernetes Cluster

We’ll use basic Minikube command to create our k8s cluster:

$ minikube start --vm-driver=virtualbox
Starting local Kubernetes v1.8.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

Let’s verify cluster is up and running:

$ kubectl --context=minikube get nodes
NAME       STATUS    ROLES     AGE       VERSION
minikube Ready <none> 3m v1.8.0

Minikube comes with Kubernetes Dashboard addon turned on. You can use it to poke around the cluster. Any information that you can see via dashboard can be as easily obtained with kubectl command.

Start dashboard:
$ minikube dashboard

Create Namespace

It’s a good practice to create a namespaces where all of our services will live:

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: examples

Apply namespace definition:

$ kubectl --context=minikube apply -f namespace.yaml

Install External Dependencies

Our examples depend on PostgreSQL, Redis and RabbitMQ. The fastest way to install these 3rd party dependencies is to use community maintained Helm Charts.

Let’s install Tiller (Helm’s server-side component)

$ helm init --kube-context=minikube
$HELM_HOME has been configured at /Users/bob/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Happy Helming!

Let’s verify that Helm client can talk to Tiller server

$ helm version --kube-context=minikube
Client: &version.Version{SemVer:”v2.7.0", GitCommit:”08c…ba4", GitTreeState:”clean”}
Server: &version.Version{SemVer:”v2.7.0", GitCommit:”08c…ba4", GitTreeState:”clean”}

Deploy RabbitMQ, PostgreSQL and Redis

Run these commands one by one:

$ helm --kube-context=minikube install --name broker --namespace examples stable/rabbitmq
$ helm --kube-context=minikube install --name db --namespace examples stable/postgresql --set postgresDatabase=orders
$ helm --kube-context=minikube install --name cache --namespace examples stable/redis

RabbitMQ, PostgreSQL and Redis are now installed along with persistent volumes, kubernetes services, config maps and any secrets required a.k.a. Amazeballs™!

Verify all pods are running:

$ kubectl --context=minikube --namespace=examples get pods
NAME                             READY STATUS  RESTARTS AGE
broker-rabbitmq-6c8d7c4554–8nklq 1/1 Running 0 22m
cache-redis-6cbfd95f66-vlkn5 1/1 Running 0 22m
db-postgresql-67f5c64dc4–8s9vf 1/1 Running 0 49s

There is a known bug with minikube version: v0.24.1 when bounding persistent volumes. If this has not been addressed when you read this please follow these steps to fix it.

Deploy Example Services

To deploy our services, we’ll have to create Kubernetes deployment definition files. Most of the time (in real world) you would want to use some dynamic data during your deployments e.g. to define image tags. The easiest way to do this is to create Helm Charts for each of our service and use Helm to deploy them.

Our charts are organized as follows:

charts
├── gateway
│ ├── Chart.yaml
│ ├── templates
│ │ ├── NOTES.txt
│ │ ├── deployment.yaml
│ │ ├── ingress.yaml
│ │ └── service.yaml
│ └── values.yaml
├── orders
│ ├── Chart.yaml
│ ├── templates
│ │ ├── NOTES.txt
│ │ └── deployment.yaml
│ └── values.yaml
└── products
├── Chart.yaml
├── templates
│ ├── NOTES.txt
│ └── deployment.yaml
└── values.yaml

Each chart is comprised of:

  • Charts.yaml file containing description of the chart.
  • values.yaml file containing default values for a chart that can be overwritten during the release.
  • templates folder where all Kubernetes definition files live.

All of our charts contain deployment.yaml template where main Nameko Service deployment definition lives. Gateway chart has additional definitions for ingress and kubernetes service which are required to enable inbound traffic.

Example of products deployment.yaml:

apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ .Chart.Name }}
labels:
app: {{ .Chart.Name }}
tag: {{ .Values.image.tag }}
revision: "{{ .Release.Revision }}"
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
app: {{ .Chart.Name }}
spec:
containers:
- image: nameko/nameko-example-products:{{ .Values.image.tag }}
name: {{ .Chart.Name }}
env:
- name: REDIS_HOST
value: cache-redis
- name: REDIS_INDEX
value: "11"
- name: REDIS_PORT
value: "6379"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: cache-redis
key: redis-password
- name: RABBIT_HOST
value: broker-rabbitmq
- name: RABBIT_MANAGEMENT_PORT
value: "15672"
- name: RABBIT_PORT
value: "5672"
- name: RABBIT_USER
value: user
- name: RABBIT_PASSWORD
valueFrom:
secretKeyRef:
name: broker-rabbitmq
key: rabbitmq-password
restartPolicy: Always

As you can see this template is using values coming from Chart and Values files as well as dynamic Release information. Passwords from secrets created by Redis and RabbitMQ releases are also referenced and passed to a container as REDIS_PASSWORD and RABBIT_PASSWORD environment variables respectively.

Please read The Chart Template Developer’s Guide to learn about creating your own charts.

To route traffic to our gateway service we’ll be using ingress. For ingress to work Ingress Controller has to be enabled on our cluster:

$ minikube addons enable ingress

Let’s deploy our products chart:

$ helm upgrade products charts/products --install \
--namespace=examples --kube-context=minikube \
--set image.tag=latest
Release "products" does not exist. Installing it now.
NAME: products
LAST DEPLOYED: Thu Jan 11 17:08:51 2018
NAMESPACE: examples
STATUS: DEPLOYED
RESOURCES:
==> v1beta2/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
products 1 1 1 0 0s
NOTES:
Thank you for installing Products Service!

We used--set image.tag=latest to set custom image tag to be used for this release. You can do the same for any values defined in values.yaml file.

Let’s release orders and gateway services:

$ helm upgrade orders charts/orders --install \
--namespace=examples --kube-context=minikube \
--set image.tag=latest
Release "orders" does not exist. Installing it now.
(...)
$ helm upgrade gateway charts/gateway --install \
--namespace=examples --kube-context=minikube \
--set image.tag=latest
Release "gateway" does not exist. Installing it now.
(...)

Let’s list all of our Helm releases:

$ helm --kube-context=minikube list
NAME      REVISION  STATUS    CHART             NAMESPACE
broker 1 DEPLOYED rabbitmq-0.5.3 examples
cache 1 DEPLOYED redis-0.7.1 examples
db 1 DEPLOYED postgresql-0.7.1 examples
gateway 1 DEPLOYED gateway-0.1.0 examples
orders 1 DEPLOYED orders-0.1.0 examples
products 1 DEPLOYED products-0.1.0 examples

And again let’s verify pods are happily running:

$ kubectl --context=minikube --namespace=examples get pods
NAME                         READY     STATUS    RESTARTS   AGE
broker-rabbitmq-6c8d7c4554 1/1 Running 0 22m
cache-redis-6cbfd95f66 1/1 Running 0 22m
db-postgresql-67f5c64dc4 1/1 Running 0 49s
gateway-cbdff8cf 1/1 Running 0 1m
orders-7995b49c59 1/1 Running 0 2m
products-66894ff474 1/1 Running 0 5m

Run examples

We can now verify our gateway api is working as expected by executing sample requests found in main README of Nameko Examples repository.

We will replace localhost:8003 with minikube IP:

$ minikube ip
192.168.99.101

Create Product

$ curl -XPOST -d '{"id": "the_odyssey", "title": "The Odyssey", "passenger_capacity": 101, "maximum_speed": 5, "in_stock": 10}' 'http://192.168.99.101/products'
{"id": "the_odyssey"}

Get Product

$ curl 'http://192.168.99.101/products/the_odyssey'
{
"id": "the_odyssey",
"title": "The Odyssey",
"passenger_capacity": 101,
"maximum_speed": 5,
"in_stock": 10
}

Create Order

$ curl -XPOST -d '{"order_details": [{"product_id": "the_odyssey", "price": "100000.99", "quantity": 1}]}' 'http://192.168.99.101/orders'
{"id": 1}

Get Order

$ curl 'http://192.168.99.101/orders/1'
{
"id": 1,
"order_details": [
{
"id": 1,
"quantity": 1,
"product_id": "the_odyssey",
"image": "http://www.example.com/airship/images/the_odyssey.jpg",
"price": "100000.99",
"product": {
"maximum_speed": 5,
"id": "the_odyssey",
"title": "The Odyssey",
"passenger_capacity": 101,
"in_stock": 9
}
}
]
}

Wrap-up

Running Nameko services in Kubernetes is really simple. Please get familiar with Helm Charts included in example repository and try adding one of your own.

Happy microservicing!