Appearance
CI/CD 流水线与镜像发布
本篇是 Docker + Go 系列教程的最后一篇。前面四篇我们解决了容器化、镜像优化、多服务编排、生产最佳实践的问题,但整个流程还是手动的:手动构建、手动推镜像、手动部署。本篇将把这一切自动化——通过 CI/CD 流水线,在代码提交后自动构建镜像、扫描漏洞、签名、发布到 Registry,并介绍镜像版本管理策略、多平台发布、私有 Registry 搭建、镜像签名(Cosign)以及滚动更新、蓝绿、金丝雀等部署策略。学完本篇,你将拥有一个从代码到生产的完整自动化闭环。
一、CI/CD 概念简介
1. 什么是 CI/CD
- CI(Continuous Integration,持续集成):开发者频繁地把代码合并到主分支,每次合并自动运行测试、构建,尽早发现问题。
- CD(Continuous Delivery/Deployment,持续交付/部署):CI 通过后,自动把应用打包、发布、部署到各个环境。
text
代码提交 CI 阶段 CD 阶段
┌────┐ ┌──────────┐ ┌──────────┐
│git │ ───> │ 构建、测试│ ─────────> │ 发布镜像 │ ──> 部署到生产
│push│ │ 扫描、签名│ │ 推送仓库 │
└────┘ └──────────┘ └──────────┘2. CI/CD 的价值
- 一致性:每次构建都走相同流程,避免「在我机器上能跑」。
- 快速反馈:提交后几分钟内知道是否破坏了构建。
- 可追溯:每个镜像对应一个 git commit,可以追溯到代码变更。
- 安全:自动扫描漏洞,未通过不发布。
- 减少人为错误:避免手动构建、手动推镜像的低级失误。
3. 常用 CI/CD 工具
| 工具 | 类型 | 特点 |
|---|---|---|
| GitHub Actions | SaaS/自托管 | 与 GitHub 深度集成,配置简单 |
| GitLab CI/CD | SaaS/自托管 | GitLab 内置,功能全面 |
| Jenkins | 自托管 | 老牌,插件丰富,配置复杂 |
| CircleCI | SaaS | 速度快,配置简洁 |
| Drone | 自托管 | Go 编写,轻量 |
| ArgoCD | GitOps | Kubernetes 专用 CD 工具 |
本篇重点讲 GitHub Actions 和 GitLab CI/CD,它们是当前最主流的选择。
二、GitHub Actions 构建 Go 镜像
1. GitHub Actions 基础
GitHub Actions 是 GitHub 内置的 CI/CD 服务。在项目 .github/workflows/ 目录下放 YAML 文件,就会在特定事件(push、PR、release 等)触发执行。
核心概念:
- Workflow:一个 YAML 文件,定义完整流程。
- Job:工作流由若干 job 组成,job 之间可以串行或并行。
- Step:一个 job 由若干 step 组成,按顺序执行。
- Action:可复用的步骤,类似函数。
- Runner:执行 job 的机器,GitHub 提供 Ubuntu/Windows/macOS 托管 runner。
2. 准备项目
假设项目结构:
text
myapp/
├── main.go
├── go.mod
├── go.sum
├── Dockerfile
└── .github/
└── workflows/
└── docker.ymlmain.go(完整可运行):
go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"runtime"
"time"
)
var (
version = "dev"
commit = "none"
buildTime = "unknown"
)
type Info struct {
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"build_time"`
GoVersion string `json:"go_version"`
Now time.Time `json:"now"`
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(Info{
Version: version,
Commit: commit,
BuildTime: buildTime,
GoVersion: runtime.Version(),
Now: time.Now(),
})
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
port := os.Getenv("APP_PORT")
if port == "" {
port = "8080"
}
fmt.Printf("Starting version=%s on port %s\n", version, port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Printf("failed: %v\n", err)
os.Exit(1)
}
}Dockerfile:
dockerfile
FROM golang:1.22-alpine AS builder
ENV CGO_ENABLED=0 GOOS=linux
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_TIME=unknown
RUN go build -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildTime=${BUILD_TIME}" -o server .
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata && \
addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /build/server /app/server
ENV TZ=Asia/Shanghai
USER app
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
ENTRYPOINT ["/app/server"]3. workflow.yml:自动构建并推送到 GHCR
GHCR(GitHub Container Registry)是 GitHub 提供的镜像仓库,与 GitHub 仓库无缝集成。GitHub Actions 内置 GITHUB_TOKEN,无需额外配置即可推送到 GHCR。
创建 .github/workflows/docker.yml:
yaml
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Run tests
run: |
go test -v ./...
go vet ./...
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
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=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=sha-,format=short
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
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
build-args: |
VERSION=${{ steps.meta.outputs.version }}
COMMIT=${{ github.sha }}
BUILD_TIME=${{ steps.meta.outputs.date }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: sarif
output: trivy-results.sarif
exit-code: "1"
severity: "CRITICAL,HIGH"
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif说明:上述 YAML 中
$\{\{ \}\}是 GitHub Actions 的表达式语法,用于在运行时求值。例如$\{\{ secrets.GITHUB_TOKEN \}\}会替换为内置 token,$\{\{ github.sha \}\}替换为本次提交的完整 commit hash。这些只出现在 YAML 代码块里,由 GitHub Actions 解析。
4. workflow 关键点解读
触发条件:
yaml
on:
push:
branches: [main] # 推到 main 分支触发
tags: ["v*"] # 推 v 开头的 tag 触发(如 v1.0.0)
pull_request:
branches: [main] # 针对 main 的 PR 触发权限:推送 GHCR 需要 packages: write,上传扫描结果需要 security-events: write。
metadata-action:自动根据触发事件生成镜像标签。例如:
- push 到 main:生成
main和latest标签。 - push tag v1.2.3:生成
1.2.3、1.2、1标签。 - PR #5:生成
pr-5标签。 - 每次:生成
sha-abc1234标签(commit 短 hash)。
缓存:cache-from: type=gha 和 cache-to: type=gha,mode=max 用 GitHub Actions 缓存加速构建,第二次构建会快很多。
Trivy 扫描:构建后扫描镜像,发现 CRITICAL 或 HIGH 漏洞则 exit-code: 1 让 job 失败。结果以 SARIF 格式上传到 GitHub Security 标签页。
5. 验证流水线
提交代码后,在 GitHub 仓库的 Actions 标签页可以看到运行情况。成功后镜像会出现在右侧 Packages 区域,地址类似:
text
ghcr.io/<用户名>/<仓库名>:latest
ghcr.io/<用户名>/<仓库名>:1.2.3拉取测试:
bash
docker pull ghcr.io/<用户名>/<仓库名>:latest
docker run --rm -p 8080:8080 ghcr.io/<用户名>/<仓库名>:latest
curl http://localhost:8080三、GitLab CI/CD 集成
GitLab CI/CD 是 GitLab 内置的 CI/CD 工具,配置文件是项目根目录的 .gitlab-ci.yml。
1. .gitlab-ci.yml 编写
yaml
stages:
- test
- build
- scan
- deploy
variables:
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
# Go 测试
test:
stage: test
image: golang:1.22-alpine
script:
- go test -v ./...
- go vet ./...
# 构建并推送镜像
build:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- docker build
--build-arg VERSION=$CI_COMMIT_TAG
--build-arg COMMIT=$CI_COMMIT_SHORT_SHA
--build-arg BUILD_TIME=$CI_JOB_STARTED_AT
-t $IMAGE
-t $IMAGE_LATEST
.
- docker push $IMAGE
- docker push $IMAGE_LATEST
only:
- main
- tags
# 漏洞扫描
scan:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity CRITICAL,HIGH $IMAGE
only:
- main
- tags
# 部署到 staging
deploy_staging:
stage: deploy
image: alpine:3.19
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
script:
- ssh $DEPLOY_USER@$STAGING_HOST "docker pull $IMAGE_LATEST && docker restart myapp"
environment:
name: staging
only:
- main2. GitLab CI 关键点
- stages:定义阶段顺序,前一阶段全成功才进入下一阶段。
- variables:内置变量如
$CI_REGISTRY_IMAGE(项目镜像地址)、$CI_COMMIT_SHORT_SHA(短 commit hash)、$CI_COMMIT_TAG(tag 名)。 - services: docker:24-dind:Docker in Docker,让 job 可以运行 docker 命令。
$CI_REGISTRY_PASSWORD等:GitLab 内置 token,无需手动配置即可登录内置 Registry。- only/except:控制 job 在哪些分支/tag 触发。
- environment:声明部署环境,GitLab 会有专门的 Environments 视图。
3. GitLab Container Registry
GitLab 自带容器仓库,地址形如 registry.gitlab.com/<组>/<项目>。在项目 Settings → Container Registry 中查看。拉取需要登录:
bash
docker login registry.gitlab.com -u <用户名> -p <token>
docker pull registry.gitlab.com/<组>/<项目>:latest四、镜像版本管理策略
镜像标签是版本管理的核心,直接影响部署的可追溯性和回滚能力。
1. 语义化版本(Semantic Versioning)
格式:MAJOR.MINOR.PATCH
- MAJOR:不兼容的 API 变更。
- MINOR:向后兼容的新功能。
- PATCH:向后兼容的 bug 修复。
对应到 git tag 和镜像标签:
bash
git tag v1.2.3
git push origin v1.2.3
# CI 自动构建出镜像 myapp:1.2.3、myapp:1.2、myapp:1通常同时打三个标签,方便不同粒度的引用:
myapp:1.2.3:精确版本,生产部署用这个。myapp:1.2:指向 1.2.x 的最新,方便小版本自动升级。myapp:1:指向 1.x.x 的最新,方便大版本内自动升级。
2. latest 标签策略
latest 是个有争议的标签。建议的策略:
latest指向 main 分支的最新构建(开发/测试用)。- 生产环境绝不使用
latest,必须用精确版本号。 - 生产环境用
1.2.3或1.2.3-sha-abc1234这种可追溯标签。
为什么不用 latest?
- 不可追溯:不知道当前跑的是哪个 commit。
- 不可回滚:拉取
latest永远是最新版,回滚困难。 - 不可预测:今天和明天拉的
latest可能不同。
3. Git SHA 标签
每次构建打一个基于 commit hash 的标签:
text
myapp:sha-abc1234
myapp:sha-def5678好处:
- 精确对应到 commit,方便排查问题。
- 即使没有打 release tag,也能追踪。
- 适合开发/staging 环境频繁部署。
4. 推荐的标签组合
一个成熟的镜像应该同时有:
myapp:1.2.3:精确版本。myapp:1.2.3-sha-abc1234:版本 + commit,可追溯。myapp:1.2:minor 最新。myapp:1:major 最新。myapp:latest:main 最新(仅开发用)。
5. 不可变标签
进阶做法:让标签一旦推送就不可变(Immutable Tags)。Docker Hub 等仓库默认允许覆盖同名标签,这有风险——同一标签可能指向不同内容。一些企业 Registry(如 Harbor)支持设置某标签不可变,防止覆盖。
五、多平台镜像发布
1. buildx + push
在 CI 中用 buildx 构建多平台镜像,前面讲过本地用法。GitHub Actions 中的写法:
yaml
jobs:
build-multi:
runs-on: ubuntu-latest
steps:
- 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 GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push multi-arch
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:multi
cache-from: type=gha
cache-to: type=gha,mode=maxsetup-qemu-action 安装 QEMU 模拟器,让 x86 runner 可以构建 arm64 镜像。
2. Go 应用的多平台优化
Go 的交叉编译让多平台构建特别高效。在 Dockerfile 中用 --platform=$BUILDPLATFORM 让构建阶段在原生架构运行,通过 GOARCH 交叉编译:
dockerfile
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS=linux
ARG TARGETARCH
ENV CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH}
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o server .
FROM --platform=$TARGETPLATFORM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /build/server /server
ENTRYPOINT ["/server"]这样构建速度比用 QEMU 模拟整个构建过程快得多。
3. 验证多平台镜像
bash
docker manifest inspect ghcr.io/user/repo:multi
# 应该看到 amd64 和 arm64 两个 platform
# 在 arm64 机器上拉取,会自动拿到 arm64 版本
docker pull ghcr.io/user/repo:multi六、Docker Hub / GHCR / 私有 Registry
1. Docker Hub
Docker Hub 是最大的公共镜像仓库。
bash
# 登录
docker login -u <用户名>
# 打标签(必须包含用户名前缀)
docker tag myapp:1.0 <用户名>/myapp:1.0
# 推送
docker push <用户名>/myapp:1.0
# 拉取
docker pull <用户名>/myapp:1.0注意:免费账户有拉取频率限制(匿名 100 次/6小时,登录 200 次/6小时),生产环境不建议依赖 Docker Hub。
2. GHCR(GitHub Container Registry)
GHCR 与 GitHub 仓库集成,权限管理方便。
bash
# 用 PAT 登录(需要 write:packages 权限)
echo $GITHUB_TOKEN | docker login ghcr.io -u <用户名> --password-stdin
# 打标签
docker tag myapp:1.0 ghcr.io/<用户名>/myapp:1.0
# 推送
docker push ghcr.io/<用户名>/myapp:1.0GHCR 的优势:
- 与 GitHub 仓库权限打通,团队成员自动有访问权。
- 免费,且没有 Docker Hub 的频率限制。
- 镜像页面与代码仓库关联,方便查看。
3. 私有 Registry 搭建
生产环境通常自建私有 Registry,Harbor 是最流行的选择。
快速启动 Harbor(Docker Compose 方式):
bash
# 下载离线安装包
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
tar xzf harbor-offline-installer-v2.9.0.tgz
cd harbor
# 复制配置模板
cp harbor.yml.tmpl harbor.yml
# 编辑 harbor.yml,主要修改:
# hostname: registry.example.com
# http.port: 80
# harbor_admin_password: 强密码
# data_volume: /data/harbor
# 安装
./install.sh最简版 Registry(官方 Docker Registry):
yaml
# docker-compose.yml
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
- registry_data:/data
volumes:
registry_data:启动后即可使用:
bash
docker-compose up -d
# 推送
docker tag myapp:1.0 localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0
# 拉取
docker pull localhost:5000/myapp:1.0带 TLS 和认证的 Registry(生产用):
yaml
services:
registry:
image: registry:2
ports:
- "443:443"
environment:
REGISTRY_HTTP_ADDR: 0.0.0.0:443
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
REGISTRY_HTTP_TLS_KEY: /certs/domain.key
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
volumes:
- registry_data:/data
- ./certs:/certs:ro
- ./auth:/auth:ro
volumes:
registry_data:生成认证文件:
bash
mkdir auth
htpasswd -Bc auth/htpasswd myuser
# 输入密码七、镜像签名:Cosign
镜像签名解决「这个镜像真的是我们构建的吗?有没有被篡改?」的问题。Cosign 是 Sigstore 项目提供的签名工具,已成为事实标准。
1. 安装 Cosign
bash
# macOS
brew install cosign
# Linux
curl -L -o /usr/local/bin/cosign \
https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x /usr/local/bin/cosign2. 生成密钥对
bash
cosign generate-key-pair
# 会生成 cosign.key(私钥,保密)和 cosign.pub(公钥,公开)
# 设置 COSIGN_PASSWORD 环境变量作为私钥密码3. 签名镜像
bash
# 设置密码
export COSIGN_PASSWORD=your-password
# 签名
cosign sign --key cosign.key ghcr.io/user/myapp:1.0签名会作为 OCI artifact 推送到同一仓库。
4. 验证签名
bash
cosign verify --key cosign.pub ghcr.io/user/myapp:1.0
# 验证通过则输出签名信息
# 失败则报错5. 在 CI 中自动签名
把私钥存到 GitHub Secrets(名为 COSIGN_PRIVATE_KEY),密码存为 COSIGN_PASSWORD:
yaml
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign image
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
run: |
echo "$COSIGN_PRIVATE_KEY" > cosign.key
cosign sign --key cosign.key ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}6. Keyless 签名(推荐)
Cosign 支持基于 OIDC 的 keyless 签名,无需管理密钥:
bash
cosign sign ghcr.io/user/myapp:1.0
# 会用 GitHub Actions 的 OIDC token 签名
# 验证时用证书身份
cosign verify \
--certificate-identity https://github.com/user/repo/.github/workflows/docker.yml@refs/heads/main \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/user/myapp:1.0keyless 是更安全的做法,Sigstore 的公开透明日志(Rekor)会记录所有签名,便于审计。
八、部署策略
镜像构建出来后,如何把它部署到生产?不同策略影响更新的平滑度和回滚速度。
1. 滚动更新(Rolling Update)
逐步替换旧版本实例:先启动一个新实例,健康检查通过后,停掉一个旧实例,重复直到全部替换。
text
旧: [v1][v1][v1][v1]
↓
[v2][v1][v1][v1] 新启动 v2,停一个 v1
↓
[v2][v2][v1][v1]
↓
[v2][v2][v2][v1]
↓
[v2][v2][v2][v2]Docker Swarm 和 Kubernetes 默认使用这种策略。优点是无停机、资源利用率平稳;缺点是新旧版本会短暂共存,需要版本兼容。
2. 蓝绿部署(Blue-Green)
准备两套完全相同的环境(蓝、绿),当前生产是蓝,部署新版本到绿,验证后流量一次性切到绿。
text
蓝(v1) ──流量──> 用户 绿(v2) 待命
↓ 切换
蓝(v1) 待命 绿(v2) ──流量──> 用户优点:切换瞬时、回滚只需切回去;缺点:需要双倍资源。
用 Docker Compose 实现蓝绿:
yaml
services:
app_blue:
image: myapp:1.0
ports:
- "8081:8080"
app_green:
image: myapp:1.1
ports:
- "8082:8080"
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ronginx 配置切换 upstream 即可切流量。
3. 金丝雀发布(Canary)
先把少量流量(如 5%)导向新版本,观察一段时间没问题后逐步增加流量比例。
text
用户 ──┬── 95% ──> v1 (稳定)
└── 5% ──> v2 (金丝雀)优点:风险可控,出问题只影响小部分用户;缺点:实现复杂,需要精细的流量控制。Kubernetes + Istio/Flagger 是常用方案。
4. 策略选择
| 策略 | 停机时间 | 资源开销 | 回滚速度 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 滚动更新 | 无 | 低 | 中 | 低 | 日常发布 |
| 蓝绿 | 无 | 高(双倍) | 快 | 中 | 大版本升级 |
| 金丝雀 | 无 | 中 | 快 | 高 | 风险高的发布 |
九、完整示例:GitHub Actions 自动构建发布 Go 应用
下面给出一个完整的、生产级的 GitHub Actions 工作流,整合测试、构建、扫描、签名、多平台发布。
1. 完整 workflow 文件
.github/workflows/release.yml:
yaml
name: Release
on:
push:
branches: [main]
tags: ["v*"]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- run: go test -v -race -coverprofile=coverage.out ./...
- run: go vet ./...
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.out
build-and-push:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
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=semver,pattern={{major}}
type=sha,prefix=sha-,format=short
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
id: build
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
build-args: |
VERSION=${{ steps.meta.outputs.version }}
COMMIT=${{ github.sha }}
BUILD_TIME=${{ steps.meta.outputs.date }}
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign image (keyless)
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ env.DIGEST }}
- name: Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: sarif
output: trivy-results.sarif
exit-code: "1"
severity: "CRITICAL,HIGH"
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
release-notes:
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate release notes
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
body: |
## Docker Image
```
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
```
Signed with Cosign (keyless). Verify:
```
cosign verify \
--certificate-identity https://github.com/${{ github.repository }}/.github/workflows/release.yml@refs/tags/${{ github.ref_name }} \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
```上述 workflow 中的
$\{\{ \}\}表达式由 GitHub Actions 运行时求值。例如$\{\{ steps.build.outputs.digest \}\}引用前一步构建输出的镜像 digest,$\{\{ github.ref_name \}\}是触发构建的 tag 名(如v1.2.3)。
2. 流程说明
- test job:跑测试、vet、覆盖率。
- build-and-push job:依赖 test 通过。
- 用 QEMU + Buildx 构建多平台镜像(amd64 + arm64)。
- 推送到 GHCR,自动生成语义化版本标签。
- 用 Cosign keyless 签名。
- 用 Trivy 扫描漏洞,HIGH/CRITICAL 失败。
- release-notes job:仅 tag 触发时执行,自动生成 GitHub Release,附镜像拉取和验证命令。
3. 使用流程
bash
# 日常开发:推到 main
git push origin main
# CI 自动构建 myapp:latest, myapp:main, myapp:sha-xxxx
# 发布版本:打 tag
git tag v1.2.3
git push origin v1.2.3
# CI 自动构建 myapp:1.2.3, myapp:1.2, myapp:1
# 并创建 GitHub Release4. 验证发布产物
bash
# 拉取
docker pull ghcr.io/<user>/<repo>:1.2.3
# 验证签名
cosign verify \
--certificate-identity https://github.com/<user>/<repo>/.github/workflows/release.yml@refs/tags/v1.2.3 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/<user>/<repo>:1.2.3
# 运行
docker run --rm -p 8080:8080 ghcr.io/<user>/<repo>:1.2.3
curl http://localhost:8080/version
# 应返回 version=1.2.35. 配置 GitHub Secrets
本 workflow 大部分用内置的 GITHUB_TOKEN,无需额外配置。如果用 keyless 签名,需要确保仓库 Actions 权限开启了「Allow GitHub Actions to create and approve pull requests」和 OIDC。
如果用密钥签名(非 keyless),需要添加 Secrets:
COSIGN_PRIVATE_KEY:cosign.key 内容。COSIGN_PASSWORD:私钥密码。
十、CI/CD 最佳实践
1. 流水线设计原则
- 快速失败:测试在最前面,失败立即停止后续步骤。
- 幂等性:同一 commit 多次构建应产出相同结果。
- 最小权限:每个 job 只给需要的权限。
- 缓存:利用 buildx 缓存、Go 模块缓存加速。
- 可追溯:镜像标签包含 commit SHA,能追溯代码。
2. 安全建议
- 不要在镜像里硬编码密钥:用环境变量或 secret 管理工具。
- 用 GHCR/私有 Registry 而非 Docker Hub:避免频率限制,权限可控。
- 签名验证:生产部署前验证镜像签名。
- 扫描门禁:HIGH/CRITICAL 漏洞不通过则阻止发布。
- 依赖锁定:
go.sum锁定依赖版本,避免供应链攻击。
3. 常见问题
Q:CI 构建很慢怎么办?
A:依次检查:
- 是否启用了 buildx 缓存(
cache-from/cache-to)。 go mod download是否在COPY . .之前(利用缓存)。- 是否构建了不必要的平台(只发布 amd64 可能就够)。
- 基础镜像是否太大。
Q:镜像签名验证失败?
A:检查 keyless 签名的证书身份(identity)是否与 workflow 路径匹配,tag 触发的签名身份是 @refs/tags/v1.2.3,main 分支是 @refs/heads/main。
Q:Trivy 报漏洞但基础镜像没更新怎么办?
A:可以用 .trivyignore 文件忽略特定 CVE(短期),同时推动基础镜像更新(长期)。distroless 镜像通常更新更快。
十一、小结
本篇是 Docker + Go 系列的收官篇,把容器化的最后一环——自动化流水线——讲透了。
关键要点:
- CI/CD 价值:一致性、快速反馈、可追溯、安全。
- GitHub Actions:用
docker/build-push-action一站式构建推送,docker/metadata-action自动生成标签,Trivy 扫描门禁。 - GitLab CI:用
.gitlab-ci.yml定义阶段,dind 执行 docker 命令,内置 Registry 无缝集成。 - 版本管理:语义化版本(1.2.3)+ Git SHA(sha-abc1234)+ latest(仅开发),生产用精确版本。
- 多平台发布:buildx + QEMU 构建 amd64/arm64 镜像,Go 交叉编译加速。
- 私有 Registry:Harbor(企业级)或官方 registry(轻量),生产环境必上。
- 镜像签名:Cosign keyless 签名,Sigstore 透明日志审计,部署前验证。
- 部署策略:滚动更新(日常)、蓝绿(大版本)、金丝雀(高风险)。
至此,Docker + Go 系列教程完整覆盖了从基础概念到生产部署的全链路。回顾整个系列:
- 第一篇:Docker 基础与 Go 容器化入门
- 第二篇:多阶段构建与镜像优化(856MB → 18MB)
- 第三篇:Docker Compose 多服务编排
- 第四篇:容器化 Go 应用最佳实践(十二要素、优雅关停、安全加固)
- 第五篇:CI/CD 流水线与镜像发布(自动化闭环)
掌握这五篇内容,你已经具备了从零到生产部署一个 Go 容器化应用的完整能力。接下来可以进一步学习 Kubernetes、服务网格(Istio)、可观测性(Prometheus + Grafana + Loki)等云原生生态,向真正的云原生工程师迈进。