一种基于日志服务CLI工具实现的多区域发布方案

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 一种基于日志服务CLI工具实现的多区域发布方案

项目背景

SLS 的 命令行工具 aliyunlog 功能非常丰富,能够实现对 logstore、project、shard、index 等资源的管理操作,支持了数十条操作命令。使用也非常简单,通过 python 安装命令行工具之后,配置 AK 和region  信息即可通过简单的命令行来进行操作。

在实际项目使用中却遇到了一些问题。

发布问题

项目中进行埋点后,需要在 SLS 控制台配置报表并进行查看。正常的发布顺序为:

日常logstore(开发调试)->预发logstore(测试验证)->生产logstore(用户使用)

所以每当报表或者索引有修改时,会先在日常环境进行配置和调试,通过后再同步到预发环境让测试同学验证,验证通过后再同步到线上生产环境。

如果每次同步都是通过手动配置的话会有2个问题:

1、效率非常低,预发同步1次,生产环境因为有n个区域,所以要同步 n 次,n+1次重复劳动。

2、容易出错,手动同步一个复制粘贴搞错了很可能会导致报表不可用。

脚本封装

因为需要同步的索引和报表都属于项目,所以一开始考虑使用 aliyunlog 的 copy_logstore 命令,但实际在执行过程中发现命令行既不报错,索引和报表也没有同步成功。所以干脆自己写个脚本来实现 SLS 相关资源的发布功能。

考虑到本人对 JavaScript 比较熟悉,再加上其优秀的并发能力,所以考虑用 Node.js 编写一个脚本来实现。

以终为始,首先我们要考虑最终希望实现的具体效果,那就是把一个环境的配置信息发布到另一个环境。所以只用支持两个参数,一个是待发布的环境,一个是源环境,像下面这样:

node xxx.js [from_logstore] [to_logstore]

实际的执行过程应该是从源环境中读取报表和索引,然后更新到目标环境。这些信息就需要通过 aliyunlog 来获取了,aliyunlog 所依赖的参数我们写到对应的配置文件中,格式类似下面这样:

{
"daily": {
"host": "hsot1",
"project": "project1",
"logstore": "store1",
"AccessKeyID": "LT******",
"AccessKeySecret": "zl******"  },
"pre": {
"host": "host2",
"project": "project2",
"logstore": "store2",
"AccessKeyID": "LT***",
"AccessKeySecret": "ny***"  },
"product": [
    {
"host": "host3",
"project": "project3",
"logstore": "store3",
"AccessKeyID": "LT***",
"AccessKeySecret": "vgU***"    },
    {
"host": "host4",
"project": "project4",
"logstore": "store4",
"AccessKeyID": "LT***",
"AccessKeySecret": "vg***"    },
......  ]
}

这里由于同步是单向的,不可能从生产环境同步到预发环境,同时生产环境有多个区域,所以 project 写成数组形式。


接下来考虑如何同步。

1 同步索引。获取源 logstore 的索引,然后同步到目标 logstore。

image.png

2 同步报表。报表相对索引略微复杂一些,因为一个 logstore 下会有多个报表,为了高效需要并发执行,同步之前也需要注意修改项目信息。

image.png

3 删除配置文件。为了保证对下次操作不造成影响,需要删除临时生成的配置文件


完整的代码如下:

constenv=require('./.env.sls.json');
const { execSync, exec } =require('child_process');
const { join } =require('path');
const { writeFileSync } =require('fs');
// eslint-disable-next-line no-unused-varsconst [_nodePath, _filePath, from, to] =process.argv;
constsource=env[from];
constdistList=Array.isArray(env[to]) ?env[to] : [env[to]];
(async () => {
// 创建临时目录execSync('mkdir -p ./conf');
awaitPromise.allSettled(updateIndex(source, distList));
console.log('step1 updatedIndex')
constids=list();
constdashboards=awaitdownload(ids);
console.log('step2 download dashboard')
try {
awaitPromise.allSettled(upload(dashboards));
  } catch(e) {
console.error(e);
  }
console.log('step3 update dashboard')
execSync('rm ./conf/*')
})();
functionupdateIndex(){
letcmd=`aliyunlog log get_index_config --project_name=${source.project}--logstore_name=${source.logstore}--access-id=${source.AccessKeyID}--access-key=${source.AccessKeySecret}--region-endpoint=${source.host}--format-output=json,no_escape`;
constindex=JSON.parse(execSync(cmd).toString());
writeFileSync(`./conf/index.json`, JSON.stringify(index, null, 2)); 
constfile=`file://${join(__dirname, `./conf/index.json`)}`;
returndistList.map(dist=>newPromise((resolve, reject) => {
cmd=`aliyunlog log create_index --project_name=${dist.project}--logstore_name=${dist.logstore}--access-id=${dist.AccessKeyID}--access-key=${dist.AccessKeySecret}--region-endpoint=${dist.host}--format-output=json,no_escape --index_detail=${file}`;
exec(cmd, error=> {
if(error) {
cmd=`aliyunlog log update_index --project_name=${dist.project}--logstore_name=${dist.logstore}--access-id=${dist.AccessKeyID}--access-key=${dist.AccessKeySecret}--region-endpoint=${dist.host}--format-output=json,no_escape --index_detail=${file}`;
returnexec(cmd, err=> {
if(err) {
reject(err);
console.log(err);
          }
console.log(`Updated index for host ${dist.host}`)
resolve();
        })
      }
console.log(`Created index for host ${dist.host}`)
resolve()
    })
  }))
}
functionlist() {
constcmd=`aliyunlog log list_dashboard --project=${source.project}--access-id=${source.AccessKeyID}--access-key=${source.AccessKeySecret}--region-endpoint=${source.host}--format-output=json,no_escape`;
const { dashboardItems } =JSON.parse(execSync(cmd).toString());
returndashboardItems    .filter((it) =>/^dashboard/.test(it.dashboardName))
    .map((it) =>it.dashboardName);
}
functiondownload(dashboards) {
returnPromise.all(
dashboards.map((it) => {
returnnewPromise((resolve, reject) => {
constcmd=`aliyunlog log get_dashboard --project=${source.project}\--entity=${it}--access-id=${source.AccessKeyID}--access-key=${source.AccessKeySecret}--region-endpoint=${source.host}--format-output=json,no_escape`;
exec(cmd, (error, stdout) => {
if (error) returnreject(error);
resolve(JSON.parse(stdout.toString()));
        });
      });
    }),
  );
}
functionupload(dashboards) {
returndashboards.map((it) => {
returnnewPromise((resolve, reject) => {
distList.forEach((dist) => {
constfilePath=`./conf/dashboard-${newDate().getTime()}.${Math.random().toString(36)}.json`try {
constvalue=replaceProjectName(it, dist.project);
writeFileSync(filePath, JSON.stringify(value, null, 2));
        } catch (e) {
console.error('writeFileSync:', e);
        }
constfile=`file://${join(__dirname, filePath)}`;
constcmd=`aliyunlog log create_dashboard --project=${dist.project}\--detail=${file}--access-id=${dist.AccessKeyID}--access-key=${dist.AccessKeySecret}--region-endpoint=${dist.host}`;
exec(cmd, (error, stdout) => {
if (error) {
constcmd2=`aliyunlog log update_dashboard --project=${dist.project}\--detail=${file}--access-id=${dist.AccessKeyID}--access-key=${dist.AccessKeySecret}--region-endpoint=${dist.host}`;
returnexec(cmd2, (err, out) => {
if (err) returnreject(err);
elseconsole.info(`Updated dashboards for host ${dist.host}.`);
resolve(out.toString());
            });
          } else {
console.info(`Created dashboards for host ${dist.host}.`);
          }
resolve(stdout.toString());
        });
      });
    });
  });
}
functionreplaceProjectName(obj, value) {
constKEY='project';
for (constpropinobj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
if (prop===KEY) {
obj[prop] =value;
      } elseif (typeofobj[prop] ==='object') {
replaceProjectName(obj[prop], value);
      }
    }
  }
returnobj;
}


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
监控 Ubuntu 安全
如何在 VPS 上安装和使用 Logwatch 日志分析器和报告工具
如何在 VPS 上安装和使用 Logwatch 日志分析器和报告工具
32 1
|
1月前
|
存储 监控 Serverless
阿里泛日志设计与实践问题之Grafana Loki在日志查询方案中存在哪些设计限制,如何解决
阿里泛日志设计与实践问题之Grafana Loki在日志查询方案中存在哪些设计限制,如何解决
|
1月前
|
Prometheus Cloud Native Perl
评测Loki日志工具
评测Loki日志工具
55 0
|
3月前
|
机器学习/深度学习 数据可视化
【tensorboard】深度学习的日志信息events.out.tfevents文件可视化工具
【tensorboard】深度学习的日志信息events.out.tfevents文件可视化工具
|
1月前
|
Java 编译器 数据库
异步日志方案——spdlog
异步日志方案——spdlog
|
1月前
|
监控 关系型数据库 Linux
Linux日志管理工具:Logrotate(二)
Linux日志管理工具:Logrotate(二)
51 2
|
29天前
|
Prometheus 监控 安全
|
29天前
|
存储 Prometheus Kubernetes
在K8S中,如何收集K8S日志?有哪些方案?
在K8S中,如何收集K8S日志?有哪些方案?
|
1月前
|
存储 监控 数据可视化
在Linux中,有哪些日志管理和分析工具?
在Linux中,有哪些日志管理和分析工具?
|
1月前
|
存储 Kubernetes Java
阿里泛日志设计与实践问题之在写多查少的降本场景下,通过SLS Scan方案降低成本,如何实现
阿里泛日志设计与实践问题之在写多查少的降本场景下,通过SLS Scan方案降低成本,如何实现

相关产品

  • 日志服务