All writing
Updated 4 min readNaren

Deployments, ReplicaSets, Jobs and CronJobs

Run applications that stay up with Deployments and the ReplicaSets behind them, and tasks that finish with Jobs and CronJobs.

The previous post covered Kubernetes objects, YAML manifests, Pods and namespaces. This one moves up a level, to the objects that run and manage your applications: Deployments and ReplicaSets for services that stay up, and Jobs and CronJobs for tasks that run and finish.

Deployments

Say you want three copies of an application running at all times. If one crashes, it should be replaced automatically, and when you update the application, there should be no downtime. That is what a Deployment is for.

A Deployment is a higher-level resource that provides declarative updates for Pods and ReplicaSets. It is the recommended way to manage stateless applications in Kubernetes.

You declare the desired state, for example three replicas of an Nginx app using the image nginx:1.21.0, and Kubernetes keeps working to make that state real. The Deployment keeps the requested number of Pod replicas running. If a Pod it manages crashes or becomes unhealthy, the Deployment replaces it automatically.

When you update the application, say to a new image version, the Deployment performs a rolling update. It replaces old Pods with new ones one by one, so the update is gradual and has zero downtime. If the new version turns out to have bugs, you can roll back to the previous stable version.

A Deployment manifest

# my-nginx-deployment.yaml
apiVersion: apps/v1 # API version for Deployments
kind: Deployment
metadata:
  name: my-nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3 # Desired number of Pod replicas
  selector:
    matchLabels:
      app: nginx # How the Deployment finds its Pods (must match Pod's labels)
  template: # This is the Pod template
    metadata:
      labels:
        app: nginx # Labels applied to the Pods created by this Deployment
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.21.0 # Our application image
        ports:
        - containerPort: 80
        resources: # Best practice: define resource requests and limits
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
        livenessProbe: # Health check: is the container still alive?
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe: # Health check: is the container ready to serve traffic?
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

The fields to note:

  • apiVersion: apps/v1 is the API group Deployments live in.
  • spec.replicas is the number of identical Pods you want running.
  • spec.selector is how the Deployment identifies the Pods it owns. It must match the labels in template.metadata.labels.
  • spec.template is a Pod definition. Every Pod the Deployment creates follows it.
  • resources sets requests, the minimum CPU and memory the container is guaranteed, and limits, the maximum it is allowed. Define both for anything that runs in production.
  • livenessProbe and readinessProbe are health checks. They tell Kubernetes whether your application is running correctly and whether it is ready to receive traffic.

Deploy and manage the application

Save the manifest as my-nginx-deployment.yaml and apply it:

kubectl apply -f my-nginx-deployment.yaml

kubectl apply creates or updates resources so they match the file. Check the Deployment:

kubectl get deployments

my-nginx-deployment should be in the list. Now look at its Pods, using the label selector to filter them:

kubectl get pods -l app=nginx # Use the label selector to filter

There should be three, each named my-nginx-deployment- followed by a unique hash.

Scaling up or down is one command:

kubectl scale deployment my-nginx-deployment --replicas=5

Run kubectl get pods -l app=nginx again and there are five Pods.

For a rolling update, edit my-nginx-deployment.yaml, change image: nginx:1.21.0 to image: nginx:latest, and apply the file again:

kubectl apply -f my-nginx-deployment.yaml

Watch kubectl get pods -l app=nginx while it runs. Old Pods terminate gracefully as new ones start, so the application never goes down.

If the new version has problems, roll back to the previous one:

kubectl rollout undo deployment/my-nginx-deployment

When you are done, delete the Deployment. This also deletes its ReplicaSet and every Pod it manages:

kubectl delete deployment my-nginx-deployment

ReplicaSets, which Deployments manage

Manages Manages Manages Manages Deployment ReplicaSet Pod 1 Pod 2 Pod 3

When you created the Deployment, Kubernetes also created a ReplicaSet.

A ReplicaSet is a lower-level controller with one job: keep a specified number of identical Pod replicas running at all times. If a Pod fails, the ReplicaSet creates a new one to replace it straight away.

Deployments manage ReplicaSets. Creating a Deployment creates a ReplicaSet. A rolling update creates a new ReplicaSet for the new version and scales the old one down. You work with the Deployment and it handles the ReplicaSets, so you rarely need to create or manage one directly.

To see the ReplicaSets your Deployment created:

kubectl get replicasets

Jobs

Not every application is a long-running service. Some tasks run once and finish, like a data migration, a batch calculation or a script that generates a report. Those run as Jobs.

A Job creates one or more Pods and makes sure that a specified number of them terminate successfully. Once the task is complete, the Job is marked as finished.

A Job manifest

This Job runs a script that counts down from 9 to 1:

# countdown-job.yaml
apiVersion: batch/v1 # API version for Jobs
kind: Job
metadata:
  name: countdown-job
spec:
  template: # Pod template for the Job
    spec:
      containers:
      - name: counter
        image: busybox:1.36
        command: ["/bin/sh", "-c", "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done"]
      restartPolicy: OnFailure # Crucial for Jobs: Pods are restarted only on failure
  completions: 1 # The Job is complete after 1 successful Pod run
  parallelism: 1 # Only one Pod runs at a time

The fields to note:

  • apiVersion: batch/v1 is the API group Jobs live in.
  • spec.template.spec.restartPolicy should be OnFailure, which restarts the container if it fails, or Never, which never restarts it. Always is not allowed for Jobs.
  • spec.completions is the number of successful Pod completions the Job needs before it counts as finished.
  • spec.parallelism is the number of Pods to run in parallel.

Run the Job

Save the manifest as countdown-job.yaml and apply it:

kubectl apply -f countdown-job.yaml

Check its status:

kubectl get jobs

countdown-job shows its completions, where 1/1 means one of one has completed.

To read the output, first get the name of the Pod the Job created:

POD_NAME=$(kubectl get pods --selector=job-name=countdown-job -o jsonpath='{.items[0].metadata.name}')
echo $POD_NAME

Then read its logs, which should show the countdown:

kubectl logs $POD_NAME

Clean up the Job:

kubectl delete -f countdown-job.yaml

CronJobs

Some tasks need to run on a schedule, like a daily database backup or a weekly report. A CronJob creates Job objects on a repeating schedule, written in the familiar Unix cron format.

Schedules Creates Creates CronJob Job Pod 1 Pod 2

A CronJob manifest

This CronJob prints "Hello from the Kubernetes cluster" every minute:

# hello-cronjob.yaml
apiVersion: batch/v1 # API version for CronJobs
kind: CronJob
metadata:
  name: hello-cronjob
spec:
  schedule: "* * * * *" # Runs every minute (standard cron format)
  jobTemplate: # Template for the Job that the CronJob will create
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.36
            command: ["/bin/sh", "-c", "date; echo Hello from the Kubernetes cluster"]
          restartPolicy: OnFailure
  concurrencyPolicy: Allow # How to handle concurrent runs (Allow, Forbid, Replace)
  successfulJobsHistoryLimit: 3 # Keep history of last 3 successful Jobs
  failedJobsHistoryLimit: 1 # Keep history of last 1 failed Job

The fields to note:

  • apiVersion: batch/v1 is the API group CronJobs live in, the same as Jobs.
  • spec.schedule is the cron string. "* * * * *" means every minute.
  • spec.jobTemplate is the template for the Jobs the CronJob creates. Its structure is very close to a standalone Job.
  • spec.concurrencyPolicy decides what happens when a new Job is due while the previous one is still running: Allow, Forbid or Replace.
  • successfulJobsHistoryLimit and failedJobsHistoryLimit control how many past successful and failed Job runs stay in the cluster's history.

Run the CronJob

Save the manifest as hello-cronjob.yaml and apply it:

kubectl apply -f hello-cronjob.yaml

Check it:

kubectl get cronjobs

hello-cronjob is listed with its schedule and the time it last ran. Watch the Jobs it creates:

kubectl get jobs --watch

A new Job appears every minute. Press Ctrl+C to stop watching.

To read the logs of one run, first find the name of a recent Job, such as hello-cronjob-27827258:

kubectl get jobs

Then get the name of that Job's Pod:

JOB_NAME="hello-cronjob-xxxxxx" # Replace with an actual Job name from 'kubectl get jobs'
POD_NAME=$(kubectl get pods --selector=job-name=$JOB_NAME -o jsonpath='{.items[0].metadata.name}')
echo $POD_NAME

And read its logs:

kubectl logs $POD_NAME

Clean up the CronJob:

kubectl delete -f hello-cronjob.yaml

The next post covers how applications are reached, from outside the cluster and from inside it: Services and Ingress.