使用 Jenkins + Ansible 实现跨应用配置管理

本文继续前两篇 Jenkins + Ansible 的文章(见附录)的例子。代码仓库结构与 《使用 Jenkins + Ansible 实现 Spring Boot 自动化部署101》 介绍的相似。 但是以下改进: 增加了展示跨应用配置管理的样例(本文重点) 实现了二进制包与配置分离 跨应用配置是什么 《持续交付》的2.4.4节介绍了“跨应用配置管理”。但是书中没有明确给出它的定义。以下是笔者所理解的“跨应用配置”: 所谓跨应用配置指的是在同一个配置项同时被多个应用引用。 比如现实中同一个 Redis 的配置项(如地址、端口)就可能同时被多个业务系统引用。如下图所示。 为什么要进行跨应用的配置管理 如果没有跨应用配置的管理,我们就必须在应用1和应用2的配置文件中写死 redis 的配置项(在没有配置中心的情况下)。这样一看是没有问题的。但是笔者认为应用在到达10个以上的时候会(经常)遇到以下问题: 无法实现快速重建一整套新的环境。新的环境意味着新的 redis 地址。也意味着所有引用了 redis 地址的应用的配置都要改。手工修改很容易出错。 当你希望对现有的 redis 进行调整时,你无法评估影响面,因为你不知道哪些应用使用了这个 redis。进而,导致团队对架构优化的信心不足。 这两个问题会随着系统数量增加而加重。 那么如何实现跨应用的配置管理以解决上述问题呢? 如何实现跨应用的配置管理 如果使用如 Ansible、Puppet、Chef 这类自动化工具,跨应用的配置管理就很容易实现。因为它们的变量系统,天生就支持一处定义配置项,其它地方到处引用。对 Ansible 变量不熟悉的同学可以在文末找到学习链接。 在我们的 Nginx + Spring Boot 的例子中,对配置代码仓库(2-env-conf)进行了调整,结构如下: ├── Jenkinsfile ├── README.MD └── dev ├── group_vars │ ├── all # Ansible 默认的 all 组变量目录 │ │ └── global.yaml │ └── nginx.yaml # nginx 组变量 ├── host_vars │ ├── 192.168.52.10 │ └── 192.168.52.11 └── hosts 因为 Spring Boot 应用的端口会被 Nginx 的配置引用,所以,我们将端口的配置项放到 global.yaml 中,代码如下所示。 ...

2020-03-12 · 1 min · 191 words · 翟志军 Jack Zhai

如果张小龙谈 DevOps 平台

设计原则 张小龙谈微信: 我们没办法让10亿人来投票决定什么是好的,也投不出来。那怎么才能通过改变寻求设计的优化,让它变得更好呢?这个决策必须遵循好的设计原则。 张小龙谈 DevOps 平台: 我们没办法让所有研发团队来投票决定什么样的 DevOps 平台是好的,也投不出来。那怎么才能通过改变寻求设计的优化,让它变得更好呢?这个决策必须遵循好的设计原则。 我把这几个原则念给大家听下,大家可以对照 DevOps 平台来思考一下,会很有意思。 为软件的发布创建一个可重复且可靠的过程 将几乎所有的事情自动化 把所有的东西都纳入版本控制 提前并频繁地做让你感到痛苦的事 内建质量 “DONE” 意味着“已发布” 交付过程是每个成员的责任 持续改进 老翟插话:以上设计原则,是《持续交付》中1.6章节中写的。 做最好的工具与 996 张小龙谈微信: 一个用户每天的时间是有限的,这是次要的。最主要的是,技术的使命应该是帮助人类提高效率。 张小龙谈 DevOps 平台: 一个程序员每天的时间是有限的,这是次要的。最主要的是 DevOps 平台的使命应该是帮助研发团队提高软件发布的效率。 老翟插话:我的真实经历,当我问一个 DevOps 平台的设计人员为什么要把部署阶段设计得这么难用(效率低下)。得到的答案是怕用户部署错。这是使用老的思路来设计 DevOps 平台:如果一件事情容易出错,那我们就尽量少做。而让用户难用,就可以自然实现目的。 关于社交,关于 DevOps 本源 张小龙谈微信: 其实我们人的社交是没有发生改变的,或者说社交的需求并没有发生改变。我们在线上的社交只是线下的社交的一个映射而已。 张小谈 DevOps 平台: 其实我们研发团队的软件发布是没有发生改变的,或者说软件发布的需求并没有发生改变。我们在 DevOps 平台上的软件发布只是线下的软件发布的一个映射而已。 老翟插话:为什么开源类的 CI/CD 平台,Jenkins 占有率那么高?很大一部分原因是人们从原来的手工发布迁移到 Jenkins 上,非常的平滑,自然。纵观现在很多 DevOps 平台,把基本的构建编译的命令都隐藏起来,不允许用户轻松地看到或者修改。这是那些 DevOps 平台“难用”的原因之一。 什么是好的 DevOps 平台 张小龙谈微信: 我觉得一个好的产品不需要费口舌解释,我解释了这么多,说明我们做得不够好。 张小龙谈 DevOps 平台: 我觉得一个好的 DevOps 平台不需要费口舌解释,我解释了这么多,说明我们做得不够好。 ...

2020-01-03 · 1 min · 109 words · 翟志军 Jack Zhai

业务老大问 DevOps 改进半年后,会得什么确切结果?

要的是确切结果,不要忽悠我 前段时间,乔帮主(乔梁,《持续交付2.0》的作者)在持续交付2.0的群里发出这一句话: 业务老大问:“原来100工程师做的一个产品,用半年时间做 devops 改进。半年之后会得到什么确切的结果?” 以上是原话。 乔帮主在群发这话的意思是:如果你的业务老大问这样的问题,你该如何回答? 请注意,业务老大要的是确切结果,不要拿“虚”的东西来忽悠人。 请读者朋友思考一会。 。 。 。 客官不要急,请再思考一会鸭。 。 。 面对老大这样的提问,技术人可能觉得好笑,接着可能装作一本正经回答老大: 如果DevOps改进半年后,会使单元测试覆盖率提高到 80%。 如果DevOps改进半年后,会使A系统的部署耗时缩短到 1 分钟。 这回答真的很“确切”,但是,还是没有办法说服业务老大。注意,业务老大是不懂技术的。老大听到你的回答,估计一头雾水:什么是单元测试覆盖率?提高到 80% 后,对我的业务 KPI 又有什么关系? 别笑,这在 IT 行业里是常态。很多时候是不懂技术的老大,却又领导着一批技术人员。 面对这样的“常态”,作为技术人员,我们有必要,也有责任让不懂技术的业务老大理解 IT 行业里必要的“常识”。 回到业务老大的问题,如果你仔细思考,还真不好回答。比如,单元测试覆盖率提高到 80% 后,对我的业务 KPI 又有什么关系?能让我的业务 KPI 提高 80% 吗? 老翟的做法 当笔者看到这个问题,第一感觉就是:我们必须找到 DevOps 改进措施和业务老大关心的 KPI 之间的关系。换句话说,就是如果在100名工程师做一个产品的团队的情况下,进行 DevOps 改进半年后,会给我的业务 KPI 带来什么确切的结果? 笔者认为业务老大的问题,可以拆分成两个小问题: 有效性问题:如何证明 DevOps 改进是对业务 KPI 提升是有效的? 进度问题:怎么评估 DevOps 改进对业务 KPI 提升了多少? 如何证明 DevOps 改进是对业务 KPI 提升是有效的? 这就是我说的:我们必须找到 DevOps 改进措施和业务 KPI 之间的关系。 “DevOps 改进措施和业务 KPI 之间的关系”指的是什么?这需要针对不同的业务场景进行举例说明。 ...

2019-12-31 · 1 min · 168 words · 翟志军 Jack Zhai

从王垠面试阿里的事件看程序员招聘

餐饮行业里,有些饭店的服务员应聘是需要试工的。就是让应聘者穿上酒店的工作服,然后在工作繁忙时间,工作一段时间。 这段时间里,面试官可以观察到他在点菜时会不会与客人互动,上菜过程中的专业程度等。进而判断要不要录入他。 试工后,基础能回答这人能否胜任当前工作。 当然,“试工”对于招聘起到效果也取决于面试官。这是另一个问题了。 而 IT 行业,如果面试官不问个偏门算法,问个万万亿级流量的处理解决方案,似乎显得自己比面试者差。所以,IT 行业有个笑话:面试造火箭,入职拧螺丝。 我们是不是应该换个方式来招聘程序员?也用“试工”来遴选人才。 据我所知,ThoughtWorks 很多年前就已经采用“试工”的方式招人了。以下是笔者当时的面试经历: 一面是与 HR 简单聊聊。 二面,你必须在规定时间内完成一个家庭作业(需要写代码)。当你把作业交上去,HR 会找到公司内部的程序员帮忙看题(这样有助于缓和HR与程序员的关系,因为HR有求于程序员啊。)。 三面,他们会邀请你到办公室,然后HR找两个程序员和你结对编程(注意:这里是真实的上机写代码),内容就是在你交的作业的基础上加需求。过程中,他们会观察你,会提问你。 其实,整个三面的过程,就是试工的过程。虽然不能拿真实代码来改,但是也尽量模拟真实的工作场景:结对编程、别人对你代码的质疑等。 像 ThoughtWorks 这样试工的,在我们行业里,真的太少了。 回到阿里面试王垠(暂不说是不是受邀面试)这件事。 从赵海平的回复来看: 整个面试最关键的过程恰好是对简历上具体工作的详细了解,这个王垠在博客里完全没有提到,实际上我问了将近二十到三十分钟,我希望王垠能够意识到这部分才是面试真正考核的部分,应该尽量把自己最拿手最出彩的工作分享给面试官,详细解释为什么难,为什么有意义,为什么对公司有着深远的影响,而不是直接问面试官是做什么的,到底懂不懂,很遗憾,我恰好是做编译器的,在Facebook做了PHP编译器,在阿里巴巴领导了团队在Java里加入了透明的协程 从这一段话来看,赵海平花了很长时间问王垠的过去。 赵海平是不是可以让王垠试试解决一下自己所在团队当前遇到的技术问题,又或者让王垠试着重新实现一遍自己骄傲的“透明的协程”?这个过程,我相信是非常兴奋的。 以上两种尝试其实也算是一种试工。能解决团队遇到的问题,能做面试官能做的,应该算是能胜任他所面试的岗位了吧? 毕竟,是想招这个人来解决问题的。而不是抓住他的过去不放。 再说,有些应聘者可能真的不知道要怎么回答面试中的——真正考核的部分。所以,回答不上来,个人也觉得很正常。因为我也是那样的人。 最后,我疑问,在赵海平的这次面试里,“真正考核的部分”真的比“这个人能否真正解决问题”重要吗?HR 面,我可以理解。 后记 我们都是外人。所以,真正的背后的动机,上下文只有当事人知道。我不想评价他们个人和公司。只想讨论一下“试工”在IT行业的可能性。让更多人知道,招聘程序员,还有另一种姿势。 笔者是从这文章了解到事件的:https://www.ithome.com/0/464/417.htm 笔者的阿里三面经历:https://showme.codes/2018-06-24/alibaba-interview/

2019-12-25 · 1 min · 35 words · 翟志军 Jack Zhai

谈 DevOps 平台设计:为什么用户喜欢在构建后加一句 ls -al .

最近发现用户都喜欢构建命令最后加上一行:ls -a .。为什么呢? 是因为他们想知道执行 npm run build 后的目录的结果是什么样的。目录里到底有没有出现期望的文件。 这个功能在 Jenkins 中叫做“工作空间”。其实就是源码在 Jenkins 上下载后的目录。Jenkins 中,用户是可以直接像在查看本地文件夹一样查看这个工作空间的内容。如下图如示。 为什么用户想要看工作空间中的目录结构呢?说到底就是用户本地打包环境和平台打包环境还是有区别的。当出现构建结果不符合预期时,用户需要根据工作空间的信息来找到失败原因。 另,笔者看了行业内的几个平台,没有找到“工作空间”的功能。为什么没有这个功能呢?不知道。 后记 “工作空间”这个特性本身提高了用户在平台上的自检能力。因为平台上的信息对于用户更透明了。那么实现这个功能的成本呢?这是我们在实现前要考虑的问题。

2019-11-28 · 1 min · 18 words · 翟志军 Jack Zhai

谈 DevOps 平台设计:为什么部署后的包还是旧的包?

为什么部署后的包还是旧的包? 有位同学说他在 DevOps 平台部署后,代码还是旧的。 在我跟他确认了构建时拉取的代码是版本是对的,部署时,使用的制品包也是正确的情况下 ,我也是摸不着头脑了。 最后,这位同学在 DevOps 平台的界面上看到了部署的目标机器的IP。 这才找到了真正的原因:他们自己部署的目标机器搞错了。也就是本来想部署到 A 机器,但是部署任务填写的是 B 机器的 IP。然后自己一直在 A 机器上检查部署结果。 接着,这位同学就感叹自己对 DevOps 不熟。 然而,笔者认为,那根本不是用户对 DevOps 熟不熟的问题。而是 DevOps 平台的设计问题。 为什么用户连自己部署错了主机都不知道?笔者认为那是因为目标机器的 IP 信息在界面上不够明显,其他所有的信息就只能通过分析 DevOps 平台提供的日志才能知道了。 所以,我们的平台需要提供这两个功能: 部署清单:列出本次部署的具体内容,可以包括:制品版本,执行人,目标机器列表,部署策略,回滚策略等。 部署结果报告:制品版本,目标机器的部署结果,与上一次部署的差异(这是关键)、多次部署报告的差异性比较功能等。 部署结果报告中必须要强调此次部署与上次部署之间的差异,毕竟大多数应用不会那么频繁的更换部署机器。 后记 “DevOps” 这个概念本身已经把很多人搞得云里雾里。在网上搜索一下 DevOps 的定义就知道了。所以,用户在使用 DevOps 平台时,达不到预期值,就习惯性地认为是自己的问题。我们作为平台设计方,用户达不到预期,那就是平台设计的问题。

2019-11-27 · 1 min · 41 words · 翟志军 Jack Zhai

谈 DevOps 平台落地:你们流水线从编译到部署需要多少分钟啊?

一位同学的疑问 有一位同学问我: 你们一个流水线从编译到部署成功需要多少分钟啊。我们快的2分钟,Java 普遍10分钟,开发同学总是觉得慢,我不知道业界是什么水平。 我的回答是: 没有行业标准的,有些项目10万行代码,有些100万行代码,没法比。 后来,我又问他:你们的流水线都包括了哪些阶段? “编译,带数据库的测序,sonar,docker build,ansible deploy,mvn release”,他回答。 后来的沟通中,我得知,他们流水线时间长发生在两个阶段:带数据库的测试和下载依赖。 下载依赖慢是因为他们每次构建都重新下载依赖,这样做又是因为总是遇到缓存问题,所以,干脆就每次重新下载了。 带数据库的测试通常会慢,因为要启动应用,然后操作数据库,这个数据不确定他是类似 MySQL 这样的真实数据库,还是使用 H2 这样的内存数据库。 出乎我意料的是,他们的 SonarQube 扫描倒是很快。 所以,他们的优化点就是那两个慢的阶段。具体解决办法还要看具体情况,比如带数据库的测试是不是可以通过并行解决、构建时依赖缓存遇到的问题是不是可以通修改构建工具配置来解决。 写到这里,我想表达的是,优化流水线的速度的思路,差不多就是这样:先找到最慢的阶段,然后根据具体情况来优化。 从 DevOps 平台设计角度解决 那么,作为 DevOps 平台,我们能通过什么办法帮助用户提高流水线的速度呢? 笔者认为,只要将流水线中的每个阶段中的每个步骤的耗时都记录下来,然后显示给用户,用户自然会注意到每次流水线的执行速度差异。当然,管理层也可以对这部分内容进行考核。 同时,要将耗时进行分类,一类是用户步骤的耗时,比如执行mvn package,执行单元测试等,一类是 DevOps 平台本身的耗时,比如初始化构建环境耗时,上传制品耗时等。 为什么要进行这样的分类?是因为 DevOps 平台使用过程中,用户遇到问题,往往是区分不了,是平台的问题,还是自己的问题。这时,我们将平台的信息显示给用户,用户就可以自行判断,自行处理了。这会大大节约平台维护者的时间。而且,平台维护者也可以根据平台运行耗时统计来对平台进行有依有据的优化。这是一箭双雕。 我把这个功能叫做:流水线耗时统计。 那么这个功能,到底应该如何实现?不同的平台有不同的实现,比如基于 Jenkins 的话,在每个步骤后加上一个回调请求就可以了;基于 GitLab 的话,就不了解了。 图来自:https://wiki.jenkins.io/display/JENKINS/Pipeline+Stage+View+Plugin 后记 流水线的速度是一个很重要的指标,它直接显示了一个软件开发团队在工程方面的效率(正确性问题是另一个问题)。而流水线耗时统计功能可以有效地帮助用户提高自己的流水线速度。

2019-11-21 · 1 min · 46 words · 翟志军 Jack Zhai

谈 DevOps 平台落地:前端项目构建又失败了

同事发了一个前端构建失败的链接过来,接着就是那句:任务执行失败了,麻烦帮忙看看。 DevOps 平台的“老手”了,所以,在找我们解决问题时,都知道附上平台任务的链接。 我们打开链接,第一件事情就是看日志。是的,DevOps 平台的使用者很多都认为:在本地执行构建成功,那么在平台上构建失败就是平台的问题。所以部分人连构建日志都不看,直接把链接发给我们这些平台维护者看。 不出意外,这次又是依赖管理问题。只不过,这次是发生在前端项目上。错误截图下如下: 日志里(画红线部分)已经说得很清楚了。虽然我不清楚“Tristan”是什么,但是可以猜到是他的业务代码报这样的错。但是他本地执行没报错,那通常就是依赖的版本的问题了。 他的前端的依赖定义(package.json)类似以下这样: { ..... "dependencies": { "cookie-parser": "^1.4.3", "debug": "~2.6.9", "express": "^4.16.0", "http-errors": "^1.6.2", "morgan": "~1.9.0", "pug": "2.0.0-beta11" }, ... } 我们看到依赖的版本号的前缀有 ~,有也 ^。这是什么意思呢? ~: 前缀表示,安装大于指定的这个版本,并且匹配到 x.y.z 中 z 最新的版本。 ^: 前缀在 ^0.y.z 时的表现和 ~0.y.z 是一样的,然而 ^1.y.z 的时候,就会 匹配到 y 和 z 都是最新的版本。 也就是说,每次执行 npm install ,该项目所依赖的内容,都是有可能变的。 这对我来说是不可思议的。为什么? 因为依赖的版本代表着一个软件的基础。依赖的版本在你不知道的情况下发生变更,就好比建房子,建第一层时,地基是100个平方,建第二层时,地基突然就变成了90个平方。而前端项目中大量这种情况。 你可能会说开源前端node项目都会遵循语义化的版本号,小版本升级不会出问题的。我想说,那只是约定,还是要看那个人遵守不遵守。如果你真相信每个人都遵守,本质上是把软件开发风险的控制权交给了开源软件作者的个人习惯。开发出来的软件注定是不稳定的。 但是,为什么前端项目的依赖的版本号前普遍会加上 ~ 和 ^ 。在我亲自执行 npm install express 命令时,我知道了原因。因为在执行命令后, package.json 文件中就出现了:"express": "^4.16.0"。也是 npm 在安装依赖时,默认就给版本号加上 ^ 前缀。而很多人可能改都不会去改。这就导致了文章开头所说的那位同事的问题。 后记 真心希望大家固定下 package.json 中的依赖的版本号。这样的前端项目构建起来才有稳定的基础。 ...

2019-11-20 · 1 min · 80 words · 翟志军 Jack Zhai

谈DevOps平台落地:前端构建怎么这么变态

题记:DevOps 平台通常搭建于内网环境,不能直接外网,所以,如果你也要在内网环境构建前端,就一定会遇到本文所说的问题。 我们发现在 DevOps 平台构建前端项目时,会报这以下这样的错误: node scripts/install.js Downloading binary from https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node Cannot download “https://github.com/sass/node-sass/releases/download/v4.9.0/linux-x64-57_binding.node”: tunneling socket could not be established, statusCode=500 Hint: If github.com is not accessible in your location try setting a proxy via HTTP_PROXY, e.g. export HTTP_PROXY=http://example.com:1234 or configure npm proxy via npm config set proxy http://example.com:8080 [email protected] postinstall 以上的错误日志的意思是node在安装 node-sass 时,要去 github.com/sass/node-sass 下载一个名为 linux-x64-57_binding.node 的二进制包。然后它无法下载(其实是因为DevOps平台搭建在企业的内网,是无法直接连接外网的),就建议你设置一下系统的HTTP代理,让它能连接到 github.com。 除此之外,错误日志中,还发现了,node-sass 依赖本身的构建,还需要 Python2 环境: gyp verb check python checking for Python executable "python2" in the PATH gyp verb `which` failed Error: not found: python2 对于一个 Java 后端开发人员,看到这样的错误就懵了。心里在想: ...

2019-11-12 · 1 min · 165 words · 翟志军 Jack Zhai

谈 DevOps 平台实施:我在本地跑明明成功的,为什么在你平台跑就报错?

我在本地跑明明成功的,为什么在你平台跑就报错? 用户在 Jenkins 上跑构建时,失败了,把日志截图给我看,如下图: 在过去几个月,每个星期都会有一两个 Jenkins 用户就会给我发送类似的错误日志。 这样的日志,我通常回:请检查你们的依赖,是不是有依赖没有上传到咱们的 Nexus 仓库。验证方法是先在本地删除你的 .m2 目录,然后再执行一次构建。 当用户业务开发比较急的时候,他们还会说本文标题中的那句话。有些抱怨的意思。我都已经习惯了。 出现这样的情况,我总结大概会有以下原因: 用户对于 Maven 这类构建工具不熟悉。 用户对于依赖管理不重视,或者没有依赖管理的意识。 用户根本不看日志。 面对这三个原因,我就在思考:我们 DevOps 平台能做些什么呢? 我觉得 DevOps 平台是不是可以直截了当地告诉用户: xxx 依赖在 Nexus 仓库(maven.abc.com)中没有找到,请您先 deploy 该依赖到 Nexus 仓库后,再执行此任务。 如果能检测到缺少的依赖放在哪个代码仓库就更好了。因为这样,就可以提示用户直接到该代码仓库的 deploy 了。 这样的技术,我称为依赖AI管理技术(笑)。当然,这样的技术,应该可以应用于所有的语言。 同时,我们将这些数据(依赖管理失误)统计起来,就可以看出一个团队在依赖管理方面的能力表现了,进而可以有效的对团队进行培训,以提高相应的能力。 回到本文主题,当用户自行检查依赖后,大多数时候,用户就不会来找我了,因为问题已经解决了。可是有一次,用户还是说不行,他已经把 .m2 删除,并把依赖包上传到 Nexus 仓库了。 我检查了他的 pom.xml 文件,发现版本号的定义也是正确的。可是,放在 Jenkins 上执行时,使用的还是旧版本的类的定义。 这就奇怪了。这种情况还是头一回遇到。来来回回检查了好几次,查了好久才知道,是因为用户 deploy 依赖到 Nexus 时,deploy 的是相同的版本号,就是覆盖了原来的版本的包,但是版本没有升级。而 Maven 检测到本地就该版本的依赖,就不会重新下载了。最后,就是大家看到的,本地可以,但是 Jenkins 上就是不行。 最后的解决方式是: 用户 deploy 一个新的版本到 Nexus 仓库,并在 pom.xml 中使用新的版本。 我们将 Nexus 设置为不允许重复 deploy。 小结 经过这次事件,我们可以看出,依赖管理对于工程质量的重要性。因为,依赖管理不当,很有可能在连开发人员都不知情的情况下引入Bug。 ...

2019-11-11 · 1 min · 75 words · 翟志军 Jack Zhai