28 - CI/CD:GitHub Actions、goreleaser、自动发布
28 - CI/CD
28.1 GitHub Actions 基础
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.22', '1.23', '1.24']
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download dependencies
run: go mod download
- name: Lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
- name: Test
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.out
28.2 完整 CI 流程
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
GO_VERSION: '1.24'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
args: --timeout=5m
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
env:
DATABASE_URL: postgres://test:test@localhost:5432/testdb?sslmode=disable
- name: Upload coverage
uses: codecov/codecov-action@v3
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Build
run: |
CGO_ENABLED=0 go build -ldflags="-s -w" -o server ./cmd/server
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o server-arm64 ./cmd/server
docker:
needs: [lint, test]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}:latest
release:
needs: [lint, test, build]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28.3 goreleaser
goreleaser 自动化构建和发布多平台二进制。
# .goreleaser.yml
version: 2
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- id: server
main: ./cmd/server
binary: myapp
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.ShortCommit}}
- -X main.buildTime={{.Date}}
archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^chore:'
dockers:
- image_templates:
- "ghcr.io/myuser/myapp:{{ .Version }}-amd64"
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
release:
github:
owner: myuser
name: myapp
prerelease: auto
# 本地测试(不发布)
goreleaser release --snapshot --clean
# 构建
goreleaser build --snapshot --clean
# 发布(需要 tag)
git tag v1.0.0
git push origin v1.0.0
# GitHub Actions 自动触发 goreleaser
28.4 Makefile 集成
APP_NAME := myapp
VERSION := $(shell git describe --tags --always --dirty)
.PHONY: all lint test build release
all: lint test build
lint:
golangci-lint run ./...
test:
go test -v -race -cover ./...
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME) ./cmd/server
release:
git tag -a $(VERSION) -m "Release $(VERSION)"
git push origin $(VERSION)
snapshot:
goreleaser release --snapshot --clean
docker:
docker build -t $(APP_NAME):$(VERSION) .
clean:
rm -rf bin/ dist/
28.5 部署策略
Kubernetes 部署
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: ghcr.io/myuser/myapp:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
🏢 业务场景
- 自动测试:PR 提交时自动运行 lint 和测试
- 自动发布:打 tag 后自动构建和发布多平台二进制
- Docker 镜像:自动构建并推送到容器仓库
- Kubernetes 部署:使用 Helm chart 或 kustomize 部署