1. Overview
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.
2. Kubernetes Setup
Kubernetes (K8s) requires cluster creation and a good understanding of its architecture. Furthermore, we should be confident using the API with the kubectl command.
2.1. Cluster Setup
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.
2.2. kubectl
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.
3. Declarative vs. Imperative
Before we discuss create and apply, let’s see how we can manage objects in a cluster.
3.1. Object Management
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.
3.2. Imperative Commands and Objects Configuration
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
3.3. Declarative Objects Configuration
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
4. kubectl create
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
4.1. Error Creating the Same Resource
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
4.2. Modify Objects Before Creation
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.
4.3. One Imperative Command at a Time
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
4.4. Command Options
Let’s have a look at some of the create options:
- dry-run allows the creation of an object without sending or persisting to the server
- edit to modify the resource before the creation
- selector to identify a selector we want to filter
- save-config to add info about the last version of the object to make it usable by the apply command
5. kubectl apply
The kubectl apply command gives us more flexibility if we want to manage the life cycle of a resource in a cluster.
5.1. Replace create With apply
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?
5.2. No Error Applying the Same Resource
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
5.3. Object Agnostic
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.
5.4. Keep Track of Objects’ History
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
5.5. A New Revision for Every Change
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.
5.6. Move From create to apply
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.
5.7. Command Options
The create options are still valid here. Let’s see some other options for the apply command:
- prune delete resource objects
- grace-period sets the time we give to a resource to terminate
6. How the apply and create Commands Differ in kubectl
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.
7. Conclusion
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.