【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

有赞持续集成容器化实践

  • 2020-03-15
  • 本文字数:3773 字

    阅读完需:约 12 分钟

有赞持续集成容器化实践

一、背景介绍

目前我厂 Jenkins CI 采用的是 Master-Slave 架构, Master 和 Slave 都是物理机搭建。主要用于跑单测,集成测试等。由于早期没有专人来管理 Jenkins ,随着业务的发展 Jenkins Job 越来越多,也带来了如下问题:


  1. 当 Job 越来越多时需要通过增加 Slave 机器来解决,新增 Slave 上的软件得重新安装。

  2. 资源分配不均衡有浪费,有的 Slave 上运行的 Job 出现排队等待,而有的 Slave 处于空闲状态。并且当 Slave 处于空闲状态时,也不会完全释放掉资源。

  3. 每个 Slave 总有点差异维护起来比较麻烦。

  4. 当 Master 有故障时,整个流程都不可用。

二、整体方案设计

为了解决以上问题,减少 Jenkins 维护成本降低机器成本等。我们决定采用现下比较流行的 kubernetes Jenkins CI/CD 技术,将 Jenkins Master 和 Slave 交给 k8s 动态调度。下图是基于 K8s 搭建 Jenkins 集群的简单示意图:



从上图中可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 K8s 集群的 Node 上,Master 运行在其中一个节点,Slave 运行在各个节点上,Slave 的运行将按照需求去动态创建。


工作流程:当调用 Jenkins Master API 发起构建请求时,Jenkins k8s plugin 会根据 Job 配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当 Job 结束后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态,这样集群资源得到充分的利用。


使用容器化和 K8s 动态创建 Slave 优势:


  • Master 服务高可用,当 Jenkins Master 出现故障时,K8s 会自动创建一个新的 Jenkins Master 容器。

  • 动态伸缩合理使用资源,每次构建 Job 时,会根据配置自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 K8s 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还在该节点排队等待的情况。

  • 扩展性好,当 K8s 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

三、部署 Jenkins Master、Sonarqube

3.1 Jenkins Master 部署

由于我们采用 K8s 集群部署,首先得制作 Jenkins Master 镜像。当然也可以使用 Jenkins 官网的上镜像 jenkins/jenkins:lts,因为我们有一些需求,所以需要自己制作。下面是制作镜像中个人认为需要注意的地方:


需要 EXPOSE 2 个端口,Jenkins Web 访问端口和 JNLP 代理协议的 TCP 端口( jnlp-slave 连接 Master 使用的端口)。


JNLP 代理协议的 TCP 端口: 由于 Jenkins-Master 是在容器中启动的,所以一定要将这个端口暴露到外部,不然 Jenkins-Master 不知道 Slave 是否已经启动,会反复去创建 Pod 直到超过重试次数。


Jenkins Master 若要动态创建 Slave 需要安装配置 Kubernetes Plugin,这里可以参考 K8S 在有赞 PaaS 测试环境中的实践 里面有介绍,或在网上找资料。

3.2 Sonarqube 部署

CI/CD 中 Sonarqube 也是必不可少的,用于代码质量管理等。由于 Sonarqube 有一些规则等配置需要在启动时加载好,所以需要重新制作镜像。这里镜像制作分为 2 部分:


  • 第一部分:Mysql 镜像制作,包含 Sonar 数据库和 Sonar 用户创建,导入 Sonarqube 初始化数据,启动 Mysql。

  • 第二部分:基于上面的 Mysql 镜像再制作 Sonarqube 镜像。


这里我们是把 Mysql 和 Sonarqube 集成在一个镜像里,当然也可以分开。下面是制作 Mysql 镜像的部分 Dockerfile:


FROM mysql:5.7#设置免密登录ENV MYSQL_ALLOW_EMPTY_PASSWORD yes
#将所需文件放到容器中COPY mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnfCOPY setup.sh /mysql/setup.shCOPY privileges.sql /mysql/privileges.sqlCOPY sonar.sql /mysql/sonar.sqlCOPY init_sonar.sql /mysql/init_sonar.sqlCOPY run-entrypoint.sh /mysql/run-entrypoint.shCOPY start.sh /mysql/start.sh........#设置容器启动时执行的命令ENTRYPOINT ["/mysql/run-entrypoint.sh", "/mysql/setup.sh"]
复制代码


  • privileges.sql 创建 sonar 数据库,配置 SonarQube Server 访问数据库用户的权限。

  • sonar.sql sonarqube 初始化配置的数据库表和数据。

  • init_sonar.sql 将 sonar 数据库表和数据导入 sonar 数据库。

  • start.sh 启动 mysql 并执行以上 sql 文本。

3.3 Jenkins Slave 制作

Jenkins Java Slave 我们参考的官网制作并添加了一些我们自己包(官方提供的 jenkins/ssh-slave,官方文档中有说明,这个镜像安装了 JDK 和 sshd,有兴趣的同学也可以自己制作),其中 Nodejs 、Python Slave 制作和 Java Slave 类似,网上也有资料这里就不详细介绍了。


制作完的镜像需推送到镜像仓库中保存, 下面是构建和推送镜像的命令:


docker build -t [IMAGE:TAG] . docker tag SOURCE_IMAGE[:TAG] harbor.xxx.com/xxx/IMAGE[:TAG]docker push harbor.xxx.com/xxx/IMAGE[:TAG]
复制代码

四、系统集成


如上图所示,有需求的同学可以在有赞 QA 平台发起创建业务线容器,后台会调用 k8s api 创建 Jenkins 、 Sonarqube 容器,并返回访问地址。如下图:





这里我们使用的 k8s 客户端是 fabric8io/kubernetes-client 项目,需要在项目的 pom 文件中加入 kubernetes-client 依赖:


    <dependency>      <groupId>io.fabric8</groupId>      <artifactId>kubernetes-client</artifactId>      <version>4.1.0</version>    </dependency>
复制代码

4.1 Create Deployment

Deployment 为 Pod 和 Replica Set(下一代 Replication Controller)提供声明式更新。只需要在 Deployment 中描述你想要的目标状态是什么,Deployment controller 就会帮你将 Pod 和 ReplicaSet 的实际状态改变到您的目标状态。

4.2 Create Service

Service 通过 Label Selector 跟服务中的 Pod 绑定,为 Pod 中的服务类应用提供了一个稳定的访问入口。通过使用 Service,我们就可以不用关心这个服务下面的 Pod 的增加和减少、故障重启等,只需通过 Service 就能够访问到对应服务的容器。

4.3 Create Ingress

Service 虽然可以 LB, NodePort 对外提供服务,但是当集群服务很多的时候,NodePort 方式最大的缺点是会占用很多集群机器的端口,LB 方式最大的缺点则是每个 Service 一个 LB 又有点浪费和麻烦,并且需要 K8s 之外的支持, 而 Ingress 则只需要一个 NodePort 或者一个 LB 就可以满足所有 service 对外服务的需求。注意点:


  • K8s 集群中,将图片或是文件上传到文件服务器上,如文件大于 1M 会报错所以 Ingress 的 Annotations 需要配置下"nginx.ingress.kubernetes.io/proxy-body-size", “600m”

  • 由于 Jenkins Master 容器起来 K8s 插件配置信息都需要初始化好,就需要知道起来后 Pod Node Ip,这里可以通过 Env 来获取 Pod Node Ip


   env:    - name: MY_POD_IP     valueFrom:      fieldRef:       fieldPath: status.podIP    status.podIP :pod IP
复制代码


  • 前端访问 Jenkins Master 时会存在跨域问题,在 Ingress 中,跨域(CORS)的配置如下:


  nginx.ingress.kubernetes.io/cors-allow-headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization  nginx.ingress.kubernetes.io/cors-allow-methods: PUT, GET, POST, OPTIONS  nginx.ingress.kubernetes.io/cors-allow-origin: '*'  nginx.ingress.kubernetes.io/enable-cors: "true"  nginx.ingress.kubernetes.io/proxy-body-size: 600m
复制代码

五、K8s Web Terminal

当需要进入容器内执行一些 shell 命令时,web terminal 可以让我们更方便的访问 container,执行 shell 命令,提高工作效率。如下图:



实现:



前端用 xterm.js 库,它是模拟一个 terminal 在浏览器中,此时并没有通讯能力。需要在后端搭建 k8s-websocket 服务。前端建立 websocket,连到后台搭建的 k8s-websocket 服务端。服务端会基于 k8s 的 remotecommand 包,建立与 container 的 ssh 长连接。我们将输入输出写入到 Websocket 流中即可,当浏览器中 terminal 大小改变了,前端应该把最新的 terminal 大小发给服务端,服务端模拟终端也要相应的 resize。


遇到的问题:


由于我们使用的 kubernetes-client 当时只提供了 pod 启动时,初始化 terminal 大小的功能,未实现 resize 功能。当浏览器中的 terminal 的大小改变时,由于与初始化时传递的行列数不同,导致显示不全或显示区域过小的问题。在查阅资料的过程中发现 k8s 的 remotecommand 实际上是提供了该功能的(详情可见 remotecommand.go)。此文件中,定义 resizeChannel 为 4,即将发送的命令 byte 数组的第一位修改为 4,就可发送 resize 的相关命令。

后记

测试的时候发现 K8s Slave 调度速度比较慢,尤其是多个同类型的 Slave 并行需要等待比较长的时间,上网查询发下默认情况下 Jenkins 保守地生成代理。如果队列中有 2 个构建,不会立即生成 2 个执行程序。会产生一个执行器并等待一段时间看第一个执行器有没有被释放,然后再决定产生第二个执行器。以确保产生的每个执行者都得到最大限度的利用。如果要覆盖此行为并立即为队列中的每个构建生成执行程序,可以在 Jenkins Mater 启动时参加一下参数:


  • Dhudson.slaves.NodeProvisioner.initialDelay=0

  • Dhudson.slaves.NodeProvisioner.MARGIN=50

  • Dhudson.slaves.NodeProvisioner.MARGIN0=0.85


总而言之 K8s 博大精深,在 CI/CD 容器化的道路上还有很多知识点需要去学习。


公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2020-03-15 20:20676

评论

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

使用 awk 命令统计文本

程序员小航

后端 awk Linxu

平衡人工智能的性能要求,实现可信AI在银行业务场景的高质量应用

易观分析

人工智能

企业的文档管理策略

小炮

文档管理

Scrum实施的8个步骤

PingCode

数据行业中的建模是什么?

清林情报分析师

数据分析 行业分析 数据建模 业务思维 模型思维

聚焦无障碍阅读,福昕捐出2000套福昕高级PDF编辑器

联营汇聚

TiDB 查询优化及调优系列(四)查询执行计划的调整及优化原理

PingCAP

PingCAP Clinic 服务:贯穿云上云下的 TiDB 集群诊断服务

PingCAP

大数据学习必备 | 推荐几个牛X 的 github 项目,助你事半功倍

大数据梦想家

大数据 学习资料 Github'

云原生时代,热门监控工具对比与使用场景分析

云智慧AIOps社区

云原生 监控 Grafana Prometheus 监控宝

Kernel SIG直播:关于 Plugsched 调度器热升级 | 第 18 期

OpenAnolis小助手

Linux 直播 内核 sig 龙蜥大讲堂

「v2.4」千呼万唤的图形化编排,来了!

Jianmu

持续集成 低代码 开发工具 开源项目 节点编排

为什么穷人越穷,富人越富?

大数据梦想家

程序人生

前沿聚焦:2022最受关注的六大技术热词,你都知道吗?

华为云开发者联盟

零代码 NFT 数字人 元宇宙 云边端协同

vue + electronの文件读写

空城机

Electron 5月月更

7 款最棒的开源 React UI 库测评 - 特别针对国内使用场景推荐

蒋川

JavaScript react.js 开源 UI 组件库

eKuiper 1.5.0发布:实现无缝式工业数据采集+边缘流处理

EMQ映云科技

物联网 IoT emq 开源之夏 5月月更

IoT技术的最后决战!百万大奖究竟花落谁家?

华为云开发者联盟

IoT 华为云 iotda NSBD-IOT

技术立根,行业立范,发展立本:中国工业互联网的2.0新征程

脑极体

Java Core「4」java.util.concurrent 包简介

Samson

学习笔记 5月月更 Java core

十一、云原生网络微隔离

穿过生命散发芬芳

5月月更 微隔离

深入 HTTP/3(2)|不那么 Boring 的 SSL

SOFAStack

互联网 TLS HTTP3.0 QUIC协议 HTTP API

又一国际知名律师事务所选择福昕PDF

联营汇聚

Kube-OVN v1.10.0:新增Windows节点支持,用户自定义子网ACL等10+硬核功能

York

云原生 网络 cni

英特尔开源项目推动SYCL标准采用,打破单个厂商封闭生态系统

科技之家

百度智能小程序巡检调度方案演进之路

百度Geek说

开发一个社区网站,只要20分钟?

华为云开发者联盟

ide 敏捷开发 devcloud 网站开发

通用池化框架实践之GenericKeyedObjectPool

FunTester

末流985,秋招斩获多家大厂offer 经验分享

大数据梦想家

面经分享 大数据开发

Linux 使用 cp 命令强制覆盖功能

AlwaysBeta

Linux 运维

HDFS 细粒度锁优化,FusionInsight MRS有妙招

华为云开发者联盟

hdfs NameNode 元数据 FusionInsight MRS FGL

有赞持续集成容器化实践_文化 & 方法_余霞_InfoQ精选文章