在 Alpine Docker 容器中运行 SSH
部署 Web 应用程序时,您可能会使用Docker进行容器化。许多基础 Docker 映像,例如Node或Python正在运行Alpine Linux。它是一款出色的 Linux 发行版,既安全又非常轻量级。
然而,许多 Docker 映像提供的 Alpine 没有其OpenRCinit 系统。当尝试在 Docker 容器中使用主进程运行 sidecar 服务时,这会导致一些问题。
目录
-
目录
-
案例研究
-
配置 SSH
*配置用户
*安装 SSH
*运行 sshd
-
故障排除
-
结束
案例研究
让我们假设我们有一个托管在_somewhere_ 的远程服务器。我们正在使用此服务器来部署我们的生产应用程序。为了在隔离的环境中运行应用程序,我们将继续使用 Node.js/Python/nginx 或任何其他应用程序。为了管理流程,我们希望能够直接访问生产环境——在这种情况下是 docker 容器。 SSH 是这里的方式,但我们应该如何设置它呢?
我们可以通过 SSH 连接到远程服务器,然后使用docker exec,但这不是一个特别安全或优雅的解决方案。也许我们应该将 SSH 连接转发到 Docker 容器本身?
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--6cyVblaS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/x2g5yhndlzr7m3cu9upj.jpg)
绑定端口相当简单——我们不仅会绑定端口 443(或您可能用于您的用例的任何其他端口),还会绑定端口 22。运行命令类似于docker run -p 443:<docker_app_port> -p 22:22 <container_id>。
更具挑战性的部分是在容器内设置实际的 SSH。我们将以一个简单的 Node.js Dockerfile 作为基础。
FROM node:12.22-alpine
# added code goes here
WORKDIR /app
COPY . .
RUN yarn
RUN yarn build
CMD ["yarn", "start"]
进入全屏模式 退出全屏模式
配置SSH
为了重现与远程服务器的连接,我们将在本地运行此 Docker 映像并使用localhost进行连接。
配置用户
首先,为了能够以 root(或任何其他用户)身份登录,我们必须解锁用户并添加授权的SSH 密钥(除非您想使用非常不安全的文本密码登录)。
# create user SSH configuration
RUN mkdir -p /root/.ssh \
# only this user should be able to read this folder (it may contain private keys)
&& chmod 0700 /root/.ssh \
# unlock the user
&& passwd -u root
进入全屏模式 退出全屏模式
现在root用户已解锁以登录,并且有一个用于 SSH 配置的文件夹。但是目前任何人都可以以root身份登录,因为没有密码并且默认情况下启用密码验证。我们的目标是禁用密码验证并将我们的公钥添加为该用户的授权密钥。
您也可以为此使用其他用户 - 只需将
/root替换为所需用户主目录的路径并替换用户名。
# supply your pub key via `--build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)"` when running `docker build`
ARG ssh_pub_key
RUN echo "$ssh_pub_key" > /root/.ssh/authorized_keys
进入全屏模式 退出全屏模式
安装SSH
现在为了通过 SSH 禁用密码验证,我们必须首先安装 SSH。我将为此使用OpenSSH但其他实现应该类似地工作。
RUN apk add openrc openssh \
&& ssh-keygen -A \
&& echo -e "PasswordAuthentication no" >> /etc/ssh/sshd_config
进入全屏模式 退出全屏模式
默认情况下,像 Node 1 这样的剥离 Alpine Docker 镜像不提供 OpenRC,因此我们应该自己安装它。然后生成主机 SSH 密钥,以便客户端可以将我们的容器授权为 SSH 主机。最后,将PasswordAuthentication no附加到sshd_config的末尾以禁用通过 SSH 的密码验证。
对于实际应用程序,您将投资预先生成主机密钥,这样密钥就不会在每次构建新容器时更新。然而,这和更优雅地管理密钥超出了本文的范围。
此时我们可以启动sshd服务。
RUN rc-status \
# touch softlevel because system was initialized without openrc
&& touch /run/openrc/softlevel \
&& rc-service sshd start
进入全屏模式 退出全屏模式
但是,当您运行容器时,您会看到类似kex_exchange_identification的错误。当docker exec进入容器并执行rc-status你会看到sshd服务崩溃了。
运行sshd
服务崩溃是因为 Alpine Docker 镜像只允许启动一个进程。它实际上是一个很好的概念,它有助于使用微服务和创建 docker 组合。但是在这种特殊情况下,无法在不同的容器中运行 SSH。为了避免实际的ENTRYPOINT或CMD命令应该引导多个进程。
FROM node:12.22-alpine
ARG ssh_pub_key
RUN mkdir -p /root/.ssh \
&& chmod 0700 /root/.ssh \
&& passwd -u root \
&& echo "$ssh_pub_key" > /root/.ssh/authorized_keys \
&& apk add openrc openssh \
&& ssh-keygen -A \
&& echo -e "PasswordAuthentication no" >> /etc/ssh/sshd_config \
&& mkdir -p /run/openrc \
&& touch /run/openrc/softlevel
WORKDIR /app
COPY . .
RUN yarn
RUN yarn build
ENTRYPOINT ["sh", "-c", "rc-status; rc-service sshd start; yarn start"]
进入全屏模式 退出全屏模式
在这个最终的 Dockerfile 中,我将所有之前的RUN个命令组合成一个命令以减少层数。我们不是在RUN中运行rc-status && rc-service sshd start,而是在ENTRYPOINT中的sh -c中执行此操作。这样,Docker 容器将只执行一个会产生子进程的进程sh -c。
作为副作用,此 Node.js 应用程序在停止 Docker 容器时不会收到 SIGINT/SIGTERM 信号(或来自 Docker 的任何其他信号)。
使用docker run -p 7655:22 <container_id>运行构建的容器。在不同的终端实例中运行ssh root@localhost -p 7655。瞧——您成功地通过 SSH 连接到 Docker 容器。将 SSH 用于您的生产应用程序将是相同的,只是您将使用其 IP 而不是localhost和有效端口。
要在没有应用程序的情况下正确构建和运行容器,将
yarn start替换为node并删除yarn/yarn build指令。
故障排除
当运行 SSH 可能出现任何困难时,首先尝试docker exec进入容器并检查rc-status。sshd服务应该是started。如果它有crashed或不存在,您可能无法在ENTRYPOINT中正确启动它或在sshd_config中配置不正确。您也可能缺少 SSH 主机密钥或使用已绑定的端口。
要进一步排除故障,您可以使用docker run -p 7655:22 7656:10222 <container_id>运行容器。然后运行docker exec <container_id>并运行$(which sshd) -Ddp 10222。这将创建另一个sshd实例,该实例将侦听具有详细日志记录的不同端口 (10222)。从那里您应该会看到有关进程可能崩溃的原因的信息性消息。
如果进程没有崩溃尝试连接。在 Docker 主机上运行ssh root@localhost -p 7656。从此时起,您将在之前的终端窗口中看到其他日志,其中详细说明了连接被拒绝的原因。
结束
虽然本教程专门针对在 Alpine Docker 容器中运行 SSH,但您可以重用这些知识在其他 Linux Docker 发行版中运行 SSH。或者,在 Alpine Docker 容器中配置其他 Sidecar 服务可能会更好。无论如何,我希望你在这篇文章中找到了你想要的东西或者学到了一些新东西。
更多推荐
所有评论(0)