使用 Docker 构建已编译的应用程序
Building a compiled application with Docker
我正在构建一个用 C++ 编写的服务器,并希望使用 Docker 和 docker-compose 来部署它。 "right way"是做什么的呢?我应该从 Docker 文件调用 make
还是手动构建,上传到某个服务器,然后从 Docker 文件调用 COPY
二进制文件?
更新
对于 2017 年之后访问此问题的任何人,请参阅 by fuglede about using multi-stage Docker builds,这确实是比我 2015 年的答案(下方)更好的解决方案,远早于该答案可用。
旧答案
我的做法是 运行 在容器外构建,只将构建的输出(二进制文件和任何必要的库)复制到容器中。然后您可以将您的容器上传到容器注册表(例如,使用托管的或 运行 您自己的),然后从该注册表拉到您的生产机器上。因此,流程可能如下所示:
- 构建二进制文件
- test / sanity-check binary 本身
- 使用二进制构建容器镜像
- 测试 / sanity-check 容器映像 与二进制
- 上传到容器注册表
- 部署到 staging/test/qa,从注册表中提取
- 部署到产品,从注册表中提取
由于在生产部署之前进行测试很重要,因此您希望测试与将在生产中部署的完全相同的东西,因此您不想以任何方式提取或修改 Docker 图像建成后。
我不会 运行 构建 inside 您计划在产品中部署的容器,因为那样您的容器将具有各种额外的工件(例如临时生成生产中不需要的输出、工具等),并使用不会用于部署的内容不必要地扩展容器映像。
我的建议是完全在容器本身上开发、构建和测试。这保证了Docker开发者环境与生产环境相同的理念,见The Modern Developer Workstation on MacOS with Docker.
尤其是 C++ 应用程序,通常与共享 libraries/object 文件存在依赖关系。
我认为 Docker 上还没有用于开发、测试和部署 C++ 应用程序的标准化开发流程。
为了回答您的问题,我们目前的做法是,将容器视为您的开发环境,并在团队中实施一系列实践,例如:
- 我们的代码库(配置文件除外)始终位于共享卷(在本地计算机上)(版本在 Git 上)
- Shared/dependent 库、二进制文件等。始终 存在于容器中
- 在容器中构建和测试,在提交映像之前,清除不需要的对象文件、库等,并确保
docker diff
更改符合预期。
- Changes/updates 环境,包括共享库、依赖项,始终记录在案并与团队沟通。
我在使用 docker-compose
进行自动化构建时遇到了困难,最后我对所有内容都使用了 docker build
:
三层建筑
Run → develop → build
然后我将构建输出复制到 'deploy' 图像中:
Run → deploy
可玩的四层:
运行
- 包含申请 运行 所需的任何包 - 例如libsqlite3-0
开发
FROM <projname>:run
- 包含构建所需的包
- 例如g++、cmake、libsqlite3-dev
- Dockerfile 执行任何外部构建
- 例如构建 boost-python3 的步骤(不在包管理器存储库中)
建造
FROM <projname>:develop
- 包含来源
- Dockerfile 执行内部构建(经常更改的代码)
- 从该映像中复制构建的二进制文件以用于部署
部署
FROM <projname>:run
- 将构建的输出复制到映像中并安装
RUN
或 ENTRYPOINT
用于启动应用程序
文件夹结构如下所示:
.
├── run
│ └── Dockerfile
├── develop
│ └── Dockerfile
├── build
│ ├── Dockerfile
│ └── removeOldImages.sh
└── deploy
├── Dockerfile
└── pushImage.sh
设置构建服务器意味着执行:
docker build -f run -t <projName>:run
docker build -f develop -t <projName>:develop
每次我们构建时,都会发生这种情况:
# Execute the build
docker build -f build -t <projName>:build
# Install build outputs
docker build -f deploy -t <projName>:version
# If successful, push deploy image to dockerhub
docker tag <projName>:<version> <projName>:latest
docker push <projName>:<version>
docker push <projName>:latest
我建议人们将 Dockerfile 作为有关如何 build/run/install 项目的文档。
如果构建失败并且输出不足以进行调查,我可以 运行 /bin/bash
在 <projname>:build
中四处寻找问题所在。
我围绕这个想法 a GitHub repository 整理了起来。它适用于 C++,但您可以将它用于任何用途。
我还没有探索这个功能,但@TaylorEdmiston 指出我这里的模式与 multi-stage builds 非常相似,我在想出这个的时候并不知道。它看起来像是一种更优雅(并且有更好的记录)的方式来实现同样的事情。
虽然其他答案中提出了解决方案——尤其是 Misha Brukman 在对 about using one Dockerfile for development and one for production -- would be considered idiomatic at the time the question was written, it should be noted that the problems they are trying to solve -- and in particular the issue of cleaning up the build environment to reduce image size while still being able to use the same container environment in development and production -- have effectively been solved by multi-stage builds 的评论中提出的建议,这些建议在 Docker 17.05 中引入。
这里的想法是将 Docker 文件分成两部分,一部分基于您最喜欢的开发环境,例如与创建二进制文件有关的成熟的 Debian 基础映像你想在一天结束时部署的,另一个只是在最小的环境中运行构建的二进制文件,比如 Alpine。
通过这种方式,您可以避免 blueskin 在其中一条评论中提到的开发环境和生产环境之间可能存在的差异,同时仍然确保您的生产映像不会被开发工具污染。
文档提供了以下多阶段构建 Go 应用程序的示例,然后您可以将其采用到 C++ 开发环境(一个问题是 Alpine 使用 musl,因此您必须在您的开发环境中链接时要小心)。
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"]
我正在构建一个用 C++ 编写的服务器,并希望使用 Docker 和 docker-compose 来部署它。 "right way"是做什么的呢?我应该从 Docker 文件调用 make
还是手动构建,上传到某个服务器,然后从 Docker 文件调用 COPY
二进制文件?
更新
对于 2017 年之后访问此问题的任何人,请参阅
旧答案
我的做法是 运行 在容器外构建,只将构建的输出(二进制文件和任何必要的库)复制到容器中。然后您可以将您的容器上传到容器注册表(例如,使用托管的或 运行 您自己的),然后从该注册表拉到您的生产机器上。因此,流程可能如下所示:
- 构建二进制文件
- test / sanity-check binary 本身
- 使用二进制构建容器镜像
- 测试 / sanity-check 容器映像 与二进制
- 上传到容器注册表
- 部署到 staging/test/qa,从注册表中提取
- 部署到产品,从注册表中提取
由于在生产部署之前进行测试很重要,因此您希望测试与将在生产中部署的完全相同的东西,因此您不想以任何方式提取或修改 Docker 图像建成后。
我不会 运行 构建 inside 您计划在产品中部署的容器,因为那样您的容器将具有各种额外的工件(例如临时生成生产中不需要的输出、工具等),并使用不会用于部署的内容不必要地扩展容器映像。
我的建议是完全在容器本身上开发、构建和测试。这保证了Docker开发者环境与生产环境相同的理念,见The Modern Developer Workstation on MacOS with Docker.
尤其是 C++ 应用程序,通常与共享 libraries/object 文件存在依赖关系。
我认为 Docker 上还没有用于开发、测试和部署 C++ 应用程序的标准化开发流程。
为了回答您的问题,我们目前的做法是,将容器视为您的开发环境,并在团队中实施一系列实践,例如:
- 我们的代码库(配置文件除外)始终位于共享卷(在本地计算机上)(版本在 Git 上)
- Shared/dependent 库、二进制文件等。始终 存在于容器中
- 在容器中构建和测试,在提交映像之前,清除不需要的对象文件、库等,并确保
docker diff
更改符合预期。 - Changes/updates 环境,包括共享库、依赖项,始终记录在案并与团队沟通。
我在使用 docker-compose
进行自动化构建时遇到了困难,最后我对所有内容都使用了 docker build
:
三层建筑
Run → develop → build
然后我将构建输出复制到 'deploy' 图像中:
Run → deploy
可玩的四层:
运行- 包含申请 运行 所需的任何包 - 例如libsqlite3-0
FROM <projname>:run
- 包含构建所需的包
- 例如g++、cmake、libsqlite3-dev
- Dockerfile 执行任何外部构建
- 例如构建 boost-python3 的步骤(不在包管理器存储库中)
FROM <projname>:develop
- 包含来源
- Dockerfile 执行内部构建(经常更改的代码)
- 从该映像中复制构建的二进制文件以用于部署
FROM <projname>:run
- 将构建的输出复制到映像中并安装
RUN
或ENTRYPOINT
用于启动应用程序
文件夹结构如下所示:
.
├── run
│ └── Dockerfile
├── develop
│ └── Dockerfile
├── build
│ ├── Dockerfile
│ └── removeOldImages.sh
└── deploy
├── Dockerfile
└── pushImage.sh
设置构建服务器意味着执行:
docker build -f run -t <projName>:run
docker build -f develop -t <projName>:develop
每次我们构建时,都会发生这种情况:
# Execute the build
docker build -f build -t <projName>:build
# Install build outputs
docker build -f deploy -t <projName>:version
# If successful, push deploy image to dockerhub
docker tag <projName>:<version> <projName>:latest
docker push <projName>:<version>
docker push <projName>:latest
我建议人们将 Dockerfile 作为有关如何 build/run/install 项目的文档。
如果构建失败并且输出不足以进行调查,我可以 运行 /bin/bash
在 <projname>:build
中四处寻找问题所在。
我围绕这个想法 a GitHub repository 整理了起来。它适用于 C++,但您可以将它用于任何用途。
我还没有探索这个功能,但@TaylorEdmiston 指出我这里的模式与 multi-stage builds 非常相似,我在想出这个的时候并不知道。它看起来像是一种更优雅(并且有更好的记录)的方式来实现同样的事情。
虽然其他答案中提出了解决方案——尤其是 Misha Brukman 在对
这里的想法是将 Docker 文件分成两部分,一部分基于您最喜欢的开发环境,例如与创建二进制文件有关的成熟的 Debian 基础映像你想在一天结束时部署的,另一个只是在最小的环境中运行构建的二进制文件,比如 Alpine。
通过这种方式,您可以避免 blueskin 在其中一条评论中提到的开发环境和生产环境之间可能存在的差异,同时仍然确保您的生产映像不会被开发工具污染。
文档提供了以下多阶段构建 Go 应用程序的示例,然后您可以将其采用到 C++ 开发环境(一个问题是 Alpine 使用 musl,因此您必须在您的开发环境中链接时要小心)。
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"]