|
|
你有没有经历过这样的场景:代码写完了,本地测试也过了,然后开始手动构建镜像、打 tag、推到镜像仓库。做了三遍之后发现——第一次忘了改版本号,第二次推错了仓库,第三次终于对了,但已经浪费了一个小时。
更惨的是,团队里每个人构建出来的镜像居然不一样,因为大家本地环境不同。这种"在我机器上没问题"的噩梦,从开发阶段一直延续到了部署阶段。
今天我们就来彻底解决这个问题。用 GitHub Actions 搭建一条自动化流水线,让代码推上去之后,构建、测试、推镜像全部自动完成。你只管写代码,剩下的交给机器。
本文你将学到
GitHub Actions 的核心概念和工作原理
编写完整的 Docker 构建推送 workflow
多架构镜像构建(同时支持 x86 和 ARM)
构建缓存优化,让 CI 速度翻倍
实战调试 workflow 的常用技巧
阅读时间: 约 12 分钟
实操时间: 约 30 分钟
难度等级: 中级(需要有 Docker 基础和 GitHub 账号)
━━━━━━━━━━━━━━━━━━━━
什么是 CI/CD
先说说这两个缩写。CI 是 Continuous Integration(持续集成),CD 是 Continuous Delivery/Deployment(持续交付/部署)。
打个比方:你开了一家面包店。
CI 就像是你的自动和面机——面粉倒进去,自动揉面、发酵、检查面团质量。每次有新面粉(代码)进来,机器自动帮你处理好,发现面粉有问题(测试失败)立刻报警。
CD 就是和面之后自动送进烤箱、烤好了自动摆上货架。整个过程不需要人工介入。
对应到 Docker 的场景:
- 代码推送 → 自动运行测试 → 自动构建镜像 → 自动推送到镜像仓库 → 自动部署到服务器
- CI 阶段 CD 阶段
复制代码
GitHub Actions 核心概念
GitHub Actions 是 GitHub 内置的 CI/CD 平台,免费额度对个人项目完全够用。在动手之前,先理清几个核心概念:
- ┌─────────────────────────────────────────────┐
- │ Workflow │
- │ (.github/workflows/xxx.yml) │
- │ │
- │ ┌─────────────┐ ┌─────────────┐ │
- │ │ Job 1 │ │ Job 2 │ │
- │ │ (构建测试) │───→│ (推送镜像) │ │
- │ │ │ │ │ │
- │ │ ┌────────┐ │ │ ┌────────┐ │ │
- │ │ │ Step 1 │ │ │ │ Step 1 │ │ │
- │ │ │ 拉代码 │ │ │ │ 登录仓库│ │ │
- │ │ └────────┘ │ │ └────────┘ │ │
- │ │ ┌────────┐ │ │ ┌────────┐ │ │
- │ │ │ Step 2 │ │ │ │ Step 2 │ │ │
- │ │ │ 跑测试 │ │ │ │ 推镜像 │ │ │
- │ │ └────────┘ │ │ └────────┘ │ │
- │ └─────────────┘ └─────────────┘ │
- └─────────────────────────────────────────────┘
复制代码
Workflow(工作流):一个 YAML 文件,定义了整个自动化流程
Job(任务):Workflow 里的一组步骤,跑在同一台虚拟机上
Step(步骤):Job 里的单个操作,可以是运行命令或调用别人写好的 Action
Action(动作):可复用的步骤模块,就像积木一样拼装使用
Runner(运行器):实际执行任务的虚拟机,GitHub 免费提供
实操:搭建 Docker 自动构建流水线
第一步:准备项目
我们用一个简单的 Node.js 项目来演示。先创建项目结构:
- mkdir docker-ci-demo && cd docker-ci-demo
- git init
复制代码
创建一个简单的应用:
- const http = require('http');
- const server = http.createServer((req, res) => {
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({
- status: 'ok',
- version: process.env.APP_VERSION || '1.0.0',
- timestamp: new Date().toISOString()
- }));
- });
- server.listen(3000, () => console.log('Server running on port 3000'));
复制代码
创建:
- FROM node:20-alpine
- WORKDIR /app
- COPY app.js .
- EXPOSE 3000
- CMD ["node", "app.js"]
复制代码
第二步:编写基础 Workflow
创建- .github/workflows/docker-build.yml
复制代码 :
- name: Build and Push Docker Image
- # 触发条件:推送到 main 分支,或打 tag
- on:
- push:
- branches: [main]
- tags: ['v*']
- pull_request:
- branches: [main]
- env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}
- jobs:
- build-and-push:
- runs-on: ubuntu-latest
- # 授予 GITHUB_TOKEN 权限,用于推送到 ghcr.io
- permissions:
- contents: read
- packages: write
- steps:
- # 1. 拉取代码
- - name: Checkout code
- uses: actions/checkout@v4
- # 2. 登录到 GitHub Container Registry
- - name: Log in to Container Registry
- if: github.event_name != 'pull_request'
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- # 3. 提取镜像标签和标签
- - name: Extract metadata
- id: meta
- uses: docker/metadata-action@v5
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- tags: |
- type=ref,event=branch
- type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
- type=sha
- # 4. 构建并推送
- - name: Build and push Docker image
- uses: docker/build-push-action@v5
- with:
- context: .
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
复制代码
这个 workflow 做了什么:
代码推送到 main 或打 tag 时触发
登录 GitHub 自带的容器镜像仓库(ghcr.io,免费的)
自动根据分支名、tag 名生成镜像标签
构建 Docker 镜像并推送
第三步:提交并触发
- git add .
- git commit -m "feat: add Docker CI/CD workflow"
- git remote add origin https://github.com/你的用户名/docker-ci-demo.git
- git push -u origin main
复制代码
推送之后,打开 GitHub 仓库页面,点击 Actions 标签页,就能看到 workflow 正在运行了。
预期输出(在 Actions 页面看到的日志):
- Run docker/build-push-action@v5
- Building Docker image...
- #1 [internal] load build definition from Dockerfile
- #2 [internal] load .dockerignore
- #3 [1/2] FROM node:20-alpine
- #4 [2/2] COPY app.js .
- #5 exporting to image
- Pushing ghcr.io/yourname/docker-ci-demo:main
- Done!
复制代码
进阶:多架构构建
现在越来越多人用 ARM 架构的机器(比如 Mac M 系列芯片、AWS Graviton),如果你只构建 x86 镜像,ARM 用户拉下来跑会很慢(需要模拟执行)。多架构构建让一个镜像同时支持 x86 和 ARM。
把 workflow 升级一下:
- jobs:
- build-and-push:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- # 新增:安装 QEMU,用于模拟 ARM 架构
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- # 新增:安装 Buildx,支持多架构构建
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Log in to Container Registry
- if: github.event_name != 'pull_request'
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Extract metadata
- id: meta
- uses: docker/metadata-action@v5
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- tags: |
- type=ref,event=branch
- type=semver,pattern={{version}}
- type=sha
- - name: Build and push Docker image
- uses: docker/build-push-action@v5
- with:
- context: .
- # 关键:指定目标平台
- platforms: linux/amd64,linux/arm64
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
复制代码
核心变化只有三处:装 QEMU、装 Buildx、加 platforms 参数。就这么简单,你的镜像就同时支持 x86 和 ARM 了。
进阶:缓存优化
多架构构建有个问题——慢。因为要构建两个平台,时间直接翻倍。而且每次 CI 跑完虚拟机就销毁了,下次又要从头构建。
Docker 层缓存可以解决这个问题。思路是把构建缓存存到 GitHub Actions Cache 里,下次构建直接复用。
- - name: Build and push Docker image
- uses: docker/build-push-action@v5
- with:
- context: .
- platforms: linux/amd64,linux/arm64
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- # 缓存配置
- cache-from: type=gha
- cache-to: type=gha,mode=max
复制代码
就加了两行:和,指定用 GitHub Actions 的缓存()。表示缓存所有层,不只是最终层。
实际效果对比:
- 无缓存构建: 约 3-5 分钟(多架构)
- 有缓存构建: 约 30-60 秒(依赖没变的情况下)
复制代码
速度提升 5-10 倍,非常值得。
完整的生产级 Workflow
把上面所有内容整合起来,加上测试环节,这是一个生产可用的完整配置:
- name: Docker CI/CD Pipeline
- on:
- push:
- branches: [main]
- tags: ['v*']
- pull_request:
- branches: [main]
- env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}
- jobs:
- # 第一阶段:测试
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: '20'
- - run: npm ci
- - run: npm test
- # 第二阶段:构建并推送镜像(测试通过后才执行)
- build-and-push:
- needs: test
- runs-on: ubuntu-latest
- if: github.event_name != 'pull_request'
- permissions:
- contents: read
- packages: write
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Log in to Container Registry
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Extract metadata
- id: meta
- uses: docker/metadata-action@v5
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- tags: |
- type=ref,event=branch
- type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
- type=sha
- - name: Build and push
- uses: docker/build-push-action@v5
- with:
- context: .
- platforms: linux/amd64,linux/arm64
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
复制代码
注意这行——构建任务依赖测试任务,测试不过就不构建。这就像工厂的质检环节,不合格的产品不允许出厂。
推送到 Docker Hub
如果你想推到 Docker Hub 而不是 ghcr.io,改动很小:
- env:
- REGISTRY: docker.io
- IMAGE_NAME: 你的DockerHub用户名/你的项目名
- # 登录步骤改成:
- - name: Log in to Docker Hub
- uses: docker/login-action@v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
复制代码
需要在 GitHub 仓库的 Settings > Secrets and variables > Actions 里添加和两个密钥。Token 在 Docker Hub 的 Account Settings > Security 里生成。
调试 Workflow 的实用技巧
workflow 写错了怎么办?分享几个实用技巧:
1. 本地验证 YAML 语法
- # 安装 actionlint(workflow 语法检查工具)
- brew install actionlint # macOS
- # 或
- go install github.com/rhysd/actionlint/cmd/actionlint@latest
- # 检查语法
- actionlint .github/workflows/docker-build.yml
复制代码
2. 用 act 在本地跑 workflow
- # 安装 act
- brew install act # macOS
- # 本地运行(需要 Docker)
- act push --job build-and-push
复制代码 会用 Docker 模拟 GitHub Actions 的运行环境,不用每次都推代码触发了。
3. 加调试步骤
在 workflow 里临时加一步,打印环境信息:
- - name: Debug info
- run: |
- echo "Event: ${{ github.event_name }}"
- echo "Ref: ${{ github.ref }}"
- echo "SHA: ${{ github.sha }}"
- docker version
- docker buildx version
复制代码
常见问题 Q&A
Q1: 构建时报 "denied: permission_denied",怎么回事?
大概率是权限配置问题。检查两个地方:一是 workflow 里部分有没有;二是仓库的 Settings > Actions > General 里,"Workflow permissions" 要选 "Read and write permissions"。如果推 Docker Hub,检查 Secrets 里的 Token 是否有 push 权限。
Q2: 多架构构建太慢了,有什么办法?
除了前面说的,还可以考虑把两个架构拆成两个并行 Job 分别构建,最后用- docker buildx imagetools create
复制代码 合并成一个 manifest。这样两个架构同时构建,时间减半。另外,Dockerfile 优化也很关键——把不常变的层放前面,利用好层缓存。
Q3: 我想在 PR 的时候也构建镜像(但不推送),怎么做?
上面的配置已经支持了。关键在- push: ${{ github.event_name != 'pull_request' }}
复制代码 ,PR 触发时 push 为 false,只构建不推送。这样可以在合并前验证 Dockerfile 有没有写坏。
小结
今天我们从零搭建了一条完整的 Docker CI/CD 流水线:
基础 workflow:代码推送自动构建、自动推镜像
多架构构建:一份 Dockerfile 同时产出 x86 和 ARM 镜像
缓存优化:利用 GitHub Actions Cache 大幅加速构建
生产级配置:先测试再构建,质量门禁一个不少
自动化的好处不只是省时间。更重要的是一致性——每次构建都在相同的环境下执行,不会再出现"在我机器上没问题"的尴尬。而且整个过程有日志、可追溯、可审计,出了问题能快速定位。
明天是 Day 23,我们来聊聊私有镜像仓库。不是所有镜像都适合放在公共仓库里,公司内部的业务代码、包含敏感配置的镜像,都需要一个安全的私有仓库来存放。我们会搭建 Harbor 私有仓库,并把它接入今天的 CI/CD 流水线。 |
|