Kubernetes: Designing and Building Applications
Tags: kubernetes, docker, devops, webdev
Published: 2023-07-08 18:39:12

This article provides various aspects of application design and development in Kubernetes, covering topics such as container images, running jobs and cron jobs, building multi-container pods, utilizing Init containers, leveraging volumes and their types, and utilizing persistent volumes.

## Container Image
An image is a self-contained file that encapsulates all the essential software and executables required to execute a container. It serves as a compact and portable package that enables you to deploy and operate your application across numerous containers.
**Docker** is one tool that you use to create your own images. A **Dockerfile** defines what is contained in the image. The _docker build_ command builds an image using the Dockerfile.
Checkout to my article "[Docker: Image Creation, Management, and Registry](https://dev.to/theyasirr/demystifying-docker-images-a-comprehensive-guide-to-creation-management-and-registry-4b35)" to create and manage Docker images.

## Running Jobs and CronJobs
**Jobs** are specifically designed to execute containerized tasks and ensure their successful completion. It can also be used to run multiple Pods in parallel.
A sample manifest of a Job:

```
apiVersion: batch/v1
kind: Job
metadata:
  name: example-job
spec:
  template:
    spec:
      containers:
      - name: my-container
        image: my-image:latest
        command: ["echo", "Hello, Kubernetes!"]
      restartPolicy: Never
  backoffLimit: 3
  activeDeadlineSeconds: 10
```
The Job object is in Kubernetes "batch" APIs. The _restartPolicy_ is set to "_Never_" to ensure the Job completes without retries. The _backoffLimit_ is set to 3, meaning the Job will be retried at most 3 times in case of failures. The _activeDeadlineSeconds_ is the maximum time Kubernetes allows to run the job.

**CronJobs** are designed to execute Jobs at regular intervals based on a predefined schedule. One CronJob object is like one line of a crontab (cron table) file on a Unix system.
A sample manifest of a CronJob:

```
apiVersion: batch/v1
kind: CronJob
metadata:
  name: example-cronjob
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: my-container
            image: my-image:latest
            command: ["echo", "Hello, Kubernetes CronJob!"]
          restartPolicy: OnFailure
      backoffLimit: 3
      activeDeadlineSeconds: 10
```
The _schedule_ field uses a cron expression to define the desired schedule for running the Job. It represents minute, hour, day of the month, month, and day of the week (from left to right).

The _restartPolicy_ for a Job or CronJob Pod must be _OnFailure_ or _Never_.

## Building Multi-Container Pods
Multi-Container Pods are Pods that consist of multiple containers collaborating within a single unit. They involve the inclusion of more than one container within a shared Pod.

![Multi-Container Pods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8i4tfzo338uzdkw33hv2.png)

An example manifest of a multi-container Pod:
```
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  volumes:
    - name: shared-volume
      emptyDir: {}
  containers:
    - name: main-container
      image: main-image:latest
      volumeMounts:
        - name: shared-volume
          mountPath: /shared-data
    - name: sidecar-container
      image: sidecar-image:latest
      volumeMounts:
        - name: shared-volume
          mountPath: /shared-data
```
Multi-container Pods are recommended for scenarios where containers require tight coupling and the need to share resources like network and storage volumes.

It is advisable to utilize multi-container Pods selectively, focusing on situations where close collaboration and resource sharing between containers are necessary.
## Multi-Container Design Patterns

![Multi-Container Design Patterns](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a48y6z1kk11ftvvczj1u.png)

In the **<u>Sidecar pattern</u>**, a sidecar container works alongside the main container to provide assistance or perform complementary tasks. One example is when the main container serves files from a shared volume, and the sidecar container periodically updates those files.
In the **<u>Ambassador pattern</u>**, an ambassador container acts as a proxy for network traffic to and/or from the main container. One example is when the main container needs to communicate with a database, and the ambassador container proxies the traffic to different databases based on the environment.
In the **<u>Adapter pattern</u>**, an adapter container is responsible for transforming the output of the main container in some way. One example is when the main container produces log data in a non-standard format without timestamps, and the adapter container transforms the data by adding timestamps and converting it into a standard format.

## Using Init containers
An Init container is designed to fulfill a specific task before the main container within a Pod is initiated. It operates on a one-time basis, executing its designated job and then concluding its execution. Once the Init container completes its task, the main container within the Pod is initiated and begins its execution.
An example manifest of a Pod using Init container:

```
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
    - name: main-container
      image: main-image:latest
      command: ["sh", "-c", "echo Main container started; sleep 10; echo Main container completed"]
      ports:
        - containerPort: 8080
          protocol: TCP
  initContainers:
    - name: init-container
      image: init-image:latest
      command: ["sh", "-c", "echo Init container started; sleep 5; echo Init container completed"]
```
A few use-cases of Init containers are:
- **Separate image**: Init containers offer the capability to execute start-up tasks using a distinct image, which may involve software not included or required by the main container's image.
- **Delay startup**: Init containers provide a means to defer the start of the main container until specific prerequisites are satisfied.
- **Security**: Init containers offer a secure approach to executing sensitive start-up processes, such as consuming secrets, in isolation from the main container.

## Exploring Volumes
A volume serves as a storage solution external to the container's file system, offering additional storage capabilities for containers.
**Volume vs VolumeMounts**
<u>Volume:</u> Specified within the Pod configuration, the volume defines the specifics regarding the storage location and configuration for data within a Pod.
<u>VolumeMounts:</u> Configured within the container specification, volumeMounts associate a volume with a particular container and determine the directory path at which the volume data will be accessible during runtime.

## Volume types
The volume type determine where and how data storage is handled. There are a lot of [volume types](https://kubernetes.io/docs/concepts/storage/volumes/#volume-types) but a few important ones are:
**hostPath**
Data is stored in a specific location directly on the host file system on the Kubernetes node where the Pod is running.
An example manifest of Pod using hostPath volume:
```
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
    - name: example-container
      image: example-image:latest
      volumeMounts:
        - name: data-volume
          mountPath: /app/data
  volumes:
    - name: data-volume
      hostPath:
        path: /data
        type: Directory
```
_hostPath_ volume has further four types:

- <u>Directory</u>: Mounts an existing directory on the host
- <u>DirectoryOrCreate</u>: Mounts an existing directory on the host and creates if does not exist.
- <u>File</u>: Mounts an existing single file on the host
- <u>FileOrCreate</u>: Mounts an existing single file on the host and creates if it does not exist.

**emptyDir**
Data is stored in an automatically managed location on the host file system. Data is deleted if the Pod is deleted.
An example manifest of a Pod using _emptyDir_ volume:
```
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:
    - name: example-container
      image: example-image:latest
      volumeMounts:
        - name: data-volume
          mountPath: /app/data
  volumes:
    - name: data-volume
      emptyDir: {}
```
**persistentVolumeClaim**
Data is stored using a _persistentVolume_.

## Using PersistentVolume
A PersistentVolume provides the ability to abstract storage details from Pods, treating storage as a consumable resource without directly exposing the underlying storage implementation.

**PersistentVolume vs PersistentVolumeClaim**
A <u>PersistentVolume (PV)</u> defines an abstract storage resource that is available for consumption by Pods. It encompasses specific information regarding the type and capacity of storage that it provides.
A <u>PersistentVolumeClaim (PVC)</u> declares the need for storage and specifies the required characteristics, such as storage type. It automatically binds to an available PersistentVolume (PV) that fulfills the defined requirements. Once bound, the PVC can be mounted within a Pod like any other volume.


![PersistentVolumeClaim binding with PersistentVolume](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3zz3iu2womw9fn0docw.png)
An example of a Pod manifest YAML file that includes a PersistentVolumeClaim (PVC) and PersistentVolume (PV):

```
apiVersion: v1
kind: Pod
metadata:
  name: pvc-pod
spec:
  containers:
    - name: example-container
      image: example-image:latest
      volumeMounts:
        - name: data-volume
          mountPath: /app/data
  volumes:
    - name: data-volume
      persistentVolumeClaim:
        claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /data

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
```
This example demonstrates the usage of PVC and PV in a Pod. The PVC is bound to an available PV that meets the specified requirements, and the PV is mounted as a volume within the Pod.


By focusing on container images, running jobs and cron jobs, building multi-container pods, utilizing Init containers, exploring volumes and their types, and leveraging persistent volumes, developers can create robust and resilient applications in the Kubernetes ecosystem. Adopting these approaches and understanding the underlying concepts will empower developers to unlock the full potential of Kubernetes for their application deployments.