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.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.