冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 22 CI/CD 与 GitHub Actions

浏览:13次阅读
没有评论

共计 7974 个字符,预计需要花费 20 分钟才能阅读完成。

Docker 30 天实战系列 · 第 22 天

你有没有经历过这样的场景:代码写完了,本地测试也过了,然后开始手动构建镜像、打 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

创建一个简单的应用 app.js

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'));

创建 Dockerfile

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 做了什么:

  1. 代码推送到 main 或打 tag 时触发
  2. 登录 GitHub 自带的容器镜像仓库(ghcr.io,免费的)
  3. 自动根据分支名、tag 名生成镜像标签
  4. 构建 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

就加了两行:cache-fromcache-to,指定用 GitHub Actions 的缓存(type=gha)。mode=max 表示缓存所有层,不只是最终层。

实际效果对比:

 无缓存构建:约 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

注意 needs: test 这行——构建任务依赖测试任务,测试不过就不构建。这就像工厂的质检环节,不合格的产品不允许出厂。

推送到 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 里添加 DOCKERHUB_USERNAMEDOCKERHUB_TOKEN 两个密钥。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

act 会用 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 里 permissions 部分有没有 packages: write;二是仓库的 Settings > Actions > General 里,"Workflow permissions" 要选 "Read and write permissions"。如果推 Docker Hub,检查 Secrets 里的 Token 是否有 push 权限。

Q2: 多架构构建太慢了,有什么办法?

除了前面说的 cache-from/cache-to,还可以考虑把两个架构拆成两个并行 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 流水线。

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计7974字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。