ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

夜莺日志集中采集实战:Vector + VictoriaLogs轻量方案

夜莺日志集中采集实战:Vector + VictoriaLogs轻量方案 做可观测性这行绕不开日志采集。最近我把一套夜莺(Nightingale)监控环境的日志全部接到VictoriaLogs里采集管道用的是Vector。这套组合不算热门但实测下来很稳资源占用比那些“全家桶”方案低一截。如果你也在维护夜莺或者正在纠结日志该往哪送这篇内容可以直接照着抄。夜莺本身是个监控系统但它自己也产生日志比如服务端n9e的运行日志、告警事件、登录审计。以前排查问题只能ssh到机器上tail几台机器轮着看效率很低。我的目标很明确把夜莺所在机器的日志统一采集做成一个可检索的集中式日志平台同时尽量少占资源、少引入额外维护成本。最后定下的方案就是Vector采集 - VictoriaLogs存储查询。这篇内容适合正在使用夜莺监控、又想做日志集中管理的运维或SRE同学。里面会讲方案对比、组件原理解读、从零到一的部署步骤、VRL处理细节还有我实测遇到的坑和排查思路。如果你只是想快速搭一套夜莺日志采集直接跳到第三章把配置文件复制过去改改路径就行但我建议还是把原理看一遍遇到问题才知道往哪个方向排查。1. 项目背景与整体方案拆解1.1 夜莺日志为什么需要集中采集夜莺监控系统本身承担着指标采集、告警判定、通知发送等工作它的运行状态直接影响整个监控体系的可信度但大家往往只关注夜莺“监控了谁”忽略了夜莺自己被监控的需求。我接手这套环境时夜莺是标准的二进制多节点部署一台server、一台边缘节点日志散落在各自机器的部署目录里。真要排查一次告警丢失或者webhook发送异常得先回忆日志在哪个路径再挨个节点登录、tail、翻找效率非常低。更麻烦的是日志轮转。夜莺二进制部署的日志轮转主要靠logrotate或者运维脚本轮转之后旧文件被改名如果不做归档时间一长就查不到历史记录。告警事件虽然在数据库里有记录但具体到某条告警的通知响应过程、回调结果、失败原因数据库里根本不体现这些细节只存在于日志文件里。于是集中日志采集就变成了刚需。1.2 方案选型为什么是Vector加VictoriaLogs可能有人会问“ELK那么成熟为什么不用”我在方案评审时也认真对比过几个主流选择。先明确一点我们的目标是采集夜莺的日志而不是搞一个公司级的大数据日志平台资源占用和运维复杂度是首要考量。采集端资源占用处理能力综合感受LogstashJVM系内存至少1GB起步插件丰富处理能力强太重在多节点部署成本高Filebeat较轻管道处理能力有限复杂解析别扭适合轻量采集复杂逻辑不好写Fluent Bit轻中等插件不少勉强够用但稍复杂逻辑配置较绕Vector单二进制内存几十到几百MBVRL脚本表达能力很强轻量且灵活明显匹配这个场景我选Vector有三个具体原因。一是它的Source、Transform、Sink三件套抽象非常直观配置是TOML格式比Logstash的管道配置容易读也比Fluent Bit的插件链更结构化。二是VRL这个脚本语言处理日志真的很顺手解析JSON、正则提取、时间格式转换、条件分支都能在一个transform里完成。三是Vector本身也支持指标采集后续如果想把夜莺的性能数据也走同一条管道送出去不需要再引入新组件。存储端没有选Elasticsearch原因也很现实。ES需要集群、需要单独调JVM堆、要管理索引模板和mapping对一个小团队来说维护成本划不来。Loki用起来确实简单但日志内容检索能力偏弱而且需要follow组件或者promtail配套。VictoriaLogs是VictoriaMetrics团队做的日志数据库单文件二进制默认端口9428插入和查询都是HTTP接口语法走Lucene风格全文检索体验很接近ES但安装维护简单得多。对夜莺日志这种“类型不固定、格式多变”的场景它不强求schema收到JSON lines就直接建立倒排索引非常灵活。1.3 最终架构与数据链路整个数据链路是这样跑的夜莺节点上的日志文件 - Vector的file source读取 - remap transform做解析和字段规整 - http sink发送 - VictoriaLogs的/insert/jsonline接口写入 - 用户通过VictoriaLogs的/select/lucene查询这套架构里需要长期维护的可执行文件只有两个Vector单二进制和VictoriaLogs单二进制。中间没有消息队列、没有Redis、没有协调服务故障点非常少。夜莺节点上只需要放一个VectorVictoriaLogs单独部署在一台机器上数据集中存储、集中查询。后面所有配置和踩坑记录都是基于这条链路展开的。2. 核心组件原理与关键细节2.1 Vector的source/transform/sink体系Vector把采集链路抽象成三件事数据从哪来、数据怎么改、数据往哪去。这就像一个净水系统source是进水口的水龙头transform是中间的滤芯sink是出水口接的水桶。理解了这个抽象后面配置无论多复杂都能快速归类。这个项目里我用了三个组件。第一个是file类型的source负责读本地日志文件。file source有一个很关键的设计它会把当前读取位置记录在磁盘上进程重启后能接着读不会因为重启而重复或丢失。它还支持glob匹配文件、忽略旧文件、处理文件轮转。第二个是remap类型的transform负责执行VRL脚本所有日志解析和字段规整都在这里做。第三个是http类型的sink负责把处理后的日志事件编码成JSON lines批量POST到VictoriaLogs。这里有个容易忽略的点http sink本身没有太多业务逻辑它的核心复杂度在请求参数上。批量大小、提交间隔、并发数、超时时间都会影响写入吞吐和VictoriaLogs的负载。网上很多教程只给个最小配置上线后发现日志量大了推送不过来就是这个原因。2.2 夜莺日志的常见格式与位置夜莺的官方文档在监控官网上有完整说明其中数据模型和告警规则章节最值得先读。二进制部署的夜莺解压后的目录通常在/opt/n9e或者/home/n9e日志目录一般叫log或者logs。比较常见的日志文件包括n9e.log服务端主日志、webapi.logAPI层日志、collector.log采集器日志等。夜莺的日志格式在不同版本里略有差异。老版本大多是纯文本典型的一行长这样2024/07/19 10:00:00 [INFO] AlertRuleManager run ok, ruleCount128新版本部分模块会输出JSON格式字段类似{ts:2024-07-19T10:00:0008:00,level:info,msg:...,module:...}。如果部署方式是docker-compose容器内的日志目录需要提前用volume挂载宿主机目录Vector直接采集宿主机的挂载目录就行。另外夜莺日志的轮转策略不统一有的机器配了logrotate按天轮转有的只是按大小切割Vector的file source对轮转有专门检测逻辑后面配置时会说到。2.3 VictoriaLogs的接口与数据模型VictoriaLogs和ES最大的区别是不需要预建索引模板也不强制mapping。它的写入接口是POST到/insert/jsonline请求体是JSON lines格式每行一条日志。它有几个保留字段需要知道_msg日志正文全文搜索时默认查这个字段_timeRFC3339格式的时间戳如果不传就用服务端接收时间_stream_id后台用来标识数据流如果两条日志内容完全一样会被去重查询接口是/select/lucene语法是Lucene风格。比如查包含error关键字的日志就写queryerror查指定级别就写level:error时间范围可以用_time:[now-1h, now]过滤。字段级的精确匹配和全文检索可以混用这对夜莺场景很实用比如查某条告警事件用alert_name:内存告警把范围缩小再搜body里的关键内容。3. 从零搭建采集链路的完整实操3.1 环境规划与版本选择我先交代一下实测环境的规模。夜莺集群有三台节点一台server、两台edge节点日志量不大峰值每秒几百条一天大概几十GB。VictoriaLogs单独部署在一台4核8G的虚拟机上Vector直接装在夜莺所在的三台节点上。这样数据尽量本地采集网络传输链路短VictoriaLogs挂了也不会影响夜莺节点上Vector的采集缓存。版本方面Vector当时用0.36.x稳定版VictoriaLogs用v0.39.x。安装方式我这边全部用二进制加systemd管理没有用docker。原因是二进制文件只有一个systemd的unit文件写起来也简单底层文件读取的权限控制更加直观。日志文件目录权限如果不对Vector很容易启动成功却什么都读不到。3.2 部署VictoriaLogsVictoriaLogs部署非常简单下载压缩包解压就能跑mkdir -p /opt/victoria-logs cd /opt/victoria-logs wget https://github.com/VictoriaMetrics/VictoriaLogs/releases/download/v0.39.3-victorialogs/victoria-logs-linux-amd64-v0.39.3-victorialogs.tar.gz tar -xzf victoria-logs-linux-amd64-*.tar.gz启动命令要指定数据目录和监听端口./victoria-logs-prod -storageDataPath/data/victoria-logs-data -httpListenAddr:9428 -retentionPeriod30d我加了一个-retentionPeriod30d参数意思是日志保留30天。这个参数不是必须的但日志平台如果不设保留时间磁盘总有一天会被填满。数据目录/data/victoria-logs-data要提前建好并授予运行用户写权限。我用systemd管理unit文件里指定Uservlogs[Unit] DescriptionVictoriaLogs service Afternetwork.target [Service] ExecStart/opt/victoria-logs/victoria-logs-prod -storageDataPath/data/victoria-logs-data -httpListenAddr:9428 -retentionPeriod30d Restarton-failure Uservlogs [Install] WantedBymulti-user.target启动后验证接口curl http://localhost:9428/select/lucene?query*返回一个空结果JSON列表就说明服务正常。3.3 安装VectorVector的官方安装脚本会自动配置好apt或yum源curl -1sLf https://repositories.timber.io/public/vector/cfg/setup.sh | bash -s -- -y安装完成后默认配置目录在/etc/vector配置文件是/etc/vector/vector.toml。如果不想用官方源也可以下载deb包或者直接用docker跑docker run -d --name vector \ -v /home/n9e/log:/home/n9e/log:ro \ -v /etc/vector:/etc/vector:ro \ timberio/vector:latest-alpine我这边用systemd方式管理直接使用二进制安装后的服务即可。装好后可以执行vector --version确认版本。顺便说一句搜Vector资料的时候很容易搜到C的std::vector还有汽车电子领域的Vector CANoe工具链这完全是两码事。看文档时认准“timberio/vector”和“Vector Remap Language”避免浪费时间。3.4 编写Vector配置现在到核心环节。我在/etc/vector/vector.toml里写了这样一个完整配置[sources.n9e_file] type file include [/home/n9e/log/n9e.log, /home/n9e/log/webapi.log] read_from beginning ignore_older_secs 600 [transforms.n9e_meta] type remap inputs [n9e_file] source .meta.host get_hostname!() .meta.file .file ?? unknown .meta.service n9e-server [transforms.n9e_parse] type remap inputs [n9e_meta] source if starts_with!(.message, {) { parsed parse_json(.message) ?? {} if parsed ! {} { .msg parsed.msg ?? parsed.message ?? .message .level parsed.level ?? info ._time parse_timestamp!(parsed.ts, format: %) ?? now() del(.message) } } else { parsed_re parse_regex!(.message, r^(?Pts[0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}) [.*?]*(?Plevel[A-Z]) (?Pcontent.*)) if length(parsed_re) 4 { ._time parse_timestamp!(parsed_re[1], format: %Y/%m/%d %H:%M:%S) ?? now() .level downcase(parsed_re[2]) .message parsed_re[3] } } [sinks.vlogs] type http inputs [n9e_parse] uri http://192.168.1.10:9428/insert/jsonline [sinks.vlogs.encoding] codec json_lines [sinks.vlogs.batch] max_events 1000 timeout_secs 5 [sinks.vlogs.request] concurrency 2逐个说明关键点。source部分include可以写多个文件或glob匹配比如include [/home/n9e/log/*.log]。read_from beginning表示Vector第一次启动时从文件头开始读能把已有日志回补进VictoriaLogs如果改为end则只采集新日志。线上环境如果日志文件很大full回补会占用带宽建议第一次跑用beginning确认历史数据不需要后再改成end。ignore_older_secs 600表示超过600秒未被修改的文件不再读取避免长时间不动的旧文件浪费系统资源。meta这个transform的作用是给每条日志打上主机、文件和服务的标识。因为最终日志会汇总到VictoriaLogs多台夜莺节点混在一起时必须知道某条日志来自哪台机器、是哪个服务产生的否则排查问题依然靠猜。parse这个transform里的VRL脚本分两个分支。第一个分支处理JSON格式的日志用parse_json解析原始行遇到解析失败就用空对象兜底然后提取msg、level字段并转换成统一的_time。第二个分支处理纯文本用parse_regex正则把时间、级别、内容拆出来正则的捕获组在上文代码中我用数组下标访问下标0是完整匹配1、2、3分别对应时间、级别和内容。VRL脚本默认遇到错误会丢弃事件所以时间解析我用了非感叹号版本并且用?? now()兜底宁可时间不准确也尽量不让日志丢失。sink部分uri指向VictoriaLogs的/insert/jsonline接口encoding.codec json_lines表示每次请求按JSON lines编码发送。batch里的max_events和timeout_secs控制攒批逻辑设置1000条或5秒满足一个就提交一次。concurrency 2控制并发连接数对当前日志量来说已经足够如果想压吞吐可以提高到4或者8。3.5 写入验证与查询演示写完配置后先重启Vector并查看状态systemctl restart vector systemctl status vector手动往夜莺日志文件里追加一条测试日志echo 2024/07/19 12:00:00 [ERROR] test alert event /home/n9e/log/n9e.log等几秒后到VictoriaLogs查询curl http://192.168.1.10:9428/select/lucene?querytestANDlevel%3Aerrorstartnow-1hendnowlimit10如果返回结果里有刚才那条测试内容链路就通了。这里注意查询拼接时level:error要转码成level%3Aerror否则冒号可能被shell或代理解析出错。4. 用VRL把夜莺日志处理成干净结构4.1 针对文本和JSON两种格式的解析夜莺日志格式不统一我在VRL脚本里用了一个很实用的分支结构先看message字段是否以{开头。如果以{开头走parse_json逻辑否则走正则逻辑。这种方式比单纯分两个source文件更省事因为很多情况下同一个文件的日志格式都会混着来尤其新版夜莺部分模块在改造过程中会同时输出JSON和文本。JSON分支里要小心字段名。夜莺的JSON日志字段最常见的是ts、level、msg但也可能出现message、time、lvl。我的脚本用了parsed.msg ?? parsed.message ?? .message这种链式兜底左边取不到就取右边最终保证msg字段有值。级别字段也一样默认兜底为info避免字段缺失导致查询时筛选不完整。文本分支里正则表达式的写法是重点。夜莺老日志格式是时间 [级别] 内容或者时间 [模块] 级别 内容具体在不同版本不完全一样。我在正则里写成^日期时间 .*?(Level) 内容用非贪婪匹配跳过中间可能出现的模块名这样能兼容两种常见格式。千万不要一次性想写一个兼容所有场景的正则而是先跑几个真实日志样本看输出效果再逐步优化。4.2 字段统一与收敛日志采集到VictoriaLogs之后最重要的不是“能搜到”而是“搜的时候好用”。我的做法是强制统一三个字段。时间字段统一为_time并且格式必须是RFC3339。VictoriaLogs对时间字段校验很严格不是RFC3339格式就会忽略所以我在脚本里用parse_timestamp转换老日志里那种2024/07/19 10:00:00的格式转换失败则用当前时间兜底。级别字段统一为小写的level查询时直接level:error不用记原始日志是大写还是小写。服务字段统一叫service文件路径统一叫file多套环境混接时只要在查询条件里加service字段就能隔离。字段收敛还有一个重要操作及时删除原始字段。JSON日志被解析后danese原始message字段如果还保留事件里会同时有message、msg两个字段查询时容易分不清。我在解析成功的分支里执行了del(.message)确保最后只留一个标准字段。字段越多索引越大查询体验也会下降不是所有原始字段都值得保留。4.3 多行日志的合并处理夜莺在panic或者告警回执时堆栈信息经常跨多行。file source默认每一行都是一个独立事件堆栈会被拆成好几条记录查询起来乱成一团。解决方式是在file source里配置multiline合并。Vector的multiline配置主要看两个参数mode和start_pattern。mode continue表示后续不以start_pattern开头的行都合并到前一条事件里。我用的配置如下[sources.n9e_file] type file include [/home/n9e/log/n9e.log] multiline { mode continue, start_pattern ^[0-9]{4}/ }start_pattern用正则^[0-9]{4}/匹配以年份开头的行也就是正常日志的起始行。这样堆栈里那些缩进、纯文本代码片段就不会被当成独立事件。需要注意启用multiline后file source会缓存一部分事件来判断是否结束对日志量很大的场景会额外占一些内存但夜莺这种量级基本没有影响。5. 常见问题与排查实录5.1 Vector启动后没有任何日志被采集我遇到过好多次这种情况配置看起来没问题systemctl状态也是active但VictoriaLogs就是查不到数据。排查顺序我建议按照下面这个列表走。第一看Vector自带指标。Vector启动后默认监听localhost:8686执行curl -s localhost:8686/metrics | grep -E file_|http_|received|sent。如果file_type里的received_events为0说明文件读取就没成功问题在source如果received有值但sent为0问题在sink或者transform。第二检查运行用户权限。Vector如果以非root用户运行而夜莺日志目录权限是700文件根本没权限读取但服务不会报错。可以直接sudo -u vector tail -f /home/n9e/log/n9e.log验证。第三检查include路径。我踩过一个很典型的坑include写的是某个具体文件路径没有用glob匹配logrotate把文件改名后新文件不在include列表里Vector就停采了。改成include [/home/n9e/log/*.log]之后问题才解决。5.2 VRL时间解析报错导致整条事件丢失VRL脚本中带感叹号的函数解析失败时会直接丢弃事件。我在调试阶段写parse_timestamp!时遇到一条非标准时间格式的日志结果这条日志永远进不了VictoriaLogs排查半天才发现是这里丢的。解决办法很简单把带感叹号的函数换成不带感叹号的版本再配合??兜底值。比如._time parse_timestamp(parsed_re[1], format: %Y/%m/%d %H:%M:%S) ?? now()这样即使时间解析失败也会用当前时间顶上日志内容不会丢。我的原则是对可降级的字段用非报错版本对主机名这种必需字段才用get_hostname!()这类带感叹号的版本让问题尽早暴露。5.3 VictoriaLogs返回400 Bad Request这种问题基本都出在发送格式上。VictoriaLogs的/insert/jsonline要求每行是一个独立的JSON对象不能带数组包裹也不能一行里放逗号分隔的多个对象。排查时可以先手动POST一条数据curl -X POST http://192.168.1.10:9428/insert/jsonline \ -H Content-Type: application/json \ -d {_msg:test,_time:2024-07-19T12:00:00.000Z}如果手动发送成功再看Vector sink配置。确认encoding.codec必须是json_lines而不是text。还有一个容易忽略的点如果uri写错了比如多写了路径请求可能到不了insert接口就会返回404而不是400这时要重点检查uri。5.4 字段冲突与格式混乱日志平台跑久了最怕字段名混乱。我有一次看到一个事件里同时有ts、time、_time、timestamp四个跟时间相关的字段查询时不知道到底该用哪个非常影响效率。后来在VRL里做了严格收敛凡是解析出来的字段保留统一规范名原始字段一律删除或者改名。所以这里给一个建议每次修改VRL脚本后可以发送一条真实样本日志再到VictoriaLogs查询这条日志检查返回的JSON里字段是否干净。不要靠肉眼去猜直接看最终落库的结果。5.5 性能观察与调参这套方案部署完成后我持续观察了两周。Vector单实例内存占用大概80到150MBCPU基本稳定在1%以内在夜莺节点上完全没什么存在感。VictoriaLogs在几十GB数据量下内存约1GB查询响应基本秒级全文检索体验比想象中好。如果后续日志量翻倍优先调Vector sink里的batch.max_events和request.concurrency。batch.max_events调大能减少HTTP请求次数concurrency调大能增加并行写入。VictoriaLogs本身的并发参数不建议乱动默认已经能支撑很高的写入吞吐瓶颈一般只会在磁盘IO和网络带宽上调大单side并发反而可能把磁盘打满。6. 我的实战体会与后续扩展这套方案我已经在维护环境里跑了两个多月最大的感受是日志采集不应该一开始就设计得太重先跑通最小链路再逐步丰富解析逻辑和字段规范化远比一开始就想做一个完美平台靠谱。Vector单二进制、VictoriaLogs单二进制的组合部署和升级都非常省心夜莺集群每个节点加一个Vector就够了。最后再分享一个小细节日志采集链路跑起来后一定要把这条链路本身也纳入监控。我现在会给每台夜莺节点的磁盘空间、VictoriaLogs所在机器的磁盘使用率、Vector进程存活状态都配上告警。原因很简单日志平台一旦挂了你连排查它为什么挂的日志都看不到那时候就只能对着冷冰冰的进程列表发呆了。如果你也在为夜莺的日志发愁可以先照着这份配置搭起来跑一段时间再根据实际查询习惯去调字段和保留策略。后续我打算把这套链路的指标采集也扩展一下让夜莺主机指标和日志走同一条Vector管道统一管理等实践得差不多了再来补充。
返回列表