How to Run Commands in Kubernetes Pods on Startup

KubernetesBeginner
Practice Now

Introduction

This tutorial guides you through running commands in Kubernetes pods on startup. You will learn how to configure startup commands using the command and args fields, execute multiple commands, and handle startup failures. We will use Minikube to create a local Kubernetes environment where you can practice these concepts hands-on.

By the end of this lab, you will understand how to customize your Kubernetes pods' behavior during startup and ensure smooth application deployment. This knowledge is essential for configuring containerized applications properly in a Kubernetes environment.

Setting Up Your Kubernetes Environment

In this step, we'll prepare our environment by starting Minikube and understanding the basic Kubernetes concepts needed for this lab.

What is Kubernetes?

Kubernetes is an open-source platform designed to automate deploying, scaling, and operating application containers. At the core of Kubernetes are Pods - the smallest deployable units that can be created and managed.

Starting Minikube

Minikube is a tool that lets you run Kubernetes locally. Let's start it:

minikube start --driver=docker

This command will create a local Kubernetes cluster using Docker as the driver. The startup process may take a few minutes as it downloads necessary components.

You should see output similar to:

😄  minikube v1.30.1 on Ubuntu 22.04
✨  Using the docker driver based on user configuration
📌  Using Docker driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🔥  Creating docker container (CPUs=2, Memory=2200MB) ...
🐳  Preparing Kubernetes v1.27.4 on Docker 24.0.4 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
🌟  Enabled addons: default-storageclass, storage-provisioner
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

Verifying the Installation

Let's verify that everything is working correctly by checking the status of our Minikube cluster:

minikube status

You should see output indicating that Minikube is running:

minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

Now let's check that kubectl (the Kubernetes command-line tool) is properly configured:

kubectl get nodes

You should see one node (your Minikube instance) listed as Ready:

NAME       STATUS   ROLES           AGE     VERSION
minikube   Ready    control-plane   2m15s   v1.27.4

Understanding Kubernetes Pods

A Pod is the basic execution unit of a Kubernetes application. Each Pod represents a running process on your cluster and encapsulates one or more containers. Pods are ephemeral by design, meaning they can be created, destroyed, and recreated as needed.

In Kubernetes, you typically define Pods in YAML files. For this lab, we've already prepared some example YAML files in your environment.

Let's go to the directory where these files are located:

cd ~/project/kubernetes-examples
ls -la

You should see the following files:

total 20
drwxr-xr-x 2 labex labex 4096 Sep 21 10:00 .
drwxr-xr-x 3 labex labex 4096 Sep 21 10:00 ..
-rw-r--r-- 1 labex labex  193 Sep 21 10:00 basic-pod.yaml
-rw-r--r-- 1 labex labex  254 Sep 21 10:00 liveness-probe-pod.yaml
-rw-r--r-- 1 labex labex  312 Sep 21 10:00 multi-command-pod.yaml
-rw-r--r-- 1 labex labex  263 Sep 21 10:00 startup-command-pod.yaml

Now that our environment is set up, we're ready to start working with Kubernetes Pods and learning how to run commands on startup.

Creating Your First Pod with Basic Startup Commands

In this step, we'll create our first Kubernetes Pod and understand how it executes commands on startup. We'll examine the command field which defines what the container should run when it starts.

Exploring the Basic Pod Configuration

Let's first examine the configuration of a basic Pod. Open the basic-pod.yaml file with the nano editor:

nano basic-pod.yaml

You'll see a YAML file with the following content:

apiVersion: v1
kind: Pod
metadata:
  name: basic-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/bash", "-c", "echo 'Pod is running' && sleep 3600"]

This configuration defines:

  • A Pod named basic-pod
  • A single container named ubuntu using the ubuntu:20.04 image
  • A startup command that echoes a message and then sleeps for 3600 seconds (1 hour)

The command field specifies the executable that will run when the container starts. In this case, we're running the /bin/bash shell with the -c flag, which allows us to pass a string of commands to execute.

Press Ctrl+X to exit the nano editor.

Creating the Pod

Let's create this Pod in our Kubernetes cluster:

kubectl apply -f basic-pod.yaml

You should see output like:

pod/basic-pod created

Checking Pod Status

Now let's check if our Pod is running properly:

kubectl get pods

You should see output similar to:

NAME        READY   STATUS    RESTARTS   AGE
basic-pod   1/1     Running   0          30s

This indicates that our Pod is running successfully. The 1/1 under READY means that one container is running out of one expected container.

Viewing Pod Logs

To see the output of our startup command, we can check the logs of the Pod:

kubectl logs basic-pod

You should see:

Pod is running

This confirms that our startup command successfully executed.

Exploring the Running Container

Let's examine what's happening inside our container by executing an interactive shell:

kubectl exec -it basic-pod -- /bin/bash

Now you're inside the container's shell. Let's verify that our process is running:

ps aux

You should see the sleep command running, which is part of our startup command:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4112  3372 ?        Ss   10:05   0:00 /bin/bash -c echo 'Pod is running' && sleep 3600
root         7  0.0  0.0   4112  3536 pts/0    Ss   10:06   0:00 /bin/bash
root        14  0.0  0.0   5900  2928 pts/0    R+   10:06   0:00 ps aux
root        15  0.0  0.0   2512   580 ?        S    10:06   0:00 sleep 3600

The process with PID 1 is our startup command, and the sleep 3600 command is running as a separate process.

Exit the container shell by typing:

exit

Understanding the Pod Lifecycle

When you create a Pod, it goes through several phases:

  1. Pending: The Pod has been accepted by Kubernetes but is not yet running
  2. Running: The Pod is running with all containers
  3. Succeeded: All containers in the Pod have terminated successfully
  4. Failed: All containers have terminated, and at least one container has failed
  5. Unknown: The state of the Pod cannot be determined

Our Pod is in the Running state because our main process (sleep 3600) is still running. When this process completes or is terminated, the Pod will transition to either the Succeeded or Failed state, depending on the exit code.

Now you understand how to create a basic Pod and run commands on startup using the command field.

Using Command and Args for Startup Configuration

In this step, we'll learn how to use the command and args fields together to configure more complex startup behaviors in Kubernetes Pods.

Understanding Command and Args

Kubernetes provides two main ways to specify the command that runs when a container starts:

  1. command: Specifies the executable to run (similar to Docker's ENTRYPOINT)
  2. args: Specifies the arguments to pass to the command (similar to Docker's CMD)

Using these fields separately or together gives you flexibility in how your containers start up.

Exploring the Startup Command Pod

Let's examine the startup-command-pod.yaml file:

nano startup-command-pod.yaml

You'll see the following configuration:

apiVersion: v1
kind: Pod
metadata:
  name: startup-command-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/bash"]
      args:
        ["-c", "echo 'Custom startup message' > /tmp/startup.txt && sleep 3600"]

In this configuration:

  • The command field specifies that we want to run /bin/bash
  • The args field provides the arguments to pass to bash: -c and the string containing our commands

This separation of command and arguments makes the configuration more readable and maintainable.

Press Ctrl+X to exit the nano editor.

Creating the Startup Command Pod

Let's create this Pod:

kubectl apply -f startup-command-pod.yaml

You should see:

pod/startup-command-pod created

Checking Pod Status

Verify that the Pod is running:

kubectl get pods startup-command-pod

You should see:

NAME                  READY   STATUS    RESTARTS   AGE
startup-command-pod   1/1     Running   0          30s

Verifying the Startup Command

Now let's verify that our startup command executed correctly by checking if it created the expected file:

kubectl exec startup-command-pod -- cat /tmp/startup.txt

You should see:

Custom startup message

This confirms that our command executed successfully and created the file with the specified content.

Understanding the Difference between Command and Args

The relationship between command and args is important to understand:

  1. If you specify only command, it overrides the default ENTRYPOINT of the container image
  2. If you specify only args, it overrides the default CMD of the container image
  3. If you specify both command and args, they override both the ENTRYPOINT and CMD

Let's see what happens if we modify our Pod configuration. Create a new file called modified-command-pod.yaml:

nano modified-command-pod.yaml

Add the following content:

apiVersion: v1
kind: Pod
metadata:
  name: modified-command-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/echo"]
      args: ["This message is printed by echo"]

Save and exit with Ctrl+X, followed by Y and Enter.

Now let's create this Pod:

kubectl apply -f modified-command-pod.yaml

You should see:

pod/modified-command-pod created

Check the logs to see what happened:

kubectl logs modified-command-pod

You should see:

This message is printed by echo

Notice how the Pod executed the /bin/echo command with our specified argument, then terminated since echo exits after printing its output.

Check the Pod status:

kubectl get pods modified-command-pod

You should see something like:

NAME                   READY   STATUS      RESTARTS   AGE
modified-command-pod   0/1     Completed   0          45s

The Completed status indicates that the Pod ran to completion and exited successfully.

Now you understand how to use command and args together to configure startup behavior in Kubernetes Pods.

Executing Multiple Commands and Handling Startup Failures

In this step, we'll learn how to execute multiple commands on Pod startup and how to handle startup failures using health checks.

Executing Multiple Commands

Often, you need to run multiple commands when a container starts. There are several ways to do this in Kubernetes:

  1. Chain commands using shell operators (&&, ;, etc.)
  2. Use a script file
  3. Use an init container

Let's explore the first method by examining the multi-command-pod.yaml file:

nano multi-command-pod.yaml

You'll see:

apiVersion: v1
kind: Pod
metadata:
  name: multi-command-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/bash", "-c"]
      args:
        [
          "echo 'First command' > /tmp/first.txt && echo 'Second command' > /tmp/second.txt && sleep 3600"
        ]

This configuration chains multiple commands together using the && operator, which executes each command only if the previous one succeeds.

Press Ctrl+X to exit the editor.

Let's create this Pod:

kubectl apply -f multi-command-pod.yaml

You should see:

pod/multi-command-pod created

Now let's verify that both commands executed by checking the created files:

kubectl exec multi-command-pod -- cat /tmp/first.txt

You should see:

First command

And for the second file:

kubectl exec multi-command-pod -- cat /tmp/second.txt

You should see:

Second command

Handling Startup Failures with Health Checks

Kubernetes provides mechanisms to detect and handle startup failures:

  1. Liveness Probes: Check if the container is running; if not, Kubernetes will restart it
  2. Readiness Probes: Check if the container is ready to receive traffic
  3. Startup Probes: Check if the application has started; useful for slow-starting containers

Let's examine a Pod that uses a liveness probe:

nano liveness-probe-pod.yaml

You'll see:

apiVersion: v1
kind: Pod
metadata:
  name: liveness-probe-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/bash", "-c"]
      args: ["touch /tmp/healthy && sleep 3600"]
      livenessProbe:
        exec:
          command: ["cat", "/tmp/healthy"]
        initialDelaySeconds: 5
        periodSeconds: 5

In this configuration:

  • The container creates a file /tmp/healthy on startup
  • The liveness probe checks for this file every 5 seconds
  • If the file is missing, Kubernetes considers the container unhealthy and restarts it

Press Ctrl+X to exit the editor.

Let's create this Pod:

kubectl apply -f liveness-probe-pod.yaml

You should see:

pod/liveness-probe-pod created

Check that the Pod is running:

kubectl get pods liveness-probe-pod

You should see:

NAME                 READY   STATUS    RESTARTS   AGE
liveness-probe-pod   1/1     Running   0          30s

Now let's see what happens if we remove the health check file:

kubectl exec liveness-probe-pod -- rm /tmp/healthy

Wait about 10 seconds, then check the Pod status again:

kubectl get pods liveness-probe-pod

You should see that the container has been restarted:

NAME                 READY   STATUS    RESTARTS   AGE
liveness-probe-pod   1/1     Running   1          60s

The RESTARTS count has increased to 1, indicating that Kubernetes detected the unhealthy state and restarted the container.

Let's verify that the health check file exists again (it should have been recreated by the startup command when the container restarted):

kubectl exec liveness-probe-pod -- ls -la /tmp/healthy

You should see that the file exists again:

-rw-r--r-- 1 root root 0 Sep 21 10:30 /tmp/healthy

This demonstrates how Kubernetes can automatically recover from startup failures and maintain the desired state of your applications.

Creating a Custom Startup Script

For more complex initialization, you might want to use a custom startup script. Let's create a Pod that uses a shell script for startup:

nano script-pod.yaml

Add the following content:

apiVersion: v1
kind: Pod
metadata:
  name: script-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu:20.04
      command: ["/bin/bash", "-c"]
      args:
        - |
          cat > /tmp/startup.sh << 'EOF'
          #!/bin/bash
          echo "Script started at $(date)" > /tmp/script-log.txt
          echo "Creating configuration files..." >> /tmp/script-log.txt
          mkdir -p /tmp/config
          echo "app_name=MyApp" > /tmp/config/app.conf
          echo "version=1.0" >> /tmp/config/app.conf
          echo "Script completed successfully" >> /tmp/script-log.txt
          EOF
          chmod +x /tmp/startup.sh
          /tmp/startup.sh
          sleep 3600

This configuration:

  1. Creates a startup script in the container
  2. Makes it executable
  3. Runs the script
  4. Keeps the container running with sleep

Save and exit with Ctrl+X, followed by Y and Enter.

Let's create this Pod:

kubectl apply -f script-pod.yaml

You should see:

pod/script-pod created

Wait a moment for the Pod to start, then check its status:

kubectl get pods script-pod

You should see:

NAME         READY   STATUS    RESTARTS   AGE
script-pod   1/1     Running   0          30s

Now let's check the output of our script:

kubectl exec script-pod -- cat /tmp/script-log.txt

You should see something like:

Script started at Tue Sep 21 10:35:42 UTC 2023
Creating configuration files...
Script completed successfully

And let's verify that the configuration file was created:

kubectl exec script-pod -- cat /tmp/config/app.conf

You should see:

app_name=MyApp
version=1.0

This demonstrates how to use complex startup scripts to initialize your containers in Kubernetes.

Best Practices and Real-World Applications

In this final step, we'll explore best practices for running commands in Kubernetes Pods on startup and create a real-world application example that implements these practices.

Best Practices for Startup Commands

When configuring startup commands for Kubernetes Pods, consider these best practices:

  1. Keep startup commands idempotent: Commands should be safe to run multiple times without causing issues
  2. Use health checks: Implement liveness and readiness probes to verify successful startup
  3. Handle failures gracefully: Include error handling in your startup scripts
  4. Separate concerns: Use init containers for initialization tasks separate from your main application
  5. Limit startup time: Keep initialization quick to reduce deployment time
  6. Use environment variables: Make your startup commands configurable via environment variables
  7. Log startup progress: Output clear logs for troubleshooting

Using Init Containers for Startup Tasks

Init containers run before app containers start and are ideal for setup tasks. Let's create a Pod with an init container:

nano init-container-pod.yaml

Add the following content:

apiVersion: v1
kind: Pod
metadata:
  name: init-container-pod
spec:
  initContainers:
    - name: init-config
      image: ubuntu:20.04
      command: ["/bin/bash", "-c"]
      args:
        - |
          echo "Initializing configuration..."
          mkdir -p /work-dir/config
          echo "database_url=mysql://user:password@db:3306/mydb" > /work-dir/config/db.conf
          echo "api_key=1234567890" > /work-dir/config/api.conf
          echo "Initialization complete"
      volumeMounts:
        - name: shared-volume
          mountPath: /work-dir
  containers:
    - name: app
      image: ubuntu:20.04
      command: ["/bin/bash", "-c"]
      args:
        - |
          echo "Application starting..."
          echo "Reading configuration:"
          cat /work-dir/config/db.conf
          cat /work-dir/config/api.conf
          echo "Application running..."
          sleep 3600
      volumeMounts:
        - name: shared-volume
          mountPath: /work-dir
  volumes:
    - name: shared-volume
      emptyDir: {}

In this configuration:

  1. The init container init-config runs first and creates configuration files
  2. Both containers share a volume called shared-volume
  3. The main app container reads the configuration created by the init container

Save and exit with Ctrl+X, followed by Y and Enter.

Let's create this Pod:

kubectl apply -f init-container-pod.yaml

You should see:

pod/init-container-pod created

Check the Pod status:

kubectl get pods init-container-pod

You should see that the Pod is running:

NAME                 READY   STATUS    RESTARTS   AGE
init-container-pod   1/1     Running   0          30s

Now let's check the logs of the main container:

kubectl logs init-container-pod -c app

You should see something like:

Application starting...
Reading configuration:
database_url=mysql://user:password@db:3306/mydb
api_key=1234567890
Application running...

This confirms that the init container successfully created the configuration files, and the main container was able to read them.

Real-World Example: Web Application with Database Check

Let's create a more realistic example - a web application that checks for database availability before starting:

nano webapp-pod.yaml

Add the following content:

apiVersion: v1
kind: Pod
metadata:
  name: webapp-pod
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.28
      command: ["/bin/sh", "-c"]
      args:
        - |
          echo "Checking for database availability..."
          ## In a real scenario, this would check an actual database
          ## For this example, we'll simulate success after a short delay
          sleep 5
          echo "Database is available"
          touch /tmp/db-ready
      volumeMounts:
        - name: shared-volume
          mountPath: /tmp
  containers:
    - name: webapp
      image: nginx:1.19
      ports:
        - containerPort: 80
      command: ["/bin/sh", "-c"]
      args:
        - |
          if [ -f /tmp/db-ready ]; then
            echo "Database connection verified, starting web application..."
            ## Customize nginx configuration
            echo "<h1>Web Application Started Successfully</h1>" > /usr/share/nginx/html/index.html
            echo "<p>Connected to database</p>" >> /usr/share/nginx/html/index.html
            ## Start nginx
            nginx -g 'daemon off;'
          else
            echo "Error: Database not available"
            exit 1
          fi
      volumeMounts:
        - name: shared-volume
          mountPath: /tmp
      readinessProbe:
        httpGet:
          path: /
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 5
  volumes:
    - name: shared-volume
      emptyDir: {}

This configuration:

  1. Uses an init container to check for database availability (simulated)
  2. The main container checks for a file created by the init container before starting
  3. Includes a readiness probe to verify the application is serving traffic
  4. Uses a shared volume for communication between containers

Save and exit with Ctrl+X, followed by Y and Enter.

Let's create this Pod:

kubectl apply -f webapp-pod.yaml

You should see:

pod/webapp-pod created

Wait a moment for the Pod to start fully, then check its status:

kubectl get pods webapp-pod

You should see:

NAME         READY   STATUS    RESTARTS   AGE
webapp-pod   1/1     Running   0          30s

The 1/1 in the READY column indicates that the readiness probe has succeeded.

Let's port-forward to access the web application:

kubectl port-forward webapp-pod 8080:80 &

This command runs in the background (due to the &). Now we can access the web application using curl:

curl http://localhost:8080

You should see:

<h1>Web Application Started Successfully</h1>
<p>Connected to database</p>

This confirms that our application initialized correctly, verified database availability, and is now serving traffic.

Stop the port-forwarding process:

pkill -f "kubectl port-forward"

Cleanup

Before concluding the lab, let's clean up the resources we created:

kubectl delete pod basic-pod startup-command-pod modified-command-pod multi-command-pod liveness-probe-pod script-pod init-container-pod webapp-pod

You should see:

pod "basic-pod" deleted
pod "startup-command-pod" deleted
pod "modified-command-pod" deleted
pod "multi-command-pod" deleted
pod "liveness-probe-pod" deleted
pod "script-pod" deleted
pod "init-container-pod" deleted
pod "webapp-pod" deleted

You've now learned how to run commands in Kubernetes Pods on startup, execute multiple commands, handle failures, and apply best practices in real-world scenarios.

Summary

In this lab, you have learned how to run commands in Kubernetes Pods on startup. You've gained practical experience with:

  • Setting up a Kubernetes environment using Minikube
  • Creating and managing Pods with specific startup commands
  • Using the command and args fields to configure container behavior
  • Executing multiple commands using shell operators and scripts
  • Handling startup failures with health checks
  • Implementing best practices with init containers
  • Building real-world applications that require proper initialization

These skills are essential for deploying containerized applications in Kubernetes environments. By properly configuring startup commands, you can ensure your applications initialize correctly, verify dependencies, and handle errors gracefully.

As you continue your Kubernetes journey, remember that proper application initialization is a critical part of building reliable, scalable, and maintainable systems.