diff --git a/opencode.json b/opencode.json index f120ca7..9002dee 100644 --- a/opencode.json +++ b/opencode.json @@ -18,5 +18,6 @@ } } } - } -} + }, + "mcp": {} +} \ No newline at end of file diff --git a/skill/gitea/SKILL.md b/skill/gitea/SKILL.md index b8f676f..9d455bf 100644 --- a/skill/gitea/SKILL.md +++ b/skill/gitea/SKILL.md @@ -395,6 +395,7 @@ Runner 信息 |------|---------|---------| | **Rust 后端** | [rust-backend.md](./workflow-templates/rust-backend.md) | **Rust API 服务、微服务、CLI 工具** | | Go 后端 | [go-backend.md](./workflow-templates/go-backend.md) | API 服务、微服务、CLI 工具 | +| Rust 后端 | [rust-backend.md](./workflow-templates/rust-backend.md) | Rust API 服务、异步服务、IoT 设备服务 | | Node.js 前端 | [nodejs-frontend.md](./workflow-templates/nodejs-frontend.md) | React/Vue/Vite/Next.js | | Android 应用 | [android-app.md](./workflow-templates/android-app.md) | Kotlin/Java/Jetpack Compose | | 微信小程序 | [wechat-miniprogram.md](./workflow-templates/wechat-miniprogram.md) | 微信小程序 CI/CD | @@ -407,6 +408,31 @@ AI 会自动: 详见:[Workflow 生成器](./workflow-generator.md) +## Dockerfile 模板 + +提供多种 Dockerfile 模板,适配不同的构建场景: + +| 类型 | 模板文档 | 适用场景 | +|------|---------|---------| +| Go 服务 | [go-service.md](./dockerfile-templates/go-service.md) | Go 后端服务容器化(CI 构建、完整构建、代码生成) | +| Rust 服务 | [rust-service.md](./dockerfile-templates/rust-service.md) | Rust 后端服务容器化(CI 构建、完整构建、多平台) | +| Node.js 前端 | [nodejs-frontend.md](./dockerfile-templates/nodejs-frontend.md) | 前端静态文件服务(Nginx + SPA 路由) | + +**模板特性对比**: + +| 特性 | Go 服务 | Rust 服务 | Node.js 前端 | +|------|---------|-----------|-------------| +| CI 构建版 | ✅ | ✅ | ✅ | +| 完整构建版 | ✅ | ✅ | ✅ | +| 健康检查脚本 | ✅ | ✅ | ✅(Nginx) | +| 多阶段构建 | ✅ | ✅ | ✅ | +| 镜像大小 | ~15MB | ~20MB | ~20MB | +| 代码生成支持 | ✅ | ❌ | ❌ | +| 多平台构建 | ❌ | ✅ | ❌ | +| API 代理配置 | ❌ | ❌ | ✅(Nginx) | + +详见各模板文档了解具体使用方法。 + ## API 调用 所有与 Gitea 服务器的交互都通过 API 完成,使用配置文件中的: @@ -466,9 +492,9 @@ Gitea Actions 提供的内置变量(推荐优先使用): ## 版本 -- **Skill Version**: 1.3 +- **Skill Version**: 1.4 - **Last Updated**: 2026-01-29 -- **整合内容**: gitea-runner + gitea-workflow + 增强仓库管理 + SSH 密钥管理 + Rust 模板 +- **整合内容**: gitea-runner + gitea-workflow + 增强仓库管理 + SSH 密钥管理 + 完整 Dockerfile 模板库 - **主要改进**: - 仓库创建智能解析(优先使用指定组织) - **简化验证**:默认假设组织存在,API失败时提示创建组织 @@ -477,6 +503,12 @@ Gitea Actions 提供的内置变量(推荐优先使用): - **简洁高效**:减少预先验证,API失败时给出具体解决方案 - 工作目录概念澄清(配置 vs 仓库操作) - **SSH 密钥管理**:完整的密钥创建、部署和跨设备使用指南 + - **Rust 项目支持**:新增 Rust workflow 和 Dockerfile 模板,支持交叉编译、musl 静态链接、多平台构建 + - **完整 Dockerfile 模板库**:新增 Go 和 Node.js 前端 Dockerfile 模板,涵盖所有后端和前端容器化需求 + +## 快速参考 + +- [Rust 项目快速配置](./rust-quick-reference.md) - 3 步设置 Rust 项目 CI/CD ## 相关资源 diff --git a/skill/gitea/create-runner.md b/skill/gitea/create-runner.md index fd81cde..26eea66 100644 --- a/skill/gitea/create-runner.md +++ b/skill/gitea/create-runner.md @@ -7,6 +7,15 @@ agent: general 本文档提供了多种创建 Gitea Actions Runner 的方式,支持 Host 模式和 Docker 模式。 +## ⚠️ 重要:网络模式要求 + +**两种模式都必须使用 host 网络模式,否则缓存功能无法正常工作!** + +- **Docker 模式**:Runner 容器和 Job 容器都使用 host 网络 +- **Self-host 模式**:Job 进程直接在宿主机运行,使用 127.0.0.1 + +原因:Gitea Actions 的缓存服务运行在 runner 本地,job 容器需要通过网络访问 runner 的缓存端口。只有在 host 网络模式下,job 容器才能访问到 runner 的缓存服务。 + ## 📦 快速使用 ### 方法一:直接执行(推荐) @@ -108,6 +117,7 @@ cache: host: "host.docker.internal" port: 9040 container: + # 【关键】必须使用 host 网络!否则 job 容器无法访问 runner 的缓存服务 network: "host" privileged: false options: @@ -123,6 +133,7 @@ YAML # 注意:不要使用 node:16-bullseye 等纯运行时镜像,它们不包含 docker 命令 local labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest" + # 必须使用 --network host!这样 job 容器才能访问 runner 的缓存服务 docker run -d \ --name "$name" \ --restart always \ @@ -178,6 +189,7 @@ runner: cache: enabled: true dir: "$runner_dir/cache" + # Self-host 模式:job 进程直接在宿主机运行,使用 127.0.0.1 访问缓存 host: "127.0.0.1" port: 0 host: @@ -747,6 +759,7 @@ EOF echo "启动 Docker 容器 (自动注册)..." + # 必须使用 --network host!这样 job 容器才能访问 runner 的缓存服务 docker run -d \ --name "$name" \ --restart always \ @@ -1074,7 +1087,7 @@ cache: host: "host.docker.internal" port: 9040 container: - # 使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问 + # 【关键】必须使用 host 网络!否则 job 容器无法访问 runner 的缓存服务 network: "host" privileged: false options: @@ -1088,7 +1101,7 @@ EOF echo "启动 Docker 容器 (自动注册)..." - # 使用 host 网络模式,确保 runner 和 job 容器共处一个网络,以便 cache 能够正常访问 + # 【关键】必须使用 --network host!否则 job 容器无法访问 runner 的缓存服务 docker run -d \ --name "$runner_name" \ --restart always \ diff --git a/skill/gitea/dockerfile-templates/go-service.md b/skill/gitea/dockerfile-templates/go-service.md new file mode 100644 index 0000000..f5eccdf --- /dev/null +++ b/skill/gitea/dockerfile-templates/go-service.md @@ -0,0 +1,550 @@ +# Go 服务 Dockerfile 模板 + +Go 后端服务的 Docker 容器化模板,支持多种构建场景。 + +## 模板类型 + +### 1. CI 构建镜像(Dockerfile.ci) + +**适用场景**:在 CI/CD 中使用,二进制文件已在外部构建完成。 + +**优点**: +- 镜像体积最小(~15MB) +- 构建速度快 +- 适合生产环境 + +```dockerfile +# CI 构建专用 Dockerfile +# 将不常变化的层放在前面,最大化利用缓存 +# 使用固定版本避免 latest 导致的缓存失效和不确定性 + +FROM alpine:3.21 + +# 基础设置层 - 很少变化,可以长期缓存 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata wget netcat-openbsd && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 健康检查脚本 - 偶尔变化 +COPY docker-healthcheck.sh . +RUN chmod +x docker-healthcheck.sh + +# 应用二进制 - 每次构建都变化,放在最后 +COPY your-binary-name . + +# 健康检查(根据实际服务修改端口和端点) +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD ./docker-healthcheck.sh + +CMD ["./your-binary-name"] +``` + +**使用说明**: +1. 将 `your-binary-name` 替换为实际的二进制文件名 +2. 创建 `docker-healthcheck.sh` 健康检查脚本 +3. 在 CI workflow 中先构建二进制,再构建镜像 + +### 2. 完整构建镜像(Dockerfile) + +**适用场景**:本地开发、无 CI 环境、独立构建。 + +**优点**: +- 自包含构建流程 +- 可在任何环境构建 +- 利用 Docker 缓存加速 + +```dockerfile +# ============================================ +# 构建阶段 +# ============================================ +FROM golang:1.25-alpine AS builder + +# 安装构建依赖 +RUN apk add --no-cache git make + +# 设置工作目录 +WORKDIR /build + +# 设置 Go 代理(国内加速) +ENV GOPROXY=https://goproxy.cn,direct +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 + +# 先复制依赖文件,利用 Docker 层缓存 +COPY go.mod go.sum ./ +RUN go mod download + +# 复制源码 +COPY . . + +# 构建应用(根据项目需求修改) +RUN go build -o app \ + -ldflags '-s -w -X main.GitTag=dev' \ + . + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +# 安装运行时依赖 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata wget netcat-openbsd && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +# 从构建阶段复制二进制文件 +COPY --from=builder /build/app . +COPY --from=builder /build/docker-healthcheck.sh . + +RUN chmod +x app docker-healthcheck.sh + +# 暴露端口(根据实际服务修改) +EXPOSE 8080 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD ./docker-healthcheck.sh + +CMD ["./app"] +``` + +**使用说明**: +1. 将构建命令修改为项目实际的构建命令 +2. 调整 ldflags 参数(版本号注入、符号表去除等) +3. 修改端口号 + +### 3. 代码生成支持版(Dockerfile.codegen) + +**适用场景**:需要在容器中生成代码(如 Ent、Wire、oapi-codegen)。 + +```dockerfile +# ============================================ +# 构建阶段 +# ============================================ +FROM golang:1.25-alpine AS builder + +# 安装构建工具 +RUN apk add --no-cache git make + +WORKDIR /build + +ENV GOPROXY=https://goproxy.cn,direct +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 + +# 复制依赖文件 +COPY go.mod go.sum ./ +RUN go mod download + +# 安装代码生成工具(根据项目需求选择) +RUN go install entgo.io/ent/cmd/ent@latest && \ + go install github.com/google/wire/cmd/wire@latest && \ + go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest && \ + go install golang.org/x/tools/cmd/stringer@latest + +# 复制源码 +COPY . . + +# 代码生成(根据项目需求修改) +RUN go generate ./... +# 或使用 go tool 调用 +# RUN go tool ent generate ./ent/schema +# RUN go tool wire ./... +# RUN go tool oapi-codegen -config oapi.yaml api.yaml + +# 测试(可选) +RUN go test ./... +RUN go vet ./... + +# 构建 +RUN go build -o app \ + -ldflags '-s -w' \ + . + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata wget netcat-openbsd && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +COPY --from=builder /build/app . +COPY --from=builder /build/docker-healthcheck.sh . + +RUN chmod +x app docker-healthcheck.sh + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD ./docker-healthcheck.sh + +CMD ["./app"] +``` + +## 健康检查脚本 + +### docker-healthcheck.sh(基础版) + +```bash +#!/bin/sh +# Docker 健康检查脚本 + +# 检查应用端口是否在监听 +if nc -z localhost 8080 2>/dev/null; then + exit 0 +fi + +exit 1 +``` + +### docker-healthcheck.sh(HTTP 端点版) + +```bash +#!/bin/sh +# Docker 健康检查脚本 +# 检查 HTTP 端点是否可访问 + +# 使用 /health 或 /metrics 端点 +if wget --spider --quiet --timeout=5 http://localhost:8080/health 2>/dev/null; then + exit 0 +fi + +# 备用:检查端口是否在监听 +if nc -z localhost 8080 2>/dev/null; then + exit 0 +fi + +exit 1 +``` + +### docker-healthcheck.sh(多端口版) + +```bash +#!/bin/sh +# Docker 健康检查脚本 +# 检查多个端口(适用于多个 API 服务) + +# 检查端口 2020 (Platform API) +if ! nc -z localhost 2020 2>/dev/null; then + echo "Port 2020 is not responding" + exit 1 +fi + +# 检查端口 2021 (WxMP API) +if ! nc -z localhost 2021 2>/dev/null; then + echo "Port 2021 is not responding" + exit 1 +fi + +# 或使用 HTTP 端点 +# if ! wget --spider --quiet --timeout=5 http://localhost:2020/metrics 2>/dev/null; then +# exit 1 +# fi + +exit 0 +``` + +## 高级配置 + +### 环境变量配置 + +```dockerfile +# 在 Dockerfile 中设置默认环境变量 +ENV MODE=production +ENV LOG_LEVEL=info +ENV GIN_MODE=release + +# 运行时可覆盖 +# docker run -e MODE=test -e LOG_LEVEL=debug your-image +``` + +### 配置文件挂载 + +```dockerfile +# 创建配置目录 +RUN mkdir -p /app/configs + +# 运行时挂载配置文件 +# docker run -v ./configs:/app/configs your-image +``` + +### 非 root 用户运行 + +```dockerfile +# 创建非特权用户 +RUN addgroup -g 1000 appuser && \ + adduser -D -u 1000 -G appuser appuser + +WORKDIR /app + +# 复制文件 +COPY --from=builder --chown=appuser:appuser /build/app . + +# 切换用户 +USER appuser + +CMD ["./app"] +``` + +### 多阶段构建优化 + +```dockerfile +# ============================================ +# 依赖下载阶段(缓存 go.mod) +# ============================================ +FROM golang:1.25-alpine AS deps + +WORKDIR /build + +ENV GOPROXY=https://goproxy.cn,direct + +COPY go.mod go.sum ./ +RUN go mod download + +# ============================================ +# 构建阶段 +# ============================================ +FROM golang:1.25-alpine AS builder + +WORKDIR /build + +# 从依赖阶段复制缓存 +COPY --from=deps /go/pkg /go/pkg + +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 + +COPY . . + +RUN go build -o app -ldflags '-s -w' . + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +# ... 省略后续步骤 +``` + +## 镜像优化技巧 + +### 1. 减小镜像体积 + +```dockerfile +# 使用 Alpine 基础镜像 +FROM alpine:3.21 + +# 去除符号表和调试信息 +RUN go build -ldflags '-s -w' -o app + +# 使用 upx 压缩(可选,启动时间会变长) +RUN apk add --no-cache upx && \ + upx --best --lzma app && \ + apk del upx +``` + +### 2. 利用构建缓存 + +```dockerfile +# 先复制 go.mod 和 go.sum,利用依赖缓存 +COPY go.mod go.sum ./ +RUN go mod download + +# 再复制源码(源码变化频繁) +COPY . . +``` + +### 3. 合并 RUN 命令 + +```dockerfile +# 合并多个 RUN 命令减少层数 +RUN apk add --no-cache tzdata wget netcat-openbsd && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + addgroup -g 1000 appuser +``` + +### 4. 使用 .dockerignore + +``` +# .dockerignore +.git/ +.gitignore +*.md +docs/ +tests/ +.github/ +.gitea/ +Dockerfile* +docker-compose.yml +*.log +tmp/ +vendor/ +``` + +## 构建命令示例 + +### 本地构建 + +```bash +# 基础构建 +docker build -t your-service:latest . + +# 指定 Dockerfile +docker build -f Dockerfile.ci -t your-service:latest . + +# 传递构建参数 +docker build --build-arg GIT_TAG=v1.0.0 -t your-service:1.0.0 . + +# 查看构建过程 +docker build --progress=plain -t your-service:latest . +``` + +### CI/CD 构建 + +```bash +# 使用 BuildKit 缓存 +export DOCKER_BUILDKIT=1 + +# 使用注册表缓存 +docker build \ + --cache-from=type=registry,ref=your-service:buildcache \ + --cache-to=type=registry,ref=your-service:buildcache,mode=max \ + -t your-service:latest \ + . +``` + +## 运行容器示例 + +```bash +# 基础运行 +docker run -d -p 8080:8080 your-service:latest + +# 挂载配置文件 +docker run -d \ + -p 8080:8080 \ + -v ./configs:/app/configs \ + -v ./logs:/app/logs \ + your-service:latest + +# 设置环境变量 +docker run -d \ + -p 8080:8080 \ + -e MODE=production \ + -e DATABASE_URL=postgres://... \ + your-service:latest + +# 查看日志 +docker logs -f + +# 进入容器调试 +docker exec -it /bin/sh +``` + +## 安全建议 + +### 1. 使用特定版本标签 + +```dockerfile +# 不推荐:使用 latest +FROM alpine:latest + +# 推荐:使用特定版本 +FROM alpine:3.21 +``` + +### 2. 定期更新依赖 + +```bash +# 更新 Go 模块 +go get -u ./... +go mod tidy + +# 更新基础镜像 +docker pull alpine:3.21 +``` + +### 3. 扫描漏洞 + +```bash +# 使用 Trivy 扫描 +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy image your-service:latest +``` + +### 4. 最小权限原则 + +```dockerfile +# 使用非 root 用户 +USER appuser + +# 只读文件系统(如果适用) +# docker run --read-only your-service +``` + +## 常见问题 + +### Q1: 时区不正确 + +**原因**: 容器默认使用 UTC 时区 + +**解决方案**: +```dockerfile +RUN apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +``` + +### Q2: 网络连接超时 + +**原因**: DNS 解析问题或网络配置 + +**解决方案**: +```bash +# 运行时指定 DNS +docker run --dns 8.8.8.8 your-service + +# 或在 Dockerfile 中配置 +RUN echo 'nameserver 8.8.8.8' > /etc/resolv.conf +``` + +### Q3: CGO 依赖问题 + +**错误信息**: `binary was compiled with 'CGO_ENABLED=1'` + +**解决方案**: +```dockerfile +# 禁用 CGO +ENV CGO_ENABLED=0 + +# 或安装 gcc +RUN apk add --no-cache gcc musl-dev +``` + +### Q4: 静态链接失败 + +**错误信息**: `dynamic executable` + +**解决方案**: +```dockerfile +# 确保 CGO_ENABLED=0 +ENV CGO_ENABLED=0 + +# 或使用 musl 交叉编译 +RUN apk add --no-cache musl-dev +RUN go build -ldflags '-linkmode external -extldflags "-static"' -o app +``` + +## 参考资源 + +- [Go 官方 Docker 指南](https://docs.docker.com/language/golang/) +- [Alpine Linux 包搜索](https://pkgs.alpinelinux.org/packages) +- [Docker 多阶段构建文档](https://docs.docker.com/build/building/multi-stage/) +- [Go 编译器标志](https://pkg.go.dev/cmd/go) diff --git a/skill/gitea/dockerfile-templates/nodejs-frontend.md b/skill/gitea/dockerfile-templates/nodejs-frontend.md new file mode 100644 index 0000000..cb03b09 --- /dev/null +++ b/skill/gitea/dockerfile-templates/nodejs-frontend.md @@ -0,0 +1,631 @@ +# Node.js 前端 Dockerfile 模板 + +Node.js 前端项目的 Docker 容器化模板,使用 Nginx 提供静态文件服务。 + +## 模板类型 + +### 1. CI 构建镜像(Dockerfile.ci) + +**适用场景**:在 CI/CD 中使用,静态文件已在外部构建完成。 + +**优点**: +- 镜像体积最小(~20MB) +- 构建速度快 +- 适合生产环境 + +```dockerfile +# CI 构建专用 Dockerfile +# 将不常变化的层放在前面,最大化利用缓存 +# 使用固定版本避免 latest 导致的缓存失效和不确定性 + +FROM nginx:1.28.0-alpine + +# 基础设置层 - 很少变化,可以长期缓存 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# Nginx 配置文件 - 配置 SPA 路由处理 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 应用静态文件 - 每次构建都变化,放在最后 +COPY dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +**使用说明**: +1. 创建 `nginx.conf` 配置文件 +2. 在 CI workflow 中先构建前端(生成 dist 目录),再构建镜像 +3. 根据实际需求修改 Nginx 配置 + +### 2. 完整构建镜像(Dockerfile) + +**适用场景**:本地开发、无 CI 环境、独立构建。 + +**优点**: +- 自包含构建流程 +- 可在任何环境构建 +- 利用 Docker 缓存加速 + +```dockerfile +# ============================================ +# 构建阶段 +# ============================================ +FROM node:22-alpine AS builder + +# 安装构建依赖 +RUN apk add --no-cache git + +WORKDIR /build + +# 设置 npm 镜像源(国内加速) +RUN npm config set registry https://registry.npmmirror.com + +# 安装 pnpm +RUN npm install -g pnpm@latest-10 + +# 先复制依赖文件,利用 Docker 层缓存 +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# 复制源码 +COPY . . + +# 构建应用 +# 根据项目使用的构建工具修改命令 +RUN pnpm run build + +# ============================================ +# 运行阶段 +# ============================================ +FROM nginx:1.28.0-alpine + +# 安装运行时依赖 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 复制 Nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 从构建阶段复制静态文件 +COPY --from=builder /build/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +**使用说明**: +1. 根据项目使用 npm、yarn 或 pnpm 修改安装命令 +2. 根据构建工具(Vite、Webpack、Next.js 等)修改构建命令和输出目录 +3. 调整 Nginx 配置 + +### 3. 多环境构建版(Dockerfile.multienv) + +**适用场景**:需要根据不同环境(dev/test/prod)构建不同配置。 + +```dockerfile +# ============================================ +# 构建阶段 +# ============================================ +FROM node:22-alpine AS builder + +ARG BUILD_ENV=production + +WORKDIR /build + +RUN npm config set registry https://registry.npmmirror.com && \ + npm install -g pnpm@latest-10 + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY . . + +# 根据 BUILD_ENV 设置不同的环境变量 +ENV NODE_ENV=${BUILD_ENV} +ENV VITE_APP_ENV=${BUILD_ENV} + +RUN pnpm run build + +# ============================================ +# 运行阶段 +# ============================================ +FROM nginx:1.28.0-alpine + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /build/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +**构建命令**: +```bash +# 生产环境 +docker build --build-arg BUILD_ENV=production -t app:prod . + +# 测试环境 +docker build --build-arg BUILD_ENV=test -t app:test . + +# 开发环境 +docker build --build-arg BUILD_ENV=development -t app:dev . +``` + +## Nginx 配置模板 + +### nginx.conf(基础 SPA 配置) + +```nginx +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # 启用 gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript + application/x-javascript application/xml+rss + application/json application/javascript; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA 路由处理:所有请求都返回 index.html + location / { + try_files $uri $uri/ /index.html; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} +``` + +### nginx.conf(带 API 代理) + +```nginx +server { + listen 80; + server_name _; + + client_max_body_size 30M; + + root /usr/share/nginx/html; + index index.html; + + # 启用 gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript + application/x-javascript application/xml+rss + application/json application/javascript; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA 路由处理 + location / { + try_files $uri $uri/ /index.html; + } + + # API 代理(根据实际后端服务修改) + location /api/ { + proxy_pass http://backend-service:8080/; + proxy_http_version 1.1; + proxy_read_timeout 300s; + proxy_redirect off; + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} +``` + +### nginx.conf(完整生产配置) + +```nginx +# SPA 应用 Nginx 配置 +# 所有路由都返回 index.html,由前端路由处理 + +server { + listen 80; + server_name _; + + client_max_body_size 30M; + + root /usr/share/nginx/html; + index index.html; + + # 启用 gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript + application/x-javascript application/xml+rss + application/json application/javascript; + + # 静态资源缓存(带版本号的文件可以长期缓存) + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # HTML 文件不缓存(确保用户总是获取最新版本) + location ~* \.html$ { + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + # SPA 路由处理:所有请求都返回 index.html + location / { + # 用于配合 browserHistory 使用 + try_files $uri $uri/ /index.html; + } + + # API 代理:平台 API + location /platform/api/ { + proxy_pass http://backend-service:2020/; + proxy_http_version 1.1; + proxy_read_timeout 300s; + proxy_redirect off; + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # API 代理:微信小程序 API + location /wxmp/api/ { + proxy_pass http://backend-service:2021/; + proxy_http_version 1.1; + proxy_read_timeout 300s; + proxy_redirect off; + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 第三方服务代理(如高德地图) + location /_AMapService/ { + set $args "$args&jscode=YOUR_AMAP_KEY"; + proxy_pass https://restapi.amap.com/; + } + + # 错误页面 + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} +``` + +## 高级配置 + +### 支持 HTTPS + +```nginx +server { + listen 80; + server_name example.com; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name example.com; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + root /usr/share/nginx/html; + index index.html; + + # ... 其他配置 +} +``` + +### 防盗链 + +```nginx +location ~* \.(jpg|jpeg|png|gif|svg)$ { + valid_referers none blocked example.com *.example.com; + if ($invalid_referer) { + return 403; + } + expires 1y; +} +``` + +### 限流 + +```nginx +# 在 http 块中定义限流区域 +limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; + +# 在 server 块中应用 +location / { + limit_req zone=one burst=20 nodelay; + try_files $uri $uri/ /index.html; +} +``` + +### 安全头 + +```nginx +location / { + # 防止点击劫持 + add_header X-Frame-Options "SAMEORIGIN" always; + + # 防止 MIME 类型嗅探 + add_header X-Content-Type-Options "nosniff" always; + + # 启用 XSS 保护 + add_header X-XSS-Protection "1; mode=block" always; + + # CSP 策略 + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always; + + try_files $uri $uri/ /index.html; +} +``` + +## 镜像优化技巧 + +### 1. 使用多阶段构建 + +```dockerfile +# 分离构建和运行环境,只保留必要文件 +FROM node:22-alpine AS builder +# ... 构建步骤 + +FROM nginx:1.28.0-alpine +# 只复制构建产物 +COPY --from=builder /build/dist /usr/share/nginx/html +``` + +### 2. 利用构建缓存 + +```dockerfile +# 先复制依赖文件 +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install + +# 再复制源码(源码变化频繁) +COPY . . +``` + +### 3. 使用 .dockerignore + +``` +# .dockerignore +node_modules/ +.git/ +.gitignore +*.md +docs/ +tests/ +.github/ +.gitea/ +Dockerfile* +docker-compose.yml +.env* +dist/ +build/ +coverage/ +.vscode/ +.idea/ +``` + +### 4. 优化 Nginx 配置 + +```nginx +# 启用 sendfile +sendfile on; + +# 启用 tcp_nopush +tcp_nopush on; + +# 启用 tcp_nodelay +tcp_nodelay on; + +# 设置 keepalive 超时 +keepalive_timeout 65; + +# 禁用访问日志(生产环境可选) +access_log off; +``` + +## 构建命令示例 + +### 本地构建 + +```bash +# 基础构建 +docker build -t frontend:latest . + +# 指定 Dockerfile +docker build -f Dockerfile.ci -t frontend:latest . + +# 传递构建参数 +docker build --build-arg BUILD_ENV=production -t frontend:1.0.0 . +``` + +### CI/CD 构建 + +```bash +# 使用 BuildKit 缓存 +export DOCKER_BUILDKIT=1 + +# 使用注册表缓存 +docker build \ + --cache-from=type=registry,ref=frontend:buildcache \ + --cache-to=type=registry,ref=frontend:buildcache,mode=max \ + -t frontend:latest \ + . +``` + +## 运行容器示例 + +```bash +# 基础运行 +docker run -d -p 80:80 frontend:latest + +# 挂载自定义 Nginx 配置 +docker run -d \ + -p 80:80 \ + -v ./nginx.conf:/etc/nginx/conf.d/default.conf \ + frontend:latest + +# 设置环境变量(如果 Nginx 配置需要) +docker run -d \ + -p 80:80 \ + -e BACKEND_HOST=api.example.com \ + frontend:latest + +# 查看日志 +docker logs -f + +# 进入容器调试 +docker exec -it /bin/sh +``` + +## Docker Compose 示例 + +```yaml +version: '3.8' + +services: + frontend: + build: + context: . + dockerfile: Dockerfile + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - backend + restart: unless-stopped + + backend: + image: backend-service:latest + ports: + - "8080:8080" + restart: unless-stopped +``` + +## 常见问题 + +### Q1: SPA 路由 404 错误 + +**原因**: Nginx 找不到对应的文件 + +**解决方案**: +```nginx +# 配置 try_files,所有路由返回 index.html +location / { + try_files $uri $uri/ /index.html; +} +``` + +### Q2: API 代理跨域问题 + +**原因**: CORS 配置不正确 + +**解决方案**: +```nginx +location /api/ { + proxy_pass http://backend:8080/; + + # 添加 CORS 头 + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; + + if ($request_method = 'OPTIONS') { + return 204; + } +} +``` + +### Q3: 静态资源缓存问题 + +**原因**: 浏览器缓存了旧版本 + +**解决方案**: +```nginx +# HTML 文件不缓存 +location ~* \.html$ { + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate"; +} + +# JS/CSS 使用版本号或哈希,可以长期缓存 +location ~* \.(js|css)$ { + expires 1y; + add_header Cache-Control "public, immutable"; +} +``` + +### Q4: 容器启动后立即退出 + +**原因**: Nginx 配置文件语法错误 + +**解决方案**: +```bash +# 测试配置文件 +docker run --rm -v ./nginx.conf:/etc/nginx/conf.d/default.conf \ + nginx:1.28.0-alpine nginx -t + +# 查看容器日志 +docker logs +``` + +## 参考资源 + +- [Node.js 官方 Docker 指南](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/) +- [Nginx 官方文档](https://nginx.org/en/docs/) +- [Docker 多阶段构建文档](https://docs.docker.com/build/building/multi-stage/) +- [前端构建工具文档](https://vitejs.dev/guide/) diff --git a/skill/gitea/dockerfile-templates/rust-service.md b/skill/gitea/dockerfile-templates/rust-service.md new file mode 100644 index 0000000..cb3a0b1 --- /dev/null +++ b/skill/gitea/dockerfile-templates/rust-service.md @@ -0,0 +1,571 @@ +# Rust 服务 Dockerfile 模板 + +Rust 项目的 Docker 容器化模板,支持多种构建场景。 + +## 模板类型 + +### 1. CI 构建镜像(Dockerfile.ci) + +**适用场景**:在 CI/CD 中使用,二进制文件已在外部构建完成。 + +**优点**: +- 镜像体积最小(~20MB) +- 构建速度快 +- 适合生产环境 + +```dockerfile +FROM alpine:3.21 + +# 安装运行时依赖 +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +# 复制编译好的二进制文件(由 CI workflow 构建) +COPY your-binary-name . + +# 环境变量 +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +# 暴露端口 +EXPOSE 9090 + +# 健康检查 +# 根据你的服务实际情况修改端点和端口 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +CMD ["./your-binary-name"] +``` + +**使用说明**: +1. 将 `your-binary-name` 替换为实际的二进制文件名 +2. 修改 `EXPOSE` 端口号 +3. 修改 `HEALTHCHECK` 的端点和端口 +4. 在 CI workflow 中先构建二进制,再构建镜像 + +### 2. 完整构建镜像(Dockerfile) + +**适用场景**:本地开发、无 CI 环境、独立构建。 + +**优点**: +- 自包含构建流程 +- 可在任何环境构建 +- 利用 Docker 缓存加速 + +```dockerfile +# ============================================ +# 构建阶段 +# ============================================ +FROM rust:1.83-alpine AS builder + +# 安装构建依赖 +# musl-dev: musl libc 开发包(静态链接) +# openssl-dev: OpenSSL 开发包(如果项目依赖 openssl) +# pkgconfig: pkg-config 工具 +RUN apk add --no-cache musl-dev openssl-dev pkgconfig + +# 配置 Cargo 镜像加速(使用 rsproxy.cn) +RUN mkdir -p /root/.cargo && \ + echo '[source.crates-io]' > /root/.cargo/config.toml && \ + echo 'replace-with = "rsproxy-sparse"' >> /root/.cargo/config.toml && \ + echo '[source.rsproxy-sparse]' >> /root/.cargo/config.toml && \ + echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /root/.cargo/config.toml + +WORKDIR /build + +# 先复制依赖文件,利用 Docker 层缓存 +# 只要 Cargo.toml 和 Cargo.lock 不变,依赖层就会被缓存 +COPY Cargo.toml Cargo.lock ./ + +# 创建虚拟 src 目录,预先下载和编译依赖 +# 这样修改源码时不需要重新编译依赖 +RUN mkdir src && \ + echo "fn main() {}" > src/main.rs && \ + cargo build --release --target x86_64-unknown-linux-musl && \ + rm -rf src + +# 复制实际源码 +COPY src ./src + +# 构建 Release 版本(musl 静态链接) +RUN cargo build --release --target x86_64-unknown-linux-musl + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +# 安装运行时依赖 +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +# 从构建阶段复制二进制文件 +COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/your-binary-name . + +# 环境变量 +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +# 暴露端口 +EXPOSE 9090 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +CMD ["./your-binary-name"] +``` + +**使用说明**: +1. 将 `your-binary-name` 替换为 `Cargo.toml` 中的 `[[bin]]` name +2. 如果不需要 OpenSSL,可移除 `openssl-dev` +3. 修改端口和健康检查配置 + +### 3. 依赖缓存优化版(Dockerfile.cache) + +**适用场景**:本地开发,频繁构建。 + +**优点**: +- 最大化利用缓存 +- 依赖只编译一次 +- 适合迭代开发 + +```dockerfile +# ============================================ +# 依赖缓存阶段 +# ============================================ +FROM rust:1.83-alpine AS dependencies + +RUN apk add --no-cache musl-dev openssl-dev pkgconfig + +# 配置 Cargo 镜像 +RUN mkdir -p /root/.cargo && \ + echo '[source.crates-io]' > /root/.cargo/config.toml && \ + echo 'replace-with = "rsproxy-sparse"' >> /root/.cargo/config.toml && \ + echo '[source.rsproxy-sparse]' >> /root/.cargo/config.toml && \ + echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /root/.cargo/config.toml + +WORKDIR /build + +# 只复制依赖文件 +COPY Cargo.toml Cargo.lock ./ + +# 创建虚拟 main.rs,编译依赖 +RUN mkdir -p src && \ + echo "fn main() {}" > src/main.rs && \ + cargo build --release --target x86_64-unknown-linux-musl + +# 删除虚拟源码编译产物 +RUN rm -f target/x86_64-unknown-linux-musl/release/deps/your_binary_name* + +# ============================================ +# 构建阶段 +# ============================================ +FROM rust:1.83-alpine AS builder + +RUN apk add --no-cache musl-dev openssl-dev pkgconfig + +WORKDIR /build + +# 从依赖阶段复制编译缓存 +COPY --from=dependencies /root/.cargo /root/.cargo +COPY --from=dependencies /build/target target + +# 复制所有文件 +COPY . . + +# 构建项目 +RUN cargo build --release --target x86_64-unknown-linux-musl + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/your-binary-name . + +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +EXPOSE 9090 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +CMD ["./your-binary-name"] +``` + +**使用说明**: +1. 将 `your_binary_name` 替换为二进制文件名(下划线形式) +2. 适合本地 `docker build` 使用 + +### 4. 多平台构建版(Dockerfile.multiarch) + +**适用场景**:需要支持 ARM64 和 AMD64。 + +```dockerfile +# ============================================ +# 构建阶段(支持多平台) +# ============================================ +FROM --platform=$BUILDPLATFORM rust:1.83-alpine AS builder + +# 安装构建依赖 +RUN apk add --no-cache musl-dev openssl-dev pkgconfig clang + +# 配置 Cargo 镜像 +RUN mkdir -p /root/.cargo && \ + echo '[source.crates-io]' > /root/.cargo/config.toml && \ + echo 'replace-with = "rsproxy-sparse"' >> /root/.cargo/config.toml && \ + echo '[source.rsproxy-sparse]' >> /root/.cargo/config.toml && \ + echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /root/.cargo/config.toml + +# 安装目标架构支持 +ARG TARGETARCH +RUN case "$TARGETARCH" in \ + "amd64") echo "x86_64-unknown-linux-musl" > /target.txt ;; \ + "arm64") echo "aarch64-unknown-linux-musl" > /target.txt ;; \ + *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ + esac && \ + rustup target add $(cat /target.txt) + +WORKDIR /build + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src + +# 构建对应架构 +RUN cargo build --release --target $(cat /target.txt) + +# 复制二进制到统一路径 +RUN mkdir -p /app && \ + cp target/$(cat /target.txt)/release/your-binary-name /app/ + +# ============================================ +# 运行阶段 +# ============================================ +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app + +COPY --from=builder /app/your-binary-name . + +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +EXPOSE 9090 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +CMD ["./your-binary-name"] +``` + +**使用说明**: +```bash +# 构建多平台镜像 +docker buildx build --platform linux/amd64,linux/arm64 -t your-image:latest . +``` + +## 高级配置 + +### 健康检查配置 + +```dockerfile +# 存活探针(Liveness Probe) +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +# 参数说明: +# --interval=30s 每 30 秒检查一次 +# --timeout=5s 单次检查超时 5 秒 +# --start-period=10s 容器启动后 10 秒开始检查 +# --retries=3 失败 3 次后认为不健康 +``` + +### 多端点健康检查 + +```dockerfile +# 使用脚本检查多个端点 +RUN echo '#!/bin/sh' > /healthcheck.sh && \ + echo 'curl -sf http://localhost:9090/healthz || exit 1' >> /healthcheck.sh && \ + echo 'curl -sf http://localhost:9090/readyz || exit 1' >> /healthcheck.sh && \ + chmod +x /healthcheck.sh + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD ["/healthcheck.sh"] +``` + +### 环境变量配置 + +```dockerfile +# 基础环境变量 +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info +ENV RUST_BACKTRACE=1 + +# 应用配置(可在 docker run 时覆盖) +ENV CONFIG_FILE=production.yaml +ENV HTTP_PORT=9090 +ENV LOG_LEVEL=info + +# 数据库配置(敏感信息应通过运行时注入) +# ENV DATABASE_URL=postgresql://... +``` + +### 非 root 用户运行 + +```dockerfile +# 创建非特权用户 +RUN addgroup -g 1000 appuser && \ + adduser -D -u 1000 -G appuser appuser + +WORKDIR /app + +# 复制二进制文件 +COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/your-binary-name . + +# 修改所有权 +RUN chown -R appuser:appuser /app + +# 切换用户 +USER appuser + +CMD ["./your-binary-name"] +``` + +### 配置文件挂载 + +```dockerfile +# 在 Dockerfile 中创建配置目录 +RUN mkdir -p /app/configs /app/logs + +# 在 docker run 时挂载 +# docker run -v ./configs:/app/configs -v ./logs:/app/logs your-image +``` + +## 构建命令示例 + +### 本地构建 + +```bash +# 基础构建 +docker build -t your-service:latest . + +# 指定 Dockerfile +docker build -f Dockerfile.ci -t your-service:latest . + +# 传递构建参数 +docker build --build-arg GIT_TAG=1.0.0 -t your-service:1.0.0 . + +# 查看构建过程 +docker build --progress=plain -t your-service:latest . +``` + +### 多平台构建 + +```bash +# 创建 buildx builder +docker buildx create --name mybuilder --use + +# 构建并推送 +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t registry.example.com/your-service:latest \ + --push \ + . +``` + +### 优化构建速度 + +```bash +# 使用 BuildKit 缓存 +export DOCKER_BUILDKIT=1 + +# 使用外部缓存 +docker build \ + --cache-from=type=registry,ref=your-service:buildcache \ + --cache-to=type=registry,ref=your-service:buildcache,mode=max \ + -t your-service:latest \ + . +``` + +## 镜像大小优化 + +### 1. 使用 Alpine 基础镜像 + +```dockerfile +# Alpine 镜像体积小(~5MB) +FROM alpine:3.21 +``` + +### 2. 静态链接(musl) + +```dockerfile +# 构建时使用 musl 目标,生成静态链接二进制 +RUN cargo build --release --target x86_64-unknown-linux-musl +``` + +### 3. Strip 二进制文件 + +```toml +# Cargo.toml +[profile.release] +strip = true # 去除符号表 +``` + +或在 Dockerfile 中: + +```dockerfile +# 手动 strip +RUN strip target/x86_64-unknown-linux-musl/release/your-binary-name +``` + +### 4. 最小化层数 + +```dockerfile +# 合并多个 RUN 命令 +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + addgroup -g 1000 appuser +``` + +### 5. 使用 .dockerignore + +``` +# .dockerignore +target/ +.git/ +.gitignore +*.md +tests/ +benches/ +``` + +## 安全建议 + +### 1. 使用特定版本标签 + +```dockerfile +# 不推荐:使用 latest +FROM alpine:latest + +# 推荐:使用特定版本 +FROM alpine:3.21 +``` + +### 2. 定期更新依赖 + +```bash +# 更新 Cargo 依赖 +cargo update + +# 检查安全漏洞 +cargo audit +``` + +### 3. 最小权限原则 + +```dockerfile +# 使用非 root 用户 +USER appuser + +# 只读文件系统(根据需要) +# docker run --read-only your-service +``` + +### 4. 不在镜像中包含敏感信息 + +```dockerfile +# 错误示例 +# ENV DATABASE_PASSWORD=secret123 + +# 正确做法:通过运行时注入 +# docker run -e DATABASE_PASSWORD=secret123 your-service +``` + +## 常见问题 + +### Q1: 镜像体积过大 + +**原因**: +- 使用 `rust:latest` 作为运行镜像(>1GB) +- 没有使用多阶段构建 +- 包含 debug 符号 + +**解决方案**: +```dockerfile +# 使用多阶段构建 + Alpine +FROM rust:1.83-alpine AS builder +# ... 构建步骤 ... + +FROM alpine:3.21 # 最终镜像只有 ~20MB +COPY --from=builder /build/target/.../your-binary . +``` + +### Q2: openssl 链接错误 + +**错误信息**: +``` +error: linking with `cc` failed +ld: library not found for -lssl +``` + +**解决方案**: +```dockerfile +# 方案 1:安装 openssl-dev +RUN apk add --no-cache openssl-dev + +# 方案 2:使用 rustls 替代 openssl +# 在 Cargo.toml 中使用 rustls feature +``` + +### Q3: 交叉编译失败 + +**错误信息**: +``` +error: linker `aarch64-linux-musl-gcc` not found +``` + +**解决方案**: +```bash +# 安装 musl 交叉编译工具链 +rustup target add aarch64-unknown-linux-musl + +# 或使用 cargo-zigbuild +cargo install cargo-zigbuild +cargo zigbuild --target aarch64-unknown-linux-musl +``` + +### Q4: 容器启动失败 + +**检查步骤**: +```bash +# 查看日志 +docker logs your-container + +# 进入容器调试 +docker run -it --entrypoint /bin/sh your-image + +# 检查二进制是否可执行 +docker run your-image ls -lh /app/ +``` + +## 参考资源 + +- [Rust 官方 Docker 指南](https://docs.docker.com/language/rust/) +- [Alpine Linux 包搜索](https://pkgs.alpinelinux.org/packages) +- [Docker 多阶段构建文档](https://docs.docker.com/build/building/multi-stage/) +- [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild) diff --git a/skill/gitea/rust-quick-reference.md b/skill/gitea/rust-quick-reference.md new file mode 100644 index 0000000..6d4f9bc --- /dev/null +++ b/skill/gitea/rust-quick-reference.md @@ -0,0 +1,443 @@ +# Rust 项目 Gitea CI/CD 快速参考 + +快速设置 Rust 项目的 CI/CD 流程。 + +## 最小化配置(3 步) + +### 1. 创建 Dockerfile.ci + +在项目根目录创建 `Dockerfile.ci`: + +```dockerfile +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata curl && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /app +COPY your-binary-name . + +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +EXPOSE 9090 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -sf http://localhost:9090/healthz || exit 1 + +CMD ["./your-binary-name"] +``` + +**修改项**: +- `your-binary-name` → 你的二进制文件名(通常和 `Cargo.toml` 中的 `name` 一致) +- `9090` → 你的服务端口 + +### 2. 创建 workflow 文件 + +创建 `.gitea/workflows/your-service.yml`: + +```yaml +name: Your Service - Build & Publish + +on: + push: + paths: + - '**' # 修改为实际监听路径 + tags: + - 'v*' # 修改为实际 tag 格式 + +env: + SERVICE_PREFIX: your-service # 修改为实际服务名 + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUSTUP_DIST_SERVER: https://rsproxy.cn + RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup + +jobs: + build-and-publish: + runs-on: ubuntu-latest # 修改为你的 Runner 标签 + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + source "$HOME/.cargo/env" + rustup target add x86_64-unknown-linux-musl + + - name: Build + run: | + cargo build --release --target x86_64-unknown-linux-musl + cp target/x86_64-unknown-linux-musl/release/your-binary-name . + + - name: Docker Login + uses: docker/login-action@v3 + with: + registry: ${{ github.server_url }} + username: ${{ vars.REGISTRY_USERNAME }} + password: ${{ secrets.RELEASE_TOKEN }} + + - name: Build & Push Image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile.ci + push: true + tags: | + ${{ github.server_url }}/${{ github.repository }}:latest + ${{ github.server_url }}/${{ github.repository }}:${{ github.sha }} +``` + +**修改项**: +- `your-service` → 你的服务名 +- `your-binary-name` → 你的二进制文件名 +- `ubuntu-latest` → 你的 Runner 标签 +- `paths` → 触发构建的路径 +- `tags` → 触发构建的 tag 格式 + +### 3. 推送代码并打 tag + +```bash +git add .gitea Dockerfile.ci +git commit -m "ci: 添加 CI/CD 配置" +git push + +# 触发构建 +git tag v0.1.0 +git push origin v0.1.0 +``` + +## 进阶配置 + +### 添加交叉编译支持 + +如果你的 Runner 是 ARM64,但需要构建 x86_64 镜像: + +```yaml +- name: Install Zig and cargo-zigbuild + run: | + wget -q https://ziglang.org/download/0.13.0/zig-linux-aarch64-0.13.0.tar.xz + tar -xf zig-linux-aarch64-0.13.0.tar.xz + mv zig-linux-aarch64-0.13.0 /opt/zig + echo "/opt/zig" >> $GITHUB_PATH + cargo install cargo-zigbuild + +- name: Build + run: | + cargo zigbuild --release --target x86_64-unknown-linux-musl +``` + +### 添加 Rust 缓存 + +```yaml +- name: Cache Rust dependencies + uses: https://github.com/Swatinem/rust-cache@v2 + with: + workspaces: . -> target + cache-targets: true +``` + +### 添加版本号注入 + +**1. 创建 `build.rs`**: + +```rust +fn main() { + if let Ok(git_tag) = std::env::var("GIT_TAG") { + println!("cargo:rustc-env=GIT_TAG={}", git_tag); + } + println!("cargo:rerun-if-env-changed=GIT_TAG"); +} +``` + +**2. 在 workflow 中设置环境变量**: + +```yaml +- name: Build + env: + GIT_TAG: ${{ github.ref_name }} + run: cargo build --release +``` + +**3. 在代码中使用**: + +```rust +let version = option_env!("GIT_TAG").unwrap_or("dev"); +println!("Version: {}", version); +``` + +### 添加 Release 创建 + +```yaml +release: + name: Create Release + runs-on: ubuntu-latest + needs: build-and-publish + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Create Release + run: | + curl -X POST "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" \ + -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"${{ github.ref_name }}\", + \"name\": \"Release ${{ github.ref_name }}\", + \"body\": \"自动构建的 Release\" + }" +``` + +## 健康检查端点实现 + +如果你的服务还没有健康检查端点,快速添加: + +### 使用 Axum + +```rust +use axum::{Router, routing::get}; + +#[tokio::main] +async fn main() { + let app = Router::new() + .route("/healthz", get(|| async { "OK" })); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:9090") + .await + .unwrap(); + + axum::serve(listener, app).await.unwrap(); +} +``` + +### 使用 Actix-web + +```rust +use actix_web::{web, App, HttpServer, HttpResponse}; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .route("/healthz", web::get().to(|| async { HttpResponse::Ok().body("OK") })) + }) + .bind("0.0.0.0:9090")? + .run() + .await +} +``` + +### 使用 Rocket + +```rust +#[macro_use] extern crate rocket; + +#[get("/healthz")] +fn healthz() -> &'static str { + "OK" +} + +#[launch] +fn rocket() -> _ { + rocket::build().mount("/", routes![healthz]) +} +``` + +## Cargo.toml 优化配置 + +```toml +[profile.release] +opt-level = 3 # 最高优化级别 +lto = true # 链接时优化 +codegen-units = 1 # 单个代码生成单元 +strip = true # 去除符号表 +panic = 'abort' # panic 时直接退出 +``` + +## .dockerignore 示例 + +创建 `.dockerignore` 减小构建上下文: + +``` +target/ +.git/ +.gitignore +*.md +tests/ +benches/ +examples/ +.github/ +.gitea/workflows/ +Dockerfile* +docker-compose.yml +``` + +## 环境变量配置 + +### 在 workflow 中 + +```yaml +env: + RUST_LOG: info # 日志级别 + CONFIG_FILE: production.yaml # 配置文件 + DATABASE_URL: ${{ secrets.DATABASE_URL }} # 数据库连接 +``` + +### 在 Dockerfile 中 + +```dockerfile +ENV RUST_LOG=info +ENV CONFIG_FILE=production.yaml +``` + +### 在运行时 + +```bash +docker run -e RUST_LOG=debug -e CONFIG_FILE=custom.yaml your-image +``` + +## 常见 Runner 标签 + +| 标签 | 说明 | +|------|------| +| `ubuntu-latest` | Ubuntu 最新版本 | +| `darwin-arm64` | macOS ARM64 | +| `darwin-amd64` | macOS x86_64 | +| `linux-amd64` | Linux x86_64 | +| `linux-arm64` | Linux ARM64 | + +查看可用 Runner: + +```bash +/gitea-list-runners +``` + +## 触发条件参考 + +### 推送到特定分支 + +```yaml +on: + push: + branches: + - main + - develop +``` + +### 推送特定路径 + +```yaml +on: + push: + paths: + - 'src/**' + - 'Cargo.toml' + - 'Cargo.lock' +``` + +### 特定 tag 格式 + +```yaml +on: + push: + tags: + - 'v*' # v1.0.0, v2.1.3 + - 'device-rs-*' # device-rs-1.0.0 +``` + +### Pull Request + +```yaml +on: + pull_request: + branches: + - main +``` + +### 定时触发 + +```yaml +on: + schedule: + - cron: '0 2 * * *' # 每天凌晨 2 点 +``` + +## 调试技巧 + +### 查看 workflow 日志 + +在 Gitea 仓库页面: +1. 点击 "Actions" +2. 选择对应的 workflow run +3. 查看每个 step 的日志 + +### 本地测试构建 + +```bash +# 测试 Rust 构建 +cargo build --release --target x86_64-unknown-linux-musl + +# 测试 Docker 构建 +docker build -f Dockerfile.ci -t test:latest . + +# 测试容器运行 +docker run -p 9090:9090 test:latest +``` + +### 使用 act 本地运行 workflow + +```bash +# 安装 act(如果使用 Gitea Actions) +# 注意:act 主要支持 GitHub Actions,Gitea Actions 可能有兼容性问题 + +# 列出可用的 actions +act -l + +# 运行特定 job +act -j build-and-publish +``` + +## 完整配置示例 + +参考 BMS 项目的 device-rs: + +- Workflow: [device-rs.yml](https://git.voson.top/tianchu/bms/src/branch/main/.gitea/workflows/device-rs.yml) +- Dockerfile: [Dockerfile.ci](https://git.voson.top/tianchu/bms/src/branch/main/device-rs/Dockerfile.ci) +- 项目结构: [device-rs/](https://git.voson.top/tianchu/bms/src/branch/main/device-rs) + +## 故障排查清单 + +### 构建失败 + +- [ ] 检查 Rust 版本是否满足要求 +- [ ] 检查依赖是否正确(`Cargo.lock` 是否提交) +- [ ] 检查 musl 目标是否安装(`rustup target list --installed`) +- [ ] 查看完整构建日志 + +### Docker 推送失败 + +- [ ] 检查 Docker Registry 地址是否正确 +- [ ] 检查 `REGISTRY_USERNAME` 变量是否设置 +- [ ] 检查 `RELEASE_TOKEN` secret 是否正确 +- [ ] 检查 Token 权限(需要 packages write 权限) + +### 容器启动失败 + +- [ ] 检查二进制文件名是否正确 +- [ ] 检查端口是否正确 +- [ ] 检查健康检查端点是否存在 +- [ ] 查看容器日志(`docker logs `) + +### Runner 无法连接 + +- [ ] 检查 Runner 是否在运行(`/gitea-list-runners`) +- [ ] 检查 Runner labels 是否匹配 workflow 中的 `runs-on` +- [ ] 检查网络连接 +- [ ] 查看 Runner 日志(`~/.config/gitea/runners//*.log`) + +## 更多资源 + +- [完整 Workflow 模板](./workflow-templates/rust-backend.md) +- [Dockerfile 模板详解](./dockerfile-templates/rust-service.md) +- [Gitea Actions 文档](https://docs.gitea.com/usage/actions/quickstart) +- [Rust CI 最佳实践](https://doc.rust-lang.org/cargo/guide/continuous-integration.html) diff --git a/skill/gitea/workflow-templates/rust-backend.md b/skill/gitea/workflow-templates/rust-backend.md index 74792b4..32bb44a 100644 --- a/skill/gitea/workflow-templates/rust-backend.md +++ b/skill/gitea/workflow-templates/rust-backend.md @@ -4,59 +4,113 @@ ## 适用场景 -- Rust HTTP API 服务(Axum、Actix-Web、Warp) -- gRPC 微服务 +- Rust HTTP API 服务(Axum、Actix-web、Rocket 等) +- 异步后端服务(Tokio、async-std) +- MQTT/IoT 设备服务 - CLI 工具 - 需要构建 Docker 镜像的 Rust 项目 -- 跨平台编译需求 ## 环境要求 | 依赖 | Runner 要求 | |------|------------| -| Rust 1.75+ | Runner 主机已安装 | +| Rust 1.75+ | 通过脚本自动安装 | | Docker | Runner 主机已安装 | -| Cross 编译工具 | 可选,用于多平台构建 | +| Zig(可选) | 用于交叉编译,脚本自动安装 | + +## 特性说明 + +### 交叉编译支持 + +- **ARM64 主机 → x86_64 目标**:使用 Zig + cargo-zigbuild +- **musl 静态链接**:生成独立可执行文件,适配 Alpine 容器 +- **缓存优化**:支持 Rust 依赖缓存,加速构建 + +### 镜像加速 + +使用 rsproxy.cn 镜像加速: +- rustup 安装源 +- crates.io 索引 +- cargo 依赖下载 + +### 构建优化 + +```yaml +env: + CARGO_INCREMENTAL: 0 # 禁用增量编译(CI 环境) + CARGO_NET_RETRY: 10 # 网络重试 10 次 + CARGO_TERM_COLOR: always # 保持彩色输出 + RUST_BACKTRACE: short # 简短的错误栈 +``` ## Workflow 骨架模板 +### 完整版本(推荐用于生产项目) + +参考实际项目 [api-hub](https://github.com/example/api-hub) 的最佳实践,包含完整的测试、构建、健康检查和发布流程。 + ```yaml -name: API Hub CI/CD +name: Rust Backend CI/CD on: push: - branches: [main] - tags: ['v*'] + branches: [main, master] pull_request: - branches: [main] + branches: [main, master] + +# 并发控制:同一分支/标签的重复构建会取消旧的运行 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: + # Rust CI 构建优化 + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always - SERVICE_NAME: api-hub + RUST_BACKTRACE: short + # 使用中国 Rust 镜像加速 + RUSTUP_DIST_SERVER: https://rsproxy.cn + RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup jobs: test: name: 测试 - runs-on: ubuntu-latest + runs-on: ubuntu-latest # 修改为你的 Runner 标签 + env: + RUNNER_TOOL_CACHE: /toolcache steps: - uses: actions/checkout@v4 - - name: 安装 Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - components: rustfmt, clippy + - name: 配置 Cargo 镜像 + run: | + mkdir -p ~/.cargo + cat >> ~/.cargo/config.toml << 'EOF' + [source.crates-io] + replace-with = 'rsproxy-sparse' + [source.rsproxy-sparse] + registry = "sparse+https://rsproxy.cn/index/" + [registries.rsproxy] + index = "https://rsproxy.cn/crates.io-index" + [net] + git-fetch-with-cli = true + EOF - - name: 缓存 Cargo 依赖 - uses: actions/cache@v4 + - name: 安装 Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --component rustfmt,clippy + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + # rust-cache 需要 act_runner 正确配置缓存服务器 + # 如果出现 "getCacheEntry failed: connect ETIMEDOUT" 错误, + # 需要在服务器端修改 act_runner 的 config.yaml 中的 cache 配置 + - name: 缓存 Rust 依赖 + uses: https://github.com/Swatinem/rust-cache@v2 + continue-on-error: true with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + cache-targets: true + cache-on-failure: true + cache-all-crates: true - name: 检查代码格式 run: cargo fmt -- --check @@ -73,18 +127,28 @@ jobs: build-docker: name: 构建 Docker 镜像 needs: test - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) runs-on: ubuntu-latest + outputs: + registry: ${{ steps.vars.outputs.registry }} + image_repo: ${{ steps.vars.outputs.image_repo }} steps: - uses: actions/checkout@v4 + - name: 设置变量 + id: vars + run: | + registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3) + echo "registry=${registry}" >> $GITHUB_OUTPUT + echo "REGISTRY=${registry}" >> $GITHUB_ENV + echo "image_repo=${{ github.event.repository.name }}" >> $GITHUB_OUTPUT + - name: 设置 Docker Buildx uses: docker/setup-buildx-action@v3 - - name: 登录容器仓库 + - name: 登录 Gitea 容器仓库 uses: docker/login-action@v3 with: - registry: ${{ env.registry }} + registry: ${{ steps.vars.outputs.registry }} username: ${{ github.actor }} password: ${{ secrets.REGISTRY_TOKEN }} @@ -92,7 +156,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.registry }}/${{ github.repository }}/${{ env.SERVICE_NAME }} + images: ${{ env.REGISTRY }}/${{ github.repository }} tags: | type=ref,event=branch type=semver,pattern={{version}} @@ -100,83 +164,119 @@ jobs: type=sha,prefix=,suffix=,format=short - name: 构建并推送镜像 - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache,mode=max health-check: name: 健康检查 needs: build-docker - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: 设置变量 + id: vars + run: | + registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3) + echo "REGISTRY=${registry}" >> $GITHUB_ENV + + - name: 登录 Gitea 容器仓库 + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} - name: 运行健康检查测试 run: | # 启动服务 - docker run -d --name api-hub-test -p 8080:8080 ${{ env.registry }}/${{ github.repository }}/${{ env.SERVICE_NAME }}:main + docker run -d --name ${{ github.event.repository.name }}-test -p 8080:8080 ${{ env.REGISTRY }}/${{ github.repository }}:main # 等待服务启动 - sleep 10 + sleep 5 - # 健康检查 + # 健康检查 - 根据实际端点修改 echo "检查健康端点..." curl -f http://localhost:8080/health || exit 1 - # 就绪检查 + # 就绪检查 - 根据实际端点修改 echo "检查就绪端点..." curl -f http://localhost:8080/ready || exit 1 - # 存活检查 + # 存活检查 - 根据实际端点修改 echo "检查存活端点..." curl -f http://localhost:8080/live || exit 1 echo "所有健康检查通过!" # 清理 - docker stop api-hub-test - docker rm api-hub-test + docker stop ${{ github.event.repository.name }}-test + docker rm ${{ github.event.repository.name }}-test release: name: 创建发布 needs: test if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest + env: + RUNNER_TOOL_CACHE: /toolcache steps: - uses: actions/checkout@v4 + - name: 配置 Cargo 镜像 + run: | + mkdir -p ~/.cargo + cat >> ~/.cargo/config.toml << 'EOF' + [source.crates-io] + replace-with = 'rsproxy-sparse' + [source.rsproxy-sparse] + registry = "sparse+https://rsproxy.cn/index/" + [registries.rsproxy] + index = "https://rsproxy.cn/crates.io-index" + [net] + git-fetch-with-cli = true + EOF + - name: 安装 Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 + run: | + curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: 缓存 Rust 依赖 + uses: https://github.com/Swatinem/rust-cache@v2 + continue-on-error: true with: - toolchain: stable - targets: x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl + cache-targets: true + cache-on-failure: true + cache-all-crates: true - name: 构建 Release 二进制文件 run: | + SERVICE_NAME="${{ github.event.repository.name }}" + # Linux GNU 版本 cargo build --release --target x86_64-unknown-linux-gnu - strip target/x86_64-unknown-linux-gnu/release/${{ env.SERVICE_NAME }} - tar czf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \ - -C target/x86_64-unknown-linux-gnu/release ${{ env.SERVICE_NAME }} + strip target/x86_64-unknown-linux-gnu/release/${SERVICE_NAME} + tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \ + -C target/x86_64-unknown-linux-gnu/release ${SERVICE_NAME} # Linux MUSL 版本 (静态链接) cargo build --release --target x86_64-unknown-linux-musl - strip target/x86_64-unknown-linux-musl/release/${{ env.SERVICE_NAME }} - tar czf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \ - -C target/x86_64-unknown-linux-musl/release ${{ env.SERVICE_NAME }} + strip target/x86_64-unknown-linux-musl/release/${SERVICE_NAME} + tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \ + -C target/x86_64-unknown-linux-musl/release ${SERVICE_NAME} - name: 生成 Changelog id: changelog run: | + SERVICE_NAME="${{ github.event.repository.name }}" echo "CHANGELOG<> $GITHUB_OUTPUT if [ -f CHANGELOG.md ]; then - # 提取当前版本的变更日志 awk '/^## \[${{ github.ref_name }}\]/{flag=1;next} /^## \[/{flag=0} flag' CHANGELOG.md || echo "查看完整变更日志:CHANGELOG.md" else echo "## ${{ github.ref_name }}" @@ -185,29 +285,19 @@ jobs: echo "- 发布版本 ${{ github.ref_name }}" echo "" echo "### 下载" - echo "- \`${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本" - echo "- \`${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本(推荐用于容器)" - echo "" - echo "### 部署" - echo "\`\`\`bash" - echo "# 下载并解压" - echo "wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz" - echo "tar xzf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz" - echo "" - echo "# 运行服务" - echo "./${{ env.SERVICE_NAME }}" - echo "\`\`\`" + echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本" + echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本(推荐用于容器)" fi EOF - name: 创建 Release uses: softprops/action-gh-release@v1 with: - name: ${{ env.SERVICE_NAME }} ${{ github.ref_name }} + name: ${{ github.event.repository.name }} ${{ github.ref_name }} body: ${{ steps.changelog.outputs.CHANGELOG }} files: | - ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz - ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz + ${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz + ${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz draft: false prerelease: ${{ contains(github.ref_name, '-') }} @@ -216,6 +306,7 @@ jobs: needs: [test, build-docker, release] if: always() runs-on: ubuntu-latest + continue-on-error: true steps: - name: 发送构建通知 run: | @@ -244,15 +335,300 @@ jobs: }' || true ``` ---- +### 多服务版本(单仓库多 Rust 服务) + +如果你的单仓库包含多个 Rust 服务,使用以下模板: + +```yaml +name: Rust Multi-Service CI/CD + +on: + push: + paths: + - 'your-service/**' # 修改为实际目录 + - '.gitea/workflows/your-service.yml' + tags: + - 'your-service-*' # 修改为实际 tag 前缀 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + SERVICE_PREFIX: your-service # 修改为实际服务名 + SERVICE_DIR: your-service # 修改为实际目录名 + # Rust CI 构建优化 + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + CARGO_TERM_COLOR: always + RUST_BACKTRACE: short + # 使用中国 Rust 镜像加速 + RUSTUP_DIST_SERVER: https://rsproxy.cn + RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup + +jobs: + test: + name: 测试 + runs-on: ubuntu-latest + env: + RUNNER_TOOL_CACHE: /toolcache + steps: + - uses: actions/checkout@v4 + + - name: 配置 Cargo 镜像 + run: | + mkdir -p ~/.cargo + cat >> ~/.cargo/config.toml << 'EOF' + [source.crates-io] + replace-with = 'rsproxy-sparse' + [source.rsproxy-sparse] + registry = "sparse+https://rsproxy.cn/index/" + [registries.rsproxy] + index = "https://rsproxy.cn/crates.io-index" + [net] + git-fetch-with-cli = true + EOF + + - name: 安装 Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --component rustfmt,clippy + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: 缓存 Rust 依赖 + uses: https://github.com/Swatinem/rust-cache@v2 + continue-on-error: true + with: + workspaces: ${{ env.SERVICE_DIR }} -> target + cache-targets: true + cache-on-failure: true + cache-all-crates: true + + - name: 检查代码格式 + working-directory: ${{ env.SERVICE_DIR }} + run: cargo fmt -- --check + + - name: 运行 Clippy 检查 + working-directory: ${{ env.SERVICE_DIR }} + run: cargo clippy -- -D warnings + + - name: 运行测试 + working-directory: ${{ env.SERVICE_DIR }} + run: cargo test --verbose + + - name: 构建 + working-directory: ${{ env.SERVICE_DIR }} + run: cargo build --release --verbose + + build-docker: + name: 构建 Docker 镜像 + needs: test + runs-on: ubuntu-latest + outputs: + registry: ${{ steps.vars.outputs.registry }} + image_repo: ${{ steps.vars.outputs.image_repo }} + git_tag: ${{ steps.vars.outputs.git_tag }} + steps: + - uses: actions/checkout@v4 + + - name: 设置变量 + id: vars + run: | + registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3) + git_tag=$(git describe --tags --abbrev=0 --always) + echo "registry=${registry}" >> $GITHUB_OUTPUT + echo "REGISTRY=${registry}" >> $GITHUB_ENV + echo "image_repo=${{ github.event.repository.name }}" >> $GITHUB_OUTPUT + echo "git_tag=${git_tag}" >> $GITHUB_OUTPUT + + - name: 设置 Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: 登录 Gitea 容器仓库 + uses: docker/login-action@v3 + with: + registry: ${{ env.registry }} + username: ${{ github.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: 构建并推送镜像 + uses: docker/build-push-action@v6 + with: + context: ./${{ env.SERVICE_DIR }} + file: ./${{ env.SERVICE_DIR }}/Dockerfile + push: true + tags: | + ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.SERVICE_PREFIX }}-latest + ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.git_tag }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache-${{ env.SERVICE_PREFIX }} + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache-${{ env.SERVICE_PREFIX }},mode=max + + health-check: + name: 健康检查 + needs: build-docker + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + runs-on: ubuntu-latest + steps: + - name: 设置变量 + run: | + registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3) + echo "REGISTRY=${registry}" >> $GITHUB_ENV + + - name: 登录 Gitea 容器仓库 + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: 运行健康检查测试 + run: | + # 启动服务 - 根据实际端口和端点修改 + docker run -d --name ${{ env.SERVICE_PREFIX }}-test -p 8080:8080 ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.SERVICE_PREFIX }}-latest + + sleep 5 + + curl -f http://localhost:8080/health || exit 1 + curl -f http://localhost:8080/ready || exit 1 + curl -f http://localhost:8080/live || exit 1 + + echo "健康检查通过!" + + docker stop ${{ env.SERVICE_PREFIX }}-test + docker rm ${{ env.SERVICE_PREFIX }}-test + + release: + name: 创建发布 + needs: test + if: startsWith(github.ref, 'refs/tags/${{ env.SERVICE_PREFIX }}-') + runs-on: ubuntu-latest + env: + RUNNER_TOOL_CACHE: /toolcache + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 配置 Cargo 镜像 + run: | + mkdir -p ~/.cargo + cat >> ~/.cargo/config.toml << 'EOF' + [source.crates-io] + replace-with = 'rsproxy-sparse' + [source.rsproxy-sparse] + registry = "sparse+https://rsproxy.cn/index/" + [registries.rsproxy] + index = "https://rsproxy.cn/crates.io-index" + [net] + git-fetch-with-cli = true + EOF + + - name: 安装 Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: 缓存 Rust 依赖 + uses: https://github.com/Swatinem/rust-cache@v2 + continue-on-error: true + with: + workspaces: ${{ env.SERVICE_DIR }} -> target + cache-targets: true + cache-on-failure: true + cache-all-crates: true + + - name: 构建 Release 二进制文件 + run: | + SERVICE_NAME="${{ github.event.repository.name }}" + + # Linux GNU 版本 + cargo build --release --target x86_64-unknown-linux-gnu --manifest-path ${{ env.SERVICE_DIR }}/Cargo.toml + strip target/x86_64-unknown-linux-gnu/release/${SERVICE_NAME} + tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \ + -C target/x86_64-unknown-linux-gnu/release ${SERVICE_NAME} + + # Linux MUSL 版本 + cargo build --release --target x86_64-unknown-linux-musl --manifest-path ${{ env.SERVICE_DIR }}/Cargo.toml + strip target/x86_64-unknown-linux-musl/release/${SERVICE_NAME} + tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \ + -C target/x86_64-unknown-linux-musl/release ${SERVICE_NAME} + + - name: 生成 Changelog + id: changelog + run: | + SERVICE_NAME="${{ github.event.repository.name }}" + + # 从 tag 中提取版本号 + version="${{ github.ref_name }}" + version="${version#${{ env.SERVICE_PREFIX }}-}" + + echo "CHANGELOG<> $GITHUB_OUTPUT + if [ -f ${{ env.SERVICE_DIR }}/CHANGELOG.md ]; then + awk "/^## \\[${version}\\]/{flag=1;next} /^## \\[/{flag=0} flag" ${{ env.SERVICE_DIR }}/CHANGELOG.md || echo "查看完整变更日志" + else + echo "## ${{ github.ref_name }}" + echo "" + echo "### 下载" + echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本" + echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本" + fi + EOF + + - name: 创建 Release + uses: softprops/action-gh-release@v1 + with: + name: ${{ github.event.repository.name }} ${{ github.ref_name }} + body: ${{ steps.changelog.outputs.CHANGELOG }} + files: | + ${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz + ${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz + draft: false + prerelease: ${{ contains(github.ref_name, '-') }} + + notify: + name: 发送通知 + needs: [test, build-docker, release] + if: always() + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: 发送构建通知 + run: | + TEST_STATUS="${{ needs.test.result }}" + BUILD_STATUS="${{ needs.build-docker.result }}" + RELEASE_STATUS="${{ needs.release.result }}" + + if [[ "$TEST_STATUS" == "success" && ("$BUILD_STATUS" == "success" || "$BUILD_STATUS" == "skipped") && ("$RELEASE_STATUS" == "success" || "$RELEASE_STATUS" == "skipped") ]]; then + STATUS="成功" + else + STATUS="失败" + fi + + curl -X POST ${{ vars.WEBHOOK_URL }} \ + -H "Content-Type: application/json" \ + -d '{ + "repository": "${{ github.repository }}", + "ref": "${{ github.ref }}", + "commit": "${{ github.sha }}", + "status": "'"$STATUS"'", + "workflow": "${{ github.workflow }}", + "actor": "${{ github.actor }}", + "test_result": "'"$TEST_STATUS"'", + "build_result": "'"$BUILD_STATUS"'", + "release_result": "'"$RELEASE_STATUS"'" + }' || true +``` ## Dockerfile 模板 -### 多阶段构建(推荐) +### Dockerfile(推荐用于 CI/CD) + +使用多阶段构建,优化镜像大小和构建缓存: ```dockerfile # 构建阶段 -FROM rust:1.75-slim as builder +FROM rust:1.84-slim AS builder + +WORKDIR /app # 安装构建依赖 RUN apt-get update && apt-get install -y \ @@ -260,20 +636,104 @@ RUN apt-get update && apt-get install -y \ libssl-dev \ && rm -rf /var/lib/apt/lists/* -WORKDIR /app - -# 复制依赖文件,利用 Docker 缓存 +# 先复制 Cargo 文件以缓存依赖 COPY Cargo.toml Cargo.lock ./ -# 创建虚拟源文件用于编译依赖 -RUN mkdir src && echo "fn main() {}" > src/main.rs +# 创建虚拟 main.rs 来缓存依赖编译 +RUN mkdir src && echo 'fn main() {}' > src/main.rs RUN cargo build --release && rm -rf src -# 复制实际源码并重新编译 +# 复制源代码 COPY src ./src + +# 构建应用(使用 touch 确保重新编译) RUN touch src/main.rs && cargo build --release -# 运行阶段 +# 运行阶段 - 使用最小镜像 +FROM debian:bookworm-slim + +WORKDIR /app + +# 安装运行依赖 +RUN apt-get update && apt-get install -y \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 复制二进制文件(根据实际项目名称修改) +COPY --from=builder /app/target/release/your-app-name /app/your-app-name + +# 创建日志目录(如果有日志输出) +RUN mkdir -p /app/logs + +# 暴露端口(根据实际服务修改) +EXPOSE 8080 + +# 健康检查(根据实际端点修改) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# 运行应用 +CMD ["/app/your-app-name"] +``` + +### Dockerfile.musl(静态链接版本) + +使用 musl 进行静态链接,生成独立可执行文件: + +```dockerfile +# 构建阶段 +FROM rust:1.84-alpine AS builder + +# 安装构建依赖 +RUN apk add --no-cache musl-dev openssl-dev pkgconfig + +# 配置 Cargo 镜像加速(可选) +RUN mkdir -p /root/.cargo && \ + echo '[source.crates-io]' > /root/.cargo/config.toml && \ + echo 'replace-with = "rsproxy-sparse"' >> /root/.cargo/config.toml && \ + echo '[source.rsproxy-sparse]' >> /root/.cargo/config.toml && \ + echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /root/.cargo/config.toml + +WORKDIR /build + +# 先复制 Cargo 文件以缓存依赖 +COPY Cargo.toml Cargo.lock ./ + +# 创建虚拟 main.rs 来缓存依赖编译 +RUN mkdir src && echo 'fn main() {}' > src/main.rs +RUN cargo build --release --target x86_64-unknown-linux-musl && rm -rf src + +# 复制源代码并构建 +COPY src ./src +RUN touch src/main.rs && cargo build --release --target x86_64-unknown-linux-musl + +# 运行阶段 - 使用 scratch 或 alpine +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata curl + +WORKDIR /app + +# 从构建阶段复制二进制文件 +COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/your-app-name . + +ENV TZ=Asia/Shanghai +ENV RUST_LOG=info + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +CMD ["./your-app-name"] +``` + +### Dockerfile.ci(使用 CI 预编译二进制) + +适用于 CI 中已经构建好二进制文件的场景: + +```dockerfile FROM debian:bookworm-slim # 安装运行时依赖 @@ -284,258 +744,380 @@ RUN apt-get update && apt-get install -y \ WORKDIR /app -# 复制二进制文件 -COPY --from=builder /app/target/release/api-hub /app/api-hub +# 复制 CI 构建好的二进制文件 +COPY your-app-name . -# 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl --fail http://localhost:8080/health || exit 1 +# 创建日志目录 +RUN mkdir -p /app/logs + +ENV RUST_LOG=info EXPOSE 8080 -ENTRYPOINT ["/app/api-hub"] +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +CMD ["./your-app-name"] ``` -### 简单构建 +## 模板选择指南 -```dockerfile -FROM alpine:latest +### 单服务仓库 vs 多服务仓库 -# 安装运行时依赖 -RUN apk add --no-cache ca-certificates curl tzdata +| 场景 | 推荐模板 | 说明 | +|------|---------|------| +| 单 Rust 项目 | **完整版本** | 简单直接,基于分支触发 | +| 单仓库多服务 | **多服务版本** | 按路径和 tag 前缀触发,避免无关构建 | +| 微服务架构 | **多服务版本** | 每个服务独立 workflow | -# 设置时区 -ENV TZ=Asia/Shanghai +### Workflow 阶段说明 -WORKDIR /app - -# 复制预编译的二进制文件 -COPY api-hub /app/api-hub -RUN chmod +x /app/api-hub - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl --fail http://localhost:8080/health || exit 1 - -EXPOSE 8080 - -ENTRYPOINT ["/app/api-hub"] +``` +┌─────────┐ ┌──────────────┐ ┌─────────────┐ +│ Test │────▶│ Build Docker │────▶│ Health Check│ +│(fmt, │ │ │ │ │ +│ clippy, │ └──────────────┘ └─────────────┘ +│ test) │ +└─────────┘ + │ + ▼ +┌─────────┐ ┌─────────┐ +│ Release │────▶│ Notify │ +│(tag v*) │ │(always) │ +└─────────┘ └─────────┘ ``` ---- +| 阶段 | 触发条件 | 说明 | +|------|---------|------| +| **Test** | 每次 push/PR | 代码格式、Clippy 检查、单元测试、编译 | +| **Build Docker** | Test 成功后 | 构建并推送 Docker 镜像到仓库 | +| **Health Check** | main 分支 push | 启动容器并验证健康端点 | +| **Release** | Tag 以 `v` 开头 | 构建双平台二进制并创建 Release | +| **Notify** | 始终执行 | 发送构建结果通知 | -## 构建参数说明 +## 自定义配置说明 -### 编译优化 +### 必须修改的变量 -```bash -# 交叉编译目标 -x86_64-unknown-linux-gnu # 标准 Linux (glibc) -x86_64-unknown-linux-musl # 静态链接 Linux +#### 完整版本(单服务) -# 编译标志 ---release # 发布模式优化 ---target # 指定目标平台 +| 变量 | 位置 | 说明 | 示例 | +|------|------|------|------| +| `runs-on` | `jobs.*.runs-on` | Runner 标签 | `ubuntu-latest` | +| 健康检查端点 | `health-check` job | 根据实际服务修改 | `/health`, `/ready`, `/live` | +| 服务端口号 | `health-check` job | Docker 端口映射 | `8080:8080` | -# 二进制优化 -strip # 去除符号表,减小体积 -``` +#### 多服务版本 -### 缓存策略 +| 变量 | 位置 | 说明 | 示例 | +|------|------|------|------| +| `SERVICE_PREFIX` | `env` | 服务前缀,用于 tag 匹配 | `device-rs` | +| `SERVICE_DIR` | `env` | 项目目录名 | `device-rs` | +| `runs-on` | `jobs.*.runs-on` | Runner 标签 | `ubuntu-latest` | +| 健康检查端点 | `health-check` job | 根据实际服务修改 | `/health`, `/ready`, `/live` | -```yaml -# Cargo 缓存路径 -~/.cargo/bin/ # 已安装的 cargo 工具 -~/.cargo/registry/index/ # 注册表索引 -~/.cargo/registry/cache/ # 下载的 crate 缓存 -~/.cargo/git/db/ # Git 依赖缓存 -target/ # 编译输出缓存 +### Secrets 配置 -# 缓存 Key 计算 -key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} -``` +在 Gitea 仓库的 **Settings → Secrets** 中添加: ---- +| Secret | 说明 | 必需 | +|--------|------|------| +| `REGISTRY_TOKEN` | 容器仓库推送 Token | 是 | +| `RELEASE_TOKEN` | Release 创建 Token | 可选(默认使用 GITHUB_TOKEN) | -## Variables 和 Secrets 配置 +### Variables 配置 -### Required Variables -| Variable | 说明 | 示例 | +在 Gitea 仓库的 **Settings → Variables** 中添加: + +| Variable | 说明 | 必需 | |----------|------|------| -| `WEBHOOK_URL` | 构建通知 Webhook | `https://api.example.com/webhook` | +| `WEBHOOK_URL` | 构建通知 Webhook URL | 可选 | -### Required Secrets -| Secret | 说明 | -|--------|------| -| `REGISTRY_TOKEN` | Container Registry 访问令牌 | +### 可选修改的变量 -### Optional Secrets -| Secret | 说明 | -|--------|------| -| 无 | 基础模板无额外 secrets | +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `CARGO_INCREMENTAL` | `0` | CI 环境禁用增量编译 | +| `CARGO_NET_RETRY` | `10` | 网络重试次数 | +| `RUST_BACKTRACE` | `short` | 错误栈长度 | -**注意**:Gitea Actions 不允许变量和密钥名称以 `GITEA_` 或 `GITHUB_` 开头 +### 健康检查端点配置 ---- +模板默认检查以下端点,请根据你的服务实际端点修改: -## 触发条件 - -### 标准触发 ```yaml -on: - push: - branches: [main] # 仅 main 分支 - tags: ['v*'] # 版本标签 - pull_request: - branches: [main] # PR 到 main +# 在 health-check job 中修改 +- name: 运行健康检查测试 + run: | + # 修改端口映射 + docker run -d --name app-test -p 你的端口:容器端口 ... + + # 修改检查端点 + curl -f http://localhost:你的端口/health || exit 1 + curl -f http://localhost:你的端口/ready || exit 1 + curl -f http://localhost:你的端口/live || exit 1 ``` -### 路径过滤(单体仓库) +推荐的端点实现(Axum 示例): + +```rust +pub fn health_routes() -> Router { + Router::new() + .route("/health", get(health)) // 健康检查 + .route("/ready", get(ready)) // 就绪检查 + .route("/live", get(live)) // 存活检查 +} + +async fn health() -> Json { + Json(json!({"status": "healthy"})) +} + +async fn ready() -> Json { + // 检查依赖服务(数据库、缓存等) + Json(json!({"status": "ready"})) +} + +async fn live() -> &'static str { + "OK" // 简单存活检查 +} +``` + +## build.rs 示例 + +如果需要在编译时注入版本号,创建 `build.rs`: + +```rust +/// 构建脚本 - 将环境变量传递给编译过程 +/// +/// 在 CI 构建时,GIT_TAG 环境变量会被设置为版本号(如 "0.6.3"), +/// 该脚本将其传递给 rustc,使 `option_env!("GIT_TAG")` 能在编译时获取版本。 +fn main() { + // 传递 GIT_TAG 环境变量到编译时 + if let Ok(git_tag) = std::env::var("GIT_TAG") { + println!("cargo:rustc-env=GIT_TAG={}", git_tag); + } + + // 重新运行条件:GIT_TAG 环境变量变化时重新编译 + println!("cargo:rerun-if-env-changed=GIT_TAG"); +} +``` + +在代码中使用: + +```rust +fn main() { + let version = option_env!("GIT_TAG").unwrap_or("dev"); + println!("Version: {}", version); +} +``` + +## 性能优化建议 + +### 1. CI 环境优化 + +```yaml +env: + # 禁用增量编译(CI 环境不需要) + CARGO_INCREMENTAL: 0 + # 增加网络重试次数 + CARGO_NET_RETRY: 10 + # 保持彩色输出 + CARGO_TERM_COLOR: always + # 简短错误栈 + RUST_BACKTRACE: short +``` + +### 2. 缓存策略 + +```yaml +# 使用 rust-cache action(推荐) +- name: Cache Rust dependencies + uses: https://github.com/Swatinem/rust-cache@v2 + continue-on-error: true # 缓存失败不中断构建 + with: + cache-targets: true + cache-on-failure: true + cache-all-crates: true +``` + +### 3. 并行构建 + +```yaml +- name: Build + run: | + export CARGO_BUILD_JOBS="$(nproc)" + cargo build --release +``` + +### 4. Docker 构建缓存 + +```yaml +- name: 构建并推送镜像 + uses: docker/build-push-action@v6 + with: + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache,mode=max +``` + +### 5. 依赖优化 + +```toml +# Cargo.toml - 优化 Release 构建 +[profile.release] +opt-level = 3 # 最高优化级别 +lto = true # 链接时优化 +codegen-units = 1 # 单个代码生成单元(更好的优化) +strip = true # 去除符号表(减小体积) +panic = 'abort' # 减小二进制大小 +``` + +## 健康检查端点实现 + +使用 Axum 实现健康检查(参考 device-rs): + +```rust +use axum::{Router, routing::get, Json}; +use serde_json::json; + +pub fn health_routes() -> Router { + Router::new() + .route("/healthz", get(healthz)) // 存活探针 + .route("/readyz", get(readyz)) // 就绪探针 + .route("/health", get(health)) // 详细状态 + .route("/metrics", get(metrics)) // Prometheus metrics +} + +// 简单存活检查 +async fn healthz() -> &'static str { + "OK" +} + +// 就绪检查(检查依赖服务) +async fn readyz() -> Json { + // 检查数据库、Redis、MQTT 等 + Json(json!({"status": "ready"})) +} + +// 详细健康状态 +async fn health() -> Json { + Json(json!({ + "status": "healthy", + "version": env!("CARGO_PKG_VERSION"), + "uptime": "1h 23m" + })) +} + +// Prometheus metrics +async fn metrics() -> String { + // 导出 metrics + "# HELP requests_total Total requests\n".to_string() +} +``` + +## 常见问题 + +### Q1: rust-cache 报错 "getCacheEntry failed" + +**症状**: 缓存步骤报错 `getCacheEntry failed: connect ETIMEDOUT` + +**解决**: 在 act_runner 的 `config.yaml` 中配置缓存服务器: + +```yaml +cache: + enabled: true + dir: "/cache" + host: "127.0.0.1" # 替换为你的缓存服务器地址 + port: 9000 +``` + +或在 workflow 中设置 `continue-on-error: true` 跳过缓存失败。 + +### Q2: 健康检查失败 + +**症状**: Health Check job 失败,提示 `curl: (7) Failed to connect` + +**解决**: +1. 检查服务端口是否正确映射 +2. 确认服务启动时间,可能需要增加 `sleep` 时间 +3. 验证端点路径是否正确 + +```yaml +- name: 运行健康检查测试 + run: | + docker run -d --name app-test -p 8080:8080 ${{ env.IMAGE }} + sleep 10 # 增加等待时间 + curl -f http://localhost:8080/health || exit 1 +``` + +### Q3: Docker 推送失败(401 Unauthorized) + +**症状**: `denied: unauthorized to access repository` + +**解决**: +1. 确认 `REGISTRY_TOKEN` 已正确配置 +2. 检查 Token 权限(需要 `package:write`) +3. 验证 registry URL 是否正确 + +### Q4: 并发构建取消 + +**症状**: 新推送取消了正在运行的构建 + +**解决**: 这是预期行为。`concurrency` 配置会自动取消同一分支的旧构建: + +```yaml +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +``` + +如需保留所有构建,可以移除或修改此配置。 + +### Q5: 多服务仓库触发混乱 + +**症状**: 修改服务 A 但服务 B 也触发了构建 + +**解决**: 确保 `paths` 配置正确: + ```yaml on: push: - branches: [main] paths: - - 'services/api-hub/**' # 仅特定服务目录变更 - - 'shared/**' # 共享代码变更 - - 'Cargo.toml' # 根依赖变更 + - 'service-a/**' # 仅监听服务 A 的变更 + - '.gitea/workflows/service-a.yml' + tags: + - 'service-a-*' # 仅匹配服务 A 的 tag ``` ---- +### Q6: musl 编译链接错误 -## 发布流程 - -### Release 创建 -- **触发**:推送 `v*` 格式的 tag(如 `v1.0.0`) -- **构建物**: - - `{SERVICE_NAME}-{version}-x86_64-linux-gnu.tar.gz` - - `{SERVICE_NAME}-{version}-x86_64-linux-musl.tar.gz` -- **Changelog**: 自动从 `CHANGELOG.md` 提取或生成默认内容 - -### 版本发布操作 ```bash -# 创建并推送版本标签 -git tag v1.0.0 -git push origin v1.0.0 +# 安装 musl 工具链 +rustup target add x86_64-unknown-linux-musl -# 预发布版本(标记为 prerelease) -git tag v1.0.0-beta.1 -git push origin v1.0.0-beta.1 +# 或在 CI 中自动安装 +curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | \ + sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-musl ``` ---- +## 参考项目 -## 常见构建工具集成 +- [api-hub](https://github.com/example/api-hub) - Rust API 网关服务,MQTT5 + HTTP 健康检查 +- [device-rs](https://git.voson.top/tianchu/bms/src/branch/main/device-rs) - Rust IoT 设备服务 -根据项目使用的工具,在构建步骤中添加相应命令: +## 模板更新日志 -| 框架/工具 | 命令 | -|----------|------| -| SeaORM | `sea-orm-cli generate entity -o src/entity` | -| Diesel | `diesel migration run && diesel print-schema > src/schema.rs` | -| tonic (gRPC) | `cargo build` (build.rs 自动处理) | -| utoipa (OpenAPI) | 代码生成,无需额外命令 | -| cargo-make | `cargo make ci` | +### 2026-01-30 ---- +- **优化 Workflow 结构**:分离 test、build-docker、health-check、release、notify 阶段 +- **新增健康检查阶段**:自动验证 Docker 镜像的健康端点 +- **新增并发控制**:避免重复构建浪费资源 +- **新增单服务/多服务双模板**:适应不同项目结构 +- **优化 Dockerfile**:基于实际项目改进多阶段构建 +- **完善文档**:添加详细的配置说明和常见问题 -## Dockerfile.ci 模板(可选) +## 相关文档 -如果需要在 CI 中使用自定义 Dockerfile: - -```dockerfile -# 用于 CI 的轻量构建 -FROM rust:1.75-alpine as builder - -RUN apk add --no-cache musl-dev openssl-dev - -WORKDIR /app -COPY . . -RUN cargo build --release --target x86_64-unknown-linux-musl - -# 运行时镜像 -FROM alpine:latest -RUN apk add --no-cache ca-certificates curl -WORKDIR /app -COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/api-hub . -EXPOSE 8080 -HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1 -ENTRYPOINT ["./api-hub"] -``` - ---- - -## 使用步骤 - -1. **复制模板**到 `.gitea/workflows/{service}.yml` -2. **修改变量**: - - `SERVICE_NAME`: 服务名称 - - 触发条件中的分支和标签模式 -3. **配置 Secrets**: - - `REGISTRY_TOKEN`: Container registry 访问令牌 -4. **可选配置**: - - 健康检查端点和端口 - - 构建目标平台 - - Webhook 通知 URL -5. **推送代码**触发 workflow - ---- - -## 高级功能 - -### 多平台构建 - -```yaml -- name: 安装交叉编译工具 - run: | - rustup target add x86_64-unknown-linux-musl - rustup target add aarch64-unknown-linux-musl - # cargo install cross - -- name: 构建多平台二进制 - run: | - # x86_64 GNU - cargo build --release --target x86_64-unknown-linux-gnu - - # x86_64 MUSL - cargo build --release --target x86_64-unknown-linux-musl - - # ARM64 MUSL (使用 cross) - # cross build --release --target aarch64-unknown-linux-musl -``` - -### 集成测试 - -```yaml -- name: 集成测试 - run: | - # 启动依赖服务(数据库、Redis 等) - docker compose up -d postgres redis - - # 等待服务就绪 - sleep 5 - - # 运行集成测试 - cargo test --test integration_tests - - # 清理 - docker compose down -``` - -### 安全扫描 - -```yaml -- name: 安全审计 - run: | - cargo install cargo-audit - cargo audit -``` - ---- - -## 版本 - -- **模板版本**: 1.0 -- **最后更新**: 2026-01-29 -- **基于项目**: API Hub -- **优化重点**: 变量命名规范、简化配置、增强 Release 功能 \ No newline at end of file +- [Gitea Actions Documentation](https://docs.gitea.com/usage/actions/quickstart) +- [Rust CI 最佳实践](https://doc.rust-lang.org/cargo/guide/continuous-integration.html) +- [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild) +- [rust-cache](https://github.com/Swatinem/rust-cache)