Kubernetes has been out for a few years (its initial release was back in June 2014) and though the coding community is aware of and has heard about its greater capabilities, a lot of people still haven’t used it yet. If you’re not using Kubernetes yet, you’re not alone. Part of the hesitation stems from what I’ve heard a few developers say: that there is no proper guide available (meaning that there are such an overwhelming amount of articles out there, adding to the fact that they’re spread across multiple websites rather than in a single place) or they fear starting something new. In this tutorial, I aim to simplify things a bit for you by using the basic idea of building a Python application with Docker and deploying it to a Kubernetes service.

Learning objectives

After completing this tutorial, you’ll be able to:

  • Containerize a Flask application by using Docker and deploy it to the IBM Cloud Kubernetes Service.

Prerequisites

In order to complete this tutorial, you’ll need the following prerequisites:

Estimated time

It should take around 45 minutes to complete this tutorial.

Steps

Create a Kubernetes cluster

open_kubernetes_service

  • Click Create Cluster.

create_cluster

  • Select the Region where you want to deploy the cluster, type in a name for your cluster, then click Create Cluster.
  • Depending on your account (Paid or Free), select the appropriate cluster type.
  • It takes some time for the cluster to get ready (around 30 minutes).

region

  • Once the cluster is ready, click on your cluster’s name and you will be redirected to a new page with information about your cluster and worker node.

cluster_worker_node

  • Click on the Worker Nodes tab to note the cluster’s Public IP.

worker_nodes_tab

Containerize your Flask application

  • In your project directory, create a file named “Dockerfile.” Suggestion: Name your file exactly “Dockerfile,” nothing else.

dockerfile

A “Dockerfile” is used to indicate to Docker a base image, the Docker settings you need, and a list of commands you would like to have executed to prepare and start your new container.

  • In the file, paste this code:

      FROM python:2.7
      LABEL maintainer="Kunal Malhotra, kunal.malhotra1@ibm.com"
      RUN apt-get update
      RUN mkdir /app
      WORKDIR /app
      COPY . /app
      RUN pip install -r requirements.txt
      EXPOSE 5000
      ENTRYPOINT [ "python" ]
      CMD [ "app.py" ]
    

Explanation and breakdown of the above Dockerfile code

  1. The first part of the code above is:

     FROM python:2.7
    

    Because this Flask application uses Python 2.7, we want an environment that supports it and already has it installed. Fortunately, DockerHub has an official image that’s installed on top of Ubuntu. In one line, we will have a base Ubuntu image with Python 2.7, virtualenv, and pip. There are tons of images on DockerHub, but if you would like to start off with a fresh Ubuntu image and build on top of it, you could do that.

  2. Let’s look at the next part of the code:

     LABEL maintainer="Kunal Malhotra, kunal.malhotra1@ibm.com"
     RUN apt-get update
    
  3. Note the maintainer and update the Ubuntu package index. The command is RUN, which is a function that runs the command after it.

     RUN mkdir /app
     WORKDIR /app
     COPY . /app
    
  4. Now it’s time to add the Flask application to the image. For simplicity, copy the application under the /app directory on our Docker Image.

    WORKDIR is essentially a cd in bash, and COPY copies a certain directory to the provided directory in an image. ADD is another command that does the same thing as COPY, but it also allows you to add a repository from a URL. Thus, if you want to clone your git repository instead of copying it from your local repository (for staging and production purposes), you can use that. COPY, however, should be used most of the time unless you have a URL.

  5. Now that we have our repository copied to the image, we will install all of our dependencies, which is defined in the requirements.txt part of the code.

     RUN pip install --no-cache-dir -r requirements.txt
    
  6. We want to expose the port(5000) the Flask application runs on, so we use EXPOSE.

     EXPOSE 5000
    
  7. ENTRYPOINT specifies the entrypoint of your application.
     ENTRYPOINT [ "python" ]
     CMD [ "app.py" ]
    

Build an image from the Dockerfile

Open the terminal and type this command to build an image from your Dockerfile: docker build -t <image_name>:<tag>

build_image

Run your container locally and test

After you build your image succesfully, type: docker run -d -p 5000:5000 app

This command will create a container that contains all the application code and dependencies from the image and runs it locally.

create_container

create_container

Push the image to the IBM Cloud Registry

  1. From your account dashboard, go to IBM Cloud Kubernetes Service.
  2. From the left navigation menu, select Private Repositories.

    private_repositories

  3. Install the Container Registry plug-in.

     ibmcloud plugin install container-registry -r Bluemix
    
  4. Log in to your IBM Cloud account.

     ibmcloud login -a <cloud_foundary_end_point_for_the_region>
    
  5. Name and create your namespace. Use this namespace for the rest of the Quick Start.

     ibmcloud cr namespace-add <namespace>
    
  6. Log your local Docker daemon into the IBM Cloud Container Registry.

     ibmcloud cr login
    
  7. Choose a repository and tag by which you can identify the image.

     docker tag <image_name> <region_url>/<namespace>/<image_name>:<tag>
    
  8. Push the image.

     docker push <region_url>/<namespace>/<image_name>:<tag>
    

    docker_push

  9. Verify that your image is in your private registry.

     ibmcloud cr image-list
    

    verify_image

Create configuration files for Kubernetes

Once the image is successfully uploaded to the private registry, go to your project directory and create two files: deployment.yaml and service.yaml.

create_files

  1. In the deployment.yaml file, paste this code:

     apiVersion: extensions/v1beta1
     kind: Deployment
     metadata:
       name: flask-node-deployment
     spec:
       replicas: 1
       selector:
         matchLabels:
           app: flasknode
       template:
         metadata:
           labels:
             app: flasknode
         spec:
           containers:
           - name: flasknode
             image: registry.ng.bluemix.net/flask-node/app
             imagePullPolicy: Always
             ports:
             - containerPort: 5000
    
  2. In the service.yaml file, paste this code:

     apiVersion: v1
     kind: Service
     metadata:
       name: flask-node-deployment
     spec:
       ports:
       - port: 5000
         targetPort: 5000
       selector:
         app: flasknode
    

Explanation and breakdown of the deployment.yaml code

  1. A deployment named flask-node-deployment is created, indicated by the .metadata.name field.
  2. The deployment creates one replicated pod, indicated by the replicas field.
  3. The selector field defines how the Deployment finds which Pods to manage. In this case, we simply select on one label defined in the Pod template (app: flasknode). However, more sophisticated selection rules are possible, as long as the Pod template itself satisfies the rule.
  4. The pod template’s specification, .template.spec, indicates that the pods run one container, flasknode, which runs the app private registry image.
  5. The deployment opens port 5000 for use by the Pods.

Explanation and breakdown of the service.yaml code

  1. The service.yaml‘s specification will create a new service object named flask-node-deployment which targets TCP port 5000 on any Pod with the “app=flasknode” label. This Service will also be assigned an IP address (sometimes called the cluster IP), which is used by the service proxies (see below). The Service’s selector will be evaluated continuously and the results will be POSTed to an Endpoints object also named flask-node-deployment.

  2. Note that a service can map an incoming port to any targetPort. By default the targetPort will be set to the same value as the port field. Perhaps more interesting is that targetPort can be a string, referring to the name of a port in the backend Pods. The actual port number assigned to that name can be different in each backend Pod. This offers a lot of flexibility for deploying and evolving your Services. For example, you can change the port number that pods expose in the next version of your backend software, without breaking clients.

Deploy your application to Kubernetes

  1. Target the IBM Cloud Kubernetes Service region where you want to work.

     ibmcloud cs region-set us-south
    
  2. Set the context for the cluster in your CLI.

    a. Get the command to set the environment variable and download the Kubernetes configuration files.

     ibmcloud cs cluster-config cluster_kunal
    

    b. Set the KUBECONFIG environment variable. Copy the output from the previous command and paste it in your terminal. The command output should look similar to the following.

    > export KUBECONFIG=/Users/$USER/.bluemix/plugins/container-service/clusters/< cluster_name >/< cluster_configuration_file.yaml>
    
  3. Verify that you can connect to your cluster by listing your worker nodes.

     kubectl get nodes
    
  4. Create the deployment.

     kubectl create -f deployment.yaml
    

    create_deployment

  5. Create the service.

     kubectl create -f service.yaml
    

    create_service

    create_service

  6. Finally, go to your browser and ping the Public IP of your worker node.

    ping_public)ip

Resources and references

  1. Kubernetes Documentation
  2. Deploy a microservices app on IBM Cloud by using Kubernetes
  3. Tutorial: Deploying apps into clusters