有没有一种方法将多个Docker镜像合并成一个容器?

156

我现在有几个 Dockerfiles。

其中一个是用于 Cassandra 3.5,它是 FROM cassandra:3.5

我还有一个用于 Kafka 的 Dockerfile,但它相对复杂。它是 FROM java:openjdk-8-fre ,运行一条较长的命令来安装 Kafka 和 Zookeeper。

最后,我有一个使用 SBT 编写的 Scala 应用程序。

对于该 Dockerfile,它是 FROM broadinstitute/scala-baseimage,这为我提供了所需的 Java 8、Scala 2.11.7 和 STB 0.13.9。

也许我不理解 Docker 的工作原理,但我的 Scala 程序具有 Cassandra 和 Kafka 作为依赖项,并且出于开发目的,我希望其他人能够简单地使用 Dockerfile 克隆我的存储库,然后能够构建具有 Cassandra、Kafka、Scala、Java 和 SBT 的环境,以便他们可以编译源代码。但我遇到了很多问题。

我该如何合并这些 Dockerfiles?我如何简单地创建一个包含这些内容的环境?


11
不合并Docker镜像,而是使用Docker Compose进行组合:https://docs.docker.com/compose/ - generalhenry
@generalhenry 如果我想的话,我不是可以只复制粘贴所需的Docker内容来获取Cassandra 3.5,并将其放入我的主Dockerfile中,以获取Java、Scala和SBT吗? - David
虽然你可以在一个单一的容器中运行所有内容,但这很少是理想的。容器允许你干净地分离你的网络、扩展、日志、监控等。 - generalhenry
7
@generalhenry 好的,那通常是你想做的事情。但是,如果你需要使用 rust 来编译从 PyPi 安装的二进制 Python 包呢?这种情况下,你可能需要将 rust 和 python 的 docker 镜像结合起来使用,而简单地组合它们是行不通的。 - Tobias Bergkvist
9个回答

146
你可以使用 Docker 1.17 引入的“多阶段构建”功能。看一下这个:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  

然后正常构建镜像:

docker build -t alexellis2/href-counter:latest

来源: https://docs.docker.com/develop/develop-images/multistage-build/

最终结果是与之前相同的微型生产镜像,但复杂度显著降低。您不需要创建任何中间映像,也不需要将任何工件提取到本地系统。

它是如何工作的?第二个FROM指令以alpine:latest镜像为基础启动新的构建阶段。COPY --from=0只将先前阶段中构建的工件复制到此新阶段中。Go SDK和任何中间工件都被留在后面,并未保存在最终镜像中。


33
假设我想要合并两个基础图像,这些图像中有很多内容,并且它们不是由我维护的。例如,如果我想运行具有GPU加速功能的Rust应用程序,则希望我的图像是nvidia-dockerrustlang/rust:nightly的合并。这些图像又被放置在其他图像之上。为了使用多层构建进行此操作,我必须知道并指定我想要复制到另一个图像中的一个图像的所有文件 - 看起来似乎不可能,特别是因为该集合可能会随着上游图像的更改而发生变化。我理解得对吗? - masonk
9
我曾成功地执行以下操作: FROM a/a:latest FROM b/b:latest COPY --from=0 / /这种做法可能并不好,但它确实有效。我主要出于个人兴趣而尝试,不打算将其用于生产环境中。 - McP
8
这个该死的东西对我没用。就好像第一个“FROM”被完全忽略了一样。 - DimiDak
2
同样的问题:我想执行 FROM image1; CMD image1command; FROM image2; CMD image2command; 但是它根本不起作用。始终只有第二个命令。 - CGFoX
2
@LuizFelipe,不会啊,为什么要这样做呢?这可以减少代码复制。 - Mohammed Noureldin
显示剩余3条评论

29
你不能合并dockerfiles,因为可能会发生冲突。你需要创建一个新的dockerfile或构建一个自定义镜像。
TL;DR; 如果你当前的开发容器包含了所有需要的工具并且正常工作,那么将其保存为镜像并将其上传到仓库,并创建一个dockerfile从该仓库的镜像中拉取。
详细信息: 构建自定义镜像比使用公共镜像创建dockerfile要容易得多,因为你可以将任何hack和mod存储到镜像中。为此,请使用基本Linux镜像(或broadinstitute/scala-baseimage)启动空容器,安装所需的任何工具并配置它们,直到一切都正确工作,然后将其(容器)保存为镜像。从此镜像创建一个新容器并测试是否可以通过docker-compose(或你想要做/构建的方式)在其上构建代码。如果工作正常,则拥有一个可用于上传到仓库以便其他人拉取的工作基础镜像。
要使用公共镜像构建dockerfile,你需要将所有hack、mod和设置放在dockerfile本身上。也就是说,你需要将每个命令行使用的命令放入文本文件中,并将任何hack、mod和设置缩小为命令行。最后,你的dockerfile将自动创建一个镜像,你不需要将此镜像存储到仓库中,只需要给其他人提供dockerfile即可,他们可以在自己的docker容器中启动该镜像。
请注意,一旦你拥有一个工作的dockerfile,你可以轻松地进行调整,因为每次使用dockerfile时它都会创建一个新的镜像。对于自定义镜像,你可能会遇到需要重新构建镜像的问题,因为存在冲突。例如,所有工具都与openjdk配合使用,直到安装了一个不起作用的工具。修复可能涉及卸载openjdk并使用oracle,但你为已安装的所有工具做的所有配置都被破坏了。

7
自从引入多阶段构建之后,这个答案就已经过时了。 - slikts

27
以下答案适用于docker 1.7及以上版本: 我更喜欢使用--from=NAMEfrom image as NAME。为什么?你可以使用--from=0及以上,但是当你在dockerfile中有许多docker stages时,这可能会变得更难管理。 示例:
FROM golang:1.7.3 as backend
WORKDIR /backend
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN  #install some stuff, compile assets....
    
FROM golang:1.7.3 as assets
WORKDIR /assets
RUN ./getassets.sh

FROM nodejs:latest as frontend 
RUN npm install
WORKDIR /assets
COPY --from=assets /asets .
CMD ["./app"] 

FROM alpine:latest as mergedassets
WORKDIR /root/
COPY --from=frontend . /
COPY --from=backend ./backend .
CMD ["./app"]

注意:正确管理 Dockerfile 将有助于更快地构建 Docker 镜像。在内部,Docker 使用 Docker 层缓存来帮助这个过程,在镜像需要重新构建时也是如此。


这太聪明了!!!!!太喜欢了!!我现在可以将我的基础 Docker 镜像与相同架构/OS 的镜像合并了。 - Marcello DeSales
浪费了这么多时间之后,你救了我!! - undefined

11
是的,您可以将许多软件打包到一个Docker镜像中(GitLab就是这样做的,一个包含Postgres和其他所有内容的镜像),但generalhenry是正确的-这不是使用Docker的典型方式。
正如您所说,Cassandra和Kafka是Scala应用程序的依赖项,它们不是应用程序的一部分,因此它们不应该都在同一个镜像中。
使用Docker Compose编排许多容器需要添加额外的管理层,但它能够提供更大的灵活性:
  • 您的容器可以有不同的生命周期,因此当您有一个新版本的应用程序需要部署时,您只需要运行一个新的应用程序容器,您可以保留依赖项的运行;
  • 您可以在任何环境中使用相同的应用程序镜像,为您的依赖项使用不同的配置 - 例如,在开发中,您可以运行基本的Kafka容器,并在生产中将其集群化在多个节点上,您的应用程序容器是相同的;
  • 您的依赖项也可以被其他应用程序使用 - 因此,多个消费者可以在不同的容器中运行,并且所有人都可以使用相同的Kafka和Cassandra容器;
  • 此外,还提到了所有的可伸缩性、日志记录等。

5

什么时候需要“组合”Docker镜像?

正如其他人在这里指出的,通常情况下您不希望将数据库和应用程序放入同一个Docker镜像中。理想情况下,您希望Docker镜像包装一个“单一进程”/“运行时”。这允许每个进程单独进行扩展/缩小和重新启动。

假设您想使用一些共享的C库/可执行文件,但这些文件不在您正在使用的镜像的软件包管理器中,但是其他人已经创建了一个包含它们的预编译镜像-并且您可能不想在构建过程中重新编译这些二进制文件(这取决于此操作需要多长时间)。是否有一种方法可以快速创建一个POC-Docker镜像,其中包含所有这些可执行文件/库,基于现有的镜像?

Docker和组合

相关讨论:https://github.com/moby/moby/issues/3378

Docker缺乏一个好的组合镜像的方法。您可以使用从其他镜像中复制单个文件或整个文件系统到自己的镜像中。没有内置的方法将另一个镜像中的环境变量复制到自己的镜像中。
话虽如此,我个人创建了一个自定义Dockerfile前端/解析器, 添加了一个INCLUDE <image>关键字。这会将整个文件系统以及环境变量复制到您的镜像中:
DOCKER_BUILDKIT=1 docker build -t myimage .

#syntax=bergkvist/includeimage
FROM alpine:3.12.0
INCLUDE rust:1.44-alpine3.12
INCLUDE python:3.8.3-alpine3.12

nixpkgs.dockerTools

如果您想要真正可组合的Docker构建,请查看dockerToolsnixpkgs。这也将导致更多可重现(通常非常小的)镜像。请参见https://nix.dev/tutorials/building-and-running-docker-images
docker load < $(nix-build docker-image.nix)

# docker-image.nix
let
  pkgs = import <nixpkgs> {};
  python = pkgs.python38;
  rustc = pkgs.rustc;
in pkgs.dockerTools.buildImage {
  name = "myimage";
  tag = "latest";
  contents = [ python rustc ];
}

请问如何使用您的项目?我需要安装GoLang吗?在安装GoLang之后该怎么做?我需要运行.go文件吗?还是只需要将您的项目拉到我的电脑上,然后INCLUDE命令就可用了? - Quang Hoàng Minh
@QuangHoàngMinh,您只需要安装Docker即可使用语法扩展。#syntax=bergkvist/includeimage是指用于解释Dockerfile的Docker镜像。 - Tobias Bergkvist

2

Docker 不会对镜像进行合并,但如果Dockerfile可用,你可以将它们组合起来并打包成一个大镜像进行构建。有时候这样做是有意义的,然而对于在容器中运行多个进程,大部分 Docker 教义都认为这种方式不太理想,特别是在微服务架构中(不过规矩总是要被打破的,对吧?)


1
你不能将多个docker镜像合并到一个容器中。在Moby问题中查看详细讨论,如何通过Dockerfile将几个图像组合成一个
对于您的情况,最好不要包含整个Cassandra和Kafka镜像。应用程序只需要Cassandra Scala驱动程序和Kafka Scala驱动程序。容器应该只包括驱动程序。

0
我想要一个带有一些Node.js服务端渲染的nginx服务器。我只是简单地执行了以下操作:
FROM nginx:alpine-slim

RUN apk add nodejs-current npm

然后我将一个shell脚本添加到/docker-entrypoint.d/目录中,该脚本通过pm2启动了我的node.js服务器的后台进程。

-3

我需要Gitlab CI中的docker:latest和python:latest镜像。以下是我想到的:

FROM ubuntu:latest
RUN apt update
RUN apt install -y sudo
RUN sudo apt install -y docker.io
RUN sudo apt install -y python3-pip
RUN sudo apt install -y python3
RUN docker --version
RUN pip3 --version
RUN python3 --version

在我构建并将其推送到我的Docker Hub仓库之后:

docker build -t docker-hub-repo/image-name:latest path/to/Dockerfile
docker push docker-hub-repo/image-name:latest

不要忘记在推送之前执行docker login 希望能对你有所帮助。

这个答案可以简化为“在Dockerfile中安装所需的一切”。同时,您可以从FROM python开始安装docker。 - OneCricketeer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接