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: 5The fields to note:
apiVersion: apps/v1is the API group Deployments live in.spec.replicasis the number of identical Pods you want running.spec.selectoris how the Deployment identifies the Pods it owns. It must match the labels intemplate.metadata.labels.spec.templateis a Pod definition. Every Pod the Deployment creates follows it.resourcessetsrequests, the minimum CPU and memory the container is guaranteed, andlimits, the maximum it is allowed. Define both for anything that runs in production.livenessProbeandreadinessProbeare 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.yamlkubectl apply creates or updates resources so they match the file. Check the Deployment:
kubectl get deploymentsmy-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 filterThere 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=5Run 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.yamlWatch 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-deploymentWhen you are done, delete the Deployment. This also deletes its ReplicaSet and every Pod it manages:
kubectl delete deployment my-nginx-deploymentReplicaSets, which Deployments manage
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 replicasetsJobs
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 timeThe fields to note:
apiVersion: batch/v1is the API group Jobs live in.spec.template.spec.restartPolicyshould beOnFailure, which restarts the container if it fails, orNever, which never restarts it.Alwaysis not allowed for Jobs.spec.completionsis the number of successful Pod completions the Job needs before it counts as finished.spec.parallelismis 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.yamlCheck its status:
kubectl get jobscountdown-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_NAMEThen read its logs, which should show the countdown:
kubectl logs $POD_NAMEClean up the Job:
kubectl delete -f countdown-job.yamlCronJobs
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.
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 JobThe fields to note:
apiVersion: batch/v1is the API group CronJobs live in, the same as Jobs.spec.scheduleis the cron string."* * * * *"means every minute.spec.jobTemplateis the template for the Jobs the CronJob creates. Its structure is very close to a standalone Job.spec.concurrencyPolicydecides what happens when a new Job is due while the previous one is still running:Allow,ForbidorReplace.successfulJobsHistoryLimitandfailedJobsHistoryLimitcontrol 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.yamlCheck it:
kubectl get cronjobshello-cronjob is listed with its schedule and the time it last ran. Watch the Jobs it creates:
kubectl get jobs --watchA 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 jobsThen 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_NAMEAnd read its logs:
kubectl logs $POD_NAMEClean up the CronJob:
kubectl delete -f hello-cronjob.yamlThe next post covers how applications are reached, from outside the cluster and from inside it: Services and Ingress.