Kubernetes - Operations ======================= .. image:: images/k8s_service.png :align: center Now that we've covered the core components of Kubernetes, it's time to put it into operations. In this module you'll create Pods, Deployments, and Services. The image above depicts how we tie all these components together. When Kubernetes gets the run command, it will first look to see if the image is held locally on the cache by Kubelet. If there is not a local image, Kubelet will *pull* the image from a container registry. You have already created a namespace *test* and this will isolate resources. Kubernetes will create and assign the pods, while the Kubelet will get the image and stand up the container. Kubernetes, through the deployment manifest, watches all the pods tagged with labels matching the tags. Then, Kubernetes will expose the deployment through the service manifest. This happens the same way, through matching label tags. Let's jump into the operational items that make Kubernetes run. Operations - Container registry ------------------------------- We'll briefly talk about *container registries*. A container registry is a storage area for storing container and the most commonly used is Docker Hub which we use for the registry during this class. When you return to your jobs however, your company will most likely use a private container registry. Hosted in one of the cloud service providers, Github or Gitlab, and with some access controls. In Kubernetes this is done with a *docker-registry* secret. A secret is another Kubernetes object used for storing sensitive information. In this class, you'll not have to set any of this up. | - `Kubernetes Secret `_ - `Kubernetes Private Registry `_ Operations - Pod ---------------- You'll create a new pod using the Kubernetes imperative command shown below. When creating pods, best practice is to specifically define the image tag (version) and not use *latest*. .. code-block:: bash :caption: Pod Creation kubectl run testpod --image=nginx:1.21 -n test Now, verify your pod is running: - ``kubectl get pod -n test`` Once you have verified the pod is running, you'll delete the pod: - ``kubectl delete pod testpod -n test`` | Vim is not known for being overly friendly to copy/paste commands. Rather than have you spend time doing that and making sure all the indentions are correct, let's review what the manifest file would look like to deploy our Nginx container in a pod called *testpod*. .. note:: YAML can be very fussy on indentation, please pay **close attention** to your indentation .. code-block:: yaml :caption: Pod Manifest apiVersion: v1 kind: Pod metadata: name: testpod namespace: test spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 Let's explain the directives from above. | **apiVersion** determines what version of the API will be used for creation | **kind** specifies the type of object to be created, and is defined in the apiVersion | **metadata** describes information of an object that allows for the unique identification of that object | **spec** defines the desired state of the object in Kubernetes | **containers** specifies the containers to be built inside the pod | **name** defines the name of the container | **image** defines what image to use | **ports** defines what ports the container will listen on | A helpful resource is the *kubectl api-resource* command output. Here you can see each object type (kind), the corresponding shortname and the apiVersion associated. The shortname is very useful to save keystrokes, especially for those of you planing to pass the Certified Kubernetes Administrator (CKA) certification. | .. code-block:: bash :caption: API Resources kubectl api-resources | Now, back to creating pods. You can use the *dry-run=client* feature to have Kubernetes write the manifest for you. This process allows you to run your Kubernetes command without submitting it to the cluster. .. code-block:: bash :caption: Pod Dry Run kubectl run testpod --image=nginx:1.21 --port 80 -n test --dry-run=client -o yaml | Notice the *-o* output flag. You can also ask Kubernetes to output *json* format as well. You can also direct the output to a file by using ``>``. An example would be ``kubectl run testpod --image=nginx --dry-run=client -o yaml > testpod.yaml``. Let's try it out. Now that your manifest file is ready, time to apply it to Kubernetes. .. code-block:: bash :caption: Pod Dry Run kubectl run testpod --image=nginx:1.21 --port 80 -n test --dry-run=client -o yaml > testpod.yaml .. code-block:: bash :caption: Testpod manifest kubectl apply -f testpod.yaml Notice in the cli command we did not specify the namespace, that is because we defined the namespace in the manifest file. This is always a good practice to prevent pods from showing up the default namespace. One last step will walk through in this section is the *edit* command. To do this, we will edit the pod we've just created. Currently you are running *testpod* on an older version of Nginx. We will edit the manifest to update the version. .. code-block:: bash :caption: Edit kubectl edit pod testpod -n test We will focus on this line in the returned data: .. code-block:: bash :caption: Update :emphasize-lines: 3 spec: containers: - image: nginx:1.21 imagePullPolicy: IfNotPresent Arrow your cursor down to the *image* line and press ``i``. This command allows you to edit the file. You'll be changing the tagged version from **1.21** to **1.25**. Once this change is made use the vim write and quit command, press: | ``ESC`` (escape key) | ``:wq`` You should see the pod was edited. .. code-block:: bash :caption: Edit pod/testpod edited Now to verify the updated pod we'll use the describe command. .. code-block:: bash :caption: Describe kubectl describe pod testpod -n test Output from describe should look like the below. Showing Kubernetes, along with Kubelet, have terminated the existing container version 1.21 and pulled the container image, created and started the container. .. code-block:: bash :emphasize-lines: 7-10 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 6m47s default-scheduler Successfully assigned test/testpod to k3s-leader.lab Normal Pulled 6m47s kubelet Container image "nginx:1.21" already present on machine Normal Killing 104s kubelet Container testpod definition changed, will be restarted Normal Pulling 104s kubelet Pulling image "nginx:1.25" Normal Pulled 98s kubelet Successfully pulled image "nginx:1.25" in 6.203075695s (6.203083694s including waiting) Normal Created 98s (x2 over 6m47s) kubelet Created container testpod Normal Started 97s (x2 over 6m47s) kubelet Started container testpod This concludes the pod section. Official Documentation - `Kubernetes Pod `_ Operations - Deployment ----------------------- .. code-block:: bash :caption: Deployment kubectl create deployment lab-deploy --image=nginx:1.22 --replicas=3 -n test You should see the deployment has run from the below sample returned output: .. code-block:: bash :caption: Deployment Output lab@k3s-leader:~$ kubectl get deploy lab-deploy -n test NAME READY UP-TO-DATE AVAILABLE AGE lab-deploy 3/3 3 3 10s Let's validate your deployment, your output should match the above. .. code-block:: bash :caption: Get Deployment kubectl get deploy lab-deploy -n test Now you'll describe the deployment, take note of the lines showing **Selector** info (what pods will be in the deployment), **Replicas** how many pods are desired to be up and running. .. code-block:: Selector: app=lab-deploy Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable Please describe your deployment .. code-block:: bash :caption: Describe Deployment kubectl describe deploy lab-deploy -n test Now you'll delete the deployment .. code-block:: bash :caption: Delete Deployment kubectl delete deploy lab-deploy -n test For this section you'll be doing some of the exact steps we did for Pod's section. We'll cover some important parts of the manifest file that enable the deployment to build containers for the deployment. This is an example deployment manifest to explain directives. .. code-block:: bash :caption: Sample Deployment Manifest apiVersion: apps/v1 kind: Deployment metadata: name: lab-deploy namespace: test labels: app: lab-deploy spec: replicas: 3 selector: matchLabels: app: lab-deploy template: metadata: labels: app: lab-deploy spec: containers: - name: nginx image: nginx:1.22 ports: - containerPort: 80 | **labels** this sets the label for the deployment. Labels make searching faster and easier | **spec** specification that contains other manifest resources. Here the spec directive is defining the deployment with pod count and container images - **replicas** specifies how many pods are expected to be running - **selector** looks for matching labels will become part of the deployment - **template** sets the build for the containers that are to become part of the deployment; sets labels, container image and ports .. code-block:: bash :caption: Deployment Manifest kubectl create deployment lab-deploy --image=nginx:1.22 --replicas=3 -n test --dry-run=client -o yaml > lab-deploy.yaml .. note:: You can use the command ``cat lab-deploy.yaml`` to view the manifest file As you've done in previous lab, the above command will create a new deployment named *lab-deploy*. The command specifies the image version, replica count, namespace and again using the *dry-run* command to not submit the command to Kubernetes and output it to file. Now that the manifest file has been created, time to let Kubernetes work its magic. .. code-block:: bash :caption: Deploy kubectl apply -f lab-deploy.yaml You should now see you deployment has been created. ``deployment.apps/lab-deploy created`` Kubernetes has become so popular because of its many features in how it can run workloads and be customized. One of these impressive features is *scaling*. Scaling allows you to increase or decrease pod counts. You can even set scaling to occur during resource consumption. When configuring scaling to happen based on consumption (or lack of), this is called *auto-scaling*. In this lab, we will focus on manually scaling resources in the deployment. To do this, we will adjust the number of *replicas* specified in the manifest. .. code-block:: bash :caption: Scale kubectl scale --replicas=5 deploy/lab-deploy -n test You should now see the deployment scale up ``deployment.apps/lab-deploy scaled`` Now let's describe the new deployment: .. code-block:: bash :caption: Describe Deployment kubectl describe deploy/lab-deploy -n test .. code-block:: bash :caption: Output Describe Deployment :emphasize-lines: 32 lab@k3s-leader:~$ kubectl describe deploy/lab-deploy -n test Name: lab-deploy Namespace: test CreationTimestamp: Sun, 07 Jan 2024 19:26:55 -0500 Labels: app=lab-deploy Annotations: deployment.kubernetes.io/revision: 1 Selector: app=lab-deploy Replicas: 5 desired | 5 updated | 5 total | 5 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=lab-deploy Containers: nginx: Image: nginx:1.22 Port: Host Port: Environment: Mounts: Volumes: Conditions: Type Status Reason ---- ------ ------ Progressing True NewReplicaSetAvailable Available True MinimumReplicasAvailable OldReplicaSets: NewReplicaSet: lab-deploy-cb697555 (5/5 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set lab-deploy-cb697555 to 5 from 3 Official Documentation - `Kubernetes Deployment `_ Operations - Service -------------------- .. code-block:: bash :caption: Service kubectl expose deployment lab-deploy --type=NodePort --port=80 --target-port=80 --name=lab-deploy-svc --selector=app=lab-deploy -n test In the above command, you are telling Kubernetes to expose the deployment (lab-deploy) as a NodePort service. NodePort means that the TCP or UDP port will open on all nodes. The default ports are 30000-32767. You can alter this default or even specify the port you'd like. Types of service types: - **Cluster IP** This is the default service, which is used to expose a service on a cluster-internal IP. This means the service is only accessible from inside the cluster. - **Node Port** This exposes a service on each node's IP at a static port, so the service is accessible from outside the cluster. - **Load Balancer** This uses a cloud provider's load balancer to access a service from outside the cluster. - **External Name** This maps a service to the contents of a predefined external name field by returning a CNAME record with its value. - **Headless** This headless service is used for pod grouping when a stable IP address is not required. To test out our service, you'll need to find what NodePort port was enabled with the *describe* command. .. code-block:: bash :caption: Describe Service kubectl describe service lab-deploy-svc -n test Describe service output: .. code-block:: bash :caption: Output :emphasize-lines: 6,14, 15 lab@k3s-leader:~$ kubectl describe service lab-deploy-svc -n test Name: lab-deploy-svc Namespace: test Labels: app=lab-deploy Annotations: Selector: app=lab-deploy Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.43.171.70 IPs: 10.43.171.70 Port: 80/TCP TargetPort: 80/TCP NodePort: 31612/TCP Endpoints: 10.42.3.124:80,10.42.4.63:80,10.42.5.173:80 Session Affinity: None External Traffic Policy: Cluster Events: Test connectivity by issuing a curl to the localhost on the port listed in the *describe* command output. .. code-block:: bash :caption: Curl curl http://10.1.1.6:31612 From inside the cluster, the new service (thanks to CoreDNS) will have an A record entry. The format for the fqdn entry is: - service name - namespace - svc (for service) - cluster.local Here is an example of the *lab-deploy-svc* fqdn: *lab-deploy-svc.test.svc.cluster.local* We'll look at this in the next lab for troubleshooting. Official Documentation - `Kubernetes Service `_