配置文件那些事
深夜的办公室灯火通明,为了今晚的大版本上线,同事们已经连续奋战了几十个日日夜夜。"但愿今晚平安上线,接下来就能休息几天了",小明这样想着,打开终端登录服务器,等待操作时间到来。
作为一名运维工程师,这样的版本升级小明经历过无数次,况且在之前进行过数次模拟升级,这次想必也不会出什么太大的问题。
升级时间到了,小明深吸一口气,手指开始在键盘上飞舞,停止进程、备份文件、更新程序、启动、验证,一系列步骤有条不紊的进行着……
“小明,为什么我这里vi打开文件是乱码?”小明停下手中的事情,循声望去,原来是开发人员小刚。“把你的终端编码改一下” “在哪里改?” “就在设置那里” “找不到啊,过来帮忙看看啦” 小明无奈起身,走到小刚身边,教他修改终端字符编码……
同事们都在紧张的忙碌着,墙上挂钟那红色的指针也在不知疲惫的一圈又一圈的行进。。。
“警告警告,XX服务器 /data 占用率 100%,请及时处理。”小明赶紧登录检查,发现该目录下的一个 log 占了90%的磁盘空间,里面的 xx.debug.log 占用达几十个G。看来是有人打开了调试模式,小明一边清理日志,一边在群里发问。
原来是小刚验证的时候发现逻辑有点对不上,然后把调试模式打开了,大量的信息很快就把磁盘空间撑爆。处理完这个问题,小明继续之前的工作。。。
“小明,我加了个配置项,需要同步一下全部服务器。”小刚走了过来,“好,没问题。”小明平静的说到,心想这家伙又来搞事。
过了一会,小刚又过来了:“不好意思啊,刚刚那个配置项要改一下,改成xxx。” 小明有点想骂人了,但还是平淡的说:“要改成什么?”
……
配置文件那些痛
故事有些夸大的成份,但里面的场景确实有发生过,相信很多运维同学也是深有体会。维护一套系统,少不了的就是配置文件,故事里修改配置的几大痛点就是我想要解决的:
- 连接服务器的终端字符编码不一,配置内容可能显示乱码
- 不同的人修改了配置项,不知道改了什么内容
- 修改了配置项后如何同步
- 同步后又反悔了,怎样快速改回之前的内容
如果我们提供一个统一的修改入口,那么编码不一的问题是不是就解决了?
有人修改了内容,我们就保存一个快照;后面若是反悔,可以马上使用这个快照,是不是同时满足了保存修改和修改历史?
快速同步,从主控端分发过去行不行?
我看行!
配置文件管理
使用B/S架构来做配置管理是很自然的事情,实现起来也比较简单:
- 浏览器作为统一的修改入口
- 后端保存文件的修改历史,每一次修改都生成一个新的修订号
- 同步到相关服务器就是将配置内容下发
那么问题又来了:
单个配置文件管理起来不难,要是多个不同的文件呢?多个文件我只改了其中一个呢?新增加一个文件又不想改动原来的呢?
配置文件包
上述问题,我们可以引入“包”这个概念,一个“包”里可以有一个或多个配置文件,我们对“包”作一个快照,也就是历史版本,那么问题就迎刃而解。
比如可以设计成下图的样子:
A B C 代表3个配置文件包,里面的 a.con b.conf aa.txt 就是具体的文件,数字代表修订号。这样就保存了修改历史,方便回溯,对应的数据模型用 django 的 models 来表示:
class ConfRevision(models.Model):
name = models.CharField(max_length=48, help_text='名称')
revision = models.PositiveIntegerField(help_text='修订号')
is_default = models.BooleanField(default=False, help_text='是否默认')
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
description = models.CharField(max_length=120, blank=True, null=True)
class ConfDetail(models.Model):
FILE_TYPE = (
('ini', 'ini'),
('conf', 'conf'),
('json', 'json'),
('yml', 'yml'),
('toml', 'toml'),
)
name = models.CharField(max_length=48, help_text='配置文件名称')
type = models.CharField(max_length=4, choices=FILE_TYPE)
content = models.TextField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
description = models.CharField(max_length=120, blank=True, null=True)
rev = models.ForeignKey(ConfRevision, on_delete=models.DO_NOTHING)
内容下发
有句名言如是说:“手里拿着锤子,看什么都像是钉子”。
ansible 作为运维利器简直是深得我心,有了这把锤子,很多操作都是经由它去完成的,比如文件下发、备份、系统操作等。一开始就是想着用ansible去同步全部主机的内容,但是一些情况下会有问题:
- 假如当前有个待同步的主机宕机,本次的同步会漏掉一部分主机
- 资源抢占问题:多个人同时修改,如果后面的修改先同步到目标主机,而前面的修改延迟到达,导致内容覆盖,本来应该全网统一的内容在个别机器上不一致
为了尽量避免上面情况发生,我们需要另一种方式,即使出现极端情况也能保证配置文件全网一致性:
在每台主机上启动一个代理,从一个中心存储拉取内容,当中心存储的内容变化时通知代理。
分析配置文件,全部是文本内容,很方便使用 key-value 存储,进而想到业界知名的 k-v 存储redis,然而数据持久化并非redis擅长,排除。
etcd 倒是一个不错的选择,满足我们所有要求:
- 支持 key-value
- 分布式、强一致性
- 支持发布-订阅模式
那么决定是他了。
配置同步代理
主机上的代理程序要简单、支持etcd、少依赖,有现成的 confd 就不用重新造轮子了。
功能实现
当收到新增请求(因为做了版本管理,所以每次的修改请求都是创建一个新的版本),后台的处理流程:
- 校验数据:非空校验、内容格式校验
- 写入数据库,同时将内容写入etcd
- 主机上的 confd 监听到对应 key 的内容变化,同步内容到本地
若要回退,只需要将对应的历史版本内容写入etcd
而且记录历史版本,搞事的人就无法甩锅,大家可以尽情的嘲讽他~~~
这里实现了全网配置统一,但是如果有某个新功能需要个性化的配置,又该怎么办呢?欲知方法如何,且听下回分解。