# Rust 后端服务 Workflow 模板 适用于 Rust 后端 API 服务、微服务、CLI 工具的 CI/CD workflow。 ## 适用场景 - Rust HTTP API 服务(Axum、Actix-web、Rocket 等) - 异步后端服务(Tokio、async-std) - MQTT/IoT 设备服务 - CLI 工具 - 需要构建 Docker 镜像的 Rust 项目 ## 环境要求 | 依赖 | Runner 要求 | |------|------------| | Rust 1.75+ | 通过脚本自动安装 | | Docker | Runner 主机已安装 | | 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: Rust Backend CI/CD on: push: branches: [main, master] pull_request: 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 RUST_BACKTRACE: short # 使用中国 Rust 镜像加速 RUSTUP_DIST_SERVER: https://rsproxy.cn RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup jobs: test: name: 测试 runs-on: ubuntu-latest # 修改为你的 Runner 标签 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 # 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: cache-targets: true cache-on-failure: true cache-all-crates: true - name: 检查代码格式 run: cargo fmt -- --check - name: 运行 Clippy 检查 run: cargo clippy -- -D warnings - name: 运行测试 run: cargo test --verbose - name: 构建 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 }} 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: 登录 Gitea 容器仓库 uses: docker/login-action@v3 with: registry: ${{ steps.vars.outputs.registry }} username: ${{ github.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: 提取元数据 id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ github.repository }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix=,suffix=,format=short - name: 构建并推送镜像 uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} 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' || github.ref == 'refs/heads/master') runs-on: ubuntu-latest steps: - 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 ${{ github.event.repository.name }}-test -p 8080:8080 ${{ env.REGISTRY }}/${{ github.repository }}:main # 等待服务启动 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 ${{ 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 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: 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/${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/${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 }}" echo "" echo "### 变更内容" 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 ``` ### 多服务版本(单仓库多 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.84-slim AS builder WORKDIR /app # 安装构建依赖 RUN apt-get update && apt-get install -y \ pkg-config \ libssl-dev \ && rm -rf /var/lib/apt/lists/* # 先复制 Cargo 文件以缓存依赖 COPY Cargo.toml Cargo.lock ./ # 创建虚拟 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 # 安装运行时依赖 RUN apt-get update && apt-get install -y \ ca-certificates \ curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # 复制 CI 构建好的二进制文件 COPY your-app-name . # 创建日志目录 RUN mkdir -p /app/logs 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"] ``` ## 模板选择指南 ### 单服务仓库 vs 多服务仓库 | 场景 | 推荐模板 | 说明 | |------|---------|------| | 单 Rust 项目 | **完整版本** | 简单直接,基于分支触发 | | 单仓库多服务 | **多服务版本** | 按路径和 tag 前缀触发,避免无关构建 | | 微服务架构 | **多服务版本** | 每个服务独立 workflow | ### Workflow 阶段说明 ``` ┌─────────┐ ┌──────────────┐ ┌─────────────┐ │ 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** | 始终执行 | 发送构建结果通知 | ## 自定义配置说明 ### 必须修改的变量 #### 完整版本(单服务) | 变量 | 位置 | 说明 | 示例 | |------|------|------|------| | `runs-on` | `jobs.*.runs-on` | Runner 标签 | `ubuntu-latest` | | 健康检查端点 | `health-check` job | 根据实际服务修改 | `/health`, `/ready`, `/live` | | 服务端口号 | `health-check` job | Docker 端口映射 | `8080:8080` | #### 多服务版本 | 变量 | 位置 | 说明 | 示例 | |------|------|------|------| | `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` | ### Secrets 配置 在 Gitea 仓库的 **Settings → Secrets** 中添加: | Secret | 说明 | 必需 | |--------|------|------| | `REGISTRY_TOKEN` | 容器仓库推送 Token | 是 | | `RELEASE_TOKEN` | Release 创建 Token | 可选(默认使用 GITHUB_TOKEN) | ### Variables 配置 在 Gitea 仓库的 **Settings → Variables** 中添加: | Variable | 说明 | 必需 | |----------|------|------| | `WEBHOOK_URL` | 构建通知 Webhook URL | 可选 | ### 可选修改的变量 | 变量 | 默认值 | 说明 | |------|--------|------| | `CARGO_INCREMENTAL` | `0` | CI 环境禁用增量编译 | | `CARGO_NET_RETRY` | `10` | 网络重试次数 | | `RUST_BACKTRACE` | `short` | 错误栈长度 | ### 健康检查端点配置 模板默认检查以下端点,请根据你的服务实际端点修改: ```yaml # 在 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: paths: - 'service-a/**' # 仅监听服务 A 的变更 - '.gitea/workflows/service-a.yml' tags: - 'service-a-*' # 仅匹配服务 A 的 tag ``` ### Q6: musl 编译链接错误 ```bash # 安装 musl 工具链 rustup target add x86_64-unknown-linux-musl # 或在 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 设备服务 ## 模板更新日志 ### 2026-01-30 - **优化 Workflow 结构**:分离 test、build-docker、health-check、release、notify 阶段 - **新增健康检查阶段**:自动验证 Docker 镜像的健康端点 - **新增并发控制**:避免重复构建浪费资源 - **新增单服务/多服务双模板**:适应不同项目结构 - **优化 Dockerfile**:基于实际项目改进多阶段构建 - **完善文档**:添加详细的配置说明和常见问题 ## 相关文档 - [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)