开发者学堂课程【ALPD 云架构师系列:云原生 DevOps 36计-阿里云云效出品:源代码管理及软件配置】学习笔记,与课程紧密连接,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/772/detail/13499
源代码管理及软件配置
内容介绍:
一. 在软件交互的过程中,以代码为中心软件的作用
二. 如何管理代码或软件配置?
三. 问题:假设所有的代码都保存在一个代码库,且所有人均可访问,代码库应该怎么组织?
四. 问题:软件配置经常被修改,被发布,它属于代码吗?
五. 问题:alpd-demo 应用里面有没有启动配置和运行配置的例子?
六. Takeaways
一. 在软件交互的过程中,以代码为中心软件的作用
1.保存最终的交互制品。通过代码来描述,要交互成一个什么样子。代码定义了系统应该怎么工作,代码定义了我们的软件是怎么交付的,代码定义了我们的环境应该是怎么样,所有东西都是围绕代码。
2.代码是整个团队协作的一个工具。
二. 如何管理代码或软件配置?
上图是某个公司的一个代码的组织结构,把目录当做一个代码组, qinglong 是下面的子代码组,aTeam 又是一个子代码组。
这个代码组织结构,都有什么问题?
1. 目录中同时有中文和英文,代码组的命名方式混乱。
2. 用代码库存储外部二进制文件。整个代码库的性能消耗非常大,方式不合理。
3. 同一归属的代码库保存在不同的代码组中。
4. 公共库保存在子代码组里。
5. 应用的文档(或测试)与应用分开存放。
三. 问题:假设所有的代码都保存在一个代码库,且所有人均可访问,代码库应该怎么组织?
答案:代码组(+子代码组)+Git 代码库=大库
认识代码的组织方式的时候,代码是可以分组的。
比如这个代码组对应的代码就用一个代码库。假设所有的代码都保存在一个代码库,且所有人均可访问,代码库应该怎么组织?代码组,子代码组还有代码库,这些这么多的资源如何在一起?
本质上,它要组合成一个大库的一个形式,用很多不同的小库加上代码组来形成这个逻辑上的这个概念。
看上图例子,它合理的一个代码组织结构应该是什么样?
首先,这个系统里面有两个应用,一个是risk 一个是data, 然后每个应用下面有很多的服务,或者文档,然后有一个空公共的一个 model 叫 common-lib ,这个是被所有的应用所依赖的。
然后,把从属于同一个应用的这些给他库的放到一起。另外,让这个 common 那个放到他该有的地方去。这样就分得清,分得近了。然后不像刚才一样,按照团队来去划分 ,而是按照应用组划分,那这样就会比较清楚。
1. 实践建议
⑴代码库的内容:
①软件的源代码(Production Code)
软件的源代码就是生产模式的,另外像名单、测试这些东西,也应该放在那些地方,因为它们都是一伙的。
②将文档(和测试)的 git 库放到其相关应用组下
③不要将一些制品(如系统二进制包)保存在代码库中
很多公司里很常见的一个问题就是,把一些制品(如系统二进制包)保存在代码库中,这其实是有点问题的,因为本身是二进制文件领头的时候,代码库里的容量很大,一般流量大、小文件多,同时这个文件是怎么来的无法追溯。
⑵代码库的组织结构:
①按照系统、应用和模块的层次来组织代码库(分清层次)
②同一个系统/应用层级的所有内容位于同一个代码组下(归纳整理)
⑶代码库的可见性:
①通用代码库放在其通用级别都可以访问的位置
哪些是是一些人看的,哪些是给所有人看的,哪些应该是放给某一个应用里的所有人看的,这些东西要考虑清楚。
通用代码库放在其通用级别都可以访问的位置,因为所有用户都依赖它,如果它是一个工作的东西,就应该在公共的位置,同时可以看到,并且能够去改。这就相当于公共的地方意味着所有人都对它有诉求,所以放在公共位置也更容易大家去修改它。
②除核心算法等少数代码库外,尽量代码库的访问在同一系统/应用对有相关人员公开
这样让大家能看到全貌,否则我不知道它存在哪里,这样的话我会产生一些陷阱,比如我可能不知道去哪改,或者随便建个模,就会带来一些风险。而且可能未来对整个软件的价格来说也没有好处。但如果很难做到这一点,也建议在代码库里给一个结构,让整个参与这个项目的人都知道一个大概的整体是怎么样。
这就是一个代码的简单的组织方式。在整个过程当中,所有的开发者都是围绕着代码库,然后做相应的一些协作活动。
2. 软件交付过程,其本质是开发者围绕代码库的协作过程
这里铺设的操作都是围绕在 commit 上去展开的,分三点相应的借鉴。
⑴ SMALL
①一个应用一个 Git 仓库
②不在 Git 上存储构建产物和其他二进制文件
small什么意思,就是说这个库应该要尽可能的小。这里不太建议很大,尤其在目前基础设施的这种状况下。就应该一个应用就是一个 Git 仓库。而不是一个仓库里包含多个应用,这个情况下,当你维护起来就很不方便。
虽然把构建存在我的仓库给别人很方便,但是这其实不太好,这样很难追溯这个构建产物是现在的代码产生出来的,还是之前产生出来的。
⑵ LINEAR
①多用 rebase,少用 merge
②避免无效 commit
不要那种无意义的merge,就这个merge应该是明确的,那么尽量的用rebase操作。
避免无效 commit,就有一个空间很长,后面有好多,但是里面大部分是无效的,都是一些没有用的。可能只是写了一行代码还没写完,这个就不完整。
⑶ ATOMIC
①一个 commit 解决一个问题
②问题尽可能小
原子性意思是一个原子操作。原子性有什么好处呢,原子性是解决一个特定的问题。比如修复一个pas,或者加了一个功能,或加了一个API,这就是一个非常明确的问题。然后就一个 commit 解决一个问题,这样追溯就很容易追溯,就知道这个是做了什么,下个是做了什么。
第二个问题,问题不能很大。不能写了两千行代码解决了一个问题提交,这非常危险。因为这中间没有实验,没节奏。对开发者来讲,比较好的情况就是很快速的有一个阶段性的成果,并且能持续有反馈,持续的往返目标去贴近它。但如果前面憋了很久,一直也不知道怎么样最后一把搞定,这对开发体验是不太好的,而且协作上来讲也不好。因为别人不知道你做多少了,所以问题要尽可能的尽可能的小。
3. Commit 的常见的反模式
⑴无效的 commit,如 Merge branch'develop'of https://codeup.aliyun.com/abc/xyz into develop
几乎在所有的公司,可能随便拉开一个代码过去,发现怎么有这么多本地和远程的这种情况。本来一个被搞定的里面没有用正确方式,这个就导致很多无效的,这样就会对commit的追溯能力产生很大的影响。
⑵巨型 commit,一个 commit 里面包含了大量的代码变化,且属于多个实现目的
比如做了三千行代码,就完全找不着它想干什么,这就非常危险。
⑶半成品的 commit,如包含有基本语法问题或实现错误的代码的 commit
比如我要去吃饭了,就先提交了,可能这个代码连编译都过不了,但这个其实是不好的。
⑷分支间的互相 merge
如果分支间的互相合并多了,就会发现commit的追溯就很难了,而且很有可能出问题。应该有唯一的主干,或者说一个明确的主干,这样往那边去合或从那边来,而不应该是互相的merge。
四. 问题:软件配置经常被修改,被发布,它属于代码吗?
答案:配置是另一种形式的代码
在实际工作中,配置有可能不存在仓库里,可能在配置中心或者一个系统里,但是配置就是一种代码。
1. 启动相关的配置
(DB 连接串,容器 CPU 规格,启动模式,DNS 服务地址)
⑴构建到镜像中或作为启动参数传入。
⑵启动之后不再更改,无需动态监听其变化。
⑶对这类配置的修改,会生成新的容器或 Pod。
⑷需要对运行中的服务进行重启,才能使这类配置的修改生效。
2. 运行相关的配置
(日志级别,黑白名单,特性开关,监控采样频率)
⑴通常通过监听某个服务或文件来获取和更新。
比如,读一个白名单,他的配置的更新不需要修改容器和pod,但是更新完全跟容器和pod没关系,就更新了。
⑵配置的更新无需修改容器和 Pod。
⑶运行中的容器通过持续监听配置的变化,有变化后自动生效无需重启。
五. 问题:alpd-demo 应用里面有没有启动配置和运行配置的例子?
如上图例子,这个是 ssh 服务的一个配置,这里 k8s 这些在文件上,这个就是在启动的时候,设的一个参数。另一个,我们在那个 query 服务里面,有一个WEATHER-API-KEY, 通过 secret 的方式注入到alpd里面,用环境变量去进入渠道。所以在其实运行的时候,是一样的配置,可以不去关心这个容器的重启,就当他生效。
从另外一个角度来去看一下配置的不同层次。
位于越内存的配置,修改成本是越高。
为什么这个箭头是从上往下的?如果是编码级别的,改的话 我要经过所有下面的阶段才能够上线,如果是运营阶段的话, 其实是不需要动前面的部分的,所以如果是这个配置位于越内层,那它的成本显然是越高的。
六. Takeaways 回顾要点
1. 不可变构建:相同的源码+相同的环境+相同的构建脚本=>一致的软件制品
2. 通过 Docker file 描述构建环境、构建脚本及运行环境
3. 构建的准确性>构建的效率
4. One process per container,避免采用富容器,降低镜像的大小
5. 按逻辑单库的结构组织代码库和代码组
6. 软件交付过程,其本质是开发者围绕代码库的协作过程,按照 SLA(Small、Linear、Atomic)的原则提交代码
7. 根据使用方式区分启动配置和运行配置,将环境相关的配置与镜像分离
8. 尽量将配置定义在外层,以减少修改的成本
整个逻辑是要交付一个东西要标准,要不可变。标准通过容器镜像的方式去标准化;不可变因为终态要保证稳定可预期的系统服务,所以交付的质品希望是不可变的,在构建的依赖,构建环境,构建脚本这些是要确定性的。从代码构建的源头是从代码开始,代码的组织是按什么样的方式组织;代码提交是按什么样的原则,从配置角度,有启动配置,运行时的配置,怎样最小化的修改运行成本,尽可能让配置放在最外层。