QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

知乎容器化构建系统设计和实践

  • 2020-03-26
  • 本文字数:4315 字

    阅读完需:约 14 分钟

知乎容器化构建系统设计和实践

Table of Content:


  • 关于

  • 背景

  • 完整的生命周期

  • 达到的目标以及中间遇到的问题

  • 较低的接入成本,较高的定制能力

  • 语言开放性

  • 减少不稳定构建,降低问题复现成本

  • 缓存的设计

  • 依赖获取稳定性

  • 更低的排查错误的成本

  • 规范和标准的落地抓手

  • 高可用和可扩展的集群

  • Job 调度策略

  • 集群设计和故障恢复

  • 监控报警

  • 后续的计划

  • 参考资料

关于

知乎应用平台团队基于 Jenkins Pipeline 和 Docker 打造了一套持续集成系统。Jenkins Master 和 Slave 基于 Docker 部署,每次构建也是在容器中进行。目前有三千个 Jenkins Job,支撑着整个团队每日近万次的构建和部署量。


整个系统的设计目标是具备以下的能力:


  • 较低的应用接入成本,较高的定制能力:写一个构建系统配置文件成本要尽可能简单方便,或者可以通过模板一键创建,但又要能满足应用的各种定制化的需求。

  • 具备语言开放性和部署多样性:平台需要能支撑业务技术选型上的多语言,同时,要能满足应用不同的部署类型,如单纯的打包发布,或者进一步部署到物理机、容器、离线任务平台等。

  • 构建快和稳定,复现问题成本低:每次构建都在干净的容器中,减少非应用本身问题带来的构建异常。同时,如果构建出现问题,在权限控制的前提下,要能方便开发者自己调试和排查。

  • 推动业界标准以及最佳实践,同时在代码合并之前就能更好把控住质量。

  • 整个集群高可用,可扩展,以及具备较低的运维成本。

背景

知乎选用 Jenkins 作为构建方案,因其强大和灵活,且有非常丰富的插件可供使用和扩展。


早期,应用数量较少时,每个开发者都手动创建并维护着几个 Job,各自编写 Jenkins Job 的配置,以及手动触发构建。随着服务化以及业务类型,开发者以及 Jenkins Job 数量的增加,我们面临了以下的问题:


  • 每个开发者都需要去理解 Jenkins 的基本配置和触发逻辑,使得配置创建和维护成本高。

  • 构建在物理机上进行,每个应用可能有着不同的版本依赖,构建时会遇到版本冲突,甚至上线之后发现行为不一致导致故障等。

  • 构建一旦失败,需要开发者能登录 Jenkins Slave 所在的物理机进行调试,权限控制成为了一个问题


于是,一个能方便应用接入构建部署的系统,成为了必须。

完整的生命周期

知乎的构建工作流主要是以下两种场景:


  • 只有 Master 分支的代码可以用于线上部署,但支持指定任意的分支进行构建

  • 所有对 Master 分支的修改必须通过 Merge Request 来进行。为了避免潜在代码冲突导致测试结果不准的情况,对 Merge Request 上的代码进行构建前,会模拟跟 Master 分支的代码做一次合并。


一个 Commit 从提交到最后部署,会经历以下的环节:



  1. 开发者提交代码到 GitLab。

  2. GitLab 通过 Webhook 通知到 ZAE (Zhihu App Engine, 知乎的私有云平台)。

  3. ZAE 将构建的上下文信息,如 GitLab 仓库 ID,ZAE 应用信息给到构建系统 Lavie。目前只处理用户提交 MR 以及合并到 Master 分支的事件。

  4. 构建系统 Lavie 读取应用仓库中的配置文件后生成配置,触发一个构建。在构建过程中获取动态生成的 Jenkinsfile,生成 Dockerfile 构建出应用的镜像,并跑起容器,在容器中执行构建,测试等应用指定的步骤。

  5. 测试成功之后,分别往物理机部署平台,容器部署平台,离线任务平台上传 Artifact,注册待发布版本的信息,并 Slack 通知用户结果。

  6. 构建结束,用户在 ZAE 上可以进行后续操作,如选择一个候选版本进行部署。


每个应用的拉取代码,准备数据库,处理测试覆盖率,发送消息,候选版本的注册等通用的部分,都会由构建系统统一处理,而接入构建系统的应用,只需要在代码仓库中包含一个约定格式的配置文件。

达到的目标以及中间遇到的问题

1. 较低的接入成本,较高的定制能力

构建系统去理解应用要做的事情靠的是约定格式的 yaml 配置文件,而我们希望这个配置文件能足够简单,声明上必要的部分,如环境、构建、测试步骤就能开始构建。


同时,也要有能力提供更多的定制功能让应用可以使用,如选择系统依赖和版本,缓存的路径,是否需要构建系统提供 MySQL 以及需要的 MySQL 版本等。以及可以根据应用的类别自动生成配置文件。


一个最简单的应用场景:


base_image: python2/jessie
build: - buildout
test: unittest: - bin/test --cover-package=pin --with-xunit --with-coverage --cover-xml
复制代码


一个更多定制化的场景:


base_image: py_node/jessiedeps:  - libffi-devbuild:  - buildout  - cd admin && npm install && gulptest:  deps:    - mysql:5.7  unittest:    - bin/test --cover-package=lived,liveweb --with-xunit --with-coverage  coverage_test:    report_fpath: coverage.xmlpost_build:  scripts:    - /bin/bash scripts/release_sentry.shartifacts:  targets:    - docker    - tarballcache:  directories:    - admin/static/components    - admin/node_modules
复制代码


为了尽可能满足多样化的业务场景,我们主要将配置文件分为三部分:声明环境和依赖、构建相关核心环节、声明 Artifact 类型。


声明环境和依赖


  • image,基础镜像,需要指明已提前准备好的语言镜像

  • deps,dependencies 的简写, 声明使用的系统依赖以及对应的版本


构建相关核心环节


  • build,构建的步骤,如 buildout, npm install ,或者执行一个脚本

  • test,测试环节,应用需要声明构建的步骤,也可以在这里定制使用的 MySQL 以及对应的版本。构建系统会每次为其创建新的数据库,将关键信息 export 为环境变量。

  • post build,最后一个环节,如发包,发 Slack 、邮件通知,或发布一个 Sentry release 等


声明 Artifact 类型


artifact,用于选择部署的类型, 目前支持的有:


  • tarball:构建系统会将整个应用 Workspace 打包上传到 HDFS 用于后续的物理机部署

  • docker:镜像会被 push 到私有的 Docker Registry 用于容器部署

  • static:应用指定的路径打包后会被上传到 HDFS,用于后续的静态资源部署

  • offline: 应用指定的文件会被上传到离线平台,用于离线任务的执行

2. 语言开放性

早期所有的构建都在物理机上进行,构建之前需要提前在物理机上安装好对应的系统依赖,而如果遇到所需要的版本不同时,调度和维护的成本就高了很多。随着团队业务数量和种类的增加,技术选型的演进,这样的挑战越来越大。于是构建系统整体的优化方向由物理机向 Docker 容器化前进,如今,所有构建都容器中进行,基础的语言镜像由应用自己选择。


目前镜像管理的方式是:


  • 我们会事先准备好系统的基础镜像

  • 在系统镜像的基础上,会构建出不同的语言镜像供应用使用,如 Python,Golang,Java,Node,Rust 的各种版本以及混合语言的镜像。

  • 在应用指定的image语言镜像之上,会安装上 deps 指定的系统依赖,再构建出应用的镜像,应用会在这个环境里面进行构建测试等。



语言这一层的 Dockerfile 会被严格 review,通过的镜像才能被使用,以更好了解和支持业务技术选型和使用场景。

3. 减少不稳定构建,降低问题复现成本

缓存的设计

最开始构建的缓存是落在对应的 Jenkins Slave 上的,随着 Slave 数量的增多,应用构建被分配到不同 Slave 带来的代价也越来越大。


为了让 Slave 的管理更加灵活以及构建速度和 Slave 无关,我们最后将缓存按照应用使用的镜像和系统依赖作为缓存的标识,上传到 HDFS。在每次构建前拉取,构建之后再上传更新。


针对镜像涉及到的语言,我们会对常见的依赖进行缓存,如 eggs, node_modules, .ivy2/cache, .ivy2/repository。应用如果有其他的文件想要缓存,也支持在配置文件中指定。

依赖获取稳定性

在对整个构建时间的开销和不稳定因素的观察中,我们发现拉取外部依赖是个非常耗时且失败率较高的环节。


为了让这个过程更加稳定,我们做了以下的事情:


  • 完善内部不同语言的源

  • 在不同语言的基础镜像中放入优先使用内部源的配置

  • 搭建 HTTP Proxy,提供给以上覆盖不到的场景

更低的排查错误的成本

本地开发和构建环境存在明显的差异,可能会出现本地构建成功但是在构建系统失败的情况。


为了让用户能够快速重现,我们在项目 docker-ssh 的基础上做了二次开发,支持直接 ssh 到容器进行调试。由于容器环境与其他人的构建相隔离,我们不必担心 ssh 权限导致的各种安全问题。构建失败的容器会多保留一天,之后便被回收。

4. 规范和标准的落地抓手

我们希望能给接入到构建系统的提高效率的同时,也希望能推动一些标准或者好的实践,比如完善测试。


围绕着测试和测试覆盖率,我们做了以下的事情:


  • 配置文件中强制要有测试环节。

  • 应用测试结束之后,取到代码覆盖率的报告并打点。在提交的 Merge Request 评论中会给出现在的值和主分支的值的比较,以及最近主分支代码覆盖率的变化趋势。

  • 在知乎有应用重要性的分级,对于重要的应用,构建系统会对其要求有测试覆盖率报告,以及更高的测试覆盖率。



对于团队内或者业界的基础库,如果发现有更稳定版本或者发现有严重问题,构建系统会按照应用的重要性,从低到高提示应用去升级或者去掉对应依赖。


5. 高可用和可扩展的集群

Job 调度策略


Jenkins Master 只进行任务的调度,而实际执行是在不同的 Jenkins Node 上。


每个 Node 会被赋予一些 label 用于任务调度,比如:mysql:5.6, mysql:5.7, common 等。构建系统会根据应用的类型分配到不同的 label,由 Jenkins Master 去进一步调度任务到对应的 Node 上。


高可用设计


集群的设计如下,一个 Node 对应的是一台物理机,上面跑了 Jenkins Slave (分别连 Master 和 Master Standby),Docker Deamon 和 MySQL(为应用提供测试的 MySQL)。



Slave 连接 Master 等待被调度,而当 Jenkins Slave 出现故障时,只需摘掉这台 Slave 的 label,后续将不会有任务调度调度上来。


而当 Jenkins Master 故障时,如果不能短时间启动起来时,集群可能就处于不可用状态了,从而影响整个构建部署。为了减少这种情况带来的不可用,我们采用了双 Master 模型,一台作为 Standby,如果其中一台出现异常就切换到另一台健康的 Master。


监控和报警


为了更好监控集群的运行状态,及时发现集群故障,我们加了一系列的监控报警,如:


  • 两个 Jenkins Master 是否可用,当前的排队数量情况。

  • 集群里面所有 Jenkins Node 的在线状态,Node 被命中的情况。

  • Jenkins Job 的执行时间,是否有不合理的过长构建或者卡住。

  • 以及集群机器的 CPU,内存,磁盘使用情况。

后续的计划

在未来我们还希望完善以下的方面:


  • Jenkins Slave 能更根据集群的负载情况进行动态扩容。

  • 一个节点故障时能自动下掉并重新分配已经在上面执行的任务。一个 Master down 掉能被主动探测到并发生切换。

  • 在 Merge Request 的构建环节推动更多的质量保证标准实施,如更多的接口自动化测试,减少有问题的代码被合并到主分支。


主要开发者(排名不分先后):


@Amyyyyy


@Cosven

参考资料


2020-03-26 19:00794

评论

发布
暂无评论
发现更多内容

火山引擎VeDI:新增微信小程序广告A/B实验功能,助力企业降低获客成本

字节跳动数据平台

数据库 大数据 ab测试 企业号 1 月 PK 榜 对比实验

阿里云 Flink 原理分析与应用:深入探索 MongoDB Schema Inference

Apache Flink

foobar2000 for mac多功能音频播放器 v2.6.1免激活版

Rose

mac音乐播放器 foobar2000中文版 foobar2000破解版

万字图解|深入揭秘 (数据链路层、物理层) 工作原理

云舒编程

IP 物理层 路由 图解网络 数据链路层

QSpace Pro 一款简洁高效的多窗格文件管理器,灵活且实用!

Rose

Mac软件 QSpace 多窗格文件管理器

【技术探讨】如何选择一款距离远的无线通信模块?

Geek_ab1536

CES 2024的亮点仅仅聚焦AI深度赋能和产业创新吗?| DALL-E 3、Stable Diffusion等20+ 图像生成模型综述

GPU算力

人工智能大模型多场景应用原理解析

百度开发者中心

人工智能 图像识别 大模型

合合信息启信数据发布园区金融解决方案,助力银行精准服务“十四五”特色产业

合合技术团队

大数据 金融 合合信息 启信慧眼

WorkPlus移动应用管理平台,助力企业实现高效移动办公

BeeWorks

苹果macos效率神器alfred5新功能介绍 及alfred 5汉化包下载

Rose

mac软件下载 Alfred 5破解版 Alfred 中文 Mac效率办公软件

《Hive编程指南》读书笔记

京东科技开发者

租赁舞台LED屏的注意事项及问题排除

Dylan

活动 LED显示屏 led显示屏厂家 效果广告

跨境电商如何利用item_get-根据ID取商品详情(shopee.item_get)提升用户体验?

技术冰糖葫芦

API 编排

NineData和Klustron完成产品兼容互认证

NineData

数据库 数据管理 NineData Klustron 泽拓昆仑

WorkPlus构建便捷高效的企业移动门户平台

BeeWorks

想在 Mac 里装 Windows ?试试 Parallels Desktop虚拟机!

Rose

Windows系统 Mac双系统安装 Parallels Desktop

得物从零构建亿级消息推送系统的送达稳定性监控体系技术实践

JackJiang

网络编程 即时通讯 IM

大规模集群下,如何快速实现无死角网络连通性的主动巡检

ii2day

云原生 压力测试 Cloud Native kubernetes 运维 自动巡检

软件测试/测试开发|学习两个个月后拿到4个知名企业Offer,他是怎么做到的?

霍格沃兹测试开发学社

AI大模型在电商商家端自定义报表分析中的应用与实践

百度开发者中心

人工智能 电商 大模型

热更新适配ibatis原理浅析

京东科技开发者

AI大模型低成本快速定制秘诀:RAG和向量数据库

百度开发者中心

人工智能 数据库 大模型

荣耀开发者大会 2023 · 一张图读懂极致体验分论坛

荣耀开发者服务平台

用游戏盾会掉线吗,游戏出现掉线或者卡顿的可能有哪些原因

德迅云安全杨德俊

定制+轻量级低代码:满足客户个性需求的最佳实践

天津汇柏科技有限公司

低代码 软件定制开发 软件开发定制

万字图解 | 深入揭秘IP层工作原理

云舒编程

IP MTU 路由表 子网划分 图解网络

4个知名企业Offer拿到手软,他是怎么做到的?附面试真题

测试人

软件测试

三个方面浅析数据对大语言模型的影响

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 大语言模型

测试管理 | 入班第二个月后拿到4个知名企业Offer,他是怎么做到的?

测吧(北京)科技有限公司

测试

知乎容器化构建系统设计和实践_文化 & 方法_Amyyyyy_InfoQ精选文章