Google Cloud Kubernetes Tutorial

Introduction

In the era of Internet, instead of setting up a server on our own, we deploy our website applications widely on cloud service providers. Google Cloud provides several hosting options , including App Engine and Kubernetes Engine, for our websites. For simple applications, App Engine is desired because it is simple and Google Cloud manages everything for you, so you don’t have to worry about scalability or anything else. However, when the applications become sophisticated and require complicated environment setups, Kubernetes Engine becomes a good choice.

In this blog post, I am going to present a self-contained tutorial for setting up a containerized application on Google Cloud Kubernetes Engine.

Dependencies

  • Google Cloud SDK
  • Docker
  • Git

Google Cloud Platform Console actually has all the dependencies installed. We could directly run our command in the console.

Tutorial

Application

In this project, we are going to use Google’s official application as an example. In case Google is going to change its official example, I have forked the repository to my account.

1
2
3
4
5
# Git 
$ git clone https://github.com/leimao/kubernetes-engine-samples.git
$ cd kubernetes-engine-samples/quickstart/python/
$ ls
app.py Dockerfile

The app is a simple Flask app which runs at port 8080 by default and prints “Hello World”. In app.py, we have

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
target = os.environ.get('TARGET', 'World')
return 'Hello {}!\n'.format(target)

if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))

The Dockerfile sets up the most basic environment the app requires and starts app.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Use the official Python image.
# https://hub.docker.com/_/python
FROM python:3.7

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . .

# Install production dependencies.
RUN pip install Flask gunicorn

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app

We could almost treat the app as a black box for now, and just have to remember this app will run and listen to port 8080 in the container.

Docker Container

We set up some configuration variables.

1
2
$ gcloud config set project [PROJECT_ID]
$ gcloud config set compute/zone [COMPUTE_ENGINE_ZONE]

In our case, we use

1
2
$ gcloud config set project leimao-app
$ gcloud config set compute/zone us-central1-a

We have to first build Docker image for our application on our local machine.

1
2
3
4
5
6
7
8
9
10
# Set an environment variable for project_id for convenience.
$ export PROJECT_ID="$(gcloud config get-value project -q)"
$ echo $PROJECT_ID
leimao-app
# Build Docker image
$ docker build -t gcr.io/${PROJECT_ID}/hello-app:v1 .
# Check the Docker image built
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/python-app/hello-app v1 c0cd4f15736d 17 seconds ago 929MB

The next step is to upload the Docker image to the Google Cloud container registry. To do that, we need to authenticate the Google Cloud container registry.

1
$ gcloud auth configure-docker

Upload the Docker image to the Google Cloud container registry.

1
$ docker push gcr.io/${PROJECT_ID}/hello-app:v1

To check the Docker image has been uploaded successfully.

1
2
3
4
5
6
7
8
# Check the image
$ gcloud container images list --repository=gcr.io/${PROJECT_ID}
NAME
gcr.io/leimao-app/hello-app
# Check the tag
$ gcloud container images list-tags gcr.io/${PROJECT_ID}/hello-app
DIGEST TAGS TIMESTAMP
98c9598a764a v1 2019-08-06T16:04:53

Create Kubernetes Container Cluster

The best way to configure a container cluster is actually to go to https://console.cloud.google.com/kubernetes, select the right configurations, and click command line to get the command line for creating the cluster.

Here, for simplicity, we create a cluster of 3 nodes and use the rest of the configurations by default. Among the 3 nodes, one node is the master node and the rest two nodes are worker nodes. This step may take a while.

1
2
3
$ gcloud container clusters create hello-cluster --num-nodes=3
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
hello-cluster us-central1-a 1.12.8-gke.10 35.192.51.130 n1-standard-1 1.12.8-gke.10 3 RUNNING

To check the cluster available in the account.

1
2
3
$ gcloud container clusters list
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
hello-cluster us-central1-a 1.12.8-gke.10 35.192.51.130 n1-standard-1 1.12.8-gke.10 3 RUNNING

To check the VM instances created.

1
2
3
4
5
$ gcloud compute instances list
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
gke-hello-cluster-default-pool-c162bbac-5jrp us-central1-a n1-standard-1 10.128.0.28 35.222.201.163 RUNNING
gke-hello-cluster-default-pool-c162bbac-6h9v us-central1-a n1-standard-1 10.128.0.17 35.202.61.204 RUNNING
gke-hello-cluster-default-pool-c162bbac-fnhz us-central1-a n1-standard-1 10.128.0.76 35.225.198.205 RUNNING

To check the node information.

1
$ kubectl describe nodes

Deploy Application on Cluster

You may have created a Kubernetes engine cluster previously, or you would like to choose one of the many existing clusters, we need to specify the cluster we want to deploy our app container.

1
$ gcloud container clusters get-credentials hello-cluster

To deploy our application with the app name of hello-web using the image registry we just created.

We create a yaml file named deployment.yaml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
labels:
app: hello
spec:
selector:
matchLabels:
app: hello
tier: web
template:
metadata:
labels:
app: hello
tier: web
spec:
containers:
- name: hello-app
image: gcr.io/leimao-app/hello-app:v1
ports:
- containerPort: 8080
# Set environment variables for the container.
env:
- name: PORT
value: "8080"

Then we run

1
$ kubectl apply -f deployment.yaml

The app would be deployed then. Apps in Kubernetes are called Pods.

To check the deployment status,

1
2
3
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
hello-web 1 1 1 1 84s

To check the pods status,

1
2
3
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-web-67878c7d86-v8mfj 1/1 Running 0 2m23s

Sometimes the STATUS will be abnormal values, which means that something is going wrong in your container. You would have to delete the deployment by

1
$ kubectl delete deployment hello-web

In some tutorials from Google Cloud, they suggest creating deployment using command line arguments rather than using yaml file.

1
$ kubectl create deployment hello-web --image=gcr.io/${PROJECT_ID}/hello-app:v1

I think this method is inferior because you would not configure a lot of things in detail and you would not be able to pass an environment variable to the container. In our case, the environment variable is necessary for the container to start correctly.

Expose Application to Internet

Although the application has been deployed, it has not been exposed to the internet so outsiders would not be able to see.

To expose the application to the internet, again we would be using yaml file. Create a service.yaml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# The hello service provides a load-balancing proxy over the hello-app
# pods. By specifying the type as a 'LoadBalancer', Kubernetes Engine will
# create an external HTTP load balancer.
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: LoadBalancer
selector:
app: hello
ports:
- port: 80
targetPort: 8080
1
$ kubectl apply -f service.yaml

Check the services. Allocating external IP might be slow.

1
2
3
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.15.244.113 <pending> 80:32759/TCP 8s

Check the services again after a while.

1
2
3
4
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.15.244.113 35.202.208.161 80:32759/TCP 111s
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 136m

It is equivalent to do this using command line arguments.

1
$ kubectl expose deployment hello-web --type=LoadBalancer --port 80 --target-port 8080

The --port flag specifies the port number configured on the Load Balancer, and the --target-port flag specifies the port number that the hello-app container is listening on.

Check whether the application has been exposed.

1
2
3
4
$ curl 35.202.208.161
Hello World!
$ curl 35.202.208.161:80
Hello World!

Alternatively, open our web browser and go to http://35.202.208.161/ or http://35.202.208.161:80/. Note that currently this application only supports http protocol.

Upgrade Kubernetes Cluster

We would like to add two additional replicas (cluster candidates) to deployment for the same application. We modify the deployment.yaml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
labels:
app: hello
spec:
selector:
matchLabels:
app: hello
tier: web
# Set replicas here
replicas: 3
template:
metadata:
labels:
app: hello
tier: web
spec:
containers:
- name: hello-app
image: gcr.io/leimao-app/hello-app:v1
ports:
- containerPort: 8080
# Set environment variables for the container.
env:
- name: PORT
value: "8080"

To apply the changes,

1
$ kubectl apply -f deployment.yaml

To check the deployment status,

1
2
3
$ kubectl get deployment hello-web
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
hello-web 3 3 3 3 67m

To check the pod status,

1
2
3
4
5
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-web-67878c7d86-jgphn 1/1 Running 0 72s
hello-web-67878c7d86-v8mfj 1/1 Running 0 67m
hello-web-67878c7d86-z6mbl 1/1 Running 0 72s

But we still use one external IP.

1
2
3
4
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.15.244.113 35.202.208.161 80:31209/TCP 49m
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 3h4m

Now, you have multiple instances of your application running independently of each other and you can use the kubectl scale command to adjust the capacity of your application.

The load balancer you provisioned in the previous step will start routing traffic to these new replicas automatically.

Deploy New Version of Application

We would like to change our “Hello World” application to “Hello Underworld” application. So we are going to change app.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
target = os.environ.get('TARGET', 'Underworld')
return 'Hello {}!\n'.format(target)

if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))

Build and upload the new container.

1
2
$ docker build -t gcr.io/${PROJECT_ID}/hello-app:v2 .
$ docker push gcr.io/${PROJECT_ID}/hello-app:v2

We also change our deployment.yaml to use the new container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
labels:
app: hello
spec:
selector:
matchLabels:
app: hello
tier: web
replicas: 3
template:
metadata:
labels:
app: hello
tier: web
spec:
containers:
- name: hello-app
image: gcr.io/leimao-app/hello-app:v2
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"

To apply the changes,

1
$ kubectl apply -f deployment.yaml

We could check that the update was successful.

1
2
$ curl 35.202.208.161
Hello Underworld!

Clearn Up Application

To delete service,

1
2
$ kubectl delete service hello
service "hello" deleted

We could see the service was deleted.

1
2
3
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 3h42m

But the container cluster instance was still there.

1
2
3
$ gcloud container clusters list
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
hello-cluster us-central1-a 1.12.8-gke.10 35.192.51.130 n1-standard-1 1.12.8-gke.10 3 RUNNING

We delete the container cluster.

1
$ gcloud container clusters delete hello-cluster

The Docker image was still in the container registry. We check the container image information.

1
2
3
$ gcloud container images list --repository=gcr.io/${PROJECT_ID}
NAME
gcr.io/leimao-app/hello-app

Check the tag information.

1
2
3
4
$ gcloud container images list-tags gcr.io/leimao-app/hello-app
DIGEST TAGS TIMESTAMP
e9652084691e v2 2019-08-06T20:04:02
98c9598a764a v1 2019-08-06T16:04:53

We delete all the images related to hello-app.

1
$ gcloud container images delete gcr.io/${PROJECT_ID}/hello-app:v1 gcr.io/${PROJECT_ID}/hello-app:v2

Now there are no images for hello-app in the container registry.

1
$ gcloud container images list --repository=gcr.io/${PROJECT_ID}

Final Remarks

It seems that it is more desirable to use yaml to configure the Kubernetes settings instead of using command line arguments.

References

Author

Lei Mao

Posted on

08-06-2019

Updated on

08-06-2019

Licensed under


Comments