第 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 章 — 问题排查 →