如何在 kubernetes pod 中正确创建用于创建 SSH 隧道的 sidecar 容器

Posted

技术标签:

【中文标题】如何在 kubernetes pod 中正确创建用于创建 SSH 隧道的 sidecar 容器【英文标题】:How to properly create sidecar container for creating SSH tunnel in kubernetes pod 【发布时间】:2021-09-29 00:25:57 【问题描述】:

我在 AWS 中有一个需要从 Kubernetes 连接的数据库,但该数据库中的安全设置阻止了这种情况。我的解决方案是从 Kubernetes pod 内通过 SSH 隧道连接到代理,并通过该隧道连接到 AWS 中的数据库。

但是,我不太确定如何在 Kubernetes 中真正实现这一点,因为 sidecar 容器会抛出“CrashLoopBackOff”错误。

我的 Dockerfile 很薄。它是一个 alpine 容器,除了复制一个处理隧道的 shell 脚本之外,实际上什么都不做。

Dockerfile

FROM alpine:3.14.0

COPY tunnel.sh /

RUN apk update && apk add curl \
    wget \
    nano \
    bash \
    ca-certificates \
    openssh-client

RUN chmod +x /tunnel.sh
RUN mkdir ~/.ssh

RUN ssh-keyscan -Ht ecdsa proxysql-sshtunnel.domain.com > ~/.ssh/known_hosts

CMD /bin/bash

tunnel.sh

#!/bin/bash
ssh -i /keys/sql_proxy.private -L 3306:10.0.0.229:6033 centos@proxysql-sshtunnel.domain.com -N

他们的 SSH 密钥从 Kubernetes 中的秘密卷挂载到 pod。我的部署如下所示:

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: accounts-deployment
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: api-accounts
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    spec:
      containers:
      - image: gcr.io/xxxxxxxx/accounts:VERSION-2.0.6
        imagePullPolicy: Always
        name: accounts
        resources: 
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /tmp
          name: accounts-keys
          readOnly: true
        - mountPath: /var/www/html/var/spool
          name: mail-spool
      - image: gcr.io/xxxxxxxx/sql-proxy:latest
        imagePullPolicy: IfNotPresent
        name: sql-proxy
        args:
          - -c
          - /tunnel.sh
        command:
          - /bin/bash
        resources: 
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /keys
          name: keys-sql-proxy
          readOnly: true
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: 
      terminationGracePeriodSeconds: 30
      volumes:
      - name: accounts-keys
        secret:
          defaultMode: 420
          secretName: accounts-keys
      - name: spoonity-sql-proxy
        secret:
          defaultMode: 420
          secretName: spoonity-sql-proxy
      - emptyDir: 
        name: mail-spool
status:

...
- image: gcr.io/xxxxxxxx/sql-proxy:latest
  imagePullPolicy: IfNotPresent
  name: sql-proxy
  args:
    - -c
    - /tunnel.sh
  command:
    - /bin/bash
  resources: 
  terminationMessagePath: /dev/termination-log
  terminationMessagePolicy: File
  volumeMounts:
    - mountPath: /keys
      name: keys-sql-proxy
      readOnly: true
...

我从 Kubernetes 得到的唯一日志是:“/bin/bash: line 1: /tunnel.sh: No such file or directory

如果我尝试使用 docker run sql-proxy:latest /tunnel.sh 在 docker 中本地运行容器,则会收到另一个错误,抱怨密钥不存在(这正是我期望看到的)。

不确定这个问题出在哪里。

编辑:尝试在本地重建容器并手动包含密钥。我能够成功启动容器。所以看起来这绝对是 Kubernetes 的问题,但我真的不知道为什么。

【问题讨论】:

从下面的答案“建议将 CMD 更改为您要运行的实际命令,而不是通过 kubernetes 传递它。”这是选项之一。其次是将命令/参数重写为this structure 的想法。我用你的选择进行了测试,它对我的​​情况不起作用。 【参考方案1】:

这里的问题是您可能正在将文件复制到容器的/ 目录,但是当您启动容器时,shell 从~/ 目录启动。所以它找不到文件。

在 Dockerfile 的开头添加一个 WORKDIR 语句,这将确保您在启动容器时知道从哪里开始。

FROM alpine:3.14.0

WORKDIR /usr/src/app

COPY tunnel.sh .

RUN apk update && apk add curl \
    wget \
    nano \
    bash \
    ca-certificates \
    openssh-client

RUN chmod +x ./tunnel.sh

RUN mkdir ~/.ssh

RUN ssh-keyscan -Ht ecdsa proxysql-sshtunnel.domain.com > ~/.ssh/known_hosts

CMD /bin/bash

另外,建议将 CMD 更改为您要运行的实际命令,而不是通过 kubernetes 传递。

【讨论】:

这并不能解决问题。我也不确定为什么会这样。该脚本位于根目录 (/)。它是从 / 在 Kubernetes 中调用的。将该绝对路径设为相对路径不应产生任何实质性影响。【参考方案2】:

所以问题就在这里:

volumes:
      - name: accounts-keys
        secret:
          defaultMode: 420
          secretName: accounts-keys
      - name: spoonity-sql-proxy
        secret:
          defaultMode: 420 #<----------- this is wrong
          secretName: spoonity-sql-proxy

SSH 需要特定的密钥权限才能连接。 Kubernetes 使用基于十进制的文件权限,所以这里的正确值应该是 384,这将在 Linux 中以正确的权限 0600 挂载密钥。

由于权限错误,每次脚本尝试执行都会失败退出,触发Kubernetes尝试重启。

仍然不确定为什么从未生成这些日志,但我通过在我的部署清单中任意更改 commandargs 来发现这一点,而不是连续 ping localhost 以便容器至少可以启动:

...
 - image: gcr.io/xxxxxxxxx/sql-proxy:latest
   command: ["ping"]
   args: ["127.0.0.1"]
...

然后我连接到正在运行的 pod,并尝试手动运行 tunnel.sh 命令。现在我可以真正了解它失败的原因,我可以修复它。

【讨论】:

以上是关于如何在 kubernetes pod 中正确创建用于创建 SSH 隧道的 sidecar 容器的主要内容,如果未能解决你的问题,请参考以下文章

理解Kubernetes的NodePort、LoadBalancer和Ingress

通过 Airflow 创建的 Kubernetes pod 保持运行状态

Kubernetes Pod 中如何查看动态创建绑定的远程磁盘使用情况

Kubernetes 如何在 StatefulSet 中跟踪哪个云盘挂载到哪个 Pod?

如何防止在 Kubernetes 中由 HPA 创建的特定时间段内新扩展的 Pod 缩减?

Kubernetes 配置Pod和容器(二)定义容器命令行和参数