请选择 进入手机版 | 继续访问电脑版

湖南新梦想

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 672|回复: 0

基于 git 和 CI/CD 的集中化配置管理服务(上)

[复制链接]

2562

主题

2961

帖子

1万

积分

论坛元老

Rank: 8Rank: 8

积分
10523
发表于 2020-12-23 10:46:26 | 显示全部楼层 |阅读模式
分享一种基于 git 和 CI/CD 的集中化配置管理服务。这种方案最大的好处就是,简单直接,可以快速先把配置管理的坑儿占好。
功能点
首先,我们先整理一下集中化配置管理的主要 feature:
可以记录、审核配置的修改
修改配置之后,应用的配置能够及时得到更新
主要思路
我们的主要思路是:将配置服务直接写成一个独立的 webserver,webserver 对外提供 http 接口,配置直接写在 webserver 的代码当中,每次提交代码时通过 CI/CD 自动发布。
这样做的好处是:
可以直接通过 git 来记录、审核配置数据的修改,每次有人要修改配置时,直接提 PR,leader review 通过之后合并到 master 分支
代码合并到 master 分支之后,通过 CI/CD 自动发布到线上
可能大家会有下面的一些顾虑:
不应该直接在代码当中硬编码MySQL的账号、密码之类的敏感数据,这样是不安全的
简单通过 http 接口来读取配置,效率不高
针对第一个问题,我们算是使用了点 “反模式” 吧。代码肯定是要确保在私有代码库当中的,你需要授权才能够访问代码库,从这个角度来说,直接在代码里面写配置数据其实也不是大问题,特别是在产品研发初期,这个时候团队规模也不大。而且,像 gitlab、github 这类的服务,本身就有很好的权限管理机制,加上 git 本身就是版本管理工具,为什么不充分使用一下呢。
第二个问题呢,其实和第一个一样:初期,服务压力较小,配置数据不复杂,通过 http 接口来读取配置,性能其实没有大问题。
种方案的意义就在于把这个配置管理的坑儿先占上,确保各个服务是通过统一的接口来读取配置的,日后可以慢慢优化。 实践发现,随着产品迭代,这种方案能够持续的时间还是挺长的,投入成本还很小。

主要功能设计和实现
我们自己的项目使用 Node.js 开发的,所以下面以 Node.js 为例,来说一下具体设计。
首先,说一下 webserver 的接口设计,接口要尽可能简化,我们只提供了一个接口:

  GET /api/profiles/:profile HTTP/1.1

profile 参数表示你想要的环境,比如:
你想要测试环境的配置,应该发送 GET /api/profiles/dev
如果想要同事 Jack 的本地开发环境配置,你应该发送 GET /api/profiles/jack-local-dev
返回的数据自然应该是 json 数据,比如像下面这种:

  {
  "revision": "5d41402abc4b2a76b9719d911017c592",
  "config": {
  "debug": true,
  "wechat": {
  "appId": "xxxx",
  "secret": "xxxxx"
  },
  "mysql": {
  "host": "localhost",
  "port": 3306
  }
  }
  }

revision 表示配置的版本,config 就是实际的配置数据啦。
根据上面的设计,我们的 webserver 服务的代码库大概是下面这样的:

  ├── Dockerfile
  ├── README.md
  ├── app.js
  └──  config
  ├── dev.yml
  ├── prod.yml
  └── jack-local-dev.yml

其中:
有一个 app.js ,里面封装了 http 接口
有一个 config 文件夹,里面放置不同环境的配置文件。我们推荐使用 .yml 文件,.yml 文件写起配置其实更清爽,当然 json 也可以
再有一个 Dockerfile 用于配置镜像打包和自动发布
实现这样一个接口,app.js 的代码也比较简单,大概就像下面这样:

  const fs = require('fs');
  const yaml = require('js-yaml');
  const hash = require('object-hash');
  const express = require('express');
  const app = express();
  app.get('/api/profiles/:profile', (req, res) => {
  const path = `${__dirname}/config/${req.params.profile}.yml`
  fs.readFile(path, {
  "ecoding": "utf-8"
  }, (e, content) => {
  if (e) {
  return res.status(500).json({
  errorId: 'internal-server-error',
  errorMsg: e.message
  });
  }
  const config = yaml.safeLoad(content);
  const revision = hash(config);
  res.json({
  config,
  revision
  });
  });
  });
  app.get('/ping', (req, res) => res.send('pong'));
  const PORT = 8080;
  app.listen(PORT, () => {
  console.log('listening on port', PORT);
  });


回复

使用道具 举报

0

主题

5

帖子

19

积分

禁止发言

积分
19
发表于 2021-5-10 15:24:08 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|湖南新梦想 ( 湘ICP备18019834号-2 )

GMT+8, 2022-5-27 08:13 , Processed in 0.045652 second(s), 20 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表