28、容器化与部署:Docker基础、Dockerfile编写、Docker Compose编排、Kubernetes简介、CI/CD流水线

说实话,很多C++后端开发者对容器化这件事有点抵触。我当年也一样——总觉得“我的二进制直接跑不就行了?搞什么容器?”直到有一次线上环境因为libc版本不一致,程序崩溃了整整两个小时……嗯,从那以后,我再也不敢小看容器化。

这一章,咱们就聊聊怎么把C++服务装进容器,怎么编排它们,以及怎么搭一套自动化的发布流水线。说白了,就是让你的服务在任何机器上都能“一键跑起来”。

为什么C++服务需要容器化?

你想想看,C++编译出来的二进制,对系统库的依赖有多强?glibc版本、OpenSSL版本、甚至CPU指令集,稍微差一点就崩给你看。容器化解决的就是这个“环境一致性”问题。

我个人习惯把容器理解成一个“轻量级虚拟机”。它共享宿主机的内核,但通过命名空间做了隔离。你的服务在容器里看到的文件系统、网络栈、进程空间,都是独立的。

核心价值: 一次构建,到处运行。开发环境、测试环境、生产环境,用同一个镜像,彻底告别“在我机器上能跑啊”这种尴尬。

Docker基础:镜像与容器

Docker有两个核心概念:镜像(Image)和容器(Container)。镜像是一个只读的模板,容器是镜像的运行实例。你可以把镜像理解成C++的类,容器就是实例化的对象。

常用的Docker命令其实没几个,我列一下日常高频使用的:

# 拉取镜像
docker pull ubuntu:22.04

# 查看本地镜像
docker images

# 运行容器(交互模式)
docker run -it --name my_container ubuntu:22.04 /bin/bash

# 后台运行容器
docker run -d --name cpp_server -p 8080:8080 my_cpp_image

# 查看运行中的容器
docker ps

# 进入容器内部
docker exec -it cpp_server /bin/bash

# 停止并删除容器
docker stop cpp_server && docker rm cpp_server
小技巧: 我习惯给每个容器加 --restart=always 参数,这样服务挂了能自动重启。生产环境尤其有用。

Dockerfile编写:从源码到镜像

Dockerfile就是构建镜像的“配方”。对于C++项目,我一般用多阶段构建(Multi-stage Build)来减小镜像体积。你想想看,编译环境里装了一堆gcc、cmake、依赖库头文件,但运行时根本不需要它们。

下面是一个典型的C++服务Dockerfile:

# 第一阶段:编译
FROM ubuntu:22.04 AS builder

RUN apt-get update && apt-get install -y \
    g++ \
    cmake \
    libboost-all-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN mkdir build && cd build && \
    cmake .. -DCMAKE_BUILD_TYPE=Release && \
    make -j$(nproc)

# 第二阶段:运行
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    libboost-system-dev \
    libboost-thread-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=builder /app/build/server .

EXPOSE 8080
CMD ["./server"]

注意看,第一阶段装了完整的编译工具链,第二阶段只复制了编译产物和运行时依赖。这样最终镜像只有几十MB,而不是几个GB。

避坑指南: 我曾经在Dockerfile里直接用 apt-get upgrade,结果每次构建都拉新包,镜像层数暴增。后来我改成只装必要的包,并且用 --no-install-recommends 减少无用依赖。

Docker Compose编排:多服务管理

当你的系统不止一个服务时——比如C++后端 + Redis + Nginx——手动启动每个容器就太累了。Docker Compose用YAML文件定义多容器应用,一条命令全部拉起。

下面是一个典型的编排文件:

version: '3.8'

services:
  cpp_server:
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  redis_data:

docker-compose up -d 就能启动所有服务。我个人习惯把环境变量写在 .env 文件里,避免硬编码。

Kubernetes简介:生产级容器编排

Docker Compose适合单机场景,但到了生产环境,你需要考虑高可用、自动扩缩容、滚动更新——这时候Kubernetes(K8s)就登场了。

K8s的核心概念有几个:

  • Pod: 最小的调度单元,里面可以跑一个或多个容器
  • Deployment: 管理Pod的副本数和滚动更新
  • Service: 提供稳定的网络入口,负载均衡到Pod
  • ConfigMap / Secret: 管理配置和敏感信息

下面是一个简单的Deployment定义:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cpp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cpp-server
  template:
    metadata:
      labels:
        app: cpp-server
    spec:
      containers:
      - name: server
        image: myregistry/cpp-server:latest
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
个人经验: 刚开始用K8s时,我总忘记加资源限制(resources.requests/limits)。结果某个服务内存泄漏,把整个节点搞挂了。现在我的每个Deployment都必配资源限制。

CI/CD流水线:自动化构建与部署

手动构建镜像、推送到仓库、再登录服务器拉取——这种操作太原始了。CI/CD流水线能帮你自动化这一切。

一个典型的流水线包含几个阶段:

  1. 代码提交: 开发者push代码到Git仓库
  2. 自动构建: CI工具(如GitHub Actions、Jenkins)拉取代码,执行编译和单元测试
  3. 镜像构建: 编译通过后,自动构建Docker镜像并推送到镜像仓库
  4. 自动部署: 通知K8s集群拉取新镜像,执行滚动更新

下面是一个GitHub Actions的示例片段:

name: Build and Deploy

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Build Docker image
      run: docker build -t cpp-server:${{ github.sha }} .

    - name: Push to registry
      run: |
        docker tag cpp-server:${{ github.sha }} registry.example.com/cpp-server:latest
        docker push registry.example.com/cpp-server:latest

    - name: Deploy to K8s
      run: kubectl set image deployment/cpp-server server=registry.example.com/cpp-server:latest
避坑指南: 我曾经把镜像tag写成 latest,结果回滚时根本不知道哪个版本是稳定的。后来我改用Git commit SHA作为tag,配合 git tag 打版本号,回滚时一目了然。

知识体系总览

下面这张图总结了本章的核心脉络,从开发到部署的全流程:

容器化与部署知识体系 C++ 源码 CMake + g++ 编译 Dockerfile 多阶段构建 Docker 镜像 推送到仓库 容器编排 Docker Compose(单机) / Kubernetes(集群) 生产部署 滚动更新 · 健康检查 · 自动扩缩容 CI/CD 流水线 代码提交 自动编译 镜像构建 自动部署 GitHub Actions / Jenkins 运维监控 日志收集 指标监控 告警通知 Prometheus + Grafana

从图中可以看到,整个流程从源码开始,经过Dockerfile构建镜像,再通过编排工具部署到生产环境。左侧的CI/CD流水线贯穿始终,右侧的监控体系保障服务稳定运行。

写在最后

容器化这件事,说白了就是给C++服务穿上一件“防弹衣”。不管底层环境怎么变,你的服务都能稳定运行。我个人从手动部署到全自动CI/CD,走了不少弯路,但一旦搭好这套体系,发布效率提升了不止一个量级。

嗯,这一章的内容就到这里。记住:镜像要小、编排要稳、流水线要自动化。把这三点做到位,你的C++服务就能在云端自由驰骋了。


公众号:蓝海资料掘金营,微信deep3321