Shell Bash能力对于运维很重要吗?

Bash能力对于一个运维重要吗? 这个问题本身该不该问?我觉得是有必要的思考的。这关乎团队的能力建设。 对于这个问题的答案,我的答案是看情况。 先说一个现实中真实发生的案例。 多年前,某部门所维护的系统经常出现一些“异常”的流量。部长让架构师对20多台机器上的系统日志进行分析,以确认是否真的有“异常IP”在做坏事。 这下可难倒这位架构师了,三天时间没有搞定这个需求。 最后,过了没有多久,这位架构师就被毕业了。 纵然这位架构师被毕业的原因很多,但是他是真不会做这个事情,也是事实。 在手工运维情况下,使用Bash对日志进行分析,在运维行业里一件很常见的事情,是必备技能。 所以,这位架构师在部长面前就是“没有能力”。 这时,你觉得Shell/Bash的能力重要吗? 再说我做DevOps这几年的感受。 我经历过将手工运维升级至自动化运维的过程。这整个过程,从手工,到使用Ansible自动化部署到虚拟机,再到使用Helm实现自动化部署应用到Kubernetes。 以上的那位架构师遇到问题,在手工运维阶段,我们也遇到过。 而且这个问题,是有一个专门的“熟手”负责。他可以熟练的同时登录上多台服务器,然后在多个窗口中娴熟地敲打命令。因为只有他懂grep哪些关键字能快速找到问题,所以,团队里的成员经常找他grep排查问题。 然而,在我们将日志进行结构化后,所有有权限的人(不论开发还是运维),只要简单的学习一个sql就可以轻松统计分析日志了。 结果就是不仅这位“熟手”的生产力被释放了,团队里其他经常排队等他帮忙的人的生产力也被释放了。 在这样的场景下,Shell/Bash的能力重要吗? Shell/Bash的能力,除了被应用于日志分析,还有一个很大应用:部署。 比如以下类似的Bash脚本(示例代码是从网上获取的): group1_deploy(){ # 代码解压部署函数 writelog "group1_code_deploy" for node in ${GROUP1_LIST};do # 循环生产服务器节点列表 cluster_node_remove $node echo "group1, cluster_node_remove $node" ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" # 分别到各web服务器节点执行压缩包解压命令 ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" # 整个自动化的核心,创建软连接 done scp ${CONFIG_DIR}/other/192.168.3.13.server.xml 192.168.3.13:/webroot/web-demo/server.xml # 将差异项目的配置文件scp到此web服务器并以项目结尾 } 当你所在部门还在使用这样的脚本进行自动化部署时,你就必须要有Shell/Bash的能力。但是,当你使用Ansible来实现wordpress的部署,几乎不需要写任何Bash脚本。 因此,团队里,你不需要招Bash高手,你只需要招一个刚毕业没多久的运维或者开发,就可以维护此Ansible脚本。代码如下: - hosts: all become: true # 省略部分代码 tasks: - name: ==> 0 - add host info lineinfile: dest=/etc/hosts line="10.0.0.10 {{ hostname }}" state=present - name: ==> 1 - add PPA of php7 (community) apt_repository: repo="ppa:ondrej/php" - name: add Nginx stable repository (deb) apt_repository: > repo='deb http://nginx.org/packages/ubuntu/ trusty nginx' state=present # 省略部分代码 - name: ==> 4 - install nginx, php-fpm, and php-mysql apt: name={{ item }} state=present with_items: - nginx - php7.0-fpm - php7.0-mysql - name: download wordpress tarball get_url: url: "https://tw.wordpress.org/wordpress-{{ wordpress_version }}-zh_TW.tar.gz" dest: /tmp/ - name: extract wordpress tarball unarchive: src: "/tmp/wordpress-{{ wordpress_version }}-zh_TW.tar.gz" dest: "{{ wordpress_parent_path }}" owner: "{{ wordpress_owner }}" group: "{{ wordpress_group }}" copy: no # 省略部分代码 - name: copy wordpress site conf for nginx template: src: ./templates/nginx-wordpress.conf.j2 dest: /etc/nginx/conf.d/nginx-wordpress.conf - name: fix listen.owner for php-fpm lineinfile: dest: /etc/php/7.0/fpm/pool.d/www.conf regexp: '^listen.owner\s*=.*$' line: "listen.owner=nginx" state: present - name: restart php-fpm service: name=php7.0-fpm state=restarted 在这样的场景下,Shell/Bash的能力重要吗? ...

2023-10-22 · 2 min · 219 words · 翟志军 Jack Zhai

微服务架构下的配置治理模式

微服务被滥用是不争的事实。被滥用的同时,很少人留意到它所带来的配置治理的问题。本文我们介绍两种常见的治理模式。 基于common的配置治理模式 当微服务数量多时,开发人员倾向于创建这样的配置文件: common-redis.json common-mysql.json common-mq.json 甚至还有会有common.json这种从名字上就不知道它的作用的配置。但是,几乎所有的微服务都会引用common.json这个配置。原因如下: 在common.json可以无脑增加配置项,不需要改业务代码; 配置项可能是被n个微服务引用,为了这一个配置项,又新增一个配置文件,不值得。common.json看起来是最合适的。反正每个微服务都已经引用了common.json。 ![](/assets/images/Pasted image 20221220161521.png) 基于common的配置,在写入配置项的时候是爽了,但是,也带来了问题: 改了common.json文件中的配置后,很难确认这个变更会影响到哪里,因为每个微服务都引用了common.json; common.json会变得越来越大; 并不是每次发布,都发布所有的微服务。所以,微服务A可能采用的是common.json的v1版本,而微服务B可能采用的是common.json的v2版本。 随着时间迁移,谁也不敢动common.json中的配置,即使有些配置项已经很久没有被使用了。 基于服务级别粒度的配置治理模式 基于服务级别粒度的配置方式,很容易理解,如下图: ![](/assets/images/Pasted image 20221220161557.png) 每个服务只引用一个配置文件。此模式完全避免了基于common的治理模式所带来的问题。但是,又带来了新的问题,即不同的微服务配置之间出现大量的重复配置。修改大量重复配置容易出错,且痛苦。 大量配置重复的问题,可以通过类似Jsonnet或者CUE这样的配置编程语言解决。如下所示: 当修改metrics.libsonnet时,我们很容易就知道这个变更将直接影响:microservice-c.jsonnet和microservice-a.jsonnet。进而,我们也就可以知道了它将间接影响microservice-c和microservice-b两个服务。 不存在没有缺点的解决方案。使用Jsonnet和CUE这样的语言,意味着一定的学习成本和现在有的工程的改造成本(引入新的构建工具和对现在有的配置的转换)。 不论哪种模式,你都必须要做到 不论哪种配置治理模式,都必须要做到:配置应该尽量小。 至于小到什么程度?这个问题回答了,作用也不大。就像菜谱上写的是10克的香精,也没有几个人在放香精时进行称重,而是凭感觉。 如果真要我回答,我的回答是:作为一个逻辑单元,多一行代码是多余,少一行代码则不行。 成本 当我提出采用一种新的配置编程语言来统一所有的配置时,团队里的人都反对。行业里其他的人,也啧啧着这样做所带来的成本。 所以,以下是我们团队经验,供大家参考: Jsonnet的学习成本:像Jsonnet这样专为配置而生的配置编程语言,语法也只有一张A4纸,非常值得投入。我们团队里两个不懂编程的刚毕业没有多久的运维小年轻,很快就上手了。多快?半天左右吧。 构建工具Bazel的学习成本:Jsonnet本身的构建命令就和Java的javac一样低级,所以,需要借助其它构建工具。我们选择Bazel。它支持Jsonnet的单元测试。我们顺便实现了配置的自动化测试。Bazel的学习只需要团队里的几个人会就可以了。这个工具本身其实不难,但是因为中文学习教程太少了,导致了学习成本高。 其它配置格式转换成Jsonnet格式的成本:这个应该是我们成本最高的,也是风险最高的。因为一个配置错,可能带来线上事故。这个过程也是一个还债的过程。以前不合理的配置,在这个过程中会被发现。我们转换的过程是 通过自动转换工具将旧配置转成json格式,json格式与jsonnet格式是兼容的,所以就相当于自动得到了jsonnet格式的配置; 将公共配置抽离出来,比如redis的配置。并对敏感配置进行加密处理。这个过程是重建配置的过程; 将所有的配置转换完成后,再与原来的配置的json格式进行内容级别的对比。如果没有区别,就代表转换成功。 因为我们很早之前就对配置进行了标准化,所以对我们来说这个转换成本和配置的量对比起来,也不算太高。而这个成本绝大多数都是由基础设施团队完成,而不是业务开发团队。

2023-06-12 · 1 min · 40 words · 翟志军 Jack Zhai

我是如何进行日志降本的

最近行业里流行降本增效。本文就一个现实中经常发生的日志成本的案例进行讨论,讨论该如何降本。 背景 假如存在一家IoT公司,它拥有1亿的在线设备(长连接着云端)。这些设备每21秒会向云端发送心跳,以进行连接的保活。假如1次连接的保活,在云端产生1条日志,那么1台设备1天产生的日志量是:24 * 60 * 60 / 21 = 4114条,假设1条日志100字节,所以: 1台设备1天保活日志数:24 * 60 * 60 / 21 = 4114条 1条日志占用100字节,1台设备1天产生保活日志:4114 * 100 = 411400byte 1天内1亿的设备产生日志:411400 * 100000000 ≈ 37.4T 我们来看下该保活日志内容: 2021-02-12:12:32:12 application-abc-prod INFO com.platform.cloud.impl keep alive, deviceId:1a2b3b3p8 从日志内容来看,没有什么“营养”,纯粹是为方便程序员debug。 这是问题吗? 一行没有营养的日志每天产生37.4T的日志,是个问题吗?对谁是个问题? 对于写那行代码的人来说,并不是问题,因为日志量大小本身并不是他的KPI。 对于运维可能是个问题,因为他们可能因此需要增加存储服务扩容的工作。但是从另一个方面来看不是个问题,因为他们有借口招更多的人。 对于大数据团队可能是个问题,因为他们发现数据清洗pipeline比之前更慢了,进而导致每日报表都被延迟了。但是从另一个角度来看不是个问题,因为这是一个表现的机会——在人员不变的情况下优化清洗速度的机会。 这对于关心成本的人来说是个问题。谁关心成本呢?这不是本文讨论的话题。 如何面对问题? 事后面对 事后面对指的是事情已经发生了,即大量无用配置已经产生了。只能采取补救止血措施。 作为运维,需要对于日志量进行多维度监控与告警。可以包括以下维度: 应用维度 实例维度 设备维度:部分设备可能有Bug,会死循环会不停进行连接请求 告警内容可以包括: 进行环比对比,出现突增 进行环比对比,突降 在收到告警后,找到相应的应用的owner,让其检查日志是否合理,不合理则需要制定改进措施。 同时,每周都向相应的owner发送日志量报告,让其了解当前生产环境的日志量情况。 Code Review时面对 Code Review时,有经验的人会看出日志问题,但是这种方式并不可靠。 写代码时,就避免 指的是在程序员写在log.info("无营养日志")的那一时刻,IDE就提醒程序员:这行日志每日将产生4114条日志,将占所有日志总量的80%,请注意! 当然,除了在IDE时提醒,我们可以在Code Review时,review机器人自动对该行代码进行评论。 这个实现起来难度会比较大。 但是并不是没有思路。笔者的思路如下,仅供参考,欢迎交流: 我们要知道,只有被请求到API,API执行时,被执行到的代码才会打日志。所以,只要能知道打日志的代码在过去一段时间内,在生产环境的相应的API被请求了多少次,我们就可以预测到增加1行日志会增加多少日志了。进而在IDE里提示,或者Code Review进行评论。 而对于那些完全新增的API,就没有办法预测了,因为没有历史纪录。 ...

2023-04-22 · 1 min · 74 words · 翟志军 Jack Zhai

精准测试不过是增量构建的副产品

前文中,我们给了“精准测试”定义: 它是一种能力,能只针对变更进行测试,而不是每次变更都进行全量测试。 同时,介绍了当前行业里的主流实现方法。个人并不看好该实现方法。 本文介绍的另一种实现精准测试的方法。在真正介绍前,我们就必须先说增量构建和Bazel。 全量构建与增量构建 在软件构建领域,存在两种构建类型:全量构建和增量构建。 全量构建指的是针对代码仓库中所有的代码进行构建; 增量构建是指只针对有变动的代码及受变动影响的相关代码进行重新构建。 从定义看,增量构建要做的事情和精准测试要做的事情几乎是一样的。只不过,把build命令,换成test命令罢了。 这也就是为什么我觉得应该把“精准测试”叫做“增量测试”才对。 目前行业里,在增量构建领域,Bazel可谓是佼佼者。 Bazel介绍 Bazel是Google 2015年开源的一款构建工具。采用声明式的方式定义所有的构建任务。Bazel叫target。 每个target声明包含了:构建类型、输入、构建方式、输出、依赖等。以下代码展示了两个构建任务: # 声明打成jar包,作为library被其他任务使用 java_library( name = "greeter", srcs = ["src/main/java/com/example/Greeting.java"], ) # 声明打包成一个可执行Jar java_binary( name = "ProjectRunner", srcs = ["src/main/java/com/example/ProjectRunner.java"], main_class = "com.example.ProjectRunner", # 依赖之前打好的library,这是实现增量构建的关键 deps = [":greeter"], ) Bazel在运行时,就会根据target声明,在内部维护一个有向依赖图,如下: 有了这个有向依赖图,Bazel就可以实现增量构建。 当用户修改了 Greeting.java 文件时,Bazel知道 //:greeter target 依赖它,所以Bazel知道要执行 //:greeter 时。同时Bazel又知道 //:ProjectRunner 依赖 //:greeter ,所以Bazel知道还要执行 //:ProjectRunner target。 以上是在同一个语言下的增量构建的案例,让我们看下多语言场景下,Bazel是如何实现增量构建的。 多语言场景下Bazel是如何实现增量构建的 如下图,在一个软件工程下,同时使用到了:Docker、Python、YAML、C++等技术。 这个依赖关系是在开发和运维在写代码的时候就定义好了的。所以,Bazel从一开始就有了这个依赖图。 Bazel允许声明不同语言的target之间的依赖,所以,很自然的,一个软件工程的完成的依赖图就有了。你不需要花额外的精力去收集。 当执行Bazel进行构建build时,Bazel发现配置文件config.yaml是被修改了,这时它就计算出接下来要执行的构建,如下图标为橙色的路径。即所有的依赖于//:config.yaml 的直接依赖和间接依赖。 但是,因为执行的是build,所以,Bazel只会build路径上的所有源代码,并不会去执行 *_test 测试任务。 这就是精准构建,不,叫增量构建:只构建需要构建的。 也许你会好奇Bazel是怎么做到的?请关注我的公众号,将来我还会分享更多Bazel的内容。 Bazel是如何精准测试 Bazel有很多子命令,有两个常用的子命令,一个是build,一个是test。这两个子命令是用于区分构建的类型的。因为有时,你可能只是想build,不想test。 接着上面的例子,同样修改了config.yaml文件,当我们执行的是test子命令,Bazel会计算出要执行的路径——和上文一样的路径,因为//:config.yaml所影响的依赖范围是一样的。 区别是,这次它除了执行build,还会运行*_test类的任务。Bazel并不会关心它是单元测试,还是集成测试,只关心该测试的大小。如下图中的标为黑色的部分: ![](/assets/images/Screen Shot 2023-11-02 at 9.30.45 PM.png) ...

2023-04-22 · 1 min · 95 words · 翟志军 Jack Zhai

精准测试是个错误

如果你已经了解了精准测试在行业的主流做法,你可以跳过相关内容。 行业里对于精准测试的定义 在网上流传着一些精准测试的定义(如果你对这些定义不感冒,可直接跳到我个人的定义): 自网易陈逸青(2020)的定义: 借助一定的技术手段、通过辅助算法对传统软件测试过程进行可视化、分析及优化的过程,使得测试过程更加可视化、智能、可信和精准。 原文:https://www.infoq.cn/article/xuu91crqa4hcjz8uomjs 来自HSBC的测试咨询专家齐磊(2021年): 通俗点讲:核心基于源代码变更分析,结合分析算法,确定影响范围,提升测试效率。 原文:https://www.infoq.cn/article/2feiv8a5kogaqlbzwosh 来自星云测试(2022年): 精准测试一句话概括就是:测试用例和代码之间的追溯,这是它最本质的东西。精准测试的本质决定了它抓住了测试的一个核心要点。 原文: https://testerhome.com/topics/34557 来自得物技术(2023年): 精准测试是基于源代码变更分析,结合一些分析算法,从而确定改动代码影响的范围,设计测试用例进行针对性测试,一方面可以提升测试效率,另一方面精准测试还可以将测试用例与程序代码之间的逻辑映射关系建立起来, 而这个过程则是通过工具去采集测试过程执行的代码逻辑及测试数据。这两个点也正是精准测试的核心:正向追溯和逆向追溯。原文: https://tech.dewu.com/article?id=43 以下是来自网易严选的架构图: ![](/assets/images/网易严选的架构图1.png]] 我个人的定义 在笔者看来,精准测试的定义应该是这样的:它是一种能力,能只针对变更进行测试,而不是每次变更都进行全量测试。注意,我指的是“变更”,而不只是“代码变更”,也就是说所有类型的变更,包括手动变更。 精准测试的思路并不复杂,分成三个步骤: 找到变更; 根据变更找到相关联的测试用例; 只执行相关联的测试用例。 其实,把这种方法叫增量测试(Incremental Testing) 更准确,更合适。毕竟你是针对增量的代码变更进行测试。 如果不是针对增量变更进行测试,你也能只执行一个你想测试的测试。难道这样不算精准测试吗? 国内行业主流的实现精准测试的方法 步骤一:找到代码变更 通过commit之间进行差异对比​。​ 步骤二:根据代码变更找到相关联的测试用例 要做到“根据代码变更找到相关联的测试用例”,我们就必须知道代码与测试用例之间的关系。 获取这个关系的做法是在执行测试的同时,做以下事情: 将流量记录下来; 将因流量而执行地代码的调用链记录下来; 将测试用例的元数据与代码调用链的关系记录下来; 这个过程就完成了对被调用代码与测试用例之间的映射关系的建立。 另,现实往往存在很多未被测试用例覆盖到的代码,这时,通过静态代码分析和测试覆盖率计算技术结合,生成未被测试到的代码的报告。 可以看出,通过以上方式“找出代码与测试用例之间的关系”的成本是极高的。所以,在这个领域会有:引流平台、测试用例管理平台、精准测试平台等等平台。这也给大家一个感觉,我们要先有一个平台才能做到精准测试。 说到底就是通过插桩技术,构建代码的执行路径,并找到​对应的测试用例之间关系。 目前在网上目前看到大多还只是针对Java语言或者C++来实现精准测试,其它的语言目前没有见到。 步骤三:只执行相关联的测试用例 当有了代码与测试用例之间的关系,只执行相关联的测试用例就简单很多了。 主流方法的坑 以下是齐磊总结的精准测试存在的问题: 基于手工测试的精准测试建立映射关系繁杂,如果需求改变频繁,用例维护以及之间的关系维护需要耗费大量时间精力。 精准测试需要一定的自动化测试的覆盖,这样做起来更有意义,例如 api 自动化测试,如果本身用例过少,与代码之间关联关系不多时,变更代码后可能不会得出什么结果。 最好有对应的用例管理系统,能够方便的帮助我们建立与代码之间的关系。 需要投入开发能力强的 QA 或者测试开发建立整套系统环境,但长远考虑,将精准测试嵌入整个公司的质量平台中,不管对于新项目还说维护项目来说都是一种提升。 项目生命周期需要较长,短期项目花费巨大精力开发和维护整套精准测试系统得不偿失。短期项目可以利用精准测试以 api 测试覆盖率作为衡量标准。不去建立繁杂的关系,只监控 UI API 测试覆盖率迭代时的变更来达到目的。 但是,个人认为齐磊总结的内容没有问题,的确都是坑。但是那些不是精准测试的坑,而是国内行业主流的实现方式的坑。直白地说就是喝水时,喝水的角度错了。 为什么主流实现方法从方向上就是错的 为什么我认为以上地坑是由实现方法导致的?以下是我的论点,欢迎讨论指正: 该方法只局限于单一语言 准确来说,精准测试不应该只针对代码的变更,而是所有的变更。更不应该只针对单一语言的变更,而是可以针对所有的语言。 因为精准测试的定义本身不局限于某种语言的代码变更,而是对一个软件工程中所有的变更而言。一次SQL的变更,你是否需要精准的知道要执行哪些测试?一个前端的CSS代码的变更,你是否需要精准的知道要执行哪些测试? 目前行业里主流的方法,只是针对单一语言下的场景而设计的。按同样的思路是无法做到多语言的。我说的多语言指提同一工程下的多语言,不是指相互独立的单语言工程。 只能在平台上做精准测试 即,我们首先需要一个平台,才能做到精准测试。 但我们希望在开发者本地开发环境就可以做到精准测试。 最后 文章标题并不是说“精准测试”本身是一个错误,是想说上述的实现方法是一个错误方法。 ...

2023-04-21 · 1 min · 74 words · 翟志军 Jack Zhai

当同事实践Everything as Code之后

背景 团队基于Jsonnet(配置语言)和Bazel(构建工具)实现Everything as Code。但是,将近一年的时间里,团队也只有少数人深入参与Everything as Code的代码的编写(其实,大多数人是不需要深入参与)。 最近,组内的一个开发同学——他之前对Everything as Code完全零基础——在运维同事的辅助下,为网关配置增加了金丝雀的能力。 在这种能力下,开发人员可以只需要改几行配置,并部署配置,就可以实现金丝雀发布。这种能力的根本就是:通过Jsonnet动态生成网关“根据Header进行流量路由”的配置的能力。 如果没有这种能力,需要手写大量的重复的YAML配置,即麻烦,又容易出错。 同事使用Everything as Code的工具后有感 在他与Everything as Code“亲密”接触一个多月后,他说出了他的感受: 直接写yaml的话,所见即所得,非常直观。但是有个致命缺陷,没法动态化生成配置,所以必须要寻找一个脚本语言。 jsonnet其实就是一门简单化的脚本语言,通过自定义方法,通过for循环,通过读取自定义的变量,更灵活生成我们想要的json。但是写代码都有一个共性,代码可以写的很复杂,现在用jsonnet了,你想看一个key的value是什么,需要看懂别人的代码才知道value是什么甚至可能理解错别人的代码,违背了配置所见即所得,这对新手来说非常痛苦。很多时候,我们不关心jsonnet的过程,我们只想看到生成最终的配置文件是什么。 如果能把云端编译好的jsonnet展示出来,就可以看到生成的最终json是啥了,但是我们没有。所以需要本地搭建bazel,但是bazel对win非常不友好,搞起来非常困难,但是本地如果不能把monorepo buil通过,那么可以宣告你无法彻底搞清楚monorepo,更不可能自定义更多自己想要的东西了。 既然是通过代码来生成json,用jsonnet和python和js甚至是用java都可以,现在我觉得jsonnet的设计者对灵活性做了一个取舍,有一些我想要的操作jsonnet没法实现。 然后他又补充到: 只要自己本地能build monorepo,那么就既能享受jsonnet的灵活性,又能通过编译好的文件做到所见即所得。可惜bazel对win不友好,新手能在win 编译通过mainrepo太难了。 总结下来,这位同事: 对Jsonnet的理解还不够:Jsonnet并不是脚本语言,而是一门可编程的配置语言,一种为配置而生的DSL(领域特定语言); 在Windows系统上使用Bazel遇到了非常大的阻力。 以上是同事针对Jsonnet和构建工具Bazel的感受。 我认为不是Bazel对Windows不友好,而是Bazel在Windows下的生态太弱了。谁叫后端服务是Linux的天下呢? 同事对Everything as Code本身的看法 接着,他说出对Everything as Code的看法: 我觉得everything as code太理想,只能面向开发人员。你不能保证运维的能力,绝对不开放给第三方使用。指不定某天某个需求就需要你的运维的某个能力,让不懂代码的人也要操作。 假如某个运维能力,要开放给产品或者测试来用,要让别人给你学个jsonnet,那要我是产品或者测试只能口吐芬芳了。所以我认为,面向开发者的部分,使用every thing as code是趋势,比较好溯源跟踪,迭代。但是架构设计上,还需要有支持面向第三方的部分,可以做到让不懂代码的人也能用到一些运维的能力。 这位同事实现的是金丝雀配置生成,是需要修改Jsonnet代码的。当产品经理需要将某用户的流量路由到后端服务的某个版本上时,这位同事就认为产品经理就需要去改Jsonnet代码了。 同事认为这就是Everything as Code的问题:不方便非开发人使用。 同事的另一层意思没有说出来:并不是所有的事情都适合as Code。 其实,有这样的误解很正常,“Everything as Code”中的“Everything”字面含义上就是所有的,一切的意思。而Code是代码的意思。 所以,“Everything as Code”字面上完整的意思是:一切皆代码。 从字面上理解“Everything as Code”,只看到了它的表面 Everything as Code的本质 “Everything as Code”中的”Code“,其实指的是配置,即:一切皆配置。 这句话本身是没有错的,如同“道生一,一生二,二生三,三生万物“这句话。 “Everything as Code”这句话并没说什么是配置,以及哪些配置该放哪。这才是我们实践时真正要考虑的问题。 配置的本质是软件的灵活(soft)部分。通过这个”灵活部分“,我们可以根据自己的需求,有限地改变软件的行为。 ...

2023-04-20 · 1 min · 83 words · 翟志军 Jack Zhai

基于Bazel + SQLFluff实现SQL lint

背景 SQL进行版本化控制后,我们希望为SQL加入lint步骤。这样做的好处是我们可以在真正执行SQL前发现问题。 本文中,我们通过Bazel执行SQLFluff以实现SQL的lint。 SQLFluff是一款使用Python语言使用的,支持SQL多方言的SQL lint工具。 它的特点是: 支持多方言。如:Snowflake、PostgreSQL、ClickHouse。所有支持的方言列表:https://docs.sqlfluff.com/en/stable/dialects.html; 可以输出正确的SQL,减少了我们手工修正SQL的工作; 同时支持命令行方式使用和API调用方式。 集成到CI/CD流水线中 在我看来,在CICD流水线中实现SQL lint有两种方式: 方式一:在流水线中增加一个SQL lint步骤; 方式二:将SQL lint的逻辑写在测试代码,执行测试步骤,就自动执行了SQL lint。 方式二是我最爱,我会在本文最后讲原因。 工程结构 . ├── BUILD.bazel ├── WORKSPACE ├── repository-hibernate-impl │ ├── BUILD.bazel │ └── src │ ├── main │ │ └── sql │ │ └── V1__runbook_table.sql │ └── test │ └── python │ ├── BUILD.bazel │ ├── requirements_lock.txt │ └── sql_test.py 步骤1: 在WORKSPACE中增加Python外部依赖 本文中我们使用的是Bazel 5.4.0,所以还在使用WORKSPACE定义外部依赖 http_archive( name = "rules_python", sha256 = "a644da969b6824cc87f8fe7b18101a8a6c57da5db39caa6566ec6109f37d2141", strip_prefix = "rules_python-0.20.0", url = "https://github.com/bazelbuild/rules_python/releases/download/0.20.0/rules_python-0.20.0.tar.gz", ) load("@rules_python//python:repositories.bzl", "py_repositories") py_repositories() load("@rules_python//python:repositories.bzl", "python_register_toolchains") python_register_toolchains( name = "python3_11", python_version = "3.11", ) load("@python3_11//:defs.bzl", interpreter_3_11 = "interpreter") load("@rules_python//python:pip.bzl", "pip_parse") # Create a central repo that knows about the dependencies needed from # requirements_lock.txt. pip_parse( name = "pip_deps", python_interpreter_target = interpreter_3_11, requirements_lock = "//repository-hibernate-impl/src/test/python:requirements_lock.txt", ) # Load the starlark macro which will define your dependencies. load("@pip_deps//:requirements.bzl", "install_deps") # Call it to define repos for your requirements. install_deps() 步骤2: 定义SQLFluff依赖 requirements_lock.txt的内容如下: ...

2023-04-17 · 2 min · 315 words · 翟志军 Jack Zhai

和行业里多家DevOps平台的同学交流后,我发现……

前段时间和不同的人交流了DevOps平台后,大概了解了行业里DevOps平台是怎么回事。 DevOps平台最大的问题 DevOps的定义是什么?面对这样的问题,我想说行业里,10个人可能有11个答案。既然DevOps的定义在行业都没有一个权威的定义,那么,对于DevOps平台是什么这样一一个问题,自然也就没有权威的定义了。这就是行业里DevOps平台最大的问题。 P.S. 对于DevOps Master证书又应该如何定义呢? 如果DevOps平台没有定义,那么,我们就无法以下类似的问题: 它解决了谁的什么问题? 行业里,它的趋势是什么? DevOps平台发展到后期,是不是AIOps? 因为,你再怎么回答这些问题,只要你与提问人对于DevOps的认知不一样,你都会与提问人内心的答案不一样。 这让我想起马斯克的名言:我现在不和人争吵了,因为我开始意识到,每个人只能在他的认知水准基础上去思考,以后有人告诉我2+2等于10,我会说,你真厉害! 谈DevOps平台的设计 我看到的是它们有很多功能:项目管理、需求管理、测试管理、流水线、制品管理、自动化部署……所有人都类似地美其名为:一站式研发云平台。 可是,当你在使用时,你会发现,它们不过是在以前的项目管理平台、测试管理平台、流水线平台、制品管理平台、部署平台等多个平台的功能的重新堆砌。而这些功能之间有有联系吗?很少。 行业里不少客户会要求DevOps平台的Dashboard,不同的角色进入要显示不一样的东西。其实就是开发者进入Dashboard应该只显示开发者关心的功能,测试人员进入Dashboard应该只显示测试用例相关的功能……这是不是可以成为康威推论的一个佐证? 总的一句,DevOps平台不过是过去多个不相关的烟囱系统,通过一个Dashboard,让它们显得像是一个平台。 研发效率有最优解吗? 和这么多人交流后,发现大家还是有一些共识的。那就是:DevOps平台应该是可以提高研发(or 运维)效率的(如果我只说研发效率,估计有人会以为我认为DevOps平台只是为研发侧服务的)。 行业里,To C产品的设计,最优解通常是未知的,是需要不断寻找的。所以,发展出各种方法论寻找方法论。这对于To C产品的设计是有效的。因为它的前提是最优解真的在于用户。 抛开产品设计方法论,对于研发效率的提升这个领域, DevOps平台的设计者应该比用户知道最优解是什么? 那我们使用To B的产品设计方法论去设计如何?个人觉得,即对,也不对。对的地方是DevOps产品毕竟是卖给企业的,所以,在设计DevOps时不得不考虑谁给它买单这个问题。不对的还是我上文提的最优解问题。 DevOps平台与Srcum、敏捷、迭代开发之间的关系 如果你的DevOps平台不与这些听起来牛x、高大上的名词关系起来,在这个行业里,你的产品估计有点难打开市场。 这就要求,我们在设计DevOps平台时,不得不考虑行业里的人对于Scrum、敏捷、迭代开发的理解。 个人觉得难点在于:在DevOps平台的设计上,无论用户希望使用何种方法论,都能在平台内实现DevOps平台本身的目标,同时平台还能保持自身的简洁。 用户使用那些方法论背后的目的才是关键。 后记 感谢这些同学花时间与我交流。本文作为一篇交流文章,目的不是为了贬低DevOps平台。

2020-11-02 · 1 min · 34 words · 翟志军 Jack Zhai

持续构建、持续测试、持续集成、持续部署、持续交付、持续.....“持续”到底是什么意思?

虽然,读者朋友可能觉得自己已经理解这些概念了,但是,还是希望读者读完。笔者从权威的书上将这些概念的定义摘抄下来,最后给出笔者对于“持续”的理解。 构建(Build): 一次构建不止是一次编译(或者动态语言中的某种称谓)。一次构建可能包含编译、测试、审查和部署以及其他一些事情。一次构建是将源代码放在一起,并验证软件可以作为一个一致的单元运行的过程。摘自《持续集成》 其实构建过程中还可以包括测试、部署。这点可能和很多人的理解有出入。这里就会有疑问了,既然构建中包括了部署,那么持续构建与持续部署又有什么关系?笔者是这样理解的,因为软件系统是需要部署了,才能测试的,所以,为了在构建过程加入测试,就必须引入部署。 部署(Deployment): 部署是一种技术领域的操作,也就是说从某处获取软件包,并按照预先设计的方案将其安装在计算节点上,并确保系统可以正常启动,但它并不定意味着“必须包含业务功能的发布或交付”。摘自《持续交付2.0》 交付(Delivery,也被称为发布): 是一个业务决策活动,通常也被称为“发布”,也就是说,如果将新的构建的特性交到客户(用户)手中,用户就可以看到并使用它们。摘自《持续交付2.0》 我们可以将代码部署上生产环境,但是我们可以通过某种技术手段,让用户看不到,也不能使用它。这就是只部署,但不交付。部署与交付的差异在于部署是技术端操作、交付是业务端决策。 这里,读者可能又有疑问了:未完成的功能,可以部署上生产环境吗?笔者的回答:是的。前提是你能控制该功能是否对用户可见。这称为功能开关。 持续集成(CI): 它是一种软件开发实践,即团队的成员经常集成他们的工作。通常每个成员每天至少集成一次——这导致每天发生多次集成。每次集成都通过自动化的构建(包括测试)来验证,从而尽快地检测出集成错误。摘自《持续集成——软件质量改进和风险降低之道》 注意,持续集成中包括了构建与测试。所以,我们在行业里经常听到的“持续测试(CT)”又是什么呢?这是笔者的疑问。 持续交付1.0(CD1.0): 持续交付是一种能力,也就是说,能够以持续方式,安全快速地把代码变更(包括特性、配置、缺陷和试验)部署到生产环境上,让用户使用。摘自《持续交付2.0——业务引领的DevOps精要》 持续交付2.0(CD2.0): “持续交付2.0”建立在“持续交付1.0”的“可持续地快速发布软件服务”及精益创业的“最小化可行产品”两种理念基础之上,强调要以业务为导向,从一开始就业务问题进行分解,并通过不断的科学探索与快速验证,减少浪费的同时,快速找到正确的业务前进方向,简称为“双环模型”。摘自《持续交付2.0》 持续集成、持续部署、持续交付之间是什么关系呢?笔者认为是:持续交付的过程会包含持续部署,持续部署的前提是持续集成。把集成与部署组合到一起,并完全自动化,这个自动化的过程,称为部署流水线。持续交付1.0强调的是交付的效率,持续交付2.0则除了强调交付的效率,还强调交付的效果。 最后,我们来谈谈“持续”,笔者是这样理解的: 所谓的“持续”,就是指经常地做。而“经常”是一个相对的概念。对于每年交付一次的软件系统,优化成每个月一次,也算是“持续”了。另,“持续”代表的是一种能力。有能力持续交付,但是业务不一定允许。要实现“持续”的能力,自动化就成为了必然的选择。 说到了“持续”就不得不说“持续改进”。上文说过,持续指的是经常做。持续改进的意思是经常做改进。持续改进的极限是無時不刻地在改进。那么,如何让一个团队无时不刻地进行改进呢?这是一个非常大的话题。关注笔者的公众号,将来会讲到。

2020-09-16 · 1 min · 21 words · 翟志军 Jack Zhai

一些持续交付的实践经验

分析团队的问题 我是2020年3月份加入该部门。刚加入时发现问题还挺多。而这些问题在行业里都很典型,比如: 分支管理不统一:虽说大部分人还是在master上开发,但是还有部分人自己拉feature分支开发。 没有统一的制品打包:Docker镜像的打包基本都是在开发人员的电脑上进行的。 对制品仓库的push权限没有管理:每个人都有push权限,而且使用的是同一个账号。 没有版本管理:在交给测试人员测试(俗称提测)时,开发在本地打包后,push上制品库的包的版本号为uat20200302(UAT是测试环境的简称)。测试通过后,再使用此版本号,部署到生产环境。结果就是你会看到生产环境运行的包的版本:uat20200302,是不是很奇怪? 没有监控:这也是很多团队的通病了。 没有单元测试:开发人员有在main方法写单元测试的,有写出来的测试是无法自动化的。 开发团队没有自己的自动化测试。 多个应用部署在同一台机器。 手工部署:每次部署都是人工登录到服务器执行部署。 数据库没有版本化。 没有代码审查。 问题还有很多,就不一一列举。在笔者看来以上问题的最终表现都是软件系统的质量低下。笔者希望通过实践持续交付以提升软件系统的质量。 但是问题是该如何实践呢?笔者认为只要掌握了它的基本原则,剩下的就是根据实际情况结合基本原则来解决问题了。 持续交付的原则 幸福的家庭都是相似的,不幸的家庭各有各的不幸——《安娜卡列尼娜》 从《持续交付》书中,基本原则有: 为软件的发布创建一个可重复且可靠的过程 将几乎所有的事情自动化 把所有的东西都纳入版本控制 提前并频繁地做让你感到痛苦的事 内建质量 “DONE”意味着“已发布” 交付过程是每个成员的责任 持续改进 我们可以这么理解这些原则:基于所有东西都要进行版本化的原则,所有的东西都要代码化。 因为代码化以后就可以放到类似Git这类版本化工具中。而代码化以后的东西就可以很容易地实现自动化。在实现自动化以后就可以为软件的发布创建一个可重复且可靠的过程。 实现自动化的过程是需要每一位成员持续参与的,因为交付过程是每个人责任。 “DONE意味着已发布”是团队每个成员都要达成的共识。达成共识后,才能更好的参与持续改进。在持续改进过程中,我们的软件系统就获得了内建质量。 笔者认为,在持续交付中,代码化与版本化是基础。 实践持续交付 在理解原则后,我们就可以开始实践了。可是该如何下手呢?笔者通常遵循以下指导思想: 先CI,后CD。 无监控,无安心觉。 先配置项版本化,后标准化,最后才有自动化。 根据指导思想,再结合团队的实际情况,笔者做出以下计划: 打包自动化。 实现基础监控(机器级别监控、中间件监控)。 实现所有的配置版本化。 实现自动化部署应用。 实现应用监控。 实现数据库版本化。 实现业务监控。 研发数据收集 …. 由于团队原来已经有日志收集机制,所以,暂且不需要实现。以上步骤只代表一个优先级。如果团队人力充足,可以同时一起做。 虽说有了计划,但是团队不具备相应的能力,什么计划都白搭。 1. 打包自动化 之所以使用“打包”这个听起来不怎么“高端”的词,而不是使用“构建”。是因为“构建”这个词,太容易引起歧义。而且打包这个词很形象,就是把源代码编译后,链接,最后打包成一个可执行包。当然不同的编程语言,打包过程可能不同。 因为大多数团队都没有写自动化测试的习惯(我们团队也不例外),让他们写自动化测试,他们只会觉得自己的工作量增加了。所以,我在团队中导入持续交付实践时,一开始就不要求自动化测试。团队意识的转变需要很长的过程。这是使用“打包”的第二个原因:它不包括自动化测试。 要实现自动化打包,其实并不难。基本步骤就是: 搭建制品库:Nexus。 搭建自动化服务:Jenkins。 在Jenkins中创建pipeline任务。 在业务代码仓库中加入Jenkinsfile,将打包逻辑写到Jenkinsfile中。 所谓打包逻辑就是你在本地开发时,利用IDE或命令将源代码编译成可执行文件的过程。打包自动化的过程,就是将你在本地执行的打包过程“搬”到自动化系统上执行,再加上一些优化。 在这个阶段中,我们需要实现: 统一制品库:收回所有人上传制品的账号。只能由Jenkins打包上传。 统一版本号:比如使用格式年-月-日-commitId-构建号来定义所有的后端应用。注意,不管使用哪种方式,你必须很容易的根据版本号找回相应的源码。 统一分支管理:使用主干开发,分支发布的模式。我们根据团队情况有稍微做了一些改变。发布并没有切分支出来,而在发布后发现某版本有Bug,我们就会从该版本的代码切一个分支出来改,打包,部署。最后再将该分支的commit cherry pick回master分支。 2. 实现基础监控 没有监控,在我们这个行业太常见了。所以,在我加入团队后,发现几乎没有任何监控,也就没有什么好惊讶的了。所以,在解决打包问题之后 ,紧接着就是给所有的机器加上监控。至少机器的CPU、硬盘、内存等要监控起来。 上一阶段,我们已经把Jenkins搭建起来,所以,Prometheus就开始自动化部署了。 使用Prometheus的原因很多,但是关键是它的配置是代码化的,非常容易版本化。持续交付的原则:将几乎所有的事情自动化、把所有的东西都纳入版本控制。像Zabbix,使用需要使用界面进行操作的,就被我排除了。 ...

2020-09-09 · 2 min · 249 words · 翟志军 Jack Zhai