一个网站需要多少容量,seo服务 文库,h5游戏是什么意思,北京网站开发品牌制品构建与管理 - Docker 镜像的最佳实践 为何使用容器#xff1f;SRE 的视角
对于 SRE 来说#xff0c;拥抱容器化#xff08;以 Docker 为代表#xff09;不仅仅是追赶技术潮流#xff0c;更是因为它直接解决了运维中的许多核心痛点#xff0c;并支撑了 SRE 的核心原则…制品构建与管理 - Docker 镜像的最佳实践 为何使用容器SRE 的视角
对于 SRE 来说拥抱容器化以 Docker 为代表不仅仅是追赶技术潮流更是因为它直接解决了运维中的许多核心痛点并支撑了 SRE 的核心原则
环境一致性 (Environment Consistency)容器将应用程序代码、运行时如 Node.js、系统工具、库和配置全部打包在一起。这个镜像在开发人员的笔记本、测试服务器和生产集群上运行时其内部环境是完全一致的从根本上消除了“环境不一致”导致的问题。不可变基础设施 (Immutable Infrastructure)我们不应该登录到正在运行的容器中去修改它这是可变操作。正确的做法是构建一个包含新代码或配置的新镜像然后用新镜像创建的容器去替换旧的容器。这种“替换而非修改”的模式使得部署过程更可预测、更可靠回滚也变得异常简单——只需重新部署上一个版本的镜像即可。依赖项隔离 (Dependency Isolation)应用的所有依赖都封装在镜像内部不会与宿主机或其他容器的依赖产生冲突。可移植性 (Portability)一个容器镜像可以在任何安装了容器运行时如 Docker, containerd的机器上运行无论是物理机、虚拟机还是云实例。可扩展性 (Scalability)容器非常轻量启动速度快这使得在 Kubernetes 这样的编排平台上快速扩缩容应用实例变得轻而易举。
编写 Dockerfile从基础到最佳实践
Dockerfile 是一个文本文件它包含了一系列指令用于告诉 Docker 如何构建一个镜像。让我们从一个简单、直观但不推荐的 Dockerfile 开始然后逐步优化它。
一个“天真”的 Dockerfile (Dockerfile.bad)
很多人刚开始可能会这么写
# Dockerfile.bad - 不推荐的写法
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD [ node, app.js ]这个 Dockerfile 能工作但存在几个严重的问题
镜像体积过大: node:18 是一个包含完整操作系统的基础镜像体积庞大。COPY . . 会将当前目录下的所有文件包括 node_modules、.git 文件夹、README.md、Dockerfile 本身等都拷贝到镜像中造成不必要的臃肿。RUN npm install 会安装 package.json 中所有的依赖包括只在开发和测试时才需要的 devDependencies。 构建缓存效率低下: Docker 构建是分层的。COPY . . 这一步只要我们修改了任何一个源代码文件这一层的缓存就会失效。由于 RUN npm install 在 COPY . . 之后代码的任何微小改动都会导致 npm install 这一步被重新执行即使 package.json 文件根本没有变化。这会极大地拖慢构建速度。 安全风险: 默认情况下容器内的进程是以 root 用户身份运行的这带来了不必要的安全风险。将 devDependencies 和完整的源代码可能包含测试文件等打包到最终的生产镜像中增大了攻击面。
最佳实践优化的 Dockerfile
现在让我们运用最佳实践来重写它。请在你的项目根目录下创建这个 Dockerfile 文件
# Dockerfile# --- 阶段 1: 基础构建环境 ---
# 使用一个更小的、官方的 slim 版本作为基础镜像
FROM node:18-slim AS base# 设置工作目录
WORKDIR /app# 优化缓存先只拷贝 package.json 和 package-lock.json
COPY package.json package-lock.json ./# 运行 npm ci它会严格按照 package-lock.json 安装所有依赖包括 devDependencies
# 这一层只有在 package-lock.json 变化时才会重新构建
RUN npm ci# 拷贝所有源代码
COPY . .# --- 阶段 2: 生产环境 ---
# 从一个干净的、相同版本的 slim 镜像开始而不是在 base 阶段的基础上继续
FROM node:18-slim AS productionWORKDIR /app# 从 base 阶段拷贝已经安装好的生产依赖
# --production 标志确保只拷贝 dependencies不包括 devDependencies
COPY --frombase /app/node_modules ./node_modules
# 拷贝应用代码和 package.json
COPY app.js .
COPY package.json .# 安全实践创建一个非 root 用户并切换到该用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
USER appuser# 暴露端口
EXPOSE 3000# 定义容器启动命令
CMD [ node, app.js ]同时在项目根目录下创建一个 .dockerignore 文件告诉 Docker 在构建时忽略哪些文件
.dockerignore
node_modules
npm-debug.log
Dockerfile*
.dockerignore
.git
.gitignore
README.md
.github这个优化后的 Dockerfile 体现了几个关键的最佳实践
多阶段构建 (Multi-stage Builds)我们定义了 base 和 production 两个阶段。base 阶段用于安装所有依赖并运行测试如果需要。production 阶段则从一个全新的干净镜像开始只从 base 阶段拷贝运行应用所必需的东西生产依赖和应用代码最终得到的生产镜像是最小化的。使用小体积的基础镜像 (node:18-slim): slim 版本比默认版本小得多减少了镜像体积和潜在的漏洞。利用构建缓存: 将 COPY package*.json ./ 和 RUN npm ci 这两个步骤放在 COPY . . 之前。这样只要 package-lock.json 文件不改变耗时的依赖安装层就会被缓存只有在代码变化时才需要重新执行后续的 COPY 操作。使用 .dockerignore: 防止不必要的文件被发送到 Docker 守护进程减小构建上下文避免敏感信息泄露。以非 root 用户运行: 创建一个专用的、无特权的用户来运行应用这是重要的安全加固措施。
4. 集成到 CI/CD 流水线
现在我们的目标是在上一篇的 CI 流水线基础上增加一个新任务 (Job)当代码测试通过并且变更被合并到 main 分支后自动构建这个优化后的 Docker 镜像并将其推送到 GitHub Container Registry (GHCR)——一个与 GitHub 仓库集成的容器镜像仓库。
修改 .github/workflows/ci.yml 文件如下
# .github/workflows/ci.yml
name: Node.js CI/CDon:push:branches: [ main ]pull_request:branches: [ main ]# 为整个工作流定义环境变量方便复用
env:REGISTRY: ghcr.io# 镜像名称将是 ghcr.io/你的用户名/你的仓库名IMAGE_NAME: ${{ github.repository }}jobs:# 第一个任务构建和测试与上一篇基本相同build-and-test:runs-on: ubuntu-lateststeps:- name: Checkout repositoryuses: actions/checkoutv4- name: Use Node.js 18.xuses: actions/setup-nodev4with:node-version: 18.xcache: npm- name: Install dependenciesrun: npm ci- name: Run linterrun: npm run lint- name: Run testsrun: npm test# 新增的第二个任务构建并推送 Docker 镜像build-and-push-image:# needs 关键字确保此任务必须在 build-and-test 成功后才运行needs: build-and-test# if 条件确保此任务只在 push 到 main 分支时运行而不是在 pull request 时if: github.event_name push github.ref refs/heads/mainruns-on: ubuntu-latest# permissions 块用于授予 GITHUB_TOKEN 额外的权限# packages: write 允许工作流向 GHCR 推送镜像permissions:contents: readpackages: writesteps:- name: Checkout repositoryuses: actions/checkoutv4# 使用官方的 docker/login-action 动作登录到 GHCR- name: Log in to the GitHub Container Registryuses: docker/login-actionv3with:registry: ${{ env.REGISTRY }}username: ${{ github.actor }} # github.actor 是触发工作流的用户名password: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN 是 GitHub Actions 自动生成的临时令牌# 使用 docker/metadata-action 动作来自动提取镜像的元数据如标签- name: Extract metadata (tags, labels) for Dockerid: metauses: docker/metadata-actionv5with:images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}# 使用 docker/build-push-action 动作来构建并推送镜像- name: Build and push Docker imageid: build-and-pushuses: docker/build-push-actionv5with:context: . # 使用当前目录作为构建上下文push: true # 明确指示要推送镜像tags: ${{ steps.meta.outputs.tags }} # 使用 metadata-action 生成的标签labels: ${{ steps.meta.outputs.labels }}# 启用 GitHub Actions 的构建缓存加快后续构建速度cache-from: typeghacache-to: typegha,modemax5. 验证结果
将新的 Dockerfile、.dockerignore 以及更新后的 ci.yml 文件提交并推送到你的 GitHub 仓库的 main 分支。进入仓库的 “Actions” 页面观察工作流的运行。你会看到 build-and-test 任务成功后build-and-push-image 任务开始执行。当整个工作流成功运行后回到你的 GitHub 仓库主页。在右侧边栏找到并点击 “Packages”。在这里你应该能看到一个与你的仓库同名的 Package点击进入后就能看到刚刚被推送上来的 Docker 镜像及其标签例如 latest 和一个基于 Git SHA 的标签。
SRE 视角的思考
通过今天的实践我们取得了巨大的进步
制品管理 (Artifact Management)我们不再仅仅是验证代码而是产出了一个版本化的、不可变的、标准化的软件制品Docker 镜像并将其存储在了一个中心的制品库 (GHCR) 中。这是实现可靠部署的前提。可复现性 (Reproducibility)任何人或任何自动化系统只要拉取特定标签如 Git SHA 标签的镜像就能获得一个完全相同的、经过测试的运行时环境。安全加固: 通过优化的 Dockerfile我们显著减小了生产镜像的体积和攻击面。效率: 多阶段构建和 CI 缓存的运用确保了我们的自动化流程在保证质量的同时也保持了高效。
总结
今天我们掌握了为何容器化对 SRE 至关重要并深入实践了如何编写一个遵循最佳实践的 Dockerfile。更重要的是我们成功地将自动化的镜像构建与推送流程无缝地集成到了我们的 CI 流水线中。
现在我们有了一个经过测试、版本化、并安全存储的软件制品。它已经整装待发准备被部署到真实的运行环境中。
在下一篇中我们将迈出从 CI 到 CD 的关键一步我们将学习如何让流水线自动地将这个镜像部署到一个 Kubernetes 集群中真正打通从代码提交到应用在云原生环境中运行的“最后一公里”。敬请期待