跳到正文
Kale的数字花园
目录 文章
/9 分钟/9 阅读/0 评论

CICD 持续集成/持续部署系统 — 面试备战手册


零、简历精简版(可直接粘贴到简历)

CICD 持续集成/持续部署平台

项目角色:核心开发 / 方案设计

技术栈:Java 17、Go 1.19、Jenkins、Kubernetes、工蜂 Git、飞书机器人

项目描述:主导设计并落地公司 CI/CD 自动化流水线,将原本需人工操作 Jenkins + 跳板机的构建部署流程全链路自动化,代码 Push 后自动触发构建、部署、通知,单次部署人工操作时间从 5 分钟降至 0。

核心工作与产出:

  • 设计工蜂 Webhook → devops 服务 → Jenkins 自动构建链路,通过轮询构建状态 + 飞书通知闭环,消除人工等待成本
  • 设计持续部署方案:构建完成后生成 DeploymentTask(TTL 7min),devops-task 服务(Go)定时拉取任务并调用 K8s API 重启 Deployment,通过轮询 unavailableReplicas 判定重启结果
  • 处理多异常场景:新服务无对应 Deployment 跳过部署、Pod 启动超时 4min 判定失败并飞书告@提交人、任务过期自动清理
  • 将手动构建部署流程(选分支 → 选镜像 → 登录跳板机 → 输入命令)全链路自动化,覆盖 dev/test/pre 多套环境

一、项目口述(面试自我介绍时用)

我们之前的构建和部署流程非常繁琐——开发要手动登录 Jenkins 页面选分支、选镜像版本,构建完了还要登录跳板机,选环境、输命令重启服务,整个过程大概要花 5 分钟,而且完全不知道什么时候能完成。

所以我主导设计了这套 CI/CD 自动化方案。持续集成这块,在工蜂上配了 Webhook,代码 Push 之后自动触发 devops 服务(Java 17),它解析事件后调用 Jenkins API 触发构建,轮询构建结果并通过飞书通知推送给开发者。持续部署这块,构建成功后会生成一个部署任务(Task),有个 Go 写的 devops-task 服务每 10 秒轮询一次待部署任务,拿到任务后调用 K8s API 重启对应 Deployment。重启过程中通过轮询 unavailableReplicas 字段判断状态——归零就是成功了,超过 4 分钟还没归零就判定失败,推飞书告警并@提交人人工介入。

这套方案上线后,整个构建部署流程从人工操作变成了全自动,开发者只需要 push 代码,剩下的全部由系统完成。

回顾下来,也有一些可以优化的地方。比如最开始构建轮询是同步阻塞的,并发高了会占满线程池,后来改成了异步回调的方式;另外频繁 push 会导致重复构建,后来加了防抖机制,2-3 分钟内多次 push 只触发最后一次构建。

flowchart LR
    A["开发者 Push 代码"] --> B["工蜂 Git Webhook"]
    B --> C["devops 服务解析事件 / 防抖 / 触发构建"]
    C --> D["Jenkins拉代码 / 编译 / 构建镜像"]
    D --> E{"构建结果"}
    E -->|"失败"| F["飞书通知@提交人排查"]
    E -->|"成功"| G["生成 DeploymentTaskTTL 7min"]
    G --> H["devops-task每 10 秒拉取任务"]
    H --> I["K8s APIPATCH Deployment annotation"]
    I --> J{"unavailableReplicas 是否归零"}
    J -->|"是"| K["部署成功飞书通知"]
    J -->|"4 分钟超时"| L["部署失败飞书告警"]

二、面试官可能会问的问题 & 参考回答

模块一:Webhook 与并发处理

Q1:如果一分钟内 push 了 20 次,你的服务会怎样?怎么处理?

最开始的设计是有问题的——20 次 push 会触发 20 次 Webhook,devops 服务会调 20 次 Jenkins 构建,前 19 次都是白跑,浪费资源。

后来我加了一个防抖(Debounce)机制:收到第一次 Webhook 后不立刻构建,而是调度一个延迟任务(比如 2 分钟后触发)。在这 2 分钟内如果又来了 Webhook,就取消上一次的延迟任务、重新计时。这样 20 次 push 最终只会触发一次构建——就是最后一次 push 之后的那一次。实现上用的是 ScheduledExecutorService 的 schedule 方法,每次取消上一次的 ScheduledFuture。

sequenceDiagram
    participant Git as 工蜂 Git
    participant Devops as devops 服务
    participant Timer as 延迟调度器
    participant Jenkins as Jenkins

    Git->>Devops: push#1 Webhook
    Devops->>Timer: 创建 2 分钟延迟构建任务
    Git->>Devops: push#2 Webhook
    Devops->>Timer: 取消上一次 ScheduledFuture
    Devops->>Timer: 重新创建延迟构建任务
    Git->>Devops: push#20 Webhook
    Devops->>Timer: 再次取消并重新计时
    Timer-->>Devops: 静默窗口结束
    Devops->>Jenkins: 只触发最后一次构建

Q2:Jenkins 构建时间很长(比如 15 分钟),你的线程内轮询会占用大量资源,怎么优化?

最初的设计确实是在一个线程里阻塞轮询 Jenkins 的构建状态,每 5 秒查一次。10 个分支就是 10 个线程空转 15 分钟,线程池很快会被耗尽。

优化方案有三种:

  1. 改为回调(Callback):让 Jenkins 构建完成后主动回调 devops 服务,线程不阻塞等待
  2. 引入消息队列:构建请求和结果都走 MQ,削峰填谷
  3. 定时调度 + 状态机:如果改不了 Jenkins,至少把轮询改成定时任务——一个调度器每 30 秒批量扫所有"构建中"的任务,批量查 Jenkins 状态,这样不管多少任务只需要一个调度线程

我们最终倾向的方案是方案一,改 Jenkins Job 的 Post-build Actions 加上回调地址。

flowchart TB
    A["旧方案线程内每 5 秒轮询 Jenkins"] --> B["构建时间越长线程占用越久"]
    B --> C["并发构建增多线程池被耗尽"]

    A --> D["优化方向"]
    D --> E["Jenkins Callback构建完成主动回调"]
    D --> F["MQ 解耦构建请求和结果异步流转"]
    D --> G["状态机 + 定时调度批量扫描构建中任务"]

    E --> H["线程不再阻塞等待资源占用更可控"]
    F --> H
    G --> H

Q3:如果 devops 服务挂了,正在构建的 Task 怎么办?

这里有个问题——如果 devops 服务宕机,内存里的 Task 会丢失。所以 Task 的存储不能只放内存,应该持久化到数据库或者 Redis。服务重启后从持久化存储里恢复未完成的 Task,重新查询 Jenkins 状态,就能继续走下去。

模块二:K8s 部署与状态判断

Q4:你怎么知道 Pod 重启成功了?重启失败了怎么判断?

重启是通过调用 K8s API,PATCH Deployment 的 spec.template.metadata.annotations,修改 kubectl.kubernetes.io/restartedAt 时间戳来实现的。

判断逻辑是轮询 Deployment 的 status 中的 unavailableReplicas 字段:

  • 重启前:replicas=1, readyReplicas=1, unavailableReplicas=0
  • 重启中:replicas=2, readyReplicas=1, unavailableReplicas=1(旧 Pod 还在,新 Pod 没就绪)
  • 重启成功:replicas=1, readyReplicas=1, unavailableReplicas=0(新 Pod 就绪,旧 Pod 已销毁)

每 10 秒轮询一次,unavailableReplicas == 0 就判定成功。如果超过 4 分钟还是大于 0,就判定失败——可能是 Flyway 迁移失败、Spring 循环依赖等问题导致新 Pod 一直起不来。

失败后会推飞书消息到群里,@提交人,人工介入排查。

sequenceDiagram
    participant Task as devops-task
    participant K8s as K8s API
    participant Deploy as Deployment
    participant Pod as Pod
    participant Feishu as 飞书

    Task->>K8s: PATCH restartedAt annotation
    K8s->>Deploy: 触发滚动更新
    Deploy->>Pod: 创建新 Pod,逐步替换旧 Pod
    loop 每 10 秒检查
        Task->>K8s: 查询 Deployment status
        K8s-->>Task: 返回 unavailableReplicas
    end
    alt unavailableReplicas == 0
        Task->>Feishu: 通知部署成功
    else 超过 4 分钟仍大于 0
        Task->>Feishu: 通知部署失败并 @提交人
    end

Q5:如果这个服务是第一次部署,K8s 里还没有对应的 Deployment,怎么处理?

devops-task 服务在执行重启任务前,会先调用 K8s API 查询该服务是否存在对应的 Deployment。如果查询返回 404(NotFound),说明这个服务还没在 K8s 上部署过。

这时候不会强行执行重启,而是将 Task 标记为 FAILED,原因写"Deployment 不存在",推飞书通知告知需要先在 K8s 上部署该服务。这样避免了无意义的重试。

Q6:K8s 的 ServiceAccount 权限是怎么配置的?

Pod 默认的 SA 是 system:anonymous,没有查询和修改 Deployment 的权限。所以需要创建一个独立的 SA(比如 admin-tester),然后通过 ClusterRoleBinding 绑定到 admin 角色上,让它有权限操作 Deployment。

当然,生产环境不应该直接给 admin 权限,应该遵循最小权限原则——单独创建一个 Role,只授予对 Deployment 的 get、list、patch 权限。

模块三:架构设计与任务管理

Q7:DeploymentTask 的生命周期是怎么管理的?

Task 有四个状态:PENDING(待处理)→ ACCEPTED(被接受)→ FINISHED(已完成)或 FAILED(失败)。

生命周期管理有几个关键点:

  1. TTL 机制:每个 Task 有 7 分钟存活时间。如果 7 分钟内没有被标记为完成,就认为是过期了,需要清理并通知用户
  2. 队列管理:用线程安全的队列存储 Task,addTask 时过滤非目标环境(非 dev/release/pre-master 分支不添加)
  3. 定时清理:clearTtlTask 定期扫描过期任务,状态为 PENDING 或 ACCEPTED 但已过期的,标记为 FAILED 并通知

这里的问题是如果用内存队列,服务重启后 Task 会丢失,所以应该持久化。

flowchart LR
    A["Jenkins 构建成功"] --> B["创建 DeploymentTaskPENDING"]
    B --> C{"是否目标环境dev / release / pre-master"}
    C -->|"否"| D["不入队"]
    C -->|"是"| E["进入任务队列TTL 7min"]
    E --> F["devops-task 拉取任务ACCEPTED"]
    F --> G{"Deployment 是否存在"}
    G -->|"404 NotFound"| H["FAILED通知需先部署服务"]
    G -->|"存在"| I["PATCH Deployment触发滚动重启"]
    I --> J{"4 分钟内是否成功"}
    J -->|"是"| K["FINISHED"]
    J -->|"否"| L["FAILED飞书告警"]
    E --> M["定时清理过期任务"]
    M -->|"超过 7 分钟未完成"| L

Q8:devops 和 devops-task 两个服务为什么要拆成两个?一个不行吗?

拆开的原因是职责和部署环境不同:

  • devops 服务(Java 17,部署在 prod):负责接收工蜂 Webhook、触发 Jenkins 构建、管理 Task 状态。它是"中枢调度器",需要部署在稳定的 prod 环境
  • devops-task 服务(Go 1.19,部署在各环境):负责在每个环境(dev/test/pre)里实际执行 K8s 操作。因为它需要访问各环境的 K8s API,所以部署在各个环境里

如果合成一个服务,就需要让这一个服务同时访问所有环境的 K8s,权限管理和网络策略会很复杂。拆开之后每个环境的服务只操作自己的 K8s,边界清晰。

flowchart TB
    Git["工蜂 Git"] --> Devops["devopsJava 17 / prodWebhook / Jenkins / Task 管理"]
    Jenkins["Jenkins"] --> Devops
    Devops --> Store["Task 存储数据库或 Redis"]

    Store --> DevTaskDev["devops-taskdev 环境"]
    Store --> DevTaskTest["devops-tasktest 环境"]
    Store --> DevTaskPre["devops-taskpre 环境"]

    DevTaskDev --> K8sDev["dev namespace只操作本环境 Deployment"]
    DevTaskTest --> K8sTest["test namespace只操作本环境 Deployment"]
    DevTaskPre --> K8sPre["pre namespace只操作本环境 Deployment"]

    Devops --> Feishu["飞书通知"]
    DevTaskDev --> Feishu
    DevTaskTest --> Feishu
    DevTaskPre --> Feishu

三、架构扩展与优化方向(加分项)

Q9:你觉得这个系统最大的瓶颈在哪里?

几个可以优化的方向:

  1. 构建链路的异步化:当前轮询 Jenkins 状态的方式需要改为回调或 MQ,解决高并发下的资源占用
  2. Task 持久化:当前如果 Task 存内存,服务重启后数据丢失,应该引入 MySQL 或 Redis
  3. 回滚机制:当前只支持"重启",如果新部署有问题,不能一键回滚到上一个版本。可以通过 K8s 的 rollout undo 实现
  4. 灰度发布:当前是全量重启,没有灰度。可以引入 K8s 的 maxSurge/maxUnavailable 配置,或者配合 Istio 做金丝雀发布
  5. 构建缓存优化:可以引入 Harbor 镜像仓库的缓存层,减少重复拉取基础镜像的时间
flowchart LR
    A["当前自动化部署"] --> B["异步化Callback / MQ / 状态机"]
    A --> C["任务可靠性MySQL 或 Redis 持久化"]
    A --> D["发布能力回滚 / 灰度 / 金丝雀"]
    A --> E["构建效率镜像缓存 / 依赖缓存"]

    B --> F["减少线程阻塞"]
    C --> G["服务重启后可恢复任务"]
    D --> H["降低发布故障影响面"]
    E --> I["缩短构建耗时"]

Q10:如果 K8s 集群有多个 namespace,你的系统怎么适配?

devops-task 服务的 DeploymentTaskHandler 里有 namespace 字段,每个环境的 handler 配置对应的 namespace。执行任务时先查 namespace 下是否存在 Deployment,存在就执行 PATCH。通过配置化的方式支持多 namespace,不需要改代码。


四、技术要点速查表

技术点 关键词 面试话术要点
Webhook 防抖 Debounce、延迟触发 2-3分钟内多次push只触发最后一次构建,用ScheduledExecutorService实现
阻塞轮询优化 异步、回调、MQ 线程阻塞等Jenkins构建是反模式,改为回调让Jenkins主动通知
K8s Deployment 重启 PATCH annotation、restartedAt 通过修改 pod template annotation 触发滚动更新
unavailableReplicas 状态判断、滚动更新 归零=成功,超时>0=失败,配合tolerableTime兜底
Task 状态机 PENDING/ACCEPTED/FINISHED/FAILED TTL 7min超时,过期自动清理
K8s SA 权限 ServiceAccount、ClusterRoleBinding 默认anonymous无权限,需创建独立SA绑定角色,生产遵循最小权限
两服务拆分 Java prod / Go 各环境 devops是中枢,devops-task是执行器,按环境拆分边界清晰
失败处理 飞书通知、@提交人、人工介入 启动失败原因复杂(Flyway/循环依赖/配置),自动修复不现实,人工最靠谱
相关阅读

27 分钟

质检模块业务全景与现状梳理

数据来源:7份技术方案文档 + 代码库实际代码扫描 质检模块从0到1经历了以下迭代,每一版都是前一版的改造和扩展: 材料问题、工艺问题、破损问题、二次工艺、印绣花工艺、规格问题、版型问题、外观问题、包装问题、功能问题、性能问题 Contro

面试#简历项目12 阅读 · 0 评论

评论需要登录。生产环境使用 GitHub 登录,本地可前往 登录页 使用开发登录。

还没有评论,来抢沙发。