使用 `profiles` 实现多环境网络隔离的最佳实践
解读
在国内一线/二线互联网公司的 Docker 岗位面试中,这道题常被用来区分“只会写 docker-compose up 的初级选手”与“能把 Compose 当编排利器的高级工程师”。
面试官真正想听的是:
- 你能否用 Compose profiles 把“开发、测试、预发、生产”四套环境放在同一仓库却互不干扰;
- 你能否在 网络层 做到逻辑隔离,避免开发容器直连生产数据库;
- 你能否给出 镜像、变量、Secret、资源限制 的配套方案,让隔离不止停留在“能跑起来”;
- 你能否说明 CI/CD 如何识别 profiles 并自动打标签、推送到不同 Harbor 项目,实现“代码一次提交,环境自动分流”。
答不到“网络隔离”与“CI/CD 结合”这两点,基本会被判为“仅了解语法”。
知识点
- Compose profiles 本质:docker-compose.yml 中
profiles:列表控制服务是否默认启动,等价于“条件式服务”,解决“一个文件管多环境”的痛点。 - 网络隔离三要素:
- 每个 profile 绑定独立
networks段,网络名加环境后缀(dev、test、uat、prod); - 使用
internal: true禁止出站,防止开发容器蹭外网; - 利用
ipam.config.subnet给不同环境分配 RFC1918 不同段,避免网段冲突。
- 每个 profile 绑定独立
- 镜像策略:
- 基础镜像统一用公司私有 Harbor,多阶段构建把编译环境与运行环境分离;
- 每个 profile 在 CI 中打 独立 tag(
app:dev-{commit}、app:prod-{commit}),防止“latest 地狱”。
- Secret 与变量:
.env.dev、.env.prod由 GitLab CI 变量 加密注入,不在仓库明文存放;- 数据库口令通过 Docker Compose
secrets挂载,非 root 用户只读,满足国内等保三级要求。
- 资源与策略:
- 开发环境
deploy.resources.limits.memory=1G,生产4G,防止开发把节点打爆; - 生产 profile 强制加
read_only: true与tmpfs,满足国内金融客户“容器不可写”合规要求。
- 开发环境
- CI/CD 识别:
- Git 分支名映射 profile:
feature/*→ dev,release/*→ uat,main→ prod; - 在
.gitlab-ci.yml中用COMPOSE_PROFILES=$CI_COMMIT_REF_NAME自动注入,无需人工改文件。
- Git 分支名映射 profile:
答案
最佳实践分五步落地,可直接写进简历“项目亮点”。
-
单文件多环境
仓库根目录只保留一份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}]}} -
启动命令
开发: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 隔离。 -
网络隔离验证
- 开发容器
ping 10.0.0.5(生产数据库)不可达,因 prod 网络internal: true; - 使用
docker network inspect确认网段无重叠,满足公司 SRE 红线和等保测评。
- 开发容器
-
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 库开启签名与不可变标签。 -
安全加固
- 所有镜像 非 root 运行,Dockerfile 最后加
USER 1001:1001; - 生产 profile 强制启用
no-new-privileges:true与seccomp:unconfined白名单; - 数据库连接串通过
docker compose secret挂载到/run/secrets/db_url,Go 代码只读一次,避免落盘。
- 所有镜像 非 root 运行,Dockerfile 最后加
落地后效果:
- 一套 Compose 文件维护 4 套环境,MR 冲突降低 70%;
- 网络隔离通过公司安全部 黑盒扫描 0 高危;
- 镜像体积由 1.2 GB 降到 180 MB(多阶段构建 + alpine 基础镜像),** Harbor 存储节省 55%**。
拓展思考
-
如果公司强制要求“生产必须跑在 Kubernetes”,如何把 profiles 思路迁移到 Helm sub-chart?
答:用condition字段等价 profiles,dev 环境启用redis.enabled=false,prod 启用redis.enabled=true,并在values-prod.yaml里把 NetworkPolicy 设为拒绝跨命名段,实现 K8s 层网络隔离。 -
当开发环境需要 热重载代码 而生产完全静态,如何共用同一镜像?
答:开发 profile 启动时挂载volume: ./src:/app/src,并在 Dockerfile 把ENTRYPOINT写成dumb-init --reload-dev || dumb-init --static-prod,通过 环境变量 切换,避免维护两份镜像。 -
国内金融客户要求“容器出网必须走 Squid 白名单代理”,如何在 profiles 里实现?
答:给 prod 网络加external: proxy_net,并在 Compose 里加https_proxy=http://squid:3128;dev 网络保持bridge直连,既满足监管,又不拖慢开发调试效率。
把以上三点准备成 2 分钟电梯陈述,面试官会默认你“不仅懂 Docker,还懂国内合规与云原生演进”,通过率至少提一档。