强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 16 章:Docker 与 CI/CD

第 16 章:Docker 与 CI/CD

16.1 Docker 基础构建

16.1.1 基本 Dockerfile

# Dockerfile.build
FROM ubuntu:24.04 AS builder

# 避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive

# 安装构建工具
RUN apt-get update && apt-get install -y \
    cmake \
    g++ \
    ninja-build \
    git \
    pkg-config \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /project

# 先复制 CMakeLists.txt 和依赖文件(利用 Docker 缓存)
COPY CMakeLists.txt .
COPY cmake/ cmake/

# 复制源码
COPY src/ src/
COPY include/ include/

# 配置和构建
RUN cmake -S . -B build \
    -G Ninja \
    -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_TESTING=ON

RUN cmake --build build --parallel $(nproc)

# 运行测试
RUN ctest --test-dir build --output-on-failure --parallel $(nproc)

# 安装到临时目录
RUN cmake --install build --prefix /opt/myapp

16.1.2 多阶段构建

# 阶段一:构建
FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    cmake g++ ninja-build git pkg-config \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /project
COPY . .

RUN cmake -S . -B build -G Ninja \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/opt/myapp

RUN cmake --build build --parallel $(nproc)
RUN cmake --install build

# 阶段二:运行时
FROM ubuntu:24.04 AS runtime

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

# 从构建阶段复制安装产物
COPY --from=builder /opt/myapp /usr/local

# 创建非 root 用户
RUN useradd -m appuser
USER appuser

ENTRYPOINT ["myapp"]
CMD ["--help"]

16.1.3 最小运行时镜像

# 使用 distroless 镜像
FROM gcr.io/distroless/cc-debian12:latest AS runtime

COPY --from=builder /opt/myapp/bin/myapp /usr/local/bin/
COPY --from=builder /opt/myapp/lib/ /usr/local/lib/

ENTRYPOINT ["myapp"]

16.2 构建缓存优化

16.2.1 利用 Docker 层缓存

FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y cmake g++ ninja-build \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /project

# 第一层:依赖声明文件(变化最少)
COPY CMakeLists.txt .
COPY vcpkg.json .
COPY cmake/ cmake/

# 第二层:安装依赖(缓存命中率高)
RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release || true

# 第三层:源代码(变化最频繁)
COPY src/ src/
COPY include/ include/

# 第四层:重新配置和构建
RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
RUN cmake --build build --parallel $(nproc)

16.2.2 ccache 集成

FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
    cmake g++ ninja-build ccache \
    && rm -rf /var/lib/apt/lists/*

# 挂载 ccache 缓存
ENV CCACHE_DIR=/cache/ccache
ENV CCACHE_MAXSIZE=2G

WORKDIR /project
COPY . .

# 使用 ccache
RUN cmake -S . -B build -G Ninja \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_BUILD_TYPE=Release

RUN cmake --build build --parallel $(nproc)
# 运行时挂载缓存目录
docker build --mount=type=cache,target=/cache/ccache -t myapp .

16.2.3 BuildKit 缓存挂载

# syntax=docker/dockerfile:1
FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y cmake g++ ninja-build \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /project
COPY . .

# 使用 BuildKit 缓存
RUN --mount=type=cache,target=/cache/ccache \
    export CCACHE_DIR=/cache/ccache && \
    cmake -S . -B build -G Ninja \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_BUILD_TYPE=Release

RUN cmake --build build --parallel $(nproc)

16.3 交叉编译 Docker

16.3.1 ARM 交叉编译

FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
    cmake g++-aarch64-linux-gnu ninja-build \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /project
COPY . .

# 工具链文件
COPY cmake/aarch64-toolchain.cmake .

RUN cmake -S . -B build -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=aarch64-toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release

RUN cmake --build build --parallel $(nproc)

# ARM 运行时
FROM arm64v8/ubuntu:24.04 AS runtime
COPY --from=builder /project/build/myapp /usr/local/bin/
ENTRYPOINT ["myapp"]
# cmake/aarch64-toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

16.4 CI/CD 集成

16.4.1 GitHub Actions

# .github/workflows/build.yml
name: Build and Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        build_type: [Debug, Release]
    
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - name: Setup CMake
        uses: lukka/get-cmake@latest

      - name: Configure
        run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}

      - name: Build
        run: cmake --build build --parallel

      - name: Test
        run: ctest --test-dir build --output-on-failure --parallel

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.os }}-${{ matrix.build_type }}
          path: build/test-results/

16.4.2 带 vcpkg 的 GitHub Actions

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup vcpkg
        uses: lukka/run-vcpkg@v11
        with:
          vcpkgGitCommitId: 'a42af01b72c28a8e1d7b48107b33e4f286a55ef6'

      - name: Configure
        run: cmake --preset vcpkg

      - name: Build
        run: cmake --build --preset vcpkg

      - name: Test
        run: ctest --preset vcpkg

16.4.3 GitLab CI

# .gitlab-ci.yml
stages:
  - build
  - test
  - package

.build_template: &build_template
  image: ubuntu:24.04
  before_script:
    - apt-get update && apt-get install -y cmake g++ ninja-build
  script:
    - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE
    - cmake --build build --parallel $(nproc)
    - ctest --test-dir build --output-on-failure

build:debug:
  <<: *build_template
  variables:
    BUILD_TYPE: Debug
  stage: build

build:release:
  <<: *build_template
  variables:
    BUILD_TYPE: Release
  stage: build

test:
  stage: test
  script:
    - ctest --test-dir build --output-on-failure

package:
  stage: package
  script:
    - cmake --install build --prefix /opt/myapp
    - cd build && cpack -G TGZ
  artifacts:
    paths:
      - build/*.tar.gz

16.4.4 Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'ubuntu:24.04'
            args '-v /var/cache/ccache:/cache/ccache'
        }
    }
    
    environment {
        CCACHE_DIR = '/cache/ccache'
    }
    
    stages {
        stage('Configure') {
            steps {
                sh '''
                    apt-get update && apt-get install -y cmake g++ ninja-build ccache
                    cmake -S . -B build -G Ninja \
                        -DCMAKE_C_COMPILER_LAUNCHER=ccache \
                        -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
                        -DCMAKE_BUILD_TYPE=Release
                '''
            }
        }
        
        stage('Build') {
            steps {
                sh 'cmake --build build --parallel $(nproc)'
            }
        }
        
        stage('Test') {
            steps {
                sh 'ctest --test-dir build --output-on-failure'
            }
        }
        
        stage('Package') {
            steps {
                sh 'cd build && cpack -G TGZ'
                archiveArtifacts artifacts: 'build/*.tar.gz'
            }
        }
    }
    
    post {
        always {
            junit 'build/test-results/*.xml'
        }
    }
}

16.5 测试与覆盖率

16.5.1 覆盖率 CI

# GitHub Actions 覆盖率
jobs:
  coverage:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y cmake g++ ninja-build lcov
      
      - name: Configure with coverage
        run: |
          cmake -S . -B build -G Ninja \
            -DCMAKE_BUILD_TYPE=Debug \
            -DENABLE_COVERAGE=ON
      
      - name: Build
        run: cmake --build build --parallel
      
      - name: Test
        run: ctest --test-dir build --output-on-failure
      
      - name: Collect coverage
        run: |
          lcov --capture --directory build --output-file coverage.info
          lcov --remove coverage.info '/usr/*' '*/test/*' --output-file coverage_filtered.info
          genhtml coverage_filtered.info --output-directory coverage_report
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: coverage_filtered.info

16.6 预设配置

16.6.1 CI 预设

{
    "version": 6,
    "configurePresets": [
        {
            "name": "ci-base",
            "hidden": true,
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build",
            "cacheVariables": {
                "BUILD_TESTING": "ON",
                "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
            }
        },
        {
            "name": "ci-release",
            "inherits": "ci-base",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release"
            }
        },
        {
            "name": "ci-debug",
            "inherits": "ci-base",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug",
                "ENABLE_COVERAGE": "ON"
            }
        }
    ],
    "buildPresets": [
        { "name": "ci-build", "configurePreset": "ci-release", "jobs": 4 }
    ],
    "testPresets": [
        {
            "name": "ci-test",
            "configurePreset": "ci-release",
            "output": {
                "outputOnFailure": true,
                "outputJUnitFile": "${sourceDir}/test-results.xml"
            }
        }
    ]
}

16.7 业务场景

场景:完整的 CI/CD 流水线

推送代码 → CI 触发 → 多平台构建 → 测试 → 覆盖率 → 制品打包 → 部署
# 完整的 CI 配置
name: CI/CD Pipeline

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: clang-format
        run: find src include -name '*.cpp' -o -name '*.h' | xargs clang-format --dry-run --Werror

  build:
    needs: lint
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        build_type: [Debug, Release]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: lukka/get-cmake@latest
      - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
      - run: cmake --build build --parallel
      - run: ctest --test-dir build --output-on-failure

  package:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
      - run: cmake --build build --parallel
      - run: cmake --install build --prefix install
      - run: cd build && cpack -G TGZ
      - uses: actions/upload-artifact@v4
        with:
          name: package
          path: build/*.tar.gz

16.8 注意事项

问题说明
Docker 层缓存合理安排 COPY 顺序以最大化缓存命中
ccache 权限确保缓存目录可写
多架构构建使用 docker buildx 或交叉编译
CI 网络限制配置代理或使用离线依赖包
测试超时设置合理的 CI 超时时间

16.9 扩展阅读


上一章:第 15 章 — 依赖管理 | 下一章:第 17 章 — 问题排查 →