如何自定义 Portainer 主题以符合企业 CI

解读

在国内金融、运营商、政务云等甲方场景,Portainer 常被选为 Docker 可视化管理平台,但默认蓝白配色与企业 CI(Corporate Identity)的 Logo、主色、字体规范冲突。面试官想验证两点:

  1. 你是否了解 Portainer 的前端架构与可插拔主题机制
  2. 你是否能把“改颜色”这件小事做成可灰度、可版本化、可一键回滚的企业级交付方案,而不是手动改完就丢。

知识点

  1. Portainer 2.x 前端基于Angular 12 + TypeScript,样式层使用**Sass 变量 + CSS 自定义属性(:root)**混合方案。
  2. 官方提供**“Theme Override”目录**:/public/assets/css/theme-override.css,在容器启动时若存在即自动注入,无需重新打包镜像
  3. 企业 CI 通常给出主色(Primary)、辅色(Secondary)、警告色(Warning)、字体家族、Logo 矢量文件五要素,需映射到 Portainer 的 Sass 变量:
    $color-primary$color-secondary$color-danger$color-warning$color-info$color-success$color-ui-1$color-ui-9
  4. 国内等保要求所有静态资源必须走内网,因此自定义主题需随镜像构建阶段打入,而不是运行时拉外网 CDN。
  5. 交付流程需符合 GitLab CI “配置即代码”:把主题文件、Dockerfile、helm values 统一放在仓库 .ci/portainer-theme/ 目录,MR 触发自动构建并推送至Harbor 私有仓库,再经Swarm stack 滚动升级到预发环境,最后由SRE 在 Jenkins 点单键发布到生产
  6. 回滚策略:利用 Swarm 的 rollback_config,把 stop-grace-period 设为 30s,确保10 秒内完成新旧主题切换,降低业务感知。

答案

步骤一:提取企业 CI 规范
拿到市场部给出的Pantone 185C 主色、思源黑体字体、Logo SVG,用工具转成 CSS 变量值:

:root {
  --color-primary: #E60012;
  --color-secondary: #FFFFFF;
  --font-family-sans-serif: "Source Han Sans CN", sans-serif;
}

步骤二:创建 theme-override.css
在源码仓库新建 .ci/portainer-theme/theme-override.css仅覆盖必要变量,避免全量复制导致后续升级冲突:

/* 顶部导航栏 */
.navbar-default {
  background-color: var(--color-primary) !important;
}
/* 按钮 */
.btn-primary {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
}
/* Logo 替换 */
.navbar-brand {
  background-image: url('assets/img/ci-logo.svg');
  background-size: contain;
  background-repeat: no-repeat;
  width: 160px;
}

步骤三:构建企业版镜像
Dockerfile 片段:

FROM portainer/portainer-ce:2.19.4-alpine
COPY .ci/portainer-theme/theme-override.css /public/assets/css/
COPY .ci/portainer-theme/ci-logo.svg /public/assets/img/

关键点:使用精确版本号而非 latest,保证可审计;构建完成后立即 docker scanTrivy 漏洞扫描,高危≥HIGH 一律打回。

步骤四:CI/CD 集成
GitLab CI 阶段:

  1. build-theme:npm 安装 sass,验证变量语法;
  2. docker-build:多阶段构建,推送至 Harbor 的私有项目 ops/portainer:ci-{commit-sha}
  3. deploy-staging:通过 Swarm docker stack deploy -c portainer-stg.yml 滚动升级,设置 update-order: start-first,零中断;
  4. test-e2e:用 Cypress 跑登录页截图比对,确保主色色值偏差 ΔE≤3;
  5. manual-prod:SRE 点击确认后,镜像重打 prod 标签,再次 Trivy 扫描,通过后发布。

步骤五:灰度与回滚
Swarm 配置:

deploy:
  replicas: 3
  update_config:
    parallelism: 1
    delay: 30s
    failure_action: rollback
    monitor: 60s
  rollback_config:
    parallelism: 1
    delay: 5s

若主题导致控制台白屏,60 秒内自动回滚到旧版本,同时飞书机器人告警。

拓展思考

  1. 多租户场景:Portainer Business 版支持“每团队独立主题”,可通过 teamId 动态注入 CSS,实现子公司 A 红色、子公司 B 蓝色的隔离需求。
  2. 暗黑模式:Angular 12 支持 prefers-color-scheme,可在 theme-override.css 追加 @media (prefers-color-scheme: dark)自动切换深色变量,满足研发人员熬夜值班体验。
  3. 合规水印:在等保三级环境,审计要求**“页面必须带用户姓名水印”**,可通过 Angular Directive 注入 Canvas 水印,与主题文件一起版本化,避免人工贴图。
  4. 性能优化:主题 SVG Logo 若大于 30 KB,需转内联 base64 并走 gzip_static on,减少一次 TLS 往返;同时利用 HTTP Cache-Control: max-age=31536000, immutable,让浏览器永久缓存,控制台首屏加载时间从 1.8 s 降到 1.1 s