如何在 Kubernetes 容器启动时运行命令

KubernetesBeginner
立即练习

简介

本教程将指导你在 Kubernetes 容器(pod)启动时运行命令。你将学习如何使用 commandargs 字段配置启动命令、执行多个命令以及处理启动失败的情况。我们将使用 Minikube 创建一个本地 Kubernetes 环境,让你可以亲身体验这些概念。

在本实验结束时,你将了解如何在启动期间自定义 Kubernetes 容器的行为,并确保应用程序顺利部署。这些知识对于在 Kubernetes 环境中正确配置容器化应用程序至关重要。

设置你的 Kubernetes 环境

在这一步,我们将通过启动 Minikube 并了解本实验所需的基本 Kubernetes 概念来准备我们的环境。

什么是 Kubernetes?

Kubernetes 是一个开源平台,旨在自动化应用程序容器的部署、扩展和运维。Kubernetes 的核心是容器(Pod)——可以创建和管理的最小可部署单元。

启动 Minikube

Minikube 是一个让你在本地运行 Kubernetes 的工具。让我们启动它:

minikube start --driver=docker

此命令将使用 Docker 作为驱动程序创建一个本地 Kubernetes 集群。启动过程可能需要几分钟,因为它会下载必要的组件。

你应该会看到类似以下的输出:

😄  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

验证安装

让我们通过检查 Minikube 集群的状态来验证一切是否正常工作:

minikube status

你应该会看到表明 Minikube 正在运行的输出:

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

现在让我们检查 kubectl(Kubernetes 命令行工具)是否已正确配置:

kubectl get nodes

你应该会看到一个节点(你的 Minikube 实例)显示为 Ready:

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

理解 Kubernetes 容器(Pod)

容器(Pod)是 Kubernetes 应用程序的基本执行单元。每个容器代表集群上的一个运行进程,并封装了一个或多个容器。容器在设计上是短暂的,这意味着它们可以根据需要创建、销毁和重新创建。

在 Kubernetes 中,你通常在 YAML 文件中定义容器。对于本实验,我们已经在你的环境中准备了一些示例 YAML 文件。

让我们进入这些文件所在的目录:

cd ~/project/kubernetes-examples
ls -la

你应该会看到以下文件:

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

现在我们的环境已经设置好,我们准备好开始使用 Kubernetes 容器并学习如何在启动时运行命令了。

使用基本启动命令创建你的第一个容器(Pod)

在这一步,我们将创建第一个 Kubernetes 容器(Pod),并了解它如何在启动时执行命令。我们将研究 command 字段,该字段定义了容器启动时应运行的内容。

探索基本容器配置

首先,让我们查看一个基本容器的配置。使用 nano 编辑器打开 basic-pod.yaml 文件:

nano basic-pod.yaml

你会看到一个包含以下内容的 YAML 文件:

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"]

此配置定义了:

  • 一个名为 basic-pod 的容器(Pod)
  • 一个名为 ubuntu 的单个容器,使用 ubuntu:20.04 镜像
  • 一个启动命令,该命令会输出一条消息,然后休眠 3600 秒(1 小时)

command 字段指定了容器启动时将运行的可执行文件。在这种情况下,我们使用 -c 标志运行 /bin/bash shell,这允许我们传递一串要执行的命令。

Ctrl+X 退出 nano 编辑器。

创建容器

让我们在 Kubernetes 集群中创建这个容器:

kubectl apply -f basic-pod.yaml

你应该会看到类似以下的输出:

pod/basic-pod created

检查容器状态

现在,让我们检查容器是否正常运行:

kubectl get pods

你应该会看到类似以下的输出:

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

这表明我们的容器正在成功运行。READY 列下的 1/1 表示在预期的一个容器中,有一个容器正在运行。

查看容器日志

要查看启动命令的输出,我们可以检查容器的日志:

kubectl logs basic-pod

你应该会看到:

Pod is running

这证实了我们的启动命令已成功执行。

探索正在运行的容器

让我们通过执行交互式 shell 来查看容器内部正在发生的事情:

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

现在你已进入容器的 shell。让我们验证我们的进程是否正在运行:

ps aux

你应该会看到休眠命令正在运行,它是我们启动命令的一部分:

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

PID 为 1 的进程是我们的启动命令,sleep 3600 命令作为一个单独的进程正在运行。

通过输入以下命令退出容器 shell:

exit

理解容器生命周期

当你创建一个容器时,它会经历几个阶段:

  1. Pending(挂起):容器已被 Kubernetes 接受,但尚未运行
  2. Running(运行中):容器正在运行,所有容器都已启动
  3. Succeeded(成功):容器中的所有容器都已成功终止
  4. Failed(失败):所有容器都已终止,并且至少有一个容器失败
  5. Unknown(未知):无法确定容器的状态

我们的容器处于 Running 状态,因为我们的主进程(sleep 3600)仍在运行。当此进程完成或被终止时,容器将根据退出代码转换为 SucceededFailed 状态。

现在你已经了解了如何创建一个基本容器,并使用 command 字段在启动时运行命令。

使用 command 和 args 进行启动配置

在这一步,你将学习如何结合使用 commandargs 字段,在 Kubernetes 容器(Pod)中配置更复杂的启动行为。

理解 command 和 args

Kubernetes 提供了两种主要方式来指定容器启动时运行的命令:

  1. **command**:指定要运行的可执行文件(类似于 Docker 的 ENTRYPOINT
  2. **args**:指定传递给命令的参数(类似于 Docker 的 CMD

单独或结合使用这些字段,能让你灵活地控制容器的启动方式。

探索启动命令容器

让我们查看 startup-command-pod.yaml 文件:

nano startup-command-pod.yaml

你会看到以下配置:

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"]

在此配置中:

  • command 字段指定要运行 /bin/bash
  • args 字段提供传递给 bash 的参数:-c 和包含命令的字符串

这种将命令和参数分开的方式,使配置更易读、更易维护。

Ctrl+X 退出 nano 编辑器。

创建启动命令容器

让我们创建这个容器:

kubectl apply -f startup-command-pod.yaml

你应该会看到:

pod/startup-command-pod created

检查容器状态

验证容器是否正在运行:

kubectl get pods startup-command-pod

你应该会看到:

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

验证启动命令

现在,让我们通过检查是否创建了预期的文件,来验证启动命令是否正确执行:

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

你应该会看到:

Custom startup message

这证实了命令已成功执行,并创建了包含指定内容的文件。

理解 command 和 args 的区别

理解 commandargs 之间的关系很重要:

  1. 如果你只指定 command,它将覆盖容器镜像的默认 ENTRYPOINT
  2. 如果你只指定 args,它将覆盖容器镜像的默认 CMD
  3. 如果你同时指定 commandargs,它们将同时覆盖 ENTRYPOINTCMD

让我们看看修改容器配置会发生什么。创建一个名为 modified-command-pod.yaml 的新文件:

nano modified-command-pod.yaml

添加以下内容:

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"]

Ctrl+X,然后按 YEnter 保存并退出。

现在,让我们创建这个容器:

kubectl apply -f modified-command-pod.yaml

你应该会看到:

pod/modified-command-pod created

查看日志,了解发生了什么:

kubectl logs modified-command-pod

你应该会看到:

This message is printed by echo

注意容器如何使用指定的参数执行 /bin/echo 命令,然后在 echo 打印输出后终止。

检查容器状态:

kubectl get pods modified-command-pod

你应该会看到类似以下内容:

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

Completed 状态表示容器已运行完成并成功退出。

现在你已经了解了如何结合使用 commandargs 来配置 Kubernetes 容器的启动行为。

执行多条命令并处理启动失败情况

在这一步,你将学习如何在容器(Pod)启动时执行多条命令,以及如何使用健康检查来处理启动失败的情况。

执行多条命令

通常,你需要在容器启动时运行多条命令。在 Kubernetes 中有几种方法可以实现这一点:

  1. 使用 shell 操作符(&&; 等)来链接命令
  2. 使用脚本文件
  3. 使用初始化容器(init container)

让我们通过查看 multi-command-pod.yaml 文件来探索第一种方法:

nano multi-command-pod.yaml

你会看到:

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"
        ]

此配置使用 && 操作符将多条命令链接在一起,只有当前一条命令成功执行时,才会执行下一条命令。

Ctrl+X 退出编辑器。

让我们创建这个容器:

kubectl apply -f multi-command-pod.yaml

你应该会看到:

pod/multi-command-pod created

现在,让我们通过检查创建的文件来验证两条命令是否都已执行:

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

你应该会看到:

First command

对于第二个文件:

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

你应该会看到:

Second command

使用健康检查处理启动失败情况

Kubernetes 提供了检测和处理启动失败的机制:

  1. 存活探针(Liveness Probes):检查容器是否正在运行;如果没有运行,Kubernetes 将重启它
  2. 就绪探针(Readiness Probes):检查容器是否准备好接收流量
  3. 启动探针(Startup Probes):检查应用程序是否已启动;对于启动缓慢的容器很有用

让我们查看一个使用存活探针的容器:

nano liveness-probe-pod.yaml

你会看到:

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

在此配置中:

  • 容器在启动时创建一个文件 /tmp/healthy
  • 存活探针每 5 秒检查一次该文件
  • 如果文件缺失,Kubernetes 会认为容器不健康并重启它

Ctrl+X 退出编辑器。

让我们创建这个容器:

kubectl apply -f liveness-probe-pod.yaml

你应该会看到:

pod/liveness-probe-pod created

检查容器是否正在运行:

kubectl get pods liveness-probe-pod

你应该会看到:

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

现在,让我们看看如果删除健康检查文件会发生什么:

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

等待约 10 秒,然后再次检查容器状态:

kubectl get pods liveness-probe-pod

你应该会看到容器已被重启:

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

RESTARTS 计数已增加到 1,表明 Kubernetes 检测到容器处于不健康状态并重启了它。

让我们验证健康检查文件是否再次存在(容器重启时,启动命令应该会重新创建该文件):

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

你应该会看到文件再次存在:

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

这展示了 Kubernetes 如何自动从启动失败中恢复,并维持应用程序的期望状态。

创建自定义启动脚本

对于更复杂的初始化操作,你可能需要使用自定义启动脚本。让我们创建一个使用 shell 脚本进行启动的容器:

nano script-pod.yaml

添加以下内容:

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

此配置:

  1. 在容器中创建一个启动脚本
  2. 使其可执行
  3. 运行该脚本
  4. 通过 sleep 命令保持容器运行

Ctrl+X,然后按 YEnter 保存并退出。

让我们创建这个容器:

kubectl apply -f script-pod.yaml

你应该会看到:

pod/script-pod created

等待片刻,让容器启动,然后检查其状态:

kubectl get pods script-pod

你应该会看到:

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

现在,让我们检查脚本的输出:

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

你应该会看到类似以下内容:

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

让我们验证配置文件是否已创建:

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

你应该会看到:

app_name=MyApp
version=1.0

这展示了如何使用复杂的启动脚本来初始化 Kubernetes 中的容器。

最佳实践与实际应用

在最后这一步,你将探索在 Kubernetes 容器(Pod)启动时运行命令的最佳实践,并创建一个实现这些实践的实际应用示例。

启动命令的最佳实践

在为 Kubernetes 容器配置启动命令时,请考虑以下最佳实践:

  1. 保持启动命令幂等:命令应能安全地多次运行而不引发问题。
  2. 使用健康检查:实现存活探针(liveness)和就绪探针(readiness)以验证启动是否成功。
  3. 优雅地处理失败:在启动脚本中包含错误处理机制。
  4. 分离关注点:使用初始化容器(init container)来执行与主应用程序分离的初始化任务。
  5. 限制启动时间:保持初始化过程快速,以减少部署时间。
  6. 使用环境变量:通过环境变量使启动命令可配置。
  7. 记录启动进度:输出清晰的日志以便进行故障排除。

使用初始化容器执行启动任务

初始化容器在应用容器启动之前运行,非常适合用于设置任务。让我们创建一个带有初始化容器的容器:

nano init-container-pod.yaml

添加以下内容:

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: {}

在此配置中:

  1. 初始化容器 init-config 首先运行并创建配置文件。
  2. 两个容器共享一个名为 shared-volume 的卷。
  3. 主应用容器读取由初始化容器创建的配置。

Ctrl+X,然后按 YEnter 保存并退出。

让我们创建这个容器:

kubectl apply -f init-container-pod.yaml

你应该会看到:

pod/init-container-pod created

检查容器状态:

kubectl get pods init-container-pod

你应该会看到容器正在运行:

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

现在,让我们检查主容器的日志:

kubectl logs init-container-pod -c app

你应该会看到类似以下内容:

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

这证实了初始化容器成功创建了配置文件,并且主容器能够读取这些文件。

实际示例:带有数据库检查的 Web 应用程序

让我们创建一个更实际的示例——一个在启动前检查数据库可用性的 Web 应用程序:

nano webapp-pod.yaml

添加以下内容:

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: {}

此配置:

  1. 使用初始化容器检查数据库可用性(模拟)。
  2. 主容器在启动前检查初始化容器创建的文件。
  3. 包含一个就绪探针以验证应用程序是否正在提供流量。
  4. 使用共享卷在容器之间进行通信。

Ctrl+X,然后按 YEnter 保存并退出。

让我们创建这个容器:

kubectl apply -f webapp-pod.yaml

你应该会看到:

pod/webapp-pod created

等待片刻,让容器完全启动,然后检查其状态:

kubectl get pods webapp-pod

你应该会看到:

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

READY 列中的 1/1 表示就绪探针已成功。

让我们进行端口转发以访问 Web 应用程序:

kubectl port-forward webapp-pod 8080:80 &

此命令在后台运行(由于 &)。现在,你可以使用 curl 访问 Web 应用程序:

curl http://localhost:8080

你应该会看到:

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

这证实了应用程序已正确初始化,验证了数据库的可用性,并且现在正在提供流量。

停止端口转发进程:

pkill -f "kubectl port-forward"

清理

在结束实验之前,让我们清理创建的资源:

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

你应该会看到:

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

现在你已经学会了如何在 Kubernetes 容器启动时运行命令、执行多条命令、处理失败情况,以及在实际场景中应用最佳实践。

总结

在本次实验中,你学习了如何在 Kubernetes 容器(Pod)启动时运行命令。你获得了以下方面的实践经验:

  • 使用 Minikube 设置 Kubernetes 环境
  • 创建并管理带有特定启动命令的容器
  • 使用 commandargs 字段配置容器行为
  • 使用 shell 操作符和脚本执行多条命令
  • 使用健康检查处理启动失败情况
  • 使用初始化容器(init container)实施最佳实践
  • 构建需要正确初始化的实际应用程序

这些技能对于在 Kubernetes 环境中部署容器化应用程序至关重要。通过正确配置启动命令,你可以确保应用程序正确初始化、验证依赖项并优雅地处理错误。

在你继续 Kubernetes 学习之旅时,请记住,正确的应用程序初始化是构建可靠、可扩展和可维护系统的关键部分。