Deploying a Software Factory to GCP with Kubernetes

Docker is a very popular facility for
simplifying the deployment of services to a Linux box. It allows to pack all the required resources to run a service
(often a micro-service) into a file system image, that can be run inside a container.
Multiple containers can run in the same physical (or virtual) box, each one being isolated from the others.
Configuring Linux for running “dockerised” services is an easy task and actually I've been running some of my
services on self-maintained servers (with a classic hosting model) for a few years.

But one of the interesting points of Docker is that a number of providers are capable of directly accepting an
image and running a container out of it, providing also to the system owner the resources for managing it. Since
people is encouraged to deploy single purpose, self-contained services, just one inside each container, the
problem arise about having multiple services deployed and managed together. Kubernetes
is a cluster manager which is able to run multiple Docker containers and manage, maintain and scale them on a
server farm. The Google Cloud Platform offers Kubernetes as part of its services.

This post is the first in a series of exercises to demonstrate how a simple Software Factory, based on open
source (Jenkins, Sonaype Nexus)
and popular (Jira) tools, can be deployed to the GCP. Let's
start with Jenkins and Nexus 3, because the process is further simplified as their producers also make available
some pre-configured Docker images capable to run in minutes.

Prerequisites

First, you need to have a local platform to perform the development. While Docker and Google Cloud SDK are native
on Linux, and there are installers for Mac OS X and Windows that trasparently use a VirtualBox Linux guest, I
suggest to explicitly create a Linux VM and work inside it. This is to avoid polluting your laptop with lots of
stuff.

Google says that, when the bonus expires, billing won't happen automatically. Now I'm from Genoa and they say
we're stingy, but we are just careful about money... so I'm strongly advising you to keep an eye on billing and
have a look at the pricing policies of GCP.

Creating the project

At this point you're ready to create a project. My idea is to create a global project for
Nexus, Jenkins and later Jira.

A single project on GCP can contains multiple, independently configured Kubernetes clusters.
A Kubernetes cluster is made of a Master API server and a set of worker VMs called nodes,
managed by the master server. A pod is a group of Docker containers, tied
together for the purposes of administration and networking. In this exercise a single-container-pod is
associated to each service (Jenkins, Nexus).

So, I selected the menu "Create Project" from the GCP dashboard and filled in the following fields:

Project Name: tidalwave-services

Advanced Options / App Engine Region: europe-west

Note that I'm applying some meaningful settings for me, operating from Italy. If you are from a different
region of the world, evaluate other regions.

Then I pushed "Create" and took note of the project id that has been assigned: it's the project
name plus an optional numeric suffix to disambiguate other projects with the same name that can be already
present. My assigned project id was the same as the name, tidalwave-services, but it could have been
something such as tidalwave-services-154394.

I connected to the Kubernetes
console and enabled the Container Engine API. I was offered a list of projects to enable, and I selected
tidalwave-services. I also enabled billing on it.

At this point it is possible to start working with the CLI. The first operation I performed was to set the
project name, that will be remembered permanently. Recall that if you have multiple projects to work on, each time
you switch to a different one you have to reset the project name.

$ gcloud config set project tidalwave-services

Submitting the Docker image

With the usual docker pull command I downloaded from the Docker Hub the image of Jenkins 2.19.3:

$ docker pull jenkins:2.19.3...

In order to prepare it for the push to GCP, I associated it with a new tag:

The schema for the new tag is mandatory: first a label related to the App Engine Region (eu.gcr.io in my case), then the name of the project (tidalwave-services) and finally the name of the image that will be pushed (jenkins).

The next step was to push the image to GCP, referring to the freshly created tag:

Docker images are usually large (hundreds of megabytes) so this operation might take a long time depending on your bandwidth. Since images are layered, each layer is pushed separately. If the connection goes down at a certain point, you can repeat the operation and layers that were succesfully pushed don't need to be sent again.

Again, it was necessary to specify the App Engine Region: one must pay attention to be consistent with previous settings. The operation took a few seconds, then it was possible to confirm it again with the web console.

Creating a pod for Jenkins

Before proceeding it was necessary to authenticate again. The command line opens Firefox that navigates to an authentication page:

$ gcloud auth application-default loginYour browser has been opened to visit:
https://accounts.google.com/o/oauth2/auth?redirect_uri=...

It was then the turn of the kubectl run command. It created a pod specifying the image to run and the port to expose. The default port used by the Jenkins Docker image is 8080.

kubectl get pods show the configured pods. Since a freshly created pod requires a few seconds to go in the Running status, the command was useful for me to wait and confirm that the previous operation was correctly completed:

In this phase it is advisable to check for error statuses (not getting to Running), that might be related to incorrect settings, for instance concerning the image name.

kubectl logs allow to see the container log of the specified pod. I could check that Jenkins was going through the boot phase, and it was also important to see the temporary password that was created for the first access:

It was possible to retrieve the URLs of the main components of a cluster by running the kubectl cluster-info command:

$ kubectl cluster-infoKubernetes master is running at https://104.155.34.229
GLBCDefaultBackend is running at https://104.155.34.229/api/v1/proxy/namespaces/kube-system/services/default-http-backend
Heapster is running at https://104.155.34.229/api/v1/proxy/namespaces/kube-system/services/heapster
KubeDNS is running at https://104.155.34.229/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://104.155.34.229/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Further very detailed diagnostics were available by means of the kubectl get events command:

$ kubectl get events<long list of events>

The service was not accessible from the internet yet. In fact, it needed to be exposed with a specific command:

An internal IP was assigned, an address valid only inside the cluster. The "LoadBalancer" type also asks GCP for assigning an external IP, that might take some seconds to be available. The command kubectl get services was used for a short time to have confirmation of the assignment of the external IP:

At this point the service was accessible from my browser at the URL http://146.148.124.188:8080. Jenkins started up with a temporary password that was previously shown in the container log; then a “first setup” wizard was shown, to finish the initial configuration. Unfortunately, a Jenkins bug prevented me from going on, and I'll take care of it later.

Creating another pod for Nexus

Now, the second round for deploying Nexus. Apart from the initial setup, it was just the matter of repeating some operations for the new image:

And so also Nexus was available after a few seconds at the URL http://104.199.48.209:8081.

In case of mistakes

During the sequence, I made some errors (mistyping some names). Once the Docker images have been pushed to GCP, it's easy and quick to delete the pieces of infrastructure just created and re-create them. The relevant command to delete pieces are:

Stopping and restarting services

While a kubectl stop command exists, it has been deprecated. Actually, stopping something means to delete it. Deletion, in fact, performs by default a gracefully shutdown. It is possibly to later restart a service by re-creating it.

But there is another approach: the kubectl scale deployment command is used to control the number of instances of a deployed service. The purpose is scalability (something that is out of the scope of the current post), but it can be used to set to zero the number of instances (which equates to remove the related pods):

While the External IPs still exist, nothing responds when you try to connect to it. Note that while it is possible to delete a pod, it is not the correct way to remove the availability of the related service: as long as the desired instance count is 1, Kubernetes will restart a new pod in its place.

Conclusion

Two services are up and running, and it's enough for the first step. They are still unusable: in fact, they run off a single Docker container, whose file system is not persistent. Any change due to using or configuring them will be lost at the next restart, resetting them to the status of a fresh installation. One of the first things to do at the next step is to provide them with a persistent file storage.

Comments are managed by
Disqus,
which makes use of a few cookies.
Please read
their cookie policy for more details. If you agree to that policy,
please click on the button below to accept it. If you don't, you can still enjoy this site without
using
Disqus
cookies, but you won't be able to see and post comments. Thanks.