Jenkins+Ansible架构迟早要遇到的问题|技术创作特训营第一期

2023-08-24 02:31:20 浏览数 (2)

1. 引言

最近在微信听书上听《万历十五年》,其中一节介绍了一位著名矛盾思想家/政治家——李贽,其以思想矛盾,个性开放,与时代格格不入著称,这也是我对他感兴趣的点。虽然不提倡向他那样一边跟寡妇妓女”勾搭“,一边骂世人知行不一,但是对鄙人的启发是,任何人和事物在某一段时间内都是相互矛盾的结合体。正所谓,福兮祸所依,祸兮福所倚,所以如果你觉得你的架构还挺稳定,那你的思想就挺危险;如果你觉得你很擅长Jenkins Ansible,别得瑟,不妨看看下面我遇到的问题。

我们都知道,作为SRE/Devops/SDLC/Business Developer,当谈到自动化部署和配置管理时,想必对Jenkins 和 Ansible都不陌生,他们都是业界非常流行的开源工具。它们分别专注于不同的领域,但又可以结合使用来构建一个完整的持续集成和持续交付(CI/CD)管道。虽然现在基于Docker和K8S的CI/CD技术横行于世,但鄙人以为基于Jenkins Ansible作为部署架构的公司因该也不在少数。鄙人曾利用此架构完成对几百个模块在上万台机器发布,在此抛砖引玉,分享一点浅薄的优化经验给那些在路上仍然裸奔的人。

2. 我们的架构是什么?

先介绍下架构,方便后面叙述问题。以Jenkins主从结构去控制管理中心节点和子节点,主节点和子节点利用ssh链接,子节点的主控节点使用Ansible管理子节点的集群机器,当然其ssh密钥是不同的,是定期更新的。在安全层面,安全策略也是点对点访问,以避免引发事故。在这里,Jenkins的主要作用就是负责管理和调度命令到集群的agent去执行和回收结果。Ansible主要用于负责分发指令,回调执行结果。

图1 部署架构图1 部署架构

3. 这样的架构会有哪些问题?

当你的集群规模比较小,或者部署不够复杂时可能不会遇到这些问题,但是一旦随着业务的复杂,部署模块的增多,集群规模成倍扩增,部署指令的复杂,下面问题多少都会碰到。

3.1. 当一条指令执行2小时,会怎样?

图2 ssh断开和Ansible轮询执行结果图2 ssh断开和Ansible轮询执行结果

你有没有遇到过这个问题,当你要去部署一个模块,这个模块需要优雅部署,这意味着我们首先需要流量的熔断,再等待已经负载的流量褪去,接下来执行我们的部署流程。好巧不巧,这些流程需要被发布系统管理;在优雅的过程中,这个服务的流量先从一个高点降到0或者我们能接受的范围,那么这个过程就会花费相当长的时间,可能1个小时或者两个小时,也就意味着你使用Playbook执行的命令需要经历这个复杂的场景,所以可能遇到的问题是,如果是同步执行,命令执行时间过长,ssh链接失去保活,如果是异步执行,那么你就要去轮训执行结果,这里也会遇到一个问题,ansible轮询结果时,非常耗节点CPU,再就是ssh频繁重联,尤其是批量部署多个机器(几十或者几百)。

那么需要怎么优化这个过程呢?

3.1.1. Ansible的异步

Ansible可以执行同步和异步的任务,让我们来了解一下Ansible的异步过程:

异步任务在Ansible中是指那些需要较长时间才能完成的任务,例如启动大规模的应用程序部署、升级操作系统等。在同步任务中,Ansible会等待每个任务完成后再继续执行下一个任务。而在异步任务中,Ansible会启动任务并立即继续执行下一个任务,而不必等待异步任务完成。让我门给个Ansible中处理异步任务例子先:

1.启动异步任务:通过在Playbook中使用async关键字,你可以指定一个任务作为异步任务。例如:

代码语言:yaml复制
   - name: Start long-running task
     command: /path/to/long_task.sh
     async: 3600  # 指定任务超时时间(以秒为单位)
     poll: 0  # 指定异步任务轮询频率
     register: async_result

在这个例子中,async参数指定了任务的超时时间,poll参数指定了轮询任务状态的频率。

2.等待任务完成:在启动异步任务后,Ansible会继续执行下一个任务,但同时会为异步任务注册一个变量(这里是async_result)。你可以使用async_result变量来检查异步任务的状态。

3.轮询任务状态:使用async_status模块来轮询异步任务的状态,例如:

代码语言:yaml复制
   - name: Wait for async task to complete
     async_status:
       jid: "{{ async_result.ansible_job_id }}"
     register: job_result
     until: job_result.finished
     retries: 30
     delay: 10

在这个例子中,async_status模块会检查异步任务的状态,直到任务完成为止。

4.处理任务结果:一旦异步任务完成,你可以使用相关的模块(例如debugnotify等)来处理任务的结果。例如:

代码语言:yaml复制
   - name: Print async task result
     debug:
       var: job_result

所以Ansible的异步过程允许你在后台运行长时间运行的任务,而不会阻塞主要的Ansible执行流程。这对于处理复杂的、耗时的操作非常有用,但需要适当的设置和处理来确保任务的顺利执行和结果处理。

3.1.2. 优化ssh链接

上述问题中我们可以使用SSH长连接来解决频繁重连耗cpu的问题,因为建立SSH连接涉及加密协商和身份验证等步骤,因此长连接可以减少这些开销,提高连接的效率。在大多数情况下,SSH会话会在用户退出或超时时自动关闭,但可以通过配置来实现长连接。在SSH配置文件(通常位于/etc/ssh/sshd_config)中添加以下行可以延长会话的超时时间:

代码语言:shell复制
ClientAliveInterval 120 #second
ClientAliveCountMax 1440 #second

上述配置会使服务器每120秒发送一个保活消息,如果在1440个保活消息(即2小时)内没有收到客户端的响应,则会终止会话。

另外一个参数是TCPKeepAlive, 可以通过在SSH客户端和服务器的SSH配置文件中启用TCP KeepAlive来确保底层TCP连接的活跃性。这可以在/etc/ssh/ssh_config(客户端)和/etc/ssh/sshd_config(服务器)中设置。

代码语言:txt复制
TCPKeepAlive yes

最后一个要说的是SSH Multiplexing,这允许你在同一个连接上同时执行多个会话。这可以通过设置ControlPersist选项来实现,以及使用SSH配置文件中的mux选项。

代码语言:txt复制
ControlPersist yes

另外可以修改ansible的配置文件,也可以达到类似效果:

代码语言:shell复制
[ssh_connection]
pipelining = True           # 启用管道化,减少通信次数
control_path = ~/.ansible/cp/ansible-ssh-%%h-%%p-%%r  # 控制连接的路径,实现持久连接
ssh_args = -o ControlMaster=auto -o ControlPersist=30m  # 控制连接参数,控制持久连接的生命周期
[persistent_connection]
command_timeout = 3600 #持久化链接

3.1.3. 优化ansible代码

假如你还是使用2.9以下的版本,那么就需要考虑降低工作进程(Worker Process)列表检查频率,可以将其设置为500毫秒或者1秒,以避免频繁检查works列表,详情可以参考https://blog.csdn.net/avenger19/article/details/102858359,这里不作赘述。

另外一个是调整DEFAULT_INTERNAL_POLL_INTERVAL参数,降低结果处理轮询的频率,,详情可以参考:https://blog.csdn.net/avenger19/article/details/102860119,这里也不作赘述。

3.1.4. 让ansible起飞的plugin

随着问题和体量增加,就想着如何加速ansible,无意间发现一个神奇的插件Mitogen,据说可以提升1-7倍的速度。主要原理是通过高效的远程过程调用来取代 ansible 默认的嵌入式与纯 python shell 调用,它不会优化模块本身的执行效率,只会尽可能快的去执行模块获取返回(执行模块前也是有一系列连接,发送数据,传输渲染脚本等操作的)来提高整体的效率。这里是官网可以参考使用:https://mitogen.readthedocs.io/en/latest/howitworks.html。用了都说好。

3.2 Jenkins ansible plugin的bug?

鄙人所遇到的现象是,当有多个host分批异步执行时(ansible支持serial等场景),假设治理10个hosts,分2批,每批5个hosts,那么奇怪的现象就出现了,第一批跑完之后,第二批的inventory文件就被删除了,然而第一批的inventory文件还存在。后来审视了下plugin的源码:https://github.com/jenkinsci/ansible-plugin/blob/main/src/main/java/org/jenkinsci/plugins/ansible/InventoryContent.java,代码里的inventory是全局变量,在变更时没有加锁,后来在完成了修复。

3.3 如何回收执行结果?

因为playbook是task by task执行的,所以为了收集每个task的运行结果,我们当然少不了使用callback plugin。首先在playbook里定义了name的规范,例如:

代码语言:shell复制
- name: STEPS-1 backup_package host_name mv_{{xxx}}_{{yyy}}

然后在callback plugin解析name,并拿到每个task的结果,callback到主节点分析处理。

4.写在最后

大明李贽生不逢时,好比现代人去了大明,不被抓去杀头,也要被看作怪胎,但是李贽就是李贽,别人说错了,也要坚信自己是对的,即使自己真的错了。因为矛盾的个体总有两面性,而非非黑即白。辩证看问题是基本逻辑,更需要长远而深邃地剖析,最后践行知行合一理念,那么你就有可能觉得本文不只是一篇教科书式的文章,而是在引导你前行。

【选题思路】

本文主要由听书所引发的一些做人做事的道理,引出对待工作对待自己的态度,进而引出问题,引导读者深入文章,再提出本人曾遇到过的一些问题,一一分析解答。最后总结,本文不是教科书,只是引导你去独立思考,付诸实践。

【写作提纲】

  1. 如何切题?——由个人经历引入并介绍当前的技术背景 2.先铺垫好路,让读者了解清楚架构,避免误入歧途 3.由问题切入,这样的架构会遇到什么问题,列举一些可能遇到的疑难问题,并一一剖析解答 4.总结升华主题。

0 人点赞