Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 19, 2024
Kubernetes is an orchestration tool for containerized applications. It uses the kubectl command line to interact with a cluster.
In this tutorial, we’ll see the difference between the kubectl create and kubectl apply commands with some examples in a running cluster.
Kubernetes (K8s) requires cluster creation and a good understanding of its architecture. Furthermore, we should be confident using the API with the kubectl command.
We can set up a K8s cluster in different ways. A good learning experience would be Kubeadm and a local K8s cluster connecting multiple virtual machines on the same network. We might want to try with Archlinux or any other Linux distribution.
Nonetheless, we can use a lightweight K8s distribution for a quick start. This allows us at least to save time for the cluster configuration setup.
If we want to skip cluster configuration and play with common commands, we can use K8s playgrounds such as Killercoda or Play with Kubernetes.
In any case, we’ll have to install kubectl. It works on the command line, although it’s not strictly a K8s component. It communicates with the K8s API using the K8s cluster configuration.
Before we discuss create and apply, let’s see how we can manage objects in a cluster.
K8s is about managing objects in a cluster. We might add or update a pod that runs, for instance, a web application or a database. We apply operations such as create, read, replace, and delete.
Imperative management means to use verb-driven commands. For example, we create an Nginx deployment:
$ kubectl create deployment nginx --image nginx
Likewise, we can extend the same concept to a configuration file:
$ kubectl create -f nginx.yaml
With declarative commands, we only need a configuration file or a list of files. K8s manages the difference from what is already in the cluster. The apply syntax is what we use in this case:
$ kubectl apply -f deployment.yaml
As we’ve already seen, the kubectl create is an imperative command. We can create many resources like services, secrets, ingress, etc.
For easiness, we’ll see a simple Nginx deployment from a YAML configuration file deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
minReadySeconds: 5
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
We can now use the create command:
$ kubectl create -f deployment.yaml
K8s notifies us of the resource creation:
deployment.apps/nginx-deployment created
We can double-check with the kubectl get pods command to see if the Nginx container is running:
NAME READY STATUS RESTARTS AGE
nginx-deployment-86dcfdf4c6-zhkw8 1/1 Running 0 88s
We’ll get an error if we now try to create, for example, the same deployment:
Error from server (AlreadyExists): error when creating "deployment.yaml": deployments.apps "nginx-deployment" already exists
It’s an option to modify an object before creation. Suppose we want to use a different Nginx version:
$ kubectl create -f deployment.yaml -o yaml --dry-run=client | kubectl set image --local -f - 'nginx=nginx:1.25.2' -o yaml | kubectl create -f -
We can also output the file and edit it in a second step:
$ kubectl create -f deployment.yaml -o yaml --dry-run=client > deployment_1.yaml && kubectl create --edit -f deployment_1.yaml
This opens up the default text editor with the YAML file for the new deployment.
Now that our resources are running in the cluster, we might want to follow up and do some operations on them.
Similarly to how we modify before creation, K8s recommends interacting with resources using one imperative command at a time.
So, let’s say we want to scale our deployment to 5 replicas. We could use the scale command:
$ kubectl scale --replicas=5 deployment nginx-deployment
Instead, we should create another YAML file, for example, deployment_scale.yaml from the previous file, and add to the deployment spec:
...
spec:
replicas: 2
...
If we want to update, we can now use the replace command:
$ kubectl replace -f deployment_scale.yml
Likewise, we might want to use the delete command to remove an object:
$ kubectl delete -f deployment.yml
Let’s have a look at some of the create options:
The kubectl apply command gives us more flexibility if we want to manage the life cycle of a resource in a cluster.
We can replace the create command with apply:
$ kubectl apply -f deployment.yaml
The deployment works as well. So, what’s the difference between the two commands?
Using the apply command with the same configuration produces an unchanged status:
deployment.apps/nginx-deployment unchanged
Instead, with a different input, we’ll get a new configuration response:
deployment.apps/nginx-deployment configured
The apply command works with any object, so it’s declarative in the object definition. We know the kind of object in the YAML file. It could be a service, a pod, etc. Thus, in the kubectl command, we avoid stating the resource’s type.
What’s relevant about the apply command is the kubectl.kubernetes.io/last-applied-configuration field in the metadata definition. We can see it from the YAML file configuration:
$ kubectl apply view-last-applied -f deployment.yaml -o yaml
The output corresponds to the current deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
name: nginx-deployment
namespace: default
spec:
minReadySeconds: 5
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.14.2
name: nginx
ports:
- containerPort: 80
Notably, we can see the last applied configuration with the get command:
$ kubectl get deployments.apps nginx-deployment -o yaml
We omit the last-applied-configuration field in the output for brevity:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
kubectl.kubernetes.io/last-applied-configuration: |
...
creationTimestamp: "2023-09-18T14:58:34Z"
generation: 1
name: nginx-deployment
namespace: default
resourceVersion: "154473"
uid: a154ad80-db02-4ec4-888d-0c56281c4816
...
Let’s say we now update the replicas to 5. This creates a new version of the deployment. For example, we can see the generation and resourceVersion change:
...
generation: 2
name: nginx-deployment
namespace: default
resourceVersion: "156401"
...
This makes K8s able to update the definition of a live object. It works by comparing the current revision with the next to come.
If we don’t use the save-config option with the create command, we can continue using the apply command later on.
K8s will warn us and set the last applied configuration with the following message:
Warning: resource deployments/nginx-deployment is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply.
kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
The create options are still valid here. Let’s see some other options for the apply command:
We’ve seen the kubectl create as an imperative command. Furthermore, if we want to change to an object definition, we’d need to use another command such as kubectl replace. It doesn’t strictly require a YAML template.
On the contrary, the kubectl apply declarative command matches the object type. K8s knows how to make changes according to the last applied configuration.
However, with the apply command, merging changes between different versions can be difficult and lead to unexpected results.
It’s worth noting that the create command with the –save-config option behaves similarly to the apply command.
In this article, we saw how the kubectl object management works with kubectl create and kubectl apply.
The apply command is more flexible and can apply changes dynamically. The create command is imperative, although with straightforward syntax.
In any case, we need to be sure not to mix up different strategies. They’re both mature commands nowadays, so it’s about choosing a direction.