如何使用 Kubernetes 在一个 yaml 文件中设置多个命令?

How to set multiple commands in one yaml file with Kubernetes?

在这个官方文档中,可以运行在yaml配置文件中命令:

https://kubernetes.io/docs/tasks/configure-pod-container/

apiVersion: v1
kind: Pod
metadata:
  name: hello-world
spec:  # specification of the pod’s contents
  restartPolicy: Never
  containers:
  - name: hello
    image: "ubuntu:14.04"
    env:
    - name: MESSAGE
      value: "hello world"
    command: ["/bin/sh","-c"]
    args: ["/bin/echo \"${MESSAGE}\""]

如果我想运行多个命令,怎么办?

command: ["/bin/sh","-c"]
args: ["command one; command two && command three"]

解释: command ["/bin/sh", "-c"] 表示 "run a shell, and execute the following instructions"。然后将 args 作为命令传递给 shell。在 shell 脚本中,分号分隔命令,并且 && 有条件地 运行 如果第一个命令成功,则执行以下命令。在上面的例子中,它总是 运行s command one 后跟 command two,只有 运行s command three 如果 command two 成功。

备选方案: 在许多情况下,您想要 运行 的某些命令可能会将最终命令设置为 运行。在这种情况下,特别是构建您自己的 Dockerfile is the way to go. Look at the RUN 指令。

如果您愿意使用 Volume 和 ConfigMap,您可以 mount ConfigMap data 作为脚本,然后 运行 该脚本:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
data:
  entrypoint.sh: |-
    #!/bin/bash
    echo "Do this"

    echo "Do that"
---
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: "ubuntu:14.04"
    command:
    - /bin/entrypoint.sh
    volumeMounts:
    - name: configmap-volume
      mountPath: /bin/entrypoint.sh
      readOnly: true
      subPath: entrypoint.sh
  volumes:
  - name: configmap-volume
    configMap:
      defaultMode: 0700
      name: my-configmap

这稍微清理了您的 pod 规范并允许编写更复杂的脚本。

$ kubectl logs my-pod
Do this
Do that

我更喜欢将参数多行化,这样最简单也最容易阅读。另外,可以在不影响镜像的情况下修改脚本,只需要重启pod即可。例如,对于 mysql 转储,容器规范可能是这样的:

containers:
  - name: mysqldump
    image: mysql
    command: ["/bin/sh", "-c"]
    args:
      - echo starting;
        ls -la /backups;
        mysqldump --host=... -r /backups/file.sql db_name;
        ls -la /backups;
        echo done;
    volumeMounts:
      - ...

这样做的原因是 yaml 实际上将“-”之后的所有行连接成一个,而 sh 运行一个长字符串 "echo starting; ls... ; echo done;"。

如果您想避免使用 ;&& 将所有命令连接成一个命令,您还可以使用 heredoc 获得真正的多行脚本:

command: 
 - sh
 - "-c"
 - |
   /bin/bash <<'EOF'

   # Normal script content possible here
   echo "Hello world"
   ls -l
   exit 123

   EOF

这对于 运行 现有的 bash 脚本很方便,但缺点是需要内部和外部 shell 实例来设置 heredoc。

只是为了提供另一种可能的选择,可以使用秘密,因为它们以卷的形式呈现给 pod:

秘密示例:

apiVersion: v1
kind: Secret 
metadata:
  name: secret-script
type: Opaque
data:
  script_text: <<your script in b64>>

Yaml 提取:

....
containers:
    - name: container-name
      image: image-name
      command: ["/bin/bash", "/your_script.sh"]
      volumeMounts:
        - name: vsecret-script
          mountPath: /your_script.sh
          subPath: script_text
....
  volumes:
    - name: vsecret-script
      secret:
        secretName: secret-script

我知道很多人会争辩说这不是必须使用秘密的目的,但它是一种选择。

恕我直言,最好的选择是使用 YAML 的原生 block scalars。具体在这种情况下,folded 样式块。

通过调用 sh -c,您可以将参数作为命令传递给您的容器,但如果您想用换行符优雅地分隔它们,您需要使用 折叠样式块,这样 YAML 就会知道将换行符转换为空格,从而有效地连接命令。

一个完整的工作示例:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  containers:
  - name: busy
    image: busybox:1.28
    command: ["/bin/sh", "-c"]
    args:
    - >
      command_1 &&
      command_2 &&
      ... 
      command_n

Here is my successful run

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - command:
    - /bin/sh
    - -c
    - |
      echo "running below scripts"
      i=0; 
      while true; 
      do 
        echo "$i: $(date)"; 
        i=$((i+1)); 
        sleep 1; 
      done
    name: busybox
    image: busybox

我不确定这个问题是否仍然有效,但由于我没有在上述答案中找到解决方案,我决定把它写下来。

我使用以下方法:

readinessProbe:
  exec:
    command:
    - sh
    - -c
    - |
      command1
      command2 && command3

我知道我的示例与 readinessProbe、livenessProbe 等相关,但怀疑容器命令也是如此。这提供了灵活性,因为它反映了在 Bash.

中编写的标准脚本

这是运行多行命令的另一种方法。

apiVersion: batch/v1
kind: Job
metadata:
  name: multiline
spec:
  template:
    spec:
      containers:
      - command:
        - /bin/bash
        - -exc
        - |
          set +x
          echo "running below scripts"
          if [[ -f "if-condition.sh" ]]; then
            echo "Running if success"
          else
            echo "Running if failed"
          fi
        name: ubuntu
        image: ubuntu
      restartPolicy: Never
  backoffLimit: 1

这是另一种方法,使用输出日志记录。

apiVersion: v1
kind: Pod
metadata:
  labels:
    type: test
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
      - name: log-vol
        mountPath: /var/mylog
    command:
        - /bin/sh
        - -c
        - >
            i=0;
            while [ $i -lt 100 ];
            do
             echo "hello $i";
             echo "$i :  $(date)" >> /var/mylog/1.log;
             echo "$(date)" >> /var/mylog/2.log;
             i=$((i+1));
             sleep 1;
            done

  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
    - name: log-vol
      emptyDir: {}