通过 SSH 推送 Docker 镜像(分布式)

Posted

技术标签:

【中文标题】通过 SSH 推送 Docker 镜像(分布式)【英文标题】:Docker image push over SSH (distributed) 【发布时间】:2015-10-13 01:28:12 【问题描述】:

TL;DR基本上,我正在寻找这个:

docker push myimage ssh://myvps01.vpsprovider.net/

我无法理解整个 Docker Hub / Registry 背后的基本原理。我知道我可以运行私有注册表,但为此我必须设置实际运行服务器的基础架构。

我偷看了 Docker 的内部工作原理(嗯,至少是文件系统),看起来 Docker 镜像层只是一堆 tarball,或多或少,带有一些复杂的文件命名。我天真地认为编写一个简单的 Python 脚本来进行分布式推/拉并非不可能,但我当然没有尝试,所以这就是我问这个问题的原因。

Docker 不能像 Git 或 Mercurial 那样只进行分布式(无服务器)推/拉,有什么技术原因吗?

我认为这将是一个巨大的帮助,因为我可以将我在笔记本电脑上构建的图像直接推送到应用服务器上,而不是先推送到某个地方的 repo 服务器,然后再从应用服务器中提取。或者我只是误解了这个概念,而注册表是我绝对需要的一个非常重要的功能?

编辑一些上下文可以解释我为什么要这样做,请考虑以下场景:

在我的笔记本电脑上完成开发、测试(OSX,运行 Docker 机器,使用 docker-compose 定义服务和依赖项) 通过脚本部署到live环境(自写,bash,对开发机器的依赖很少,基本上就是Docker机器) 部署到新的 VPS,除了 SSH 访问和 Docker 守护程序之外,几乎没有依赖项。 没有“永久”服务在任何地方运行,即我特别不想托管永久运行的注册表(尤其是所有 VPS 实例都无法访问,尽管这可能可以通过一些巧妙的 SSH 隧道解决)

目前最好的解决方案是使用 Docker 机器指向 VPS 服务器并重新构建它,但它会减慢部署速度,因为我每次都必须从源代码构建容器。

【问题讨论】:

【参考方案1】:

如果你想将 docker 镜像推送到给定的主机,Docker 中已经有一切允许这样做。下面的例子展示了如何通过 ssh 推送一个 docker 镜像:

docker save <my_image> | ssh -C user@my.remote.host.com docker load
docker save 将生成一个 docker 镜像(包括其层)的 tar 存档 -C用于ssh压缩数据流 docker load 从 tar 存档创建 docker 映像

请注意,docker registry + docker pull 命令的组合具有仅下载缺失层的优势。因此,如果您经常更新 docker 镜像(添加新层或修改最后几层),那么 docker pull 命令将产生比通过 ssh 推送完整 docker 镜像更少的网络流量。

【讨论】:

我知道加载和保存,但重点是要聪明,只推/拉不同的层。 docker save 自己输出“Cowardly refusing to save to a terminal”xD【参考方案2】:

我为这个场景制作了一个命令行实用程序。

它在服务器上建立一个临时的私有 docker 注册表,从你的 localhost 建立一个 SSH 隧道,推送你的镜像,然后自行清理。

docker save 相比,这种方法的好处是只有新图层被推送到服务器,因此上传速度更快。

通常使用像 dockerhub 这样的中间注册表是不可取的,而且很麻烦。

https://github.com/brthor/docker-push-ssh

安装:

pip install docker-push-ssh

例子:

docker-push-ssh -i ~/my_ssh_key username@myserver.com my-docker-image

最大的警告是您必须手动将本地 ip 添加到 docker 的 insecure_registries 配置中。

https://***.com/questions/32808215/where-to-set-the-insecure-registry-flag-on-mac-os

【讨论】:

【参考方案3】:

将映像保存/加载到 Docker 主机和推送到注册表(私有或集线器)是两件不同的事情。

前@Thomasleveil 已经解决了。

后者实际上确实具有仅推送所需层的“智能”。

您可以使用私有注册表和几个派生图像轻松地自行测试。

如果我们有两张图片,一张是从另一张派生的,那么做:

docker tag baseimage myregistry:5000/baseimage
docker push myregistry:5000/baseimage

将推送注册表中尚未找到的所有层。但是,当您接下来推送派生图像时:

docker tag derivedimage myregistry:5000/derivedimage
docker push myregistry:5000/derivedimage

您可能会注意到只有一个层被推送 - 前提是您的 Dockerfile 构建为只需要一层(例如,按照 Dockerfile Best Practises 链接 RUN 参数)。

在您的 Docker 主机上,您还可以运行 Dockerised 私有注册表。

见Containerized Docker registry

据我所知,截至撰写本文时,注册表推送/拉取/查询机制不支持 SSH,仅支持 HTTP/HTTPS。这与 Git 和朋友们不同。

请参阅Insecure Registry 了解如何通过 HTTP 运行私有注册表,特别注意您需要更改 Docker 引擎选项并重新启动它:

打开 /etc/default/docker 文件或 /etc/sysconfig/docker for 编辑。

根据您的操作系统,您的引擎守护程序启动选项。

编辑(或添加) DOCKER_OPTS 行并添加 --insecure-registry 标志。

例如,此标志采用您的注册表的 URL。

DOCKER_OPTS="--insecure-registry myregistrydomain.com:5000"

关闭并保存配置文件。

重启你的 Docker 守护进程

您还将找到使用自签名证书的说明,允许您使用 HTTPS。

使用自签名证书

[...]

这比不安全的注册表解决方案更安全。你必须 配置每个想要访问您的注册表的 docker 守护进程

Generate your own certificate:

mkdir -p certs && openssl req \ -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \ -x509 -days 365 -out certs/domain.crt

Be sure to use the name myregistrydomain.com as a CN.

Use the result to start your registry with TLS enabled

Instruct every docker daemon to trust that certificate.

This is done by copying the domain.crt file to /etc/docker/certs.d/myregistrydomain.com:5000/ca.crt.

Don’t forget to restart the Engine daemon.

【讨论】:

感谢您的详细解释。我用一些关于我想要完成的内容的背景更新了我的问题。基本上,我对运行 Docker 引擎本身很好,我想托管一个指向我的开发机器的 Docker 安装的注册表,通过我已经使用的 SSH 连接提供服务。我意识到目前这可能是不可能的,这意味着您的答案可能是可以接受的。 啊,我明白了。我将在我的答案中添加信息,说明我目前的理解是 SSH 不是(私有)注册表的选项。除非出现一些奇怪的 SSH 隧道场景。 另外一点:您的编辑:您的一些目标强烈倾向于编排和 CI/CD。像 Bamboo 或 Jenkins 这样的平台可以通过 Docker 做到这一点,但由于我自己仍在学习和评估这些选项,我不会把它放在我的答案中,因为我觉得它还不确定。 虽然对于较大的项目肯定有很好的编排解决方案,但我的问题应该在从一台笔记本电脑管理的小型项目的上下文中阅读,而无需访问基础架构服务器(如 CI 服务器)的资源/资源. 了解,并且在我自己的大部分工作中,我也没有使用一些大型 CI/CD 工具(但;)。我几乎每天都在使用我自己提出的解决方案建议,希望对您有用。【参考方案4】:

扩展@brthornbury 的想法。

我不想涉足运行 python,所以我想出了同样的 bash 脚本。

#!/usr/bin/env bash
SOCKET_NAME=my-tunnel-socket
REMOTE_USER=user
REMOTE_HOST=my.remote.host.com

# open ssh tunnel to remote-host, with a socket name so that we can close it later
ssh -M -S $SOCKET_NAME -fnNT -L 5000:$REMOTE_HOST:5000 $REMOTE_USER@$REMOTE_HOST

if [ $? -eq 0 ]; then
  echo "SSH tunnel established, we can push image"
  # push the image to remote host via tunnel
  docker push localhost:5000/image:latest
fi
# close the ssh tunnel using the socket name
ssh -S $SOCKET_NAME -O exit $REMOTE_USER@$REMOTE_HOST

【讨论】:

以上是关于通过 SSH 推送 Docker 镜像(分布式)的主要内容,如果未能解决你的问题,请参考以下文章

docker学习之——获取和推送镜像

Docker学习笔记(6-2)Docker镜像的获取和推送

创建支持SSH服务的镜像

通过Dockerfile创建支持SSH服务的docker镜像(centosUbuntu)

在Docker中跑Hadoop与镜像制作

基于docker commit命令实现支持ssh的操作系统镜像