使用Mxroute和Sendgrid实现邮箱服务和邮件发送服务

最近在做一个产品,需要用邮箱服务和邮件发送服务。本文以Mxroute和Sendgrid为例介绍邮箱服务和邮件发送服务的配置。但是所有的这类产品,思路都应该是一致的。 Mxroute是邮箱服务,类似Web服务,只不过,它是专门为邮箱协议而设计的。Sendgrid就是邮件发送服务,也就是你需要批量向一堆邮箱发送邮件时,就需要用邮件发送服务。本质上Sendgrid与Mxroute是两回事。但是,通常我们先配置邮箱服务,再配置邮件发送服务。 本文只为记录一下,将来忘记了可以重新拾起。 配置邮箱服务 首先,邮箱服务需要MX类型的域名解析记录。这能让邮箱服务能在整个互联网被解析到。 每一个在Mxroute付费的用户,都会被分配到一个独立的MX域名,如first.mxrouting.net 。他们应该会发邮件给你,你需要留意。 在Mxroute上配置的步骤如下: 创建一个域名。比如example.com。如果你使用的是子域名,也可以是mail.example.com。 拿到DKIM Keys等信息。在Mxroute的左边菜单中可以找到链接 在域名提供商中,再配置以下这些DNS记录 记录类型 name content 优先级 MX _dmac v=DMARC1; p=none CNAME mail <mxroute分配的独立MX域名> MX mail <mxroute分配的独立MX域名> 10 MX mail <mxroute分配的独立MX域名> 20 TXT mail <从mxroute上获取> TXT x._domainkey <从mxroute上获取> 如果你使用的是子域名,那么,还需要在 _dmas和x.domainkey 后加上 . 。例如mail子域名,就是 x.domainkey.mail。 通过以上配置,只证明我们的“邮箱服务器”已经配置好了。现在在上面创建账号,并进行测试了。如果你可以向这个账号收发邮件,就证明,你的邮箱服务已经配置完成。 配置邮件发送服务 当你有了一个邮箱账号后,你就可以Sendgrid上配置了。登录后,从左边菜单“Senders”进入列表页。 然后再点击按钮“Create new Sender”,即可创建。这部分就不细说了。因为太简单了。

2023-12-31 · 1 min · 49 words · 翟志军 Jack Zhai

使用Google OSV工具扫描依赖安全漏洞

安全漏洞是软件工程化能力的试金石 2021年年底,Log4j的漏洞陆续被公开。因为该框架被大量的开源软件依赖,所以,漏洞影响面非常大。 面对这个漏洞,我们遇到的第一个问题是:如何知道我们哪些工程使用了Log4j? 在我看来,这个漏洞是企业软件工程化的一颗非常好的试金石。因为: 如何第一时间了解到这个漏洞,反应这家企业的安全能力; 如何第一时间能找到所有使用了Log4j的位置,体现了这家企业第三方软件依赖管理能力; 替换Log4j的速度,体现企业的持续集成、持续部署的能力。 Google的开源软件安全漏洞扫描工具 今天介绍的OSC-Scanner,能加强我们第1项和第2项能力。 OSV-Scanner是Google在2022年12月13日推出的一款免费的安全扫描工具。它具有以下特点: 支持多生态系统,包括:Go、PyPI、RubyGens、Linux、Maven等16个生态系统; 同时支持直接依赖的扫描和间接依赖的扫描; 采用标准的漏洞记录格式; 从当前最大的开源软件漏洞数据库(https://osv.dev/)获取信息。这也是DenpencyTrack和Flutter安全工具的漏洞数据库。 OSV-Scanner是一款命令行工具,我们可以将它集成到我们的构建工具或者CICD Pipeline中。目前它已经被集成到Scorecard中。Scorecard是一款为开发源软件的安全健康度打分的开源软件。我们可以在Github Actions中使用它:https://github.com/ossf/scorecard/tree/main?tab=readme-ov-file#scorecard-github-action OSV-Scanner的安装 Windows: scoop install osv-scanner Mac Homebrew: brew install osv-scanner 也可以直接下载二进制包:https://github.com/google/osv-scanner/releases 具体安装文档:https://google.github.io/osv-scanner/installation/ OSV-Scanner的使用 Keras是一个使用Python编写的开源人工神经网络库。我们以它为例。命令行里运行以下命令: ./osv-scanner_1.3.6_linux_amd64 --format json keras/ 输出内容说明:keras存在一个“潜在内存泄漏”的漏洞。 当拿到json结果后,我们的DevOps平台就可以进行一些告警监控的操作。 后记 osv-scanner目前需要连osv.dev,才能使用。但是,已经开放实验功能,允许用户离线使用osv-scanner。这是自建DevOps平台的福音!

2023-12-25 · 1 min · 39 words · 翟志军 Jack Zhai

不出事故,没有人知道你重要

有同学在知乎上提问:“线上无事故,运维还重要吗?”,描述如下: 本人运维行业,本部门在近几年一直保持效率增长且极少出现重大saas生产事故,并且为其他部门输出提升方法以及友好协同提升,但是最近从各层面接到反馈说对运维的投入减少,着实想不通,线上出了事故要运维背锅,产品出了bug要运维陪着到最晚,为什么把线上环境搞得稳定了,却不重视运维岗了? 这是原贴:https://www.zhihu.com/question/497361582 以上提问的是一个运维的同学。言下之义是不出事故,没有人知道运维重要。 这位同学的的感受,过去几年,我感同深受。我相信因为这个标题而点进这篇博客的同学,也有同样的感受。 但是,为什么出事故后,是运维重要呢?而不是测试、开发或者手机端开发呢? 通常是因为运维这个角色: 线上环境,他们最清楚,通常也只有他们有权限操作线上环境,可以紧急加一个数据库索引; 他们掌握了部署能力,可以发起回滚操作; 有权限查看各个组件的情况,并诊断根因; 为团队准备基础设施能力,如金丝雀发布能力; 搭建告警监控系统、CMDB、DevOps平台等。 等等 但是,这些与是否出事故,有多大的关联性呢?我们应该统计各种事故的根因的类型的比例,才有答案。 就目前而言,我们并不能说因为我们看重运维,就不出事故。 以上的问题是从个人感受出发的提问。只是更深层次问题的表象。 从企业层面上,我的疑问是:为什么在企业里,稳定性建设通常都是一阵阵的。即出一次事故,就立个项,就加班加点去完成“稳定性”项目。 比起讨论个人感受,从企业层面讨论这个问题,似乎更有趣。 其实,除了稳定性,软件的质量建设也是一阵阵的。想想,不是吗?不出Bug,没有人知道测试重要。 也许这是所有企业的正常表现。就像人的身体,痛风(一种慢性病)不发作时,你是不会感受它的存在,也自然就不会想到要去治疗或者预防它。然而,如果平时不注意饮食和锻炼,痛风经常复发。 线上事故就如同企业的痛风。企业应对“痛风”,容易好了伤疤忘了痛。 虽说可能是所有企业的正常表现,但不是一种健康的表现。 预防痛风,只能通过健康的生活方式如: 限制或避免饮酒,尤其是啤酒。 限制或者避免饮用含糖饮料,尤其是含高果糖玉米糖浆的饮料。 限制肉类摄入量,尤其是红肉、内脏和海鲜。 保持健康的体重。如果您需要减肥,请避免断食或过快地减肥,因为这可能会暂时增加尿酸水平。 增加水和低脂乳制品的摄入量。这些有预防痛风的作用。 一个人应对痛风的健康表现应该是采用健康的生活方式。 说回企业的稳定性建设,也是一样的道理。 稳定性不是通过“一阵阵的运动”或者“一阵阵的表演”来建设的,而是通过平时健康的企业活动来实现(我无意指导别人的企业,这只是我个人的思考)。 当然,现实中,对于有些人,要维持健康的生活方式是一件很难的事情(想想有身边有多少人做到早睡早起),而另一些人是一件很自然的事。为什么呢? 相同的,一家企业为什么无法自然地做到健康的企业活动?一定要出事故,才知道X的重要性呢?(X代表任何东西) 这个问题就很大了。希望对各位读者有启发。

2023-11-21 · 1 min · 34 words · 翟志军 Jack Zhai

Bazel作为构建工具之王,将会颠覆你对CI的认知

说到构建工具,不同语言技术栈的人,想起的构建工具不同。 Java程序员想到的是Maven,前端程序员想的是NPM或者Webpack、Android程序员想到的是Gradle、Rust程序想到的是Cargo、C++程序员想到的是Make等等。 然而这些工具在Bazel面前,层次有些低。所以,我愿称Bazel是构建工具之王。 P.S. Android平台的构建,2020年已经开始了迁移到Bazel的工作。 具体地址:https://blog.bazel.build/2020/11/12/aosp_migrating_to_bazel.html Bazel介绍 Bazel是Google在2015年开源的一款构建工具。 目前使用Bazel的知名公司有:Esty、Canva、Databricks、Dropbox、Huawei、Line、LinkedIn、Stripe、Twitter、Tinder、Uber、VMware、Wix等。具体可以看:https://bazel.build/community/users 。 其中Twitter是从自家的Pants迁移到的Bazel的,具体迁移过程介绍:https://opensourcelive.withgoogle.com/events/bazelcon2020/watch?talk=day1-talk2 Facebook使用的是其自研的Buck2,但是,其与Bazel使用的是相同的远程执行的API。 除了公司,某些著名的开源软件也使用Bazel构建,包括自动化测试领域的Selenium,AI领域的TensorFlow,容器编排领域的Kubernetes等。具体还有:https://bazel.build/community/users#open-source-projects-using-Bazel 相对于其它构建工具,它的显著的特点有: 支持多语言; 支持远程分布式构建; 支持增量构建; 支持强大的密闭性; 支持构建缓存; 支持并行构建。 假设存在一个复杂的软件工程 假设存在一个软件工程中,它包含5部分:Web前端、Android端、Java后端、Go后端、嵌入式端。 作为Java后端的程序员,他们修改了一个API。但是他作为个人,他无法预知到底发生了哪些影响。 所以,他把这个问题交给了持续集成(CI),让它去发现集成问题。 在过去很长一段时间里,行业里只有一种CI模式,我称之为传统的CI模式。 殊不知,还有另一种模式。 传统的CI模式 目前行业里比较传统的CI架构,通常如下: 在这样的架构下,实现CI的步骤如下: 开发人员提交代码; Gitlab检测到开发人员提交代码,然后触发Jenkins controller执行; Jenkins controller根据该代码仓库预先设计的pipeline执行; Jenkins controller根据pipeline中的任务所需要的构建环境,将任务分配给不同的Jenkins agent; 在agent构建完成后,将制品release到制品仓库中。 如果开发者希望验证自己写的代码,就必须将代码commit到Gitlab中。因为整个验证环境被定义在CI环境的Pipeline中。而且这个过程,越大的工程,集成速度越慢。开发者也无法在本地进行全量验证。 作为Pipeline的维护者,他需要清楚知道哪些任务是可以并行执行的,并手工配置并行,这样才能加快构建速度。比如前端构建和后端构建可以并行进行。 也就是说在传统的CI模式下,开发者的效率会随着软件的规模越大而降低。换句话,这样的模式,开发效率无法scale。 案例 希望以下案例可以给你一个感性的认知。下图是Google在2010年到2015年的周commit数量。绿线代表commit总数,黄线是人数。我们取离我们最近的2015年的数据来讨论。2015年的代码量如下: 在这个代码量下,每周能达到300左右的commit。如下图: 根据持续集成的原则,每一个commit都必须构建通过。20亿行代码一次全量构建需要多久? 我们以一个开源项目作参考。apitable是一个开源的数据表格项目,它有200万左右的代码,全量构建一次需要20分钟左右。那么,根据不准确的类推,20亿行代码,全量构建一次需要:20/2,000,000 * 2000,000,000=200,000,000分钟,也就是13天左右。 在传统的CI模式下,是尽量避免执行全量构建这样庞大的代码量的。所以,传统CI模式下,通常是多仓库模式管理代码。 那么Bazel呢?Bazel如果真要构建这样庞大的代码量,估计也够呛。但是由于Bazel天然支持并行构建、构建缓存和增量构建,所以,Bazel通常不会遇到真正意义的全量构建的情况。 为什么其它公司不使用Bazel 也许有人会问:为什么阿里2018年新增的代码行(https://zhuanlan.zhihu.com/p/54435171)就有12亿,不也没有使用Bazel吗? 这个是一个好问题。 但是,无法简单的回答这个问题,而是需要深入到各自组织内部才能分析清楚。个人觉得可以从以下维度分析: 在代码仓库上工作的人员的规模:同样的代码量,不同的组织需要不同数量的人维护; 代码管理方式:阿里使用多仓库的管理办法,不需要统一的版本号; 持续集成的程度不同:阿里可能不需要对每一个commit跑一次全量。 为什么Bazel会颠覆你对CI的认知 Bazel是如何解决传统CI模式下开发效率无法scale的问题呢?其主要通过它的六个特性来解决。 首先,Bazel支持远程分布式构建。 在一个使用Bazel构建的仓库中,开发者写好代码后,不用commit代码到Git仓库,只要在本地命令行执行bazel run --remote_executor=grpc://localhost:8980 //... ,代码仓库中所有构建和测试任务都将运行在远程执行服务器。远程执行服务器越多,构建速度越快。 这一特性可以明显地提高开发者本地的开发效率。因为开发者在本地就可以执行全量构建和全量测试。 传统CI模式下,无法提升开发者本地的开发效率。 第二,Bazel支持增量构建和增量测试(精准测试)。 开发者在本地执行build命令时,Bazel检测出修改了a.java文件,所以,Bazel只将构建a.java的任务及其相关的构建任务给远程执行服务器执行。这就是增量构建。 如果开发者执行test命令,Bazel则能检测出被影响的测试,然后只运行这些测试。其实这就是精准测试了。在Bazel中,精准测试实现起来并不难。 传统CI模式下,它是不关心增量构建和增量测试的。所以,每次运行都是全量。这是一种极大的浪费。 ...

2023-11-20 · 1 min · 108 words · 翟志军 Jack Zhai

听说最近的事故都是循环依赖导致的?

​年底了,事故频发。但是都听说是因为循环依赖导致。所以,我决定来写写依赖管理领域中,通常不被重视的循环依赖问题。 循环依赖(circular dependencies)的定义 来自维基百科的定义: 在软件工程中,循环依赖是两个或多个模块之间的关系,这些模块直接或间接地相互依赖才能正常运行。此类模块也称为相互递归。 循环依赖是依赖管理领域中经常出现的一种现象,如下图: 循环依赖的不同层次以及后果 循环依赖可以发生在两个层次: 源码之间相互引用依赖; 服务之间相互调用依赖。 源码之间产生循环依赖带来的问题是:构建工具不知道应该从何处开始构建。因为构建工具无从下手。 构建工具底线这时就体现出来了,如果它可以忽略其中一个节点,“勉强”构建出一个制品,那么这个制品估计你也不敢用,因为该制品存在不确定性。这是源码层次中,循环依赖的后果。 而服务之间调用的循环依赖就更麻烦了。平时非常难发现,是不会出事故的,但一出事故就会雪崩。 因为你无法单独启动循环依赖中的任何一个服务,而循环依赖中的任何一个服务挂了,其它所有的节点都会同时挂。 循环依赖的环越大,影响面越大。 为什么会出现循环依赖 既然循环依赖从任何一个节点都法进行构建或者启动,那么它又为什么会产生?难道定义依赖的人不知道吗?Code Review的人不知道吗? 因为循环依赖是软件系统在长时间发展中,不加以合理的依赖管理所导致的。如下图。一开始只是B的V1版本依赖A的V1版本,最后变成B的V2版本与A的V3版本相互依赖。 软件系统发展的时间足够长,交接的人数足够多,软件依赖关系的规模早就超出了人类能处理的限度。 如何从根上就避免循环依赖 用人力的办法解决循环依赖,是不现实的。依赖管理这种吃力又不讨好,还拿不上台面的术语,没有人会去做。更不会得到KPI的“赏识”。 可以预料得到,如果对依赖管理治理不当,那么每几年,就可能出现一次循环依赖的事故。不发生事故的原因可能只有一个:软件的规模还不够大。 所以,我们必须想办法从根上避免循环依赖,即从依赖的定义的地方开始治理。 源代码层次,构建工具就可以帮我们依赖。只要出现循环依赖,构建就不通过。例如Make工具: make: Circular main.asm.o <- main.asm dependency dropped. 但是,如果是多仓库管理源代码的模式下,你可能还是很难避免循环依赖的情况。如果是单仓库,就不会出现这样的情况。 而服务之间的调用关系,在不同的公司定义的位置不一样。这也决定了查找循环依赖的成本。 服务之间的调用关系的定义,本质上属于配置管理的范畴。因为你总要在某个地方定义这些依赖。 服务之间的循环依赖查找,本质上就是配置管理领域中,查找配置之间的相互引用关系。 这说明配置管理的方式决定了查找服务之间循环依赖的成本。 什么样的配置管理方式才能低成本的实现查找循环依赖呢? 在这里,我提出我的方案:使用Jsonnet定义所有的配置(某些case无法覆盖),然后通过Bazel进行构建。 Jsonnet是一门专为配置定义而设计语言,语言只有一页A4纸; Bazel是一款支持增量构建、分布构建、构建缓存、支持多语言的构建工具。 通过这个方案,Bazel会自动构建一个软件依赖关系图,同时检测其中是否存在循环关系。只要循环关系存在,构建就不通过,当然就无法上线了。这样就从根上就避免了循环依赖。 如果想了解更多具体的落地方式,请关注我。并转发本文,让更多人看到这种神奇的配置管理方式。

2023-11-11 · 1 min · 43 words · 翟志军 Jack Zhai

反思一次效能提升

前天与一个大佬交流。想起自己在6年多前在团队里做的一次小小的效能提升。 改进前 在同一个产品团队,同时有前端工程师和后端工程师。他们经常需要共同协作完成features。 前端是一个传统的多页应用。前端渲染是由后端的velocity模板引擎实现的。 打包后,最终执行就是一个jar包: vm文件后缀名是velocity模板文件。它们内容大概是这样的: <html> <body> Hello $customer.Name! <table> #foreach( $mud in $mudsOnSpecial ) #if ( $customer.hasPurchased($mud) ) <tr> <td> $flogger.getPromo( $mud ) </td> </tr> #end #end </table> </body> </html> 其中一些非html代码就是Velocity Template Language。 默认情况下,一个前端工程师是不懂这门模板语言的。而且,这种模板语言对浏览器不友好,不像Thymeleaf。同时,按前端的开发习惯,他们是不可能先启动你一个Java进程后,再对前端页面进行调试。 总的来说,这样的技术栈是不利于前端本地开发的。 所以,在改进前,前后端的协作的方式是: 前后端同时进行开发; 后端负责在Java工程中实现后端的逻辑; 前端负责在另一个独立的前端工程中开发HTML和CSS; 前端完成页面的开发后,他会把前端页面html文件名和页面的逻辑告诉后端; 后端再把html页面里内容与Java工程中的vm进行对比,然后将不同的部分转换到Java工程的vm模板页面中。 这样的协作方式下,经常出现以下问题: 在将html转换到vm模板的过程中,后端有时会转换漏内容; 转换后,前端调试前端问题会很麻烦。前端需要占用一个后端和他一起调试。因为前端不懂如何启动复杂的后端工程; 命名上经常出现不一致,进而导致前端bug。对于同一个概念的情况下,前端命名叫item,后端叫project; 效能提升措施与效果 经分析,以上问题均由“技术之间的转换”导致,即人工的将html页面从一个前端工程转换到一个后端工程的vm模板语言。 所以,解决以上问题的思路就是:最好能消除html和vm的转换,又或者能减少这个转换过程的失误。 思路有了以后,解决方案有以下几个: 更换前端技术栈,不再使用Java+vm模板; 让前端学习vm模板语言,转换过程由前端完成; 优化后端开发在本地启动的流程,方便前端也能在本地启动; 减小测试环境的部署难度。让任何人都可以部署,方便前端进行调试。 方案1短时间无法做到,而且改动巨大,收益也未知。所以,最终是同时做了2、3、4。 虽然当时没有进行度量,但实际效果就是前文提到的所有问题都得到了减轻。 原因就是方案2、3、4,减少了前端需要向后端传递的信息。很多事情,前端一个人就可以解决了。 关于方案2、3,可能有人会好奇:前端去学习一门新的模板语言,成本大吗?前端在本地启动一个后端的工程去调试前端代码,成本高吗? 首先,vm模板并不是一门难的语言,只要有类C语言的基础(比如Java、JavaScript、C#等),都不难学。无非就是if-else判断、变量定义、循环等。 其次,在后端优化本地开发环境后,前端启动一个Java工程并不是一件难事。 反思 整个事件下来,本质上是前端本地开发习惯与现有技术栈的冲突。这次效能的提升需要前端开发做一些工作习惯上妥协。 而这只是表面上的问题,我们需要去思考更深层的问题: ...

2023-10-23 · 1 min · 68 words · 翟志军 Jack Zhai

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