cd ..
Kubernetes

Creating and Managing Pods with kubectl and YAML Manifests

This article is a continuation of the post Understanding Pods and kubectl. If you haven’t read the first part yet, I recommend starting there to grasp the fundamental concepts before moving on to YAML manifests.

Understanding YAML Manifests and Managing Pods with kubectl

In the previous article, we learned about Pods and how to create our first resource using the kubectl run command.

While this command is extremely useful for studies and quick tests, it doesn’t represent the most common way to work with Kubernetes in production environments.

In practice, most applications are created via YAML manifests that are versioned in a Git repository.

In this article, we will learn how to automatically generate these manifests using kubectl, understand their structure, create a Pod with multiple containers, and explore some of the most used commands for managing Pods.

Generating a YAML Manifest

Imagine we want to create a Pod using the Nginx image.

We could execute directly:

kubectl run corinthians --image=nginx

However, there’s a much better way to do this.

kubectl run corinthians \
  --image=nginx \
  --port=80 \
  --dry-run=client \
  -o yaml > pod.yaml

This command doesn’t create any resources.

It merely generates the YAML manifest and saves its content into the pod.yaml file.

Let’s understand each option.

The --image Parameter

--image=nginx

Defines which image will be used by the container.

When the Pod is created, Kubernetes will try to locate this image in a container registry.

If it doesn’t exist locally, it will be downloaded automatically.

The --port Parameter

--port=80

Informs that the container uses port 80.

This causes the manifest to contain:

ports:
  - containerPort: 80

It’s important to remember that this option does not publish the application.

For that, it will still be necessary to create a Service.

The --dry-run=client Parameter

This is one of the most useful options during studies.

--dry-run=client

The term dry-run means to execute a simulation.

Instead of sending a request to Kubernetes, kubectl assembles the resource locally.

In practice, the following happens:

kubectl


Assembles the object


Generates the YAML


Does not create any resource

Since the entire simulation happens within kubectl itself, we use the client option.

The -o yaml Parameter

The -o option stands for output.

-o yaml

It tells kubectl that we want to view the resource in YAML format.

The result will be similar to the following:

apiVersion: v1
kind: Pod

metadata:
  labels:
    run: corinthians
  name: corinthians

spec:
  containers:
  - image: nginx
    name: corinthians
    ports:
    - containerPort: 80

  dnsPolicy: ClusterFirst
  restartPolicy: Always

The > Operator

The final part of the command often causes confusion.

> pod.yaml

This does not belong to Kubernetes.

This operator is part of the Linux shell.

It redirects the command’s output to a file.

Without it, the YAML would be displayed directly in the terminal.

With it, all content will be saved to pod.yaml.

Understanding the Manifest Structure

Now that the file has been created, we can understand its structure.

apiVersion

Defines which API version will be used.

apiVersion: v1

kind

Informs which resource will be created.

kind: Pod

metadata

Stores resource identification information.

metadata:
  name: corinthians
  labels:
    run: corinthians

spec

This is the most important part of the manifest.

It describes the desired state of the resource.

Inside it, we find:

containers

Lists all containers belonging to the Pod.

containers:
- image: nginx
  name: corinthians

If Sidecars exist, they will also appear in this list.

restartPolicy

restartPolicy: Always

Defines Kubernetes’ behavior if the container terminates.

By default, Pods created this way use Always.

dnsPolicy

dnsPolicy: ClusterFirst

This policy indicates that the Pod will use the cluster’s own DNS service.

Creating the Pod

After reviewing the manifest, simply apply it.

kubectl apply -f pod.yaml

Expected result:

pod/corinthians created

Now Kubernetes will send this manifest to the API Server and initiate the Pod creation process.

Creating a Pod with Multiple Containers

Until now, we have worked with a Pod containing only one container.

However, a Pod can also run two or more containers simultaneously.

In the example below, we will create a Pod named corinthians with two containers:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: corinthians
  name: corinthians
spec:
  containers:
  - image: nginx
    name: corinthians
    resources: {}

  - image: busybox
    name: busybox
    args:
    - sleep
    - "700"

  dnsPolicy: ClusterFirst
  restartPolicy: Always

The first container runs Nginx.

The second uses the BusyBox image and executes the command:

sleep 700

This command keeps the container active for 700 seconds. Without a running process, the BusyBox container would terminate shortly after starting.

After applying the manifest:

kubectl apply -f pod.yaml

We can query the Pod with additional information:

kubectl get pods -o wide

Result:

NAME          READY   STATUS    RESTARTS        AGE    IP           NODE
corinthians   2/2     Running   9 (2m18s ago)   4h5m   10.244.2.5   giropops-worker

The READY column shows:

2/2

This means the Pod has two containers, and both are ready.

The NODE column shows that the Pod is running on the Node:

giropops-worker

We can represent this structure as follows:

Kubernetes Cluster
└── Node: giropops-worker
    └── Pod: corinthians
        ├── Container: corinthians
        │   └── Image: nginx
        └── Container: busybox
            └── Command: sleep 700

It’s important to understand that containers are not run directly by the Node as separate resources.

Both containers are inside the same Pod, and the Pod itself is running on the Node.

They also share:

In this example, the IP address 10.244.2.5 belongs to the Pod.

Understanding BusyBox Restarts

In the command output, the RESTARTS column shows that the containers have already restarted:

9 (2m18s ago)

This happens because BusyBox executes:

sleep 700

After 700 seconds, this process terminates.

Since the manifest uses:

restartPolicy: Always

Kubernetes restarts the container.

That’s why the number of restarts increases over time.

Managing Specific Containers

When a Pod has multiple containers, we need to specify which container we want to access.

To view Nginx logs:

kubectl logs corinthians -c corinthians

To query BusyBox:

kubectl logs corinthians -c busybox

We can also execute commands inside a specific container.

In the Nginx container:

kubectl exec -it corinthians -c corinthians -- sh

In the BusyBox container:

kubectl exec -it corinthians -c busybox -- sh

The -c option specifies the name of the container to be used.

Querying Pods

The first command we usually execute is:

kubectl get pods

We can also get additional information.

kubectl get pods -o wide

This option shows information such as:

Getting Details

If it’s necessary to investigate a specific resource:

kubectl describe pod corinthians

This command displays:

It’s one of the most used commands during troubleshooting processes.

Viewing Logs

To view a container’s logs, we use:

kubectl logs corinthians

If we want to follow the logs in real-time:

kubectl logs -f corinthians

If the Pod has multiple containers, it will be necessary to specify which one we want to query.

kubectl logs corinthians -c busybox

Executing Commands Inside the Container

Many times it will be necessary to access a container to perform tests or investigations.

For this, we use kubectl exec.

kubectl exec -it corinthians -- sh

This command creates a new process inside the container.

After that, we can execute commands normally.

ls

env

hostname

cat /etc/os-release

In practice, kubectl exec is much more used than kubectl attach.

Understanding kubectl attach

The attach command has a different behavior.

While exec creates a new process, attach connects our terminal to the container’s main process.

kubectl attach -it corinthians

In most situations, we use exec.

attach is usually only used for interactive applications that are already awaiting terminal input.

Removing the Pod

To remove the resource:

kubectl delete pod corinthians

After a few seconds, it will cease to exist in the cluster.

Summary

Throughout this article, we learned a commonly used daily workflow.

kubectl run


--dry-run=client


-o yaml


pod.yaml


kubectl apply


kubectl get


kubectl describe


kubectl logs


kubectl exec


kubectl delete

This workflow represents the foundation of working with Kubernetes and will be used for almost all subsequent resources we study, such as Deployments, Services, ConfigMaps, and Ingress.

In future articles, we will move on to more complex declarative resources and understand how real applications run within a Kubernetes cluster.

References

What did you think?