构建步道指南

DockerBeginner
立即练习

介绍

Pine & Path 要将 Alder Ridge 游客指南送往另一个游客中心。当前镜像可以正常使用,但除了应用,还携带 Go 编译器、源文件和构建残留。你将交付精简的运行时镜像,同时保留全部四条步道、难度筛选、详情页、本地插图和完整的 GPX 路线下载。

你应已了解如何构建和运行简单的 Docker 镜像,并认识 Dockerfile 中的 FROM、COPY 和端口发布。无需 Go 编程知识:完整的应用和素材均已提供。本实验会在使用前解释编译、构建阶段、运行时文件布局及 shell 写文件语法。公园与路线数据都是虚构的教学素材。

Docker、构建器镜像和参考服务已准备就绪。使用 Workstation 执行命令,Reference Guide 查看正常样例,Your Guide 查看你的交付成果。本实验独立于挑战。包含阅读、构建和普通错误在内,预计需要约 60 分钟;这是作者估计。

构建可正常使用的初始镜像

本步骤先确认完整指南的功能,再构建提供的初始镜像。从 Reference Guide 开始。页面显示四条步道。选择 Easy,打开 Cedar Loop,阅读距离和爬升信息,然后使用 Download GPX。GPX 是包含命名轨迹及地理坐标点的 XML;下载得到的是真实路线文件。选择 Back to trails,再选 Moderate 并打开 Ridge Path。返回全部步道。除了文字,也请留意样式表和示意地图:它们同样是运行时资源。

提供的完整步道集合

切换到 Workstation,其起始目录是 /home/labex/project/trail-guide。pwd 显示当前目录,ls 列出目录项。如果离开了项目,以下 cd 命令可切换回来:

cd /home/labex/project/trail-guide
ls

提供的目录布局如下:

源路径 运行时用途
app/main.go 待编译的 Go 程序,运行时不需要源文件
content/trails.json 全部四条步道及其关系
content/routes/ 四个 GPX 下载文件
public/ 样式表和原创示意插图
author-notes/, build-cache/ 草稿与残留文件,不是运行时资源

保持这些素材和参考服务不变。你将编辑 Dockerfile、添加 .dockerignore,并创建课程镜像和容器。Dockerfile.incomplete 是为下一步骤提供的失败示例。

cat 显示文本文件。阅读现有 Dockerfile:

cat Dockerfile

它以已准备的 Go 编译器镜像 dk02-go-builder:1 为基础。WORKDIR /guide 选择镜像内的工作目录。COPY . . 将当前构建上下文复制到该目录,其中包括最终应用不需要的文件。最后的 ENTRYPOINT 是一个 JSON 数组,用于直接启动编译后的程序。

构建命令仅为该命令设置 CGO_ENABLED=0,使这个只使用标准库的 Go 示例不依赖 C 库。go build 编译提供的程序;-trimpath 从可执行文件中移除本地源目录信息,-o /guide/guide 指定输出名称。这让这个特定程序适合空白运行基础镜像。其他程序可能需要共享库、证书或其他数据;空白基础镜像并非通用方案。

先查看编译器版本。docker run --rm 创建临时容器,并在命令结束后删除它。镜像名称后的内容指定要运行的命令:

docker run --rm dk02-go-builder:1 go version

输出表明这是 Linux 上的 Go 1.26。编译器在容器内执行,而非在 Workstation 上执行。

构建初始镜像。docker build 读取 Dockerfile;-t 为结果设置标签,末尾的 . 选择当前目录作为构建上下文:

docker build -t trail-guide:sample .

等待构建成功。标签标识的是镜像,而不是运行中的容器。要查看以字节为单位的大小,使用 docker image inspect 读取元数据,--format 选择一个字段。单引号使模板按原样传入:

docker image inspect trail-guide:sample --format '{{.Size}}'

数值随预先准备的编译器镜像而变化。初始镜像功能正常,但仍包含编译器和源码。仅凭体积小,不能证明替代镜像完整。

分离构建文件与运行时文件

本步骤在一个阶段构建程序,在另一个阶段只交付可执行文件和运行时资源。从 Workstation 的同一项目目录开始。每条 FROM 指令开启一个阶段。用 AS build 为其命名后,后续的 COPY --from=build 就能取用结果,而无需继承编译器或之前的文件系统层。

先查看一个真实的不完整交付。提供的 Dockerfile.incomplete 编译程序并复制公开插图,却遗漏了步道数据:

cat Dockerfile.incomplete

-f 选项选择这个替代 Dockerfile:

docker build -f Dockerfile.incomplete -t trail-guide:incomplete .

构建会成功,因为编译时不读取步道集合。下面的运行预期会失败,退出状态为 1:

docker run --rm trail-guide:incomplete

阅读错误:open content/trails.json: no such file or directory。可执行文件存在,但缺少运行数据。构建成功与应用完整是不同的结果。失败的临时容器已被删除。应修复常规 Dockerfile,而不是修补那个容器或修改应用。

here-document 用于保存完整的文本块。下面由 cat 提供各行,> 写入 Dockerfile,<<'DOCKERFILE' 标记一个按字面保存的文本块,以只有 DOCKERFILE 的一行结束。给起始标记加引号,可避免保存时发生 shell 展开。只替换允许编辑的 Dockerfile:

cat > Dockerfile <<'DOCKERFILE'
FROM dk02-go-builder:1 AS build
WORKDIR /src
COPY app/main.go ./main.go
RUN CGO_ENABLED=0 go build -trimpath -o /out/guide main.go

FROM scratch
WORKDIR /guide
COPY --from=build /out/guide ./guide
COPY content/ ./content/
COPY public/ ./public/
ENTRYPOINT ["/guide/guide"]
DOCKERFILE

沿着文件追踪:第一阶段将源码编译为 /out/guide。第二个 FROM 从 Docker 的空白文件系统基础 scratch 开始,不继承第一阶段。可执行文件成为 /guide/guide;步道数据和 GPX 文件保持 content 布局;样式和插图保持 public 布局。程序从工作目录读取这些相对路径。这就是只复制可执行文件不够的原因。

最终镜像没有 shell 或编译器。这在此处符合预期。应检查应用,而不是尝试在镜像内运行 shell。

构建上下文是另一个问题。.dockerignore 会在将上下文发送给构建器之前排除匹配的路径。开头的点使它成为隐藏文件。这些规则排除草稿和过期缓存,但不会排除应用或运行时资源:

cat > .dockerignore <<'IGNORE'
author-notes/
build-cache/
IGNORE

两阶段 Dockerfile 明确选择运行时文件;忽略文件则避免发送多余素材。排除上下文文件,无法移除已存储在继承层中的字节。真正将构建层留在后面的是独立的运行阶段。

构建交付镜像:

docker build -t trail-guide:lean .
docker image inspect trail-guide:lean --format '{{.Size}}'

与初始镜像的数值比较。有价值的结果是体积更小、功能完整的指南,而不是尽可能小的数字。如果 COPY 失败,阅读缺少的源路径,并对照布局表和当前目录。修正 Dockerfile 或忽略规则,再重新构建。不要创建替代数据或挂载源目录来掩盖镜像不完整的问题。

运行并重建交付的指南

本步骤将脱离源目录运行镜像,并使用完整指南。从 Workstation 开始。程序监听容器端口 8080。发布 8090:8080,就能通过 Workstation 的 8090 端口访问它,Your Guide 显示的正是该端口。-d 让服务在后台运行;--name 为这个课程容器命名。不要添加挂载:镜像必须自带资源。

docker run -d --name trail-guide -p 8090:8080 trail-guide:lean

输出是容器 ID。该容器会保持运行以提供指南服务。docker logs 读取程序输出;--tail 5 将输出限制为最后五行:

docker logs --tail 5 trail-guide

预期看到 Guide listening on :8080,这里是容器端口,而非发布的宿主端口。如果之前尝试使用的容器名称仍被占用,请先使用下面仅针对课程容器的删除命令,再重新运行启动命令。遇到缺少资源的错误,则必须先修正 Dockerfile 并重新构建。

切换到 Your Guide 并刷新。应该显示全部四条步道。选择 Easy:只显示 Cedar Loop 和 River Walk。打开 Cedar Loop,查看信息和地图,并下载 GPX。使用 Back to trails,选择 Moderate,打开 Ridge Path 并下载其路线。返回全部步道。本地插图和样式必须正常加载,轨迹必须属于所选步道。Reference Guide 仍可独立使用。

包含 GPX 下载的真实详情页

现在使用同一镜像验证替换容器。切换到 Workstation。docker rm -f 只停止并删除指定的课程容器,不删除镜像。保持 dk02-reference 运行:

docker rm -f trail-guide

创建一个不带挂载的新实例:

docker run -d --name trail-guide -p 8090:8080 trail-guide:lean

切换到 Your Guide,刷新并重复 Easy/Cedar Loop 和 Moderate/Ridge Path 的访问与下载。这个新容器从未收到工作站源目录。它的运行资源来自镜像。

保持容器运行。以后修改 Dockerfile 后,重新构建标签不会更新现有容器;应仅删除并重建自己的课程容器来运行新镜像。服务停止或连接失败,与指南针对未知路线显示的 Trail not found 页面不同。

总结

你将编译阶段与空白运行阶段分离,复制了应用真正需要的资源,并将草稿排除在构建上下文之外。精简镜像无需挂载工作站目录,即可提供完整步道集合、筛选、详情、插图和 GPX 下载。重建容器证明镜像是一份可复用的交付物,而不是借用源目录运行的进程。