Deploy, Scale and Update Your Website on Google Kubernetes Engine

Running websites and applications is hard. Things go wrong when they shouldn't, servers crash, increase in demand causes more resources to be utilized and making changes without downtime is complicated and stressful. Imagine if there was a tool that could help you do all this and even allow you to automate it! With Kubernetes, all of this is not only possible, it's easy! Today we will assume the role of a developer at a fictional company, Fancy Store, running an ecommerce website. Due to problems with scaling and outages, we are tasked with deploying our application onto the Google Kubernetes Engine (GKE)!

The exercises are ordered to reflect a common cloud developer experience:

  1. Create a GKE cluster
  2. Create a Docker container
  3. Deploy the container to GKE
  4. Expose the container via a service
  5. Scale the container to multiple replicas
  6. Modify the website
  7. Rollout a new version with zero downtime

Architecture diagram

What you'll learn

  • How to create a Google Kubernetes Engine cluster
  • How to create a Docker image
  • How to deploy Docker images to Kubernetes
  • How to scale an application on Kubernetes
  • How to perform a rolling update on Kubernetes

Prerequisites

  • A Google Cloud Platform account with administrative access to create projects or a project with Project Owner role
  • A basic understanding of Docker and Kubernetes, if not, please review the material below:

Docker - https://docs.docker.com/

Kubernetes - https://kubernetes.io/docs/home/

Self-paced environment setup

If you don't already have a Google Account (Gmail or Google Apps), you must create one. Sign-in to Google Cloud Platform console (console.cloud.google.com) and create a new project:

Screenshot from 2016-02-10 12:45:26.png

Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID.

Next, you'll need to enable billing in the Developers Console in order to use Google Cloud resources.

Running through this codelab shouldn't cost you more than a few dollars, but it could be more if you decide to use more resources or if you leave them running (see "cleanup" section at the end of this document). Google Container Engine pricing is documented here.

New users of Google Cloud Platform are eligible for a $300 free trial.

Google Cloud Shell

While Google Cloud and Kubernetes can be operated remotely from your laptop, in this codelab we will be using Google Cloud Shell, a command line environment running in the Cloud.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory, and runs on the Google Cloud, greatly enhancing network performance and authentication. This means that all you will need for this codelab is a browser (yes, it works on a Chromebook).

To activate Google Cloud Shell, from the developer console simply click the button on the top right-hand side (it should only take a few moments to provision and connect to the environment):

activateCloudShell.png

Click the "Start Cloud Shell" button:

Screen Shot 2017-06-14 at 10.13.43 PM.png

Once connected to the cloud shell, you should see that you are already authenticated and that the project is already set to your PROJECT_ID :

gcloud auth list

Command output

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

Cloud Shell also sets some environment variables by default which may be useful as you run future commands.

echo $GOOGLE_CLOUD_PROJECT

Command output

<PROJECT_ID>

If for some reason the project is not set, simply issue the following command :

gcloud config set project <PROJECT_ID>

Looking for your PROJECT_ID? Check out what ID you used in the setup steps or look it up in the console dashboard:

Project_ID.png

IMPORTANT: Finally, set the default zone and project configuration:

gcloud config set compute/zone us-central1-f

You can choose a variety of different zones. Learn more in the Regions & Zones documentation.

Now that you have your working developer environment, we need a Kubernetes cluster to deploy our website to! Before we can create a cluster, we need to make sure the proper API's are enabled. Run the following command to enable the containers api:

gcloud services enable container.googleapis.com

Now we are ready to create our cluster! Follow the steps below to create a GKE cluster named fancy-cluster with 3 nodes.

gcloud container clusters create fancy-cluster --num-nodes 3

It may take several minutes for the cluster to be created. Once the command has completed, run the following command and see the cluster's three worker VM instances:

gcloud compute instances list

Output:

NAME                                          ZONE        MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
gke-fancy-cluster-default-pool-ad92506d-1ng3  us-east4-a  n1-standard-1               10.150.0.7   XX.XX.XX.XX    RUNNING
gke-fancy-cluster-default-pool-ad92506d-4fvq  us-east4-a  n1-standard-1               10.150.0.5   XX.XX.XX.XX    RUNNING
gke-fancy-cluster-default-pool-ad92506d-4zs3  us-east4-a  n1-standard-1               10.150.0.6   XX.XX.XX.XX    RUNNING

You can also view your Kubernetes cluster and related information in the Google Cloud console. Click the menu button in the top left and scroll down to Kubernetes Engine and click Clusters. You should see your cluster named fancy-cluster.

Congratulations! You have just created your first Kubernetes cluster!

Since this is an existing website, we will just need to clone the source from our git repo, so we can focus on creating Docker images and deploying to GKE.

Run the following commands to clone the git repo to your Cloud Shell instance and change to the appropriate directory. We will also install the NodeJS dependencies so we can test our application before deploying.

cd ~
git clone https://github.com/googlecodelabs/monolith-to-microservices.git
cd ~/monolith-to-microservices
./setup.sh

This will clone our Github repo, change to the directory and install the dependencies needed to run our application locally. It may take a few minutes for this script to run.

Let's do our due diligence and test our application, run the following command to start our web server:

cd ~/monolith-to-microservices/monolith
npm start

Output:

Monolith listening on port 8080!

You can preview your application by clicking the web preview icon and selecting Preview on port 8080.

This should open a new window where you can see our Fancy Store in action!

You can close this window after viewing the website and to stop the web server process, press CTRL+C in the terminal window.

Now that we have our source files ready to go, it is time to Dockerize our application!

Normally you would have to take a two step approach that entails building a docker container and pushing it to a registry to store the image for GKE to pull from. But we can make life easier, we can use Google Cloud Build to build the Docker container and put the image in the Google Cloud Container Registry with a single command! This allows us to issue a single command to build and move our image to the container registry. To view the manual process of creating a docker file and pushing it you can go here.

Google Cloud Build will compress the files from the directory and move them to a Google Cloud Storage bucket. The build process will then take all the files from the bucket and use the Dockerfile to run the Docker build process. Since we specified the --tag flag with the host as gcr.io for the Docker image, the resulting Docker image will be pushed to the Google Cloud Container Registry.

First we need to make sure we have the Cloud Build API enable, run the following command to enable it:

gcloud services enable cloudbuild.googleapis.com

After the API is enabled, run the following command in your cloud shell terminal to start the build process:

cd ~/monolith-to-microservices/monolith
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:1.0.0 .

This process will take a few minutes, but after it is completed, there will be output in the terminal similar to the following:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ID                                    CREATE_TIME                DURATION  SOURCE                                                                                  IMAGES                              STATUS
1ae295d9-63cb-482c-959b-bc52e9644d53  2019-08-29T01:56:35+00:00  33S       gs://<PROJECT_ID>_cloudbuild/source/1567043793.94-abfd382011724422bf49af1558b894aa.tgz  gcr.io/<PROJECT_ID>/monolith:1.0.0  SUCCESS

To view your build history or watch the process in real time, you can go to the Google Cloud console. Click the menu button on the top left and scroll down to Tools → Cloud Build and click History. Here you can see a list of all your previous builds, there should only be 1 that you just created.

If you click on the build id, you can see all the details for that build including the log output.

From the build details page you can view the container image that was created by clicking on the image name in the build information section.

Now that we have containerized our website and pushed our container to the Google Container Registry, it is time to deploy to Kubernetes!

To deploy and manage applications on a GKE cluster, you must communicate with the Kubernetes cluster management system. You typically do this by using the kubectl command-line tool.

Kubernetes represents applications as Pods, which are units that represent a container (or group of tightly-coupled containers). The Pod is the smallest deployable unit in Kubernetes. In this tutorial, each Pod contains only your monolith container.

To deploy our application we will create a Deployment resource. The Deployment manages multiple copies of your application, called replicas, and schedules them to run on the individual nodes in your cluster. In this case, the Deployment will be running only one Pod of your application. Deployments ensure this by creating a ReplicaSet. The ReplicaSet is responsible for making sure the number of replicas specified are always running.

The kubectl create deployment command below causes Kubernetes to create a Deployment named monolith on your cluster with 1 replica.

Run the following command to deploy your application:

kubectl create deployment monolith --image=gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:1.0.0

Verify Deployment

To verify the Deployment was created successfully, run the following command, it may take a few moments for the pod status to be Running:

kubectl get all

Output:

NAME                            READY   STATUS    RESTARTS   AGE
pod/monolith-7d8bc7bf68-htm7z   1/1     Running   0          6m21s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.27.240.1   <none>        443/TCP   24h

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/monolith   1         1         1            1           20m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/monolith-7d8bc7bf68   1         1         1       20m

This output shows us several things. We can see our Deployment which is current, our ReplicaSet with desired pod count of 1, and our Pod which is running. Looks like everything was created successfully!

To view these resources individually, you can run commands to see them separately as well:

# Show pods
kubectl get pods

# Show deployments
kubectl get deployments

# Show replica sets
kubectl get rs

#You can also combine them
kubectl get pods,deployments

To see the full benefit of Kubernetes, let's simulate a server crash and delete the pod and see what happens!

Copy your pod name from the previous command and run the following command to delete it:

kubectl delete pod/<POD_NAME>

If you are fast enough, you can run the previous command to see all again and you should see two pods, one terminating and the other creating or running:

kubectl get all

Output:

NAME                            READY   STATUS        RESTARTS   AGE
pod/monolith-7d8bc7bf68-2bxts   1/1     Running       0          4s
pod/monolith-7d8bc7bf68-htm7z   1/1     Terminating   0          9m35s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.27.240.1   <none>        443/TCP   24h

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/monolith   1         1         1            1           24m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/monolith-7d8bc7bf68   1         1         1       24m

Why did this happen? The ReplicaSet saw that the pod was terminating and triggered a new pod to keep up the desired replica count. Later on we will see how we can scale out to ensure we have several instances running so if one goes down, our users won't see any downtime!

We have deployed our application on GKE, but we don't have a way of accessing it outside of the cluster. By default, the containers you run on GKE are not accessible from the Internet, because they do not have external IP addresses. You must explicitly expose your application to traffic from the Internet via a Service resource. A Service provides networking and IP support to your application's Pods. GKE creates an external IP and a Load Balancer (subject to billing) for your application.

Run the following command to expose your website to the Internet:

kubectl expose deployment monolith --type=LoadBalancer --port 80 --target-port 8080

Accessing The Service

GKE assigns the external IP address to the Service resource—not the Deployment. If you want to find out the external IP that GKE provisioned for your application, you can inspect the Service with the kubectl get service command:

kubectl get service

Output:

NAME         CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
monolith     10.3.251.122    203.0.113.0     80:30877/TCP     3d

Once you've determined the external IP address for your application, copy the IP address. Point your browser to this URL (such as http://203.0.113.0) to check if your application is accessible.

You should see the same website we tested earlier in your! Congratulations! You now have your website fully running on Kubernetes!

Now that we have a running instance of our application in GKE and we have exposed it to the internet, our website has become extremely popular! We need a way to scale our application to multiple instances so we can handle all this traffic. Let's see how we can scale our application up to 3 replicas.

Run the following command to scale you deployment up to 3 replicas:

kubectl scale deployment monolith --replicas=3

Verify Scaled Deployment

To verify the Deployment was scaled successfully, run the following command:

kubectl get all

Output:

NAME                            READY   STATUS    RESTARTS   AGE
pod/monolith-7d8bc7bf68-2bxts   1/1     Running   0          36m
pod/monolith-7d8bc7bf68-7ds7q   1/1     Running   0          45s
pod/monolith-7d8bc7bf68-c5kxk   1/1     Running   0          45s

NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
service/kubernetes   ClusterIP      10.27.240.1    <none>         443/TCP        25h
service/monolith     LoadBalancer   10.27.253.64   XX.XX.XX.XX   80:32050/TCP   6m7s

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/monolith   3         3         3            3           61m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/monolith-7d8bc7bf68   3         3         3       61m

You should now see 3 instances of your pod running. Also, note that your deployment and replica set now have a desired count of 3.

Your marketing team has asked you to change the homepage for your site. They think it should be more informative of who your company is and what you actually sell. In this section, we will add some text to the homepage to make the marketing team happy! It looks like one of our developers already created the changes with the file name index.js.new. We can just copy this file to index.js and our changes should be reflected. Follow the instructions below to make the appropriate changes.

Run the following commands copy the updated file to the correct file name and then print its contents to verify the changes:

cd ~/monolith-to-microservices/react-app/src/pages/Home
mv index.js.new index.js
cat ~/monolith-to-microservices/react-app/src/pages/Home/index.js

The resulting code should look like this:

/*
Copyright 2019 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
  root: {
    flexGrow: 1
  },
  paper: {
    width: "800px",
    margin: "0 auto",
    padding: theme.spacing(3, 2)
  }
}));
export default function Home() {
  const classes = useStyles();
  return (
    <div className={classes.root}>
      <Paper className={classes.paper}>
        <Typography variant="h5">
          Fancy Fashion &amp; Style Online
        </Typography>
        <br />
        <Typography variant="body1">
          Tired of mainstream fashion ideas, popular trends and societal norms?
          This line of lifestyle products will help you catch up with the Fancy trend and express your personal style.
          Start shopping Fancy items now!
        </Typography>
      </Paper>
    </div>
  );
}

We updated the React components, but we need to build the React app to generate the static files. Run the following command to build the React app and copy it into the monolith public directory:

cd ~/monolith-to-microservices/react-app
npm run build:monolith

Now that our code is updated, we need to rebuild our Docker container and publish it to the Google Cloud Container Registry. We can use the same command as we did earlier, except this time, we will update the version label!

Run the following command to trigger a new cloud build with an updated image version of 2.0.0:

cd ~/monolith-to-microservices/monolith

#Feel free to test your application
npm start

gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:2.0.0 .

In the next section we will use this image to update our application with zero downtime.

The changes are completed and the marketing team is happy with your updates! It is time to update the website without interruption to the users. Follow the instructions below to update your website.

GKE's rolling update mechanism ensures that your application remains up and available even as the system replaces instances of your old container image with your new one across all the running replicas.

From the command line, we can tell Kubernetes that we want to update the image for our deployment to a new version with the following command:

kubectl set image deployment/monolith monolith=gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:2.0.0

Verify Deployment

You can validate your deployment update by running the following command:

kubectl get pods

Output:

NAME                        READY   STATUS              RESTARTS   AGE
monolith-584fbc994b-4hj68   1/1     Terminating         0          60m
monolith-584fbc994b-fpwdw   1/1     Running             0          60m
monolith-584fbc994b-xsk8s   1/1     Terminating         0          60m
monolith-75f4cf58d5-24cq8   1/1     Running             0          3s
monolith-75f4cf58d5-rfj8r   1/1     Running             0          5s
monolith-75f4cf58d5-xm44v   0/1     ContainerCreating   0          1s

Here you will see 3 new pods being created and your old pods being shutdown. You can tell by the age which are new and which are old. Eventually, you will only see 3 pods again which will be your 3 updated pods.

To verify our changes, navigate to the external IP of the LoadBalancer service again and notice that our application has been updated.

Run the following command to list the services and view the IP address if you forgot it:

kubectl get svc

Your web site should now be displaying the text we just added to the homepage component!

If you are planning on doing the other labs in this series, don't delete your cluster yet to save time on other labs. You can delete the cluster if you are not going to continue with labs in this series.

Delete Git Repository

cd ~
rm -rf monolith-to-microservices

Delete Google Container Registry Images

NOTE: If you created other versions, you can use the same syntax to delete those images as well, this codelab assumes you followed exactly and only have two tags

# Delete the container image for version 1.0.0 of our monolith
gcloud container images delete gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:1.0.0 --quiet

# Delete the container image for version 2.0.0 of our monolith
gcloud container images delete gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:2.0.0 --quiet

Delete Google Cloud Build Artifacts from Google Cloud Storage

NOTE: If you used Cloud Build for artifacts other than this codelab, you will have to manually delete your source from Google Cloud Storage bucket gs://<PROJECT_ID>_cloudbuild/source

# The following command will take all source archives from all builds and delete them from cloud storage

# Run this command to print all sources:
# gcloud builds list | awk 'NR > 1 {print $4}' 

gcloud builds list | awk 'NR > 1 {print $4}' | while read line; do gsutil rm $line; done

Delete GKE Service

kubectl delete service monolith
kubectl delete deployment monolith

Delete GKE Cluster

gcloud container clusters delete fancy-cluster

NOTE: This command may take a little while.

You successfully deployed, scaled, and updated your website on GKE. You are now experienced with Docker and Kubernetes!

Additional Resources