BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

使用Travis在Docker Hub上管理开源Docker镜像

| 作者 Vaidik Kapoor 关注 0 他的粉丝 ,译者 Daniel_Hu 关注 1 他的粉丝 发布于 2018年8月6日. 估计阅读时间: 18 分钟 | BCCon2018全球区块链生态技术大会,将区块链技术的创新和早期落地案例带回您的企业

我最近正在开展一个小项目,学习使用Docker工具链,并了解其工作原理。 我决定构建(又一个)etcd的开源Docker镜像并将其发布在Docker Hub上。 像所有开源项目一样,构建一个有效的Docker镜像非常简单,并没有花太多时间。反而是如何镜像让更多人觉得有用会花费更多的时间。 在这个过程中,我学到了一些关于使用DockerDocker HubTravis的内容,但仍然有一些问题没有得到解答。 因此,我认为值得记录我的发现和未解答的问题,以获得反馈并提高我的理解。

首先阐述我的要求:

  1. 支持最新版本和一些旧版本的应用程序似乎是Docker社区中的常见做法。 支持常见发行版及其精简版本似乎也是一种常见做法。 因此,该镜像应该遵循社区的做法。
  2. Github上的README自动同步到Docker Hub上的描述是必须的。 我的假设是开发人员通常会在Docker Hub上发现Docker镜像。 所以只在Github上有一个很好的自述文件是不够的。 该文档也必须存在于Docker Hub上。 我不想在我的git仓库中编写文档,然后每次有更改时手动将其复制到Docker Hub。
  3. 镜像本身应该非常易于使用,既可以作为服务器运行etcd,也可以作为CLI客户端运行。 我不打算讨论这个,因为这与本章主题有点无关。 但是如果你感兴趣的话,请查看README并在Github项目中打开一个issue来反馈问题。

第一个挑战是建立一个自动化流程来构建新镜像并将其上传到Docker Hub。 有一篇很好的博客记录了如何使用Travis实现这一目标,以便每次推送到代码库都会构建一个新镜像像,并且当合并分支时,构建的新镜像将被推送到Docker Hub。 设置很简单。

尽管简单,但它不符合我的任何要求。 下面尝试逐一解决。

问题#1 - 自动同步README

虽然上面提及到的Travis构建过程可以构建并将镜像推送到Docker Hub,但它无法同步README。 Docker CLI也不支持更新Docker Hub上的描述。

Docker Hub有一个名为Automated Builds的东西,通过配置,可以达到Travis作业产生的结果。 并且,它还可以从Github仓库中提取README的内容,以便在Docker Hub上作为描述使用。 因此,我们可以使用Automated Builds来构建镜像并从Github同步README。 这解决了我们的一项要求。



Automated Builds的工作原理是我们必须在Docker Hub上配置它以指定Docker上下文的路径(即Dockerfile的路径),从该Dockerfile构建镜像并打上自己的标记。 这意味着从仓库构建的每个镜像,必须在唯一路径上存在Dockerfile。 这很快就会成为一个问题。

问题#2 - 运行测试

虽然Docker Hub上的Automated Builds解决了README同步问题,但自动构建还存在一些其他问题。 你无法将其用作运行测试和获取PR测试状态的工具。 根本没有办法从Docker Hub的构建系统获得反馈给Github。 这对于开发流程至关重要

所以有一件事是肯定的 - 如果构建失败,我们需要回到Travis运行测试并阻止PR。

问题#3 - 为多个版本构建镜像

我不仅想构建etcd的最新版本,也包括之前的一些版本,并构建这些版本的发行版本。

支持的版本:

3.3(最新)

3.2

支持的发行版本:

Debian:stretch-slim

alpine

因此,我们必须构建总共4个镜像 - 每个发行版本对应一个版本。

首先看看多版本问题。

我开始查看其他开源Docker镜像是如何实现这一点的。 结果并不令人满意。

看了MySQL镜像的Dockerfiles。 每个版本的MySQL的docker镜像都在仓库的“版本目录”中有一个专用的Dockerfile。 他们几乎没有区别。 请参阅这两个MySQL的Dockerfiles - MySQL 5.6MySQL 5.7的Dockerfile。 在76行代码中,它们只有一行不同,并且只是版本不同。

我在Nginx的Dockerfiles中观察到了一个非常相似的模式。 平均而言,每个Dockerfile中每个基本镜像的每个版本只有5-10%的行不同。

这看起来像是大量的重复代码。 我不想这样,因为每个镜像中唯一不同的是版本和包管理器(Alpine和Debian使用不同的包管理器)。

Dockerfile的ARG可用于简化此操作。 通过将值传递给docker build命令,ARG可用于定义在构建镜像时可以设置的变量。 这非常方便。 由于每个版本的Dockerfiles唯一不同的是URL中的版本本身,我可以轻松地使用它并使用相同的Dockerfile和以下命令为不同版本构建docker镜像:

docker build . -t "3.3" --build-arg version=3.3
docker build . -t "3.2" --build-arg version=3.2

在带有环境矩阵的Travis中使用它,我们可以自动为多个版本构建Docker镜像。 请参阅.travis.yml中为每个版本构建多个镜像的代码段:

language: bash
services: docker
env:
  matrix:
  - VERSION=3.3.1
  - VERSION=3.2.19
script:
  - docker build . -t "$VERSION" --build-arg version=$VERSION

这解决了多版本的多镜像问题。 但我们仍然需要解决为每个版本的每个基本镜像构建镜像的问题。

问题#4 - 为多个基本镜像进行构建

除了为多个版本构建镜像,我们还存在为多个基本镜像构建镜像的问题。 现在仅仅使用ARG是不够的。

在这种情况下,挑战变成了每个发行版可能都有自己的具体操作方式,例如包管理。 Alpine和Debian使用不同的包管理器。 因此仅使用ARG不足以处理所有更改。 还需要管理如何进行包的安装。 在这种情况下,我们需要一个安装包,但不需要运行etcd。 如果我们能够以某种方式获得没有任何额外的etcd二进制文件,我们就可以制作出完美的Docker镜像。

多阶段Docker构建和构建器模式

Docker的Multi-Stage Builds是一种干净利落地构建Docker镜像的方式。 通常在构建过程中,我们会安装大量随机包。 成功构建应用程序后,清理可能会很麻烦。

构建Docker镜像时,镜像总是越小越好。 有些人使用bash脚本编写清理指令。 有些人将开发和生产Dockerfiles分开,开发Dockerfile会产生一个较大包体的镜像,其包含开发所需的一切,生产Dockerfile会生成一个仅具有开发Docker镜像所必需工件的docker镜像。 但这两种方法都有各自的不足。

多阶段构建简化了其中的一部分,并提供了更好地组织Dockerfiles的方法。 你可以在Dockerfiles中添加多个阶段,为每个阶段构建一个完全独立的Docker镜像,并使用其他构造可以轻松地将工件从一个阶段复制到另一个阶段。 从Docker Hub上的文档中查看此示例:

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"]

首先,注意这个Dockerifle有多个FROM指令。 每条指令表示不同的构建阶段,从而产生完全不同的镜像。 COPY指令采用名为--from的参数,可用于从其他构建阶段复制文件。 使用此选项来复制你想要用来构建镜像的文件,并留下不需要的所有内容。

多阶段构建与ARG结合可以解决我们的问题。 现在可以同时使用version和base_image,并使用base_image和FROM指令在构建时选择基本镜像。

FROM debian:stretch-slim
...

FROM "$base_image"
...
COPY --from=0 /path/to/etcd-binary /usr/local/bin
CMD ["etcd"]

然后运行以下命令来构建所有的镜像:

# Version 3.3 on debian:stretch-slim
docker build . -t "3.3" --build-arg version=3.3 \
    --build-arg base_image="debian:stretch-slim"
# Version 3.3 on alpine:latest
docker build . -t "3.3:alpine" --build-arg version=3.3 \
    --build-arg base_image="alpine:latest"
# Version 3.2 on debian:stretch-slim
docker build . -t "3.2" --build-arg version=3.2 \
    --build-arg base_image="debian:stretch-slim"
# Version 3.2 on alpine:latest
docker build . -t "3.2:alpine" --build-arg version=3.2 \
    --build-arg base_image="alpine:latest"

在这里查看完整的Dockerfile

这为我们解决为多版本构建镜像和多个基本镜像代码重复的问题。

Multi-Stage Builds有一些很酷的功能。 我建议检查一下,以获得更好的Docker体验。

有了这个,可以通过环境构建矩阵提供的不同环境来构建在Travis上的所有镜像,在每个环境合并代码到主分支,构建镜像、运行测试、推送构建好的镜像到Docker Hub。 这是一个示例.travis.yml文件(上一个示例的扩展):

language: bash
services: docker
env:
  matrix:
  - VERSION=3.3.1 BASE_IMAGE=debian:stretch-slim
  - VERSION=3.2.19 BASE_IMAGE=debian:stretch-slim
  - VERSION=3.3.1 BASE_IMAGE=alpine
  - VERSION=3.2.19 BASE_IMAGE=alpine
script:
  - |
    docker build . -t "$VERSION" \
      --build-arg version=$VERSION \
      --build-arg base_image="$BASE_IMAGE"

但同步README的问题仍然存在,并且Travis似乎也无法做到。 下面看看如何解决。

问题#5 - 捆绑在一起

我们在这里取得了以下进展:

  1. 我们知道如何为每个版本和每个基本镜像构建Docker镜像。 可用一种可管理的方式执行此操作,而无需重复代码。 并且可以在Travis上进行构建。
  2. 可以在Travis上构建镜像并推送到Docker。 但是无法在Docker Hub上获得README。
  3. 可以使用Automated Builds在Docker Hub上构建镜像,并将README同步到Docker Hub。 但是我们无法为Github上的每个pull请求构建镜像,并且如果构建失败则阻止合并pull请求。 并且不会给Github任何反馈。

因此,虽然我们可以使用Travis构建所有镜像并为每个PR运行测试,但无法使用它来推送镜像。 而对于Docker Hub则相反。 实际上,最简单形式的Docker Hub的Automated Build系统不能用于基于ARG的设置,因为Automated Builds可以使用Docker上下文,即在构建设置中为“每个指定的路径”构建镜像,并期望这些路径中存在Dockerfile。 你无法在此类设置中传递CLI参数。 因此,我们的自定义docker构建命令不适用于Automated Builds。

自定义构建阶段钩子,用于Docker Hub上的自动构建

自定义构建阶段钩子允许在不同的构建阶段执行自定义脚本,从而对自动构建进行更高级的自定义。 例如,可以覆盖构建阶段以将额外参数传递给docker构建,或者可以覆盖推送阶段以推送到多个仓库。 当然,你还可以做很多事情。

这最终解决了我们所有的问题:

  1. 我们可以使用Travis设置为每个PR构建和运行测试,但不能将镜像推送到Docker Hub。
  2. 只能在主分支上使用自动构建和自定义阶段钩子。 这将把我们的镜像推送到Docker Hub并同步来自Github的README。



构建钩子脚本是很通用的。 我参考在Travis中构建镜像的脚本来编写钩子脚本!

在此处查看整个设置:

  1. Dockerfile
  2. .travis.yml
  3. 自定义构建钩子

结论

没有重复代码,易于构建的过程,良好的开发体验和README的自动同步 - 这对我来说非常有用,我对最终的设置非常满意。

有人可能会说使用Docker Automated Build和Travis很难维护。 可以通过自定义构建钩子将PR状态推送到Github,但这不是我到目前为止所探讨的,与在Docker Hub上维护Travis和Automated Builds相比,它看起来也需要更多精力。

但有一件事不确定是使用ARG构建Docker镜像的方法和多阶段构建。 虽然它适合我,但我在这个领域的经验是有限的,我想得到反馈。 你怎么看? 还有构建和分发开源Docker镜像更好的方法吗? 请通过评论部分告诉我你的想法。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

深度内容

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT