What You'll Learn

  1. Remember to use the same Google account you used to register for the workshop
  2. Recommended: Follow the workshop using the Google Cloud Cloud Console and Cloud Shell
  3. In your lab project, you'll find a GKE kubernetes cluster pre-provisioned for you
  4. Fetch your GKE cluster credentials into your kubectl config: gcloud container clusters get-credentials lab-cluster --location europe-west4-a, where lab-cluster is the name of your cluster and europe-west4-a is the region
    1. If needed, change the cluster name and location
  5. Test your kubectl config: kubectl version, kubectl cluster-info
  6. Enable kubectl autocompletion for Bash: source <(kubectl completion bash)
    1. Optionally, enable it permanently for your user: echo 'source <(kubectl completion bash)' >>~/.bashrc
  7. Clone this repo in your home directory: cd ~, git clone https://github.com/Indavelopers/codelabs-indavelopers-com.git
    1. You can also use a different location. If so, mind your repo location when changing directories in next steps

Kubernetes deploys and manages containerized applications, using the OCI container standard. We will containerize a Python Flask hello-world webapp.

Webapp

The webapp code is in codelabs-content/introduction-to-k8s/webapp/src:

Check the app locally:

  1. Work in the webapp dir: cd ~/codelabs-indavelopers-com/codelabs/introduction-to-k8s/webapp/src
  2. Activate the virtual environment: source venv/bin/activate
  3. Install Python dependencies: pip install -r requirements
  4. Run Flask in debug mode: flask --app main run --debug
  5. Check the app locally on localhost:5000 with the Cloud Shell "Web preview" option.
    1. Cancel the Flask debug server with Ctrl+C
    2. Deactivate the virtual environment: deactivate

Containerize the webapp

Create a container image using the provided Dockerfile and push it to the provided container repository in Google Artifact Registry:

  1. Check the envvar setting your Google Cloud project ID in Cloud Shell: echo $GOOGLE_CLOUD_PROJECT
    1. If needed, change the project ID (YOUR_PROJECT_ID) and set up the envvar again: gcloud config set project YOUR_PROJECT_ID, export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
  2. Create the container image locally with Docker: docker build -t europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1 .
  3. Check the containerized app locally on localhost:5000: docker run -p 5000:5000 europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1
  4. Authenticate your local Docker installation with your user account: gcloud auth configure-docker europe-west4-docker.pkg.dev
  5. Push the container image to Google Artifact Registry: docker push europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1
  6. Check the container image on Google Artifact Registry: In the Cloud Console, navigate to Google Artifact Registry, check the lab-repo Docker repository, and find your pushed container image

"Kubernetes is my container shepherd, it maintains my containerized applications for me."

  1. Kubernetes is an OSS container orchestrator for OCI containers maintained by the CNCF, initially developed by Google based on Borg.
  2. Kubernetes acts as an intermediate declarative layer, hiding the complex infrastructure from the user.
  3. This OSS intermediate layer standarizes the underlying infrastructure, so that the app can be migrated ‘as is' from one environment to another, and the user uses the same API, commands and tools.
  4. Kubernetes was designed from the ground up to be a "platform for developing platforms", with API extensibility in mind.
  5. The original design goal for Kubernetes was to become the single cluster for all applications and workloads, sharing the same resources in a more compact manner (From Google to the world: The Kubernetes origin story)

Kubernetes follows:

Architecture

Kubernetes architecture

Kubernetes Architecture

(Original image)

Pods

Pods

Pods

(Original image)

Running a Pod

You can create Kubernetes objects, like a Pod, using imperative commands. This is useful for quick tests and learning, but for production workloads, it's recommended to use declarative manifests (YAML files).

  1. Run a Pod named webapp using the webapp image you pushed to Artifact Registry: kubectl run webapp --image=europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1
  2. Check the status of your Pod. Wait for it to be in the Running state: kubectl get pods
  3. To access the application running inside the Pod from your Cloud Shell, you can use port-forwarding. This will forward traffic from a port on your local machine (Cloud Shell) to the port inside the Pod: kubectl port-forward pod/webapp 5000:5000
  4. Now you can use the Cloud Shell "Web preview" on port 5000 to see your "Hello, world!" message.
  5. Once you are done, stop the port-forwarding with Ctrl+C and delete the Pod to clean up your environment: kubectl delete pod webapp

Deploying the webapp

Declarative approach

With a declarative approach, you can manage any object with full idempotency:

Let's test this, checking at each step if the Deployment is changed or unchanged, by using kubectl apply -f MANIFEST_FILE.yaml:

Control loop

Kubernetes control plane's kube-controller-manager runs a control loop for all Kubernetes controllers, like Deployments.

The control loop checks the Objects status and spec, and if it finds a diff, performs any remediation action needed to get the status back to the desired state.

Namespaces

Namespaces allows the separation of operations, access control, and resource asignation for different applications.

Labels

Labels allow to group together several resources, even of different kinds, and can also be used as selectors:

Resources

For Kubernetes to be able to choose the best Node to deploy the Pods, and have a compact use of resources across the whole cluster, you should always asign request and limits for each container resources.

You can also add ResourceQuota to a namespace to limit maximum resources for an app.

To enforce that every container has requests and limits declared, set up min. and max. requests and limits, and more, you can use LimitRange.

Probes

Kubernetes maintains the Pods running through controller objects like Deployments, recreating Pods in an exited status if the container process crashes.

Nevertheless, many times an application becomes unresponsive or present some issues while in a running state.

In order to Kubernetes to be able to check the application status, and not only the container, there are 3 available probes:

You can find an example of the use of probes here: Configure Liveness, Readiness and Startup Probes.

Services acts as static endpoints to access application running in ephemeral Pods, distributing requests across Pods.

Services come in 4 flavours:

In Google Cloud, LoadBalancer Services are implemented as regional external, non-proxy TCP Network Load Balancers.

Deploying a LoadBalancer Service

L7 LoadBalancer

While a LoadBalancer Service is implemented as a L4 load balancer, you can also use Ingress or the new, preferred Gateway API to deploy L7 load balancers, which are implemented in Google Cloud as global external HTTPS Application Load Balancers.

Commands

Let's check in deep the most common Kubectl commands for normal operations and object troubleshooting:

Get

Get is the first basic Kubectl command and Kubernetes API method. It list objects of selected Kind in that namespace (default if not specified).

Try it:

Describe

Describe is the second basic Kubectl command and Kubernetes API method. It describes in full the spec and status of any object, but doesn't use the same manifest spec API schema (use kubectl get KIND OBJECT_NAME -o yaml for that).

Try it:

Getting firtst and then describing a Deployment is usually the best way to find it's current status and troubleshoot any problems, like the common CrashLoopBackOff error.

Logs

Logs is the third basic Kubectl command and Kubernetes API method. It returns logs for a specific Pod or, if there are multiple containers in that Pod, for a specific container.

Try it:

Exec

Exec is the fourth basic Kubectl command and Kubernetes API method. It allows to run a single command in a Pod container, or stablish an interactive TTY terminal.

Try it:

HorizontalPodAutoscaler

After manually scaling the Deployment, changing the number of replicas and redeploying it, we'll configure a HorizontalPodAutoscaler:

Now we'll explore how to deploy a new app version for the webapp Deployment. In this case, a new version will be defined by a new container image tag/version, and will be updated with a rolling update, which is the default Deployment updating policy.

First, let's create and push the new container image:

Now let's deploy the new webapp-deployment version with a rolling update:

Sometimes, new application versions present some issues and you need to revert to the previous version. In that case, Kubernetes maintains a history of Deployments revisions that you can use for a quick rollback:

If you were able to solve the issue, and want to rollback again to v2, you can rollback to that revision:

In this section, we'll explore how Kubernetes manages data storage, through 2 types of Volumes, ConfigMaps and Secrets, and through PersistentVolumes and PersistentVolumeClaims.

As we discussed, Volumes can be mounted by any container in the Pod, and can be used to store information and/or to communicate between containers in the Pod.

Ephemeral Volumes share the same lifecycle as their Pod, while PersistentVolumes persists even if the Pod has been deleted, so that the data can be persisted or the cluster admin can create a snapshot and recicle the storage.

ConfigMaps

ConfigMaps are Volumes used to store non-sensitive configuration for your applications, and can be consumed as files or environment variables.

ConfigMaps as Volumes

Let's create a ConfigMap with some test data:

Now let's create a Pod with a Volume, populated with the ConfigMap data:

ConfigMaps as environment variables

Data from a ConfigMap can also be consumed as environment variables.

Let's create another Pod which consumes the same ConfigMap data, but as environment variables:

Secrets

Secrets can be used in the same way as ConfigMaps. The only difference is that Secrets ofuscate their contents to developers, and thus can be used to store sensitive data as passwords and certificates.

Adding to the 2 previous ConfigMap examples, you can also configure all key-value pairs in a ConfigMap or Secret as environment variables:

Now create another Pod which consumes all the Secret data pairs as environment variables:

PersistentVolumes and PersistentVolumeClaims

PersistentVolumes ("PV"s) reperesent an abstraction for any storage medium that provide storage resources to the cluster, and Pods can consume, in the same way as Nodes represent an abstraction for any compute medium that provide CPU and memory resources (physical servers, VMs, IoT, containers, etc.).

There are many storage mediums available as PVs, eg.

Unlike Volumes, PVs represent persistent media that doesn't share the same lifecycle as the Pod, and whose storage persist if the Pod is deleted.

PVs are consumed in Pods in the same way as Volumes are, mounted in the file system of one or many containers, but they differ in how the resources are assigned:

PVs use PersistentVolumeClaims ("PVC"s) as a way of requesting how much storage and of which type the Pod needs, and as a way of assigning or "linking" a Pod to a specific PVs. Thus, PVCs acts in a somewhat similar manner to resource requests for CPU and memory, here for the whole Pod and not for each container, as the PVs can be mounted in more than one container.

PVs and PVCs also allow the separation of concerns or responsabilities between developers/cluster users, and operators/cluster admins:

PVCs declare the requested amount of storage, mode of operation (single mount to a Node, mounted in multiple Nodes, etc.), and request sufficient storage performance through a specific StorageClass.

GKE already declares several StorageClass by default with a dinamic provisioner for GCE Persistent Disks, which allows the GKE cluster to dinamically provision PVs as Persistent Disks if there are non availabe for the PVC.

Let's explore how to create a PVC, consume it through a Pod, and have GKE dinamically provisioning a PV as requested to assign to the PVC and the Pod:

Then, let's create a Pod that uses the PVC:

Now that we checked the use of the PVC within the Pod, let's check the persistence of the PVC data if the Pod is deleted:

This way, you created a PV dinamically through deploying a PVC and a Pod that consumes it, and checked how the PV persists if the Pod it's assigned to is deleted.