Deploy, scale, and update your website with Google Kubernetes Engine (GKE)

1. Introduction

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 a tool that could help you do all that and even allow you to automate it! With GKE, all of that is not only possible, it's easy! In this codelab, you assume the role of a developer running an ecommerce website for a fictional company—Fancy Store. Due to problems with scaling and outages, you're tasked with deploying your application to GKE!

The exercises are ordered to reflect a common cloud developer's 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. Roll out a new version with zero downtime.

Architecture diagram

ddba666bd2b02d0d.png

What you'll learn

  • How to create a GKE 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 Account with administrative access to create projects or a project with a project-owner role
  • A basic understanding of Docker and Kubernetes (If you lack a basic understanding, then please review Docker and Kubernetes now.)

2. Environment setup

Self-paced environment setup

If you don't already have a Google Account, then you must create one. Sign into Google Cloud Console and create a new project.

53dad2cefdae71da.png

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

Remember, the project ID is 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 as PROJECT_ID later.

Next, you'll need to enable billing in the Cloud Console to use Google Cloud resources. New users of Google Cloud are eligible for a $300 free trial. If you're not a new user, then don't worry because the codelab shouldn't cost you more than a few dollars. However, the codelab could cost you more money if you use more resources or leave them running (see "clean up" section at the end). For more information, see Pricing.

Cloud Shell

While you can remotely operate Google Cloud and GKE with your laptop, you will use Cloud Shell—a command-line environment running in the Cloud—for the codelab.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory and runs in 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).

  1. To activate Cloud Shell from the Cloud Console, simply click Activate Cloud Shell fEbHefbRynwXpq1vj2wJw6Dr17O0np8l-WOekxAZYlZQIORsWQE_xJl-cNhogjATLn-YxLVz8CgLvIW1Ncc0yXKJsfzJGMYgUeLsVB7zSwz7p6ItNgx4tXqQjag7BfWPcZN5kP-X3Q (it should only take a few moments to provision and connect to the environment).

I5aEsuNurCxHoDFjZRZrKBdarPPKPoKuExYpdagmdaOLKe7eig3DAKJitIKyuOpuwmrMAyZhp5AXpmD_k66cBuc1aUnWlJeSfo_aTKPY9aNMurhfegg1CYaE11jdpSTYNNIYARe01A

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

Once connected to 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>

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 Cloud Console dashboard:

R7chO4PKQfLC3bvFBNZJALLTUiCgyLEq_67ECX7ohs_0ZnSjC7GxDNxWrJJUaoM53LnqABYamrBJhCuXF-J9XBzuUgaz7VvaxNrkP2TAn93Drxccyj2-5zz4AxL-G3hzxZ4PsM5HHQ

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>
  1. Finally, set the default zone and project configuration.
gcloud config set compute/zone us-central1-f

You can choose a variety of different zones. For more information, see Regions & Zones.

3. Create a GKE cluster

Now that you have your working developer environment, you need a GKE cluster to deploy your website to! Before you create a cluster, you need to ensure that the proper APIs are enabled. Run the following command to enable the containers API:

gcloud services enable container.googleapis.com

Now, you can create your cluster! Follow the steps below to create a 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. Afterward, run the following command and see the cluster's three worker virtual machine (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 cluster and related information in the Cloud Console. Click the menu button in the top-left corner, scroll down to Kubernetes Engine, and click Clusters. You should see your cluster named fancy-cluster.

795c794b03c5d2b0.png

6b394dfb8a6031f2.png

Congratulations! You created your first cluster!

4. Clone source repository

Because this is an existing website, you only need to clone the source from the repository so that you can focus on creating Docker images and deploying to GKE.

Run the following commands to clone the source repository to your Cloud Shell instance and change it to the appropriate directory. You will also install the Node.js dependencies so that you can test your application before deploying it.

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

That clones the repository, changes the directory, and installs the dependencies needed to locally run your application. It may take a few minutes for that script to run.

Do your due diligence and test your application. Run the following command to start your 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 in the Cloud Shell menu and selecting Preview on port 8080.

5869738f0e9ec386.png

That should open a new window where you can see your Fancy Store in action!

9ed25c3f0cbe62fa.png

You can close that window after viewing the website. Press Control+C (Windows or Mac) in the terminal window to stop the web server process.

5. Create Docker container with Cloud Build

Now that your source files are ready to go, it's time to Dockerize your 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 that GKE pulls from. However, you can make life easier by using Cloud Build to create the Docker container and put the image in the Container Registry with a single command! (To view the manual process of creating a docker file and pushing it, see Quickstart for Container Registry.)

Cloud Build compresses the files from the directory and moves them to a Cloud Storage bucket. The build process then takes the files from the bucket and uses the Dockerfile to run the Docker build process. Because you specified the --tag flag with the host as gcr.io for the Docker image, the resulting Docker image gets pushed to the Container Registry.

First, you need to enable the Cloud Build API by running the following command:

gcloud services enable cloudbuild.googleapis.com

After the API is enabled, run the following command in Cloud Shell to start the build process:

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

That process takes a few minutes, but after it's completed, you can see the following output in the terminal:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
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 Cloud Console. Click the menu button in the top-left corner, scroll down to Ci/CD, then click Cloud Build, and finally click History. There, you can see a list of your previous builds, but there should only be the one that you created.

4c753ede203255f6.png

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

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

6e88ed1643dfe629.png

6. Deploy container to GKE

Now that you containerized your website and pushed the container to the Container Registry, you can deploy it to Kubernetes!

To deploy and manage applications on a GKE cluster, you must communicate with the Kubernetes cluster-management system. You typically do that 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. Here, each Pod only contains your monolith container.

To deploy your application, you need to create a Deployment. A 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 run only one Pod of your application. Deployments ensure that by creating a ReplicaSet. The ReplicaSet is responsible for making sure that the number of replicas specified are always running.

The kubectl create deployment command 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 that 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

That output shows you several things. You can see your Deployment, which is current; your ReplicaSet, with a desired Pod count of one; and your Pod, which is running. Looks like you successfully created everything!

To individually view your resources, you can run the following commands:

# 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, you can simulate a server crash, 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 that happen? The ReplicaSet saw that the pod was terminating and triggered a new pod to keep up the desired replica count. Later on, you'll see how to scale to ensure that you have several instances running so that if one goes down, your users won't see any downtime!

7. Expose GKE deployment

You deployed your application to GKE, but you 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 for your app's Pods. GKE creates an external IP and a load balancer (subject to billing) for your app.

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

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

Output:

service/monolith exposed

Accessing the service

GKE assigns the external IP address to the Service resource—not the Deployment. If you want to find 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

After you determine the external IP address for your app, copy it. Point your browser to that URL (such as http://203.0.113.0) to check whether your app is accessible.

9ed25c3f0cbe62fa.png

You should see the same website that you tested earlier! Congratulations! Your website fully runs on Kubernetes!

8. Scale GKE deployment

Now that you have a running instance of your app in GKE and exposed it to the internet, your website has become extremely popular! You need a way to scale your app to multiple instances so that you can handle the traffic. Learn to scale your application to up to three replicas.

Run the following command to scale your deployment up to three replicas:

kubectl scale deployment monolith --replicas=3

Output:

deployment.apps/monolith scaled

Verify scaled deployment

To verify that 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 see three instances of your Pod running. Also, note that your Deployment and ReplicaSet now have a desired count of three.

9. Make changes to the website

Your marketing team asked you to change your website's homepage. They think that it should be more informative by explaining what your company is and what you actually sell. In this section, you'll 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. You can copy the file to index.js and your 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 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>
  );
}

You updated the React components, but you 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 your code is updated, you need to rebuild your Docker container and publish it to the Container Registry. You can use the same command as earlier, except this time, you'll 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 .

Press Control+C (Windows or Mac) in the terminal window to stop the web server process.

In the next section, you'll use that image to update your application with zero downtime.

10. Update website with zero downtime

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

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

From the command line, you can tell Kubernetes that you want to update the image for your Deployment to a new version with the following command:

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

Output:

deployment.apps/monolith image updated

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

You see three new Pods being created and your old pods being shut down. You can tell by the ages which are new and which are old. Eventually, you will only see three Pods again, which will be your three updated Pods.

To verify your changes, navigate to the external IP of the load balancer again and notice that your app has been updated.

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

kubectl get svc

Your website should display the text that you added to the homepage component!

8006c9938dbd5aa5.png

11. Clean up

Delete Git repository

cd ~
rm -rf monolith-to-microservices

Delete Container Registry images

NOTE: If you created other versions, then you can use the same syntax to delete those images as well. This codelab assumes that you 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 Cloud Build artifacts from Cloud Storage

NOTE: If you used Cloud Build for artifacts other than this codelab, you will have to manually delete your source from the 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.

12. Congratulations!

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

Additional resources