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 kubectl config: kubectl version, kubectl cluster-infosource <(kubectl completion bash)echo 'source <(kubectl completion bash)' >>~/.bashrccd ~, git clone https://github.com/Indavelopers/codelabs-indavelopers-com.gitKubernetes deploys and manages containerized applications, using the OCI container standard. We will containerize a Python Flask hello-world webapp.
The webapp code is in codelabs-content/introduction-to-k8s/webapp/src:
main.py: Main Python Flask webapp code.requirements.py: Python module dependencies in Pip requirements format.venv: Python virtual environment.Dockerfile: Dockerfile for the container image.Check the app locally:
cd ~/codelabs-indavelopers-com/codelabs/introduction-to-k8s/webapp/srcsource venv/bin/activatepip install -r requirementsflask --app main run --debuglocalhost:5000 with the Cloud Shell "Web preview" option. Ctrl+CdeactivateCreate a container image using the provided Dockerfile and push it to the provided container repository in Google Artifact Registry:
echo $GOOGLE_CLOUD_PROJECTYOUR_PROJECT_ID) and set up the envvar again: gcloud config set project YOUR_PROJECT_ID, export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_IDdocker build -t europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1 .localhost:5000: docker run -p 5000:5000 europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1gcloud auth configure-docker europe-west4-docker.pkg.devdocker push europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v1lab-repo Docker repository, and find your pushed container image"Kubernetes is my container shepherd, it maintains my containerized applications for me."
Kubernetes follows:
localhost.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).
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:v1Running state: kubectl get podskubectl port-forward pod/webapp 5000:5000Ctrl+C and delete the Pod to clean up your environment: kubectl delete pod webappwebapp/manifests: cd ~/codelabs-indavelopers-com/codelabs/introduction-to-k8s/webapp/manifestsYOUR_PROJECT_ID in the Deployment manifest with your actual project ID: sed -i "s/YOUR_PROJECT_ID/$GOOGLE_CLOUD_PROJECT/g" webapp-deployment.yamlwebapp-deployment.yaml: cat webapp-deployment.yamlkubectl apply -f webapp-deployment.yamlkubectl get allWith 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:
kubectl apply -f webapp-deployment.yamlreplicas: 3 to replicas: 1 in webapp-deployment.yaml.edit webapp-deployment.yamlkubectl apply -f webapp-deployment.yamlkubectl get allkubectl apply -f webapp-deployment.yamlkubectl get allkubectl delete -f webapp-deployment.yaml, kubectl apply -f webapp-deployment.yamlkubectl get allKubernetes 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.
kubectl get podskubectl delete pods NAME_OF_THE_PODkubectl get allNamespaces allows the separation of operations, access control, and resource asignation for different applications.
kubectl get namespaceskubectl create -f webapp-namespace.yamlkubectl get namespacesdefault namespace: kubectl delete -f webapp-deployment.yamlkubectl apply -n webapp -f webapp-deployment.yamlkubectl get allkubectl get all -n webappLabels allow to group together several resources, even of different kinds, and can also be used as selectors:
webapp-deployment, check the Deployment and Pods labels, and Deployment selector: cat webapp-deployment.yamlapp=webapp: kubectl get pods -l app=webapp -n webappapp=webapp: kubectl get deployments -l app=webapp -n webappFor 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.
webapp-deployment: cat webapp-deployment.yamlYou 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.
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.
webapp-service in manifests: cat webapp-service.yamlkubectl apply -f webapp-service.yaml -n webappkubectl get services -n webappkubectl get services webapp-service -n webapp -wEXTERNAL_LOAD_BALANCER_IP: kubectl get services webapp-service -n webapp -o jsonpath='{.status.loadBalancer.ingress[0].ip}'curl http://EXTERNAL_LOAD_BALANCER_IPNetwork Services > Load Balancing, locate the load balancer and check its configuration.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.
Let's check in deep the most common Kubectl commands for normal operations and object troubleshooting:
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:
kubectl get allkubectl get all -n webappkubectl get pods -n webappkubectl get pods -n webapp NAME_OF_PODkubectl get deployments -n webappkubectl get deployments -n webapp webapp-deploymentkubectl get deployments -n webapp webapp-deployment -o yaml > webapp-deployment_output.yaml, cat webapp-deployment_output.yamlDescribe 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:
kubectl get pods -n webappkubectl describe pods NAME_OF_POD -n webappkubectl describe deployments webapp-deployment -n webappkubectl describe allGetting 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 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:
kubectl get pods -n webappkubectl logs NAME_OF_POD -n webappExec 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:
kubectl get pods -n webappkubectl exec NAME_OF_POD -n webapp -- ls /kubectl exec NAME_OF_POD -it -n webapp -- /bin/bashls /appwhoamiunameexitAfter manually scaling the Deployment, changing the number of replicas and redeploying it, we'll configure a HorizontalPodAutoscaler:
webapp-hpa in manifests: cat webapp-hpa.yamlkubectl apply -f webapp-hpa.yaml -n webappkubectl get hpa webapp-hpa -n webappwebapp-deployment and the webapp-hpa HPA behaviour: kubectl get hpa webapp-hpa -n webapp -wwebapp-service Service, so we can test it internally instead of through the external load balancer as SERVICE_CLUSTERIP: kubectl get service webapp-service -n webapp -o jsonpath='{.spec.clusterIP}'kubectl run -it load-generator --rm --image=busybox:1.37 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://SERVICE_CLUSTERIP; done"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:
codelabs-content/introduction-to-k8s/webapp/src: cd codelabs-content/introduction-to-k8s/webapp/srcmain.py to return a new message, eg. from return 'Hello, world!' to return 'Hello, world! v2'source venv/bin/activateflask --app main run --debuglocalhost:5000 with the Cloud Shell "Web preview" option.Ctrl+Cdeactivatev2 tag: docker build -t europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v2 .docker push europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repo/webapp:v2gcloud artifacts docker images list europe-west4-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/lab-repoNow let's deploy the new webapp-deployment version with a rolling update:
codelabs-content/introduction-to-k8s/webapp/manifests: cd codelabs-content/introduction-to-k8s/webapp/manifestswebapp-deployment.yaml to use the new v2 container image, using Cloud Shell Editor, nano, vim or emacs: edit webapp-deployment.yamlkubectl apply -f webapp-deployment.yaml -n webappkubectl get pods -n webapp -w, then Ctrl + CEXTERNAL_LOAD_BALANCER_IP: kubectl get services webapp-service -n webapp -o jsonpath='{.status.loadBalancer.ingress[0].ip}'curl http://EXTERNAL_LOAD_BALANCER_IPSometimes, 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:
kubectl rollout history deployment webapp-deployment -n webapp1: kubectl rollout history deployment webapp-deployment -n webapp --revision 1kubectl rollout undo deployment webapp-deployment -n webappkubectl rollout undo deployment webapp-deployment -n webapp --to-revision 1kubectl get pods -n webapp -w, then Ctrl + Ckubectl rollout status deployment webapp-deployment -n webappcurl http://EXTERNAL_LOAD_BALANCER_IPkubectl describe deployments webapp-deployment -n webappIf you were able to solve the issue, and want to rollback again to v2, you can rollback to that revision:
kubectl rollout undo deployment webapp-deployment -n webapp --to-revision 2kubectl rollout history deployment webapp-deployment -n webappkubectl get pods -n webapp -w, then Ctrl + Ccurl http://EXTERNAL_LOAD_BALANCER_IPIn 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 are Volumes used to store non-sensitive configuration for your applications, and can be consumed as files or environment variables.
Let's create a ConfigMap with some test data:
codelabs-content/introduction-to-k8s/webapp/manifests: cd codelabs-content/introduction-to-k8s/webapp/manifeststest-configmap.yaml in manifests, noticing the data field: cat test-configmap.yamldefault namespace: kubectl apply -f test-configmap.yamlkubectl get configmap, kubectl describe configmap test-configmapNow let's create a Pod with a Volume, populated with the ConfigMap data:
cat test-configmap-volume-pod.yamlkubectl apply -f test-configmap-volume-pod.yamlkubectl get pods, kubectl describe pod test-configmap-volume-podkubectl logs test-configmap-volume-podData 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:
cat test-configmap-env-pod.yamlkubectl apply -f test-configmap-env-pod.yamlkubectl get pods, kubectl describe pod test-configmap-env-podkubectl logs test-configmap-env-podSecrets 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:
test-secret.yaml in manifests: cat test-secret.yamldefault namespace: kubectl apply -f test-secret.yamlkubectl get secret, kubectl describe secret test-secretNow create another Pod which consumes all the Secret data pairs as environment variables:
cat test-secret-env-pod.yamlkubectl apply -f test-secret-env-pod.yamlkubectl describe pod test-secret-env-podkubectl logs test-secret-env-podPersistentVolumes ("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:
codelabs-content/introduction-to-k8s/webapp/manifests: cd codelabs-content/introduction-to-k8s/webapp/manifeststest-pvc.yaml in manifests: cat test-pvc.yamlkubectl get storageclasskubectl apply -f test-pvc.yamlkubctl get persistentvolumeclaimThen, let's create a Pod that uses the PVC:
test-pvc-pod.yaml in manifests, noticing the persistentVolumeClaim field: cat test-pvc-pod.yamlkubectl apply -f test-pvc-pod.yamlkubectl get pods, kubectl describe pod test-pvc-pod.yamlkubectl exec -it test-pvc-pod -- shecho "Test webpage in a PersistentVolume!" > /var/www/html/index.html, chmod +x /var/www/html/index.htmlcat /var/www/html/index.htmlexitNow 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:
kubectl get podskubectl delete -f test-pvc-pod.yamlkubectl get podskubectl get persistentvolumeclaimkubectl apply -f test-pvc-pod.yamlkubectl exec -it test-pvc-pod -- shcat /var/www/html/index.htmlThis 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.