写点什么

有赞持续集成容器化实践

  • 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:20669

评论

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

Redis二三事之事前预防和事中恢复

NoLongerConfused

3月月更

虎符交易所HOO持续创造今年新高,你的HOO囤够了吗?

区块链前沿News

加密资产 Hoo 虎符交易所 平台币

【ELT.ZIP】OpenHarmony啃论文俱乐部——多维探秘通用无损压缩

ELT.ZIP

OpenHarmony 压缩算法

中国AI的下一站:从两会高地奔涌向产业河谷

脑极体

企业知识管理的目标是什么?

小炮

打造优质的车联网体验,仍需注意数据安全保护

FinClip

数据预处理和特征选择

云智慧AIOps社区

数据挖掘 机器学习 算法 特征选择 数据预处理

小白入门HarmonyOS Connect设备开发的“芯”路历程

HarmonyOS开发者

芯片 HarmonyOS 设备

【直播回顾】OpenHarmony知识赋能第四期直播——标准系统HDF开发

OpenHarmony开发者

直播 HDF OpenHarmony

实用机器学习笔记二十五:超参数优化

打工人!

学习笔记 超参数调优 机器学习算法 3月月更

低代码实现探索(三十七)业务的流程,开发的框架

零道云-混合式低代码平台

java培训:SpringBoot高频面试考点分享

@零度

JAVA开发 springboot

【51单片机】室友用一把王者时间,学会了去使用数码管

謓泽

3月月更

WebRTC 简单入门

ZEGO即构

WebRTC 动手实践 音视频开发 即构科技

移动域全链路可观测架构和关键技术

阿里巴巴终端技术

架构 App 移动端 体验优化

三级等保是最高的吗?有什么用?

行云管家

网络安全 等保 等保2.0

N个技巧,编写更高效 Dockerfile|云效工程师指北

阿里云云效

阿里云 云原生 Dockerfile 部署与维护 构建工具

JavaScript深入理解之闭包

锋享前端

Java基础系列文章---异常

NoLongerConfused

3月月更

昇思MindSpore全场景AI框架 1.6版本,更高的开发效率,更好地服务开发者

Geek_32c4d0

mindspore 昇思 全场景AI框架

【IT运维】多台海外主机运维用什么工具好?

行云管家

服务器 IT运维 服务器运维 海外主机

Go HTTP Server 基于OpenTelemetry 使用Jaeger - 代码实操

非晓为骁

Go Docker Trace Jaeger OpenTelemetry

web前端培训:react高频面试题分享

@零度

前端开发 React

TiDB 可观测性方案落地探索 | “我们这么菜评委不会生气吧”团队访谈

PingCAP

如何进行数据挖掘?

郑州埃文科技

数据挖掘 数据库

Jaeger docker部署实操

非晓为骁

Docker Jaeger Go 语言 http client

教你如何解决JS/TS里特定String进行拆分然后遍历各个元素

华为云开发者联盟

JavaScript string 遍历 字符串 元素

浏览器工作原理和V8引擎

CRMEB

ICASSP 2022 | 前沿音视频成果分享:基于可变形卷积的压缩视频质量增强网络

阿里云视频云

阿里云 计算机视觉 音视频 视频编码 视频云

大数据培训:Hadoop和MPP有什么区别

@零度

hadoop MPP 大数据开发

数字化时代下,智能运维全栈监控解决方案及案例盘点

云智慧AIOps社区

运维 解决方案 场景应用 自动化运维 运维安全

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