使用 `profiles` 实现多环境网络隔离的最佳实践

解读

在国内一线/二线互联网公司的 Docker 岗位面试中,这道题常被用来区分“只会写 docker-compose up 的初级选手”与“能把 Compose 当编排利器的高级工程师”。
面试官真正想听的是:

  1. 你能否用 Compose profiles 把“开发、测试、预发、生产”四套环境放在同一仓库却互不干扰;
  2. 你能否在 网络层 做到逻辑隔离,避免开发容器直连生产数据库;
  3. 你能否给出 镜像、变量、Secret、资源限制 的配套方案,让隔离不止停留在“能跑起来”;
  4. 你能否说明 CI/CD 如何识别 profiles 并自动打标签、推送到不同 Harbor 项目,实现“代码一次提交,环境自动分流”。

答不到“网络隔离”与“CI/CD 结合”这两点,基本会被判为“仅了解语法”。

知识点

  1. Compose profiles 本质:docker-compose.yml 中 profiles: 列表控制服务是否默认启动,等价于“条件式服务”,解决“一个文件管多环境”的痛点。
  2. 网络隔离三要素
    • 每个 profile 绑定独立 networks 段,网络名加环境后缀devtestuatprod);
    • 使用 internal: true 禁止出站,防止开发容器蹭外网;
    • 利用 ipam.config.subnet 给不同环境分配 RFC1918 不同段,避免网段冲突。
  3. 镜像策略
    • 基础镜像统一用公司私有 Harbor,多阶段构建把编译环境与运行环境分离;
    • 每个 profile 在 CI 中打 独立 tagapp:dev-{commit}app:prod-{commit}),防止“latest 地狱”。
  4. Secret 与变量
    • .env.dev.env.prod 由 GitLab CI 变量 加密注入,不在仓库明文存放;
    • 数据库口令通过 Docker Compose secrets 挂载,非 root 用户只读,满足国内等保三级要求。
  5. 资源与策略
    • 开发环境 deploy.resources.limits.memory=1G,生产 4G,防止开发把节点打爆;
    • 生产 profile 强制加 read_only: truetmpfs,满足国内金融客户“容器不可写”合规要求。
  6. CI/CD 识别
    • Git 分支名映射 profile:feature/* → dev,release/* → uat,main → prod;
    • .gitlab-ci.yml 中用 COMPOSE_PROFILES=$CI_COMMIT_REF_NAME 自动注入,无需人工改文件

答案

最佳实践分五步落地,可直接写进简历“项目亮点”。

  1. 单文件多环境
    仓库根目录只保留一份 docker-compose.yml,用 profiles 区分服务:

    services:
      app:
        image: harbor.example.com/app:${TAG:-dev}
        networks: [dev]
        profiles: [dev]
      app-uat:
        image: harbor.example.com/app:${TAG:-uat}
        networks: [uat]
        profiles: [uat]
      app-prod:
        image: harbor.example.com/app:${TAG:-prod}
        networks: [prod]
        profiles: [prod]
        deploy:
          replicas: 3
          resources:
            limits: {memory: 4G}
          read_only: true
    networks:
      dev:  {driver: bridge, ipam: {config: [{subnet: 172.20.0.0/16}]}}
      uat:  {driver: bridge, ipam: {config: [{subnet: 172.21.0.0/16}]}}
      prod: {driver: overlay, attachable: false, internal: true, ipam: {config: [{subnet: 10.0.0.0/24}]}}
    
  2. 启动命令
    开发:COMPOSE_PROFILES=dev docker compose up -d
    生产:COMPOSE_PROFILES=prod docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
    通过 -f 叠加文件,生产额外加监控 sidecar,但网络仍由 profiles 隔离。

  3. 网络隔离验证

    • 开发容器 ping 10.0.0.5(生产数据库)不可达,因 prod 网络 internal: true
    • 使用 docker network inspect 确认网段无重叠,满足公司 SRE 红线和等保测评
  4. CI/CD 流水线
    GitLab Runner 内声明 COMPOSE_PROFILES=${CI_COMMIT_REF_SLUG}
    构建阶段 docker buildx build -t harbor.example.com/app:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} .
    推送阶段根据 profile 推送到 Harbor 不同项目:dev 库开启漏洞扫描,prod 库开启签名与不可变标签

  5. 安全加固

    • 所有镜像 非 root 运行,Dockerfile 最后加 USER 1001:1001
    • 生产 profile 强制启用 no-new-privileges:trueseccomp:unconfined 白名单;
    • 数据库连接串通过 docker compose secret 挂载到 /run/secrets/db_urlGo 代码只读一次,避免落盘。

落地后效果:

  • 一套 Compose 文件维护 4 套环境,MR 冲突降低 70%
  • 网络隔离通过公司安全部 黑盒扫描 0 高危
  • 镜像体积由 1.2 GB 降到 180 MB(多阶段构建 + alpine 基础镜像),** Harbor 存储节省 55%**。

拓展思考

  1. 如果公司强制要求“生产必须跑在 Kubernetes”,如何把 profiles 思路迁移到 Helm sub-chart
    答:用 condition 字段等价 profiles,dev 环境启用 redis.enabled=false,prod 启用 redis.enabled=true,并在 values-prod.yaml 里把 NetworkPolicy 设为拒绝跨命名段,实现 K8s 层网络隔离

  2. 当开发环境需要 热重载代码 而生产完全静态,如何共用同一镜像?
    答:开发 profile 启动时挂载 volume: ./src:/app/src,并在 Dockerfile 把 ENTRYPOINT 写成 dumb-init --reload-dev || dumb-init --static-prod,通过 环境变量 切换,避免维护两份镜像。

  3. 国内金融客户要求“容器出网必须走 Squid 白名单代理”,如何在 profiles 里实现?
    答:给 prod 网络加 external: proxy_net,并在 Compose 里加 https_proxy=http://squid:3128;dev 网络保持 bridge 直连,既满足监管,又不拖慢开发调试效率

把以上三点准备成 2 分钟电梯陈述,面试官会默认你“不仅懂 Docker,还懂国内合规与云原生演进”,通过率至少提一档