Note: This post was updated to use the latest stable Kubernetes Go client as of 12/14/2016.

As a continuation to the previous post, let’s now look at using Kubernetes’ API to create a simple Pod and Service to expose a Docker container running a Jupyter notebook server.

We will be using Kubernetes’ Go client, so let’s get started by getting the package:

go get -u k8s.io/client-go/

Once the package downloads, we need to add the relevant import paths in our main.go file. Note that we’ll be using the 1.5 stable version which is in the 1.5 folder. Version 2.0 is currently in alpha and will not contain the top level folder names.

package main

import (
	"fmt"

	"k8s.io/client-go/1.5/kubernetes"
	"k8s.io/client-go/1.5/pkg/api/v1"
	"k8s.io/client-go/1.5/tools/clientcmd"
	"k8s.io/client-go/1.5/rest"
)

func main()  {
	//...
}

If outside of the cluster, then we will need to instantiate the client using an existing Kubernetes config path. In most cases, the path would be $HOME/.kube/config.

config, err := clientcmd.BuildConfigFromFlags("", "<kube-config-path>")
if err != nil {
	return nil, err
}

c, err := kubernetes.NewForConfig(config)
if err != nil {
	return nil, err
}

If inside the cluster, we can instantiate the client this way instead:

config, err := rest.InClusterConfig()
if err != nil {
	return nil, err
}

c, err := kubernetes.NewForConfig(config)
if err != nil {
	return nil, err
}

Next, let’s create a Kubernetes Pod that’ll contain our Jupyter notebook container:

// Create a Pod named "my-pod"
pod, err := c.Pods(v1.NamespaceDefault).Create(&v1.Pod{
	ObjectMeta: v1.ObjectMeta{
		Name: "my-pod",
	},
	Spec: v1.PodSpec{
		Containers: []v1.Container{
			{
        Name:  "jupyter-notebook",
        Image: "jupyter/minimal-notebook",
				Ports: []v1.ContainerPort{
					{
						ContainerPort: 8888,
					},
				},
			},
		},
	},
})

Note that Pods aren’t durable, so if you want your Pod to survive node failures and maintenance, you would need to create a replication controller or a Deployment.

We can also make our Pods publicly accessible via Services. Since Services use label selectors to target Pods, we’d first need to update our Pod to include a label:

// Set Pod label so that we can expose it in a service
pod.SetLabels(map[string]string{
	"pod-group": "my-pod-group",
})

// Update Pod to include the labels
pod, err = c.Pods(v1.NamespaceDefault).Update(pod)

Then we can create a Node Port type Service that targets this Pod via the API:

// Create a Service named "my-service" that targets "pod-group":"my-pod-group"
svc, err := c.Services(v1.NamespaceDefault).Create(&v1.Service{
	ObjectMeta: v1.ObjectMeta{
		Name: "my-service",
	},
	Spec: v1.ServiceSpec{
		Type:     v1.ServiceTypeNodePort,
		Selector: pod.Labels,
		Ports: []v1.ServicePort{
			{
				Port: 8888,
			},
		},
	},
})

if err != nil {
	fmt.Println(err)
	return
}

// Print the port the service is exposed on
// You can access this notebook at http://<kubernetes node ip>:<node port>
fmt.Println(svc.Spec.Ports[0].NodePort)

That’s all there is to it! You will now be able to access your Jupyter notebook at: http://<any kubernetes node ip>:<node port>

| #kubernetes #google container engine #docker #jupyter