在本文之前我已经写了12篇与wydevops相关的文章,介绍了wydevops从初始化到Docker镜像打包,再到chart镜像打包的大致过程,重点介绍了wydevops的参数映射机制、三级功能扩展机制、调用链机制、资源生成器插件机制等核心技术和关键规则,并以Java语言项目为例对这些重要功能进行了演示说明,这些示例充分的展示了:在团队预置好参数定义的情况下,业务开发人员可轻松具备无需太多配置甚至不需配置即可将微服务从源码状态直接发布到K8S集群中的能力。 然而仅仅是向大家展示wydevops的强大还远远不够,还需要为wydevops的使用者提供更重要的易用性。一个虽然强大但很难上手的东西,是很难有大量使用者的。

        在wydevops设计之初,就使用哪种语言来开发是有考虑的,wydevops希望:

  • 能兼容Linux和Windows两种操作系统,且其实现与服务器的架构应无关;
  • 不仅具备本地工作模式,能在无网络、无仓库的情况下,打包发布不同语言实现的微服务;还要能与jenkins无缝集成,搭建完整的CI/CD流程。
  • 能以docke模式部署微服务(支持SpringCloud),也能将微服务直接部署到K8S集群中。
  • 尽量减轻甚至消除不同语言开发者对微服务打包发布相关技术细节的关注。同时还需要为运维人员提供管理微服务打包部署过程的能力。

       综合考虑后最终决定使用shell脚本来完成。同时在实际工作中接触到了很多碎片化的shell脚本,有用来进行docker镜像构建和推送的脚本,有用来进行chart镜像生成和推送的脚本,有用来在K8S中安装服务的脚本,有用来从K8S集群中卸载服务的脚本... 这些脚本往往掌握在开发人员或运维人员个人手里,无法进行统一的版本管理和维护,急需对这些脚本实现工程化、项目化管理。

       在这样的背景下我创建了wydevops项目。wydevops需要处理的文件格式主要是yaml文件,很自然的,对yaml文件的读写能力直接决定了wydevops项目的质量。

        对于yaml文件的读写,各种高级语言都提供有工具库。大部分语言都能很好的支持读操作, 大多数语言是把YAML转成Map对象,再按Map访问的方式逐层读取的。而对于写操作,大多数语言是先将YAML文件转成MAP对象,对MAP对象进行修改后再完成MAP对象转YAML格式输出到文件中。但是这些方式不适合shell脚本。shell脚本也可以引入第三方库来实现读写yaml文件,但这样会引入系统或架构兼容性问题,同时也没有找到能满足我期望的读写方式的第三方库。最后我决定直接使用shell脚本来完成YAML文件的读写。

        我期望的对yaml文件读写的API接口应该是这样的(以下为伪代码):

read {ymal文件名称}  {点分符构造的目标参数链}

write {ymal文件名称}  {点分符构造的目标参数链}  {简单值字符串或Yaml格式的单行或多行字符串}

        在坚持不懈的努力下(调试shell脚本的确太熬人了),最终为wydevops打造出了符合其要求的完全用Shell脚本实现的读写器:yaml-helper.sh (位于源码项目中script/helper目录下)。不再多说,直接上例子来展示它的魅力。

1. 准备工作        

  • 在wydevops源码项目的script/test目录中,复制test.sh文件另存为my-test.sh。
  • 清理my-test.sh文件的内容,仅保留下图中的内容。

2. 功能演示  

2.1 读写不存在的yaml文件

2.1.1 读不存在的yaml文件

    my-tesh.sh文件末尾输入如下语句:

readParam "${tmpFile}" "image.registry"

echo "----gDefaultRetVal=${gDefaultRetVal}---" 

编写完毕并完成。此时文件内容如下图:

在my-test.sh所在目录打开git bash命令行执行:bash my-test.sh。返回结果如下图: 

2.1.2 更新不存在的yaml文件

参照下图修改my-test.sh文件内容:

然后在git bash命令行执行:bash my-test.sh。返回结果如下图:

 

2.1.3 向不存在的yaml文件中插入数据

参照下图修改my-test.sh文件内容:

然后在git bash命令行执行:bash my-test.sh。返回结果如下图:

gDefaultRetVal变量yaml-helper.sh中所有方法的返回值存储变量,gDefaultRetVal变量中保存了最后一个yaml-helper.sh中的方法执行后的返回值。

gDefaultRetVal变量的值格式随调用的方法不同而有差异,详情可参考yaml-helper.sh文件中__readOrWriteYamlFile方法中的注释,关键截图如下:

这里就不详细解释返回的各数据意义了,本文着重展示yaml-helper.sh功能的强大。

这里执行insert操作后返回信息不是以”-1“开头则说明执行成功。此时在my-test.sh同目录下出现了my-test.yaml文件,其内容如下图:

结论:read或update不存在的yaml文件直接报错。insert不存在的yaml文件能成功。

特别说明:后文仅直接给出执行的语句,不在啰嗦如何更新my-test.sh文件内容了。

2.2. 读写数组

2.2.1 插入不存在的数组

代码:insertParam "${tmpFile}" "image.nodes" "[192.168.1.1,192.168.1.2,192.168.1.3]"

执行成功。此时my-test.yaml文件内容如下图:

2.2.2 读取数组值中某一项

代码:readParam "${tmpFile}" "image.nodes[1]" 

执行成功,并正确返回结果,如下图:

2.2.3 更新指定数组项的值

代码:updateParam "${tmpFile}" "image.nodes[1]" "192.168.1.100"

执行成功,并正确返回结果,如下图:

说明:执行更新操作时,指定的数组项序号不能大于该数组的最大数组项序号

2.2.4 插入新的数组项

代码:insertParam "${tmpFile}" "image.nodes[4]" "192.168.1.101"

执行成功,my-test.yaml内容如下图:

说明:插入操作中,如果目标项的序号大于了数组项的最大项序号,则会补齐缺失的数组项,缺失项的值为空串("")。

2.2.5 删除指定数组项

代码:deleteParam "${tmpFile}" "image.nodes[3]"

执行成功,my-test.yaml内容如下图:

说明:删除不存在的列表项时,gDefaultRetVal返回数据是以“-1”开头,表示执行失败。

2.3. 读写列表

2.3.1 插入列表类型参数

 代码:insertParam "${tmpFile}" "server.nodes" "- ip: 192.168.1.100\n  port: 80\n- ip: 192.168.1.101\n  port: 81"

执行成功。此时my-test.yaml文件内容如下图:

使用下图中2的方式定义好参数再插入效果也一样。

说明:对列表类参数整体(而不是其某一项)插入或更新时,要注意带上列表项前导字符“-”。

2.3.2 读取指定序号的列表项

代码:readParam "${tmpFile}" "server.nodes[0]" 

执行成功。返回内容如下图:

说明:完整返回了指定序号的整个列表项的内容。

2.3.3 读取指定序号列表项的指定属性

代码:readParam "${tmpFile}" "server.nodes[1].ip" 

执行成功。返回内容如下图:

说明:成功返回了目标属性的值。

2.3.3 更新指定序号列表项

代码:updateParam "${tmpFile}" "server.nodes[1]"  "ip: 192.168.1.111\nport:\n  number: 86\n  name: 测试端口"

执行成功。my-test.yaml内容如下图:

说明: 更新指定列表项时,数据中不需要带列表项前导符"-"。但是数据本身要符合yaml文件格式。本例给修改了port参数的值,从一个简单值变为了一个对象值。同样的也可将一个对象值更新为简单值。

2.3.4 更新指定列表项的指定属性

代码:updateParam "${tmpFile}" "server.nodes[1].port.number"  "8006"

执行成功。my-test.yaml内容如下图:

2.3.5 使用插入方式更新指定列表项的指定属性

代码:insertParam "${tmpFile}" "server.nodes[1].port.number[1].value"  "8006"

执行成功。my-test.yaml内容如下图:

说明:上述代码将number属性从一个简单值变更为了一个对象列表类参数,且补全了该对象列表的第0项(仅有列表项前导符,是个空对象),最后设置了该对象列表的第1项对象的value属性值为8006。可对第0项成功进行新的插入操作。

2.3.6 删除指定列表项

代码:deleteParam "${tmpFile}" "server.nodes[1].port.number[0]" 

执行成功。my-test.yaml内容如下图:

2.3.7 删除指定列表项的指定属性

代码:deleteParam "${tmpFile}" "server.nodes[1].port.number[0].value" 

执行成功。my-test.yaml内容如下图:

2.4. 对特殊字符"|"的支持       

2.4.1 插入以“|”开头的参数项

代码:insertParam "${tmpFile}" "test.content"  "|\nip: 192.168.1.222\nport:\n  number: 996\n  name: 测试端口A"

执行成功。my-test.yaml内容如下图:

2.4.2 更新以“|”开头的参数的值

代码:insertParam "${tmpFile}" "test.content"  "This is a test"

执行成功。my-test.yaml内容如下图:

说明:除了在目标参数行冒号后面添加竖杠开头的特殊串(|、|+、|-)外,基本与其他类型的数据读写没有区别。

2.5  功能综述

        通过上面的演示,可以看到yaml-helper.sh对yaml文件超强的读写能力,为wydevops后续的扩展和维护提供了极大的便利,极大的减轻了使用者对yaml文件读写的难度,让使用者有更多的精力集中在功能上,明显提升了后续功能开发或维护的效率。

另外,补充以下几点说明:

  • 上述内容没有演示对简单K-V对的读写操作,这个请大家自行尝试。 
  • 除删除操作外,其他操作都是可重入的,也即读取、更新或插入语句重复执行多次文件结果保持一致。
  • 插入已存在的参数时,自动按更新操作处理。
  • yaml-helper.sh在读写yaml文件内容时,会自动处理文件内容中的注释行,保留了对注释行的良好兼容性。毕竟注释能有效提高项目的易用性。
  • yaml-helper.sh文件中还有很多辅组功能方法,其中比较中重要的是yaml文件的合并方法(combine),这些功能的使用在wydevops中都有例子,请大家自行参考。
  • yaml-helper.sh是独立的组件,内部仅依赖了log.helper.sh文件。可应用于任何需要使用Shell读写yaml文件内容的场景。
  • 目前存在的功能缺陷是不支持参数名(即Key)中带点分符。这个场景是在设置ConfigMap配置文件时,文件名称是带有点分符的。目前wydevops解决这个问题采用的策略是:先用占位符替换了文件名称,在插入完成后,再使用sed命令全局替换占位符。

3. 性能优化

      wydevops实现的yaml-helper.sh,采用递归方式对目标参数进行查找,而且都是文件操作,这必然带来性能低下的问题。早期未作性能优化时完整执行一次流程,大约要20分钟以上(也可能是我用的笔记本性能差导致的),这是不能接收的,因此对yaml-helper.sh的执行性能进行优化,主要优化点如下:

  • 优化读写和查询语句,尽量减少查找范围和读取的数据量。
  • 将文件内容读取内存中缓存起来,对文件内容的查找、读写全部转为在内存中操作。
  • 在内存中设计了创建了一个文件-参数路径起始行注册表(Map对象),根据对文件内容的操作实时对其进行更新。在进行参数查找时,首先从该注册表中查询与目标路径最长匹配的项,如果查询到了则直接读取该项对应的起始行信息和相关参数,从该行开始对目标参数进行查找。在目标参数行定位查找过程中,保存或更新所有经过的参数的所在行数及其当前上下文参数的值,以便后续使用。 这个设计极大加快了对长参数链的读写速度。

4. 结束语

        本文介绍并展示了wydevops特有的Shell实现的yaml文件读写功能。这项强大的能力配合上前面介绍的参数映射机制、三层功能扩展机制、调用链机制、资源生成器插件机制共同赋予了wydevops优秀的可扩展性、可维护性和易用性。我相信,随着与wydevops的不断成长,被更多读者所熟知,它必将在DevOps领域中占据一席之地。

        今天就写到这里了,后面文章将转向写与Jenkins的集成搭建完整CICD流程的内容。

原创不易,请点赞、关注和收藏。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐