Open-falcon Agent模块rpc通信过程


1.Agent模块内容简述

在解析Agent模块通信过程的同时,需要对Agent模块的内容进行简单的概述,主要包括以下几个方面,是支持Agent通信模块的基础内容。


1.1 Open-falcon简述

Open-falcon是小米公司开源的一个企业级运维监控系统,主要使用go语言编写。总体结构分为服务端与客户端,站在使用者的角度上看,可以有非常多个客户端对应一个服务端。这里的说的客户端就是即将要介绍的Agent模块,包括Agent在监控机器的工作过程,以及Agent与服务端的通信过程。服务端是接收Agent上传的数据,保存、处理而且展示给相应的运维人员。也可以通过服务端提供的Web界面管理Agent的工作、更新等等。Open-falcon的系统架构比较复杂,Agent主要与HBS心跳服务器以及transfer数据上报模块进行通信,还包括获取主机的监控信息等。下图为小米监控官方文档的架构图。

Open-falcon Agent模块rpc通信过程_第1张图片 


1.2 Agent的系统位置

Agent在运维监控系统的位置具体如下:

 

Open-falcon Agent模块rpc通信过程_第2张图片


 

如上图,整个运维监控系统相当于一个网络环境,各个主机的Agent位于网络的末端,使用rpc通信的方式与服务器进行数据通信,包括:HBS心跳服务器、transfer数据转发。


1.3 Agent的简单介绍

Agent是一个golang开发的daemon程序,用于自发现的采集单机的各种数据和指标,这些指标包括不限于以下几个方面,共计200多项指标。

         CPU相关

         磁盘相关

         IO

         Load

         内存相关

         网络相关

         端口存活、进程存活

         ntp offset(插件)

         某个进程资源消耗(插件)

         netstatss等相关统计项采集

         机器内核配置参数

只要安装了falcon-agent的机器,就会自动开始采集各项指标,主动上报,不需要用户在server做任何配置。采集以上监控数据之外,Agent需要通过与服务端的HBS进行心跳连接通信,包括主机信息上报、同步插件、同步监控进程端口等等。falcon-agent,可以在github上找到 : https://github.com/open-falcon/agent


1.4 Agent的通信对象

 Agent的通信对象主要有以下两个:

a、HBS心跳服务器

Agent按照配置文件说明(默认以一分钟为周期)定期给HBS发送心跳连接请求,上报主机存活信息、同步主机监控插件、同步主机监控进程端口等。

b、transfer数据转发服务

Agent 按照配置文件说明(默认以一分钟为周期)定期给transfer发送主机个项监控数据,包括:cpu、磁盘、内存等等。


1.5 Agent的配置文件

Agent的配置文件位于Agent目录下,命名为cfg.josn。包括:debug、hostname、ip、plugin、heartbeat、transfer、http、collector、ignore多个选项。其中,heartbeat对应心跳服务器配置,transfer对应数据转发配置。如下图,Agent配置文件内容:

         debug为打开调试模式、hostname填写主机名字、ip为主机网络地址、heartbeat包括:开关,rpc地址端口,连接时间周期,超时时间、transfer类比heartbeat、collector收集网卡信息、ignore为不监控的项目。

Open-falcon Agent模块rpc通信过程_第3张图片



2.Agent模块通信方式

Agent模块使用单向同步的rpc通信方式,把数据信息以发送请求的方式推送到服务端,并且返回发送错误以及请求响应结果。


2.1 rpc通信方式

Agent模块推送信息时,使用tcp协议连接rpc服务端,推送数据并且接受服务器返回的响应数据后,关闭rpc连接,等待下一次的通信。正常来说,只有在Agent向HBS服务器请求插件同步、进程端口同步时,服务器才会返回相应数据,其他的请求只会返回请求状态,成功或者失败。


2.2 rpc数据结构

rpc数据结构:SingleConnRpcClient、Call、Client等,这里只关注SingleConnRpcClient即可,其他的大部分是go语言封装的rpc通信相关结构体。

struct {
       sync.Mutex
       rpcClient *rpc.Client
       RpcServer string
       Timeout  time.Duration
}

 
  

sync.MutexGo语言的排它锁,其他开发语言会不一样,根据情况使用。此处目的是实现rpc通信进程与主进程同步,主进程等待rpc通信结束后才继续往下执行任务。

rpcClient :声明一个rpc通信的客户端,用于连接服务端并且发送消息。

RpcServer :声明一个rpc通信的服务端描述,是rpcClient的通信对象,读取配置文件。

Timeout   :rpc连接超时,读取配置文件。

 

3.Agent模块通信内容采集

Agent模块通信内容的采集包括:面向HBS心跳服务器内容采集、面向transfer数据传送服务器内容采集。把采集到的主机存活信息、主机监控数据周期性上报给服务器。


3.1  内容采集相关项目

A、HBS心跳服务器

Agent与心跳服务器的通信有三个方面:主机存活信息、同步进程端口、同步监控插件信息。

主机存活信息:主机名字、主机IP地址、Agent当前版本、插件当前版本内容等信息。

Open-falcon Agent模块rpc通信过程_第4张图片


同步进程端口:向HBS服务器发送当前Agent主机名字与一个校验和,第一次发送一个空的校验和,HBS服务器每次返回一个校验和,并且保存为当前检验和,下一次发送当前检验和。



同步监控插件:向HBS服务器发送当前Agent主机名字,等待HBS返回插件列表,并且处理插件列表,此处理过程在Agent模块通信内容处理体现。



 可信IP地址:此功能暂时不考虑

B、transfer数据传送服务

 Agent传送到transfer的主机监控数据包括如下几个方面:

        机器负载信息, cpu.idle/load.1min//mem.memfree.percent/df.bytes

        硬件信息,比如功耗、风扇转速、磁盘是否可写服务监控数据,比如某个接口每分钟调用的次数,latency等等

        数据库、HBase、Redis、Openstack等开源软件的监控指标,具体信息如下:


CPU相关采集项,计算方法:通过采集/proc/stat来得到,大家可以参考sar命令的统计输出来理解。

cpu.idle:Percentage of time that the CPU or CPUs were idle and the system didnot have an outstanding disk I/O request.

cpu.busy:与cpu.idle相对,他的值等于100减去cpu.idle。

cpu.guest:Percentage of time spent by the CPU or CPUs to run a virtualprocessor.

cpu.iowait:Percentage of time that the CPU or CPUs were idle during which thesystem had an outstanding disk I/O request.

cpu.irq:Percentage of time spent by the CPU or CPUs to service hardwareinterrupts.

cpu.softirq:Percentage of time spent by the CPU or CPUs to service softwareinterrupts.

cpu.nice:Percentage of CPU utilization that occurred while executing at theuser level with nice priority.

cpu.steal:Percentage of time spent in involuntary wait by the virtual CPU orCPUs while the hypervisor was servicing another virtual processor.

cpu.system:Percentage of CPU utilization that occurred while executing at thesystem level (kernel).

cpu.user:Percentage of CPU utilization that occurred while executing at theuser level (application).

cpu.cnt:cpu核数。

cpu.switches:cpu上下文切换次数,计数器类型。

 

磁盘相关采集项,计算方法:先读取/proc/mounts拿到所有挂载点,然后通过syscall.Statfs_t拿到blocks和inode的使用情况。每个metric都会附加一组tag描述,类似mount=$mount,fstype=$fstype,其中$mount是挂载点,比如/home,$fstype是文件系统,比如ext4。

df.bytes.free:磁盘可用量,int64

df.bytes.free.percent:磁盘可用量占总量的百分比,float64,比如32.1

df.bytes.total:磁盘总大小,int64

df.bytes.used:磁盘已用大小,int64

df.bytes.used.percent:磁盘已用大小占总量的百分比,float64

df.inodes.total:inode总数,int64

df.inodes.free:可用inode数目,int64

df.inodes.free.percent:可用inode占比,float64

df.inodes.used:已用的inode数据,int64

df.inodes.used.percent:已用inode占比,float64

 

megacli工具输出,使用 megacli 工具读取 RAID 相关信息,每个metric都会附件一组tag描述,用来标明所属PD或者 VD,PD格式为PD=Enclosure_ID:SLOT_ID,比如PD=32:0表明第一块磁盘 ,VD=0 表明第一个逻辑磁盘。

sys.disk.lsiraid.pd.Other_Error_Count

sys.disk.lsiraid.pd.Predictive_Failure_Count

sys.disk.lsiraid.pd.Drive_Temperature

sys.disk.lsiraid.pd.Firmware_state:如果值不为0,则此物理磁盘出现问题

sys.disk.lsiraid.vd.cache_policy:如果值不为0,表示此逻辑磁盘缓存策略和设置不符

sys.disk.lsiraid.vd.state: 如果值不为0,表示此逻辑磁盘出现问题


SMART工具输出,使用smartctl 工具读取磁盘 SMART 信息,目前所有指标仅作为数据收集,不一定意味磁盘损坏(只是表示概率变大),每个metric都会有一组tag描述,表明盘符,例如device=/dev/sda。

sys.disk.smart.Reallocated_Sector_Ct

sys.disk.smart.Spin_Retry_Count

sys.disk.smart.Reallocated_Event_Count

sys.disk.smart.Current_Pending_Sector

sys.disk.smart.Offline_Uncorrectable

sys.disk.smart.Temperature_Celsius

 

分区读写监控,测试所有已挂载分区是否可读写,每个metric都会有一组tag描述,表示挂载点,比如mount=/home

sys.disk.rw:如果值不为0,表明此分区读写出现问题


IO相关采集项,计算方法:每秒采集一次/proc/diskstats,计算差值,都是计数器类型的。每个metric都会有一组tag描述,形如device=$device,用来表示具体的设备,比如sda1、sdb。用户可以参考iostat的帮助文档来理解具体的metric含义。

disk.io.ios_in_progress:Number of actual I/O requestscurrently in flight.

disk.io.msec_read:Total number of ms spent by all reads.

disk.io.msec_total:Amount of time during which ios_in_progress >= 1.

disk.io.msec_weighted_total:Measure of recent I/O completiontime and backlog.

disk.io.msec_write:Total number of ms spent by all writes.

disk.io.read_merged:Adjacent read requests merged in a single req.

disk.io.read_requests:Total number of reads completedsuccessfully.

disk.io.read_sectors:Total number of sectors read successfully.

disk.io.write_merged:Adjacent write requests merged in a single req.

disk.io.write_requests:total number of writes completedsuccessfully.

disk.io.write_sectors:total number of sectors writtensuccessfully.

disk.io.read_bytes:单位是byte的数字

disk.io.write_bytes:单位是byte的数字

disk.io.avgrq_sz:下面几个值就是iostat -x 1看到的值

disk.io.avgqu-sz

disk.io.await

disk.io.svctm

disk.io.util:是个百分数,比如56.43,表示56.43%


机器负载相关采集项,计算方法:读取/proc/loadavg,都是原始值类型的:

load.1min

load.5min

load.15min


内存相关采集项

计算方法:读取/proc/meminfo 中的内容,其中的mem.memfree是free+buffers+cached,mem.memused=mem.memtotal-mem.memfree。用户具体可以参考free命令的输出和帮助文档来理解每个metric的含义。

mem.memtotal:内存总大小

mem.memused:使用了多少内存

mem.memused.percent:使用的内存占比

mem.memfree

mem.memfree.percent

mem.swaptotal:swap总大小

mem.swapused:使用了多少swap

mem.swapused.percent:使用的swap的占比

mem.swapfree

mem.swapfree.percent


网络相关采集项,计算方法:读取/proc/net/dev的内容,每个metric都附加有一组tag,形如iface=$iface,标明具体那个interface,比如eth0。metric中带有in的表示流入情况,out表示流出情况,total是总量in+out,支持的metric如下:

net.if.in.bytes

net.if.in.compressed

net.if.in.dropped

net.if.in.errors

net.if.in.fifo.errs

net.if.in.frame.errs

net.if.in.multicast

net.if.in.packets

net.if.out.bytes

net.if.out.carrier.errs

net.if.out.collisions

net.if.out.compressed

net.if.out.dropped

net.if.out.errors

net.if.out.fifo.errs

net.if.out.packets

net.if.total.bytes

net.if.total.dropped

net.if.total.errors

net.if.total.packets

 

端口采集项,计算方法,通过ss -ln,来判断指定的端口是否处于listen状态。原始值类型,值要么是1:代表在监听,要么是0,代表没有在监听。每个metric都附件一组tag,形如port=$port,$port就是具体的端口。

net.port.listen


机器内核配置

kernel.maxfiles: 读取的/proc/sys/fs/file-max

kernel.files.allocated:读取的/proc/sys/fs/file-nr第一个Field

kernel.files.left:值=kernel.maxfiles-kernel.files.allocated

 kernel.maxproc:读取的/proc/sys/kernel/pid_max


ntp采集项,使用 ntpq-pn 获取本机时间相对于 ntp 服务器的 offset。

sys.ntp.offset: 本机偏移时间,单位为ms,值过大或者为0则表明有异常,需要报警


进程监控,proc.num:判断某个进程的数目,这里需要分两个场景,一种是根据进程的名字来判定,比如name=sshd;另外一种是根据cmdline来判定,比如Java的应用进程名可能都是java,根据第一种情况没法做区分,此时可以配置cmdline,如cmdline=./falcon_agent-c./cfg.ini进程资源监控

process.cpu.all:进程和它的子进程使用的sys+user的cpu,单位是jiffies

process.cpu.sys:进程和它的子进程使用的sys cpu,单位是jiffies

process.cpu.user:进程和它的子进程使用的user cpu,单位是jiffies

process.swap:进程和它的子进程使用的swap,单位是page

process.fd:进程使用的文件描述符个数

process.mem:进程占用内存,单位byte


ss命令输出

ss.orphaned

ss.closed

ss.timewait

ss.slabinfo.timewait

ss.synrecv

ss.estab

本节内容来自于open-falcon对linux运维基础采集项的说明。


3.2  内容采集数据结构

A、HBS心跳服务器

Agent与心跳服务器的通信有三个方面:主机存活信息、同步进程端口、同步监控插件信息。

主机存活信息:主机名字、主机IP地址、Agent当前版本、插件当前版本内容等信息。

Open-falcon Agent模块rpc通信过程_第5张图片


使用rpc通信发送以上信息后,返回rpc通信结果,发送失败写入日志,成功与否,进入下一次通信周期再次发送。发送源代码如下:

Open-falcon Agent模块rpc通信过程_第6张图片


time.Sleep(interval)是通信周期,interval来自于配置文件。


同步进程端口:向HBS服务器发送当前Agent主机名字与一个校验和,第一次发送一个值为nil的校验和,HBS服务器每次返回一个校验和,并且保存为当前检验和,下一次发送当前检验和。



发送HBS请求后,Agent等待HBS返回同步端口的信息,并且保存到BuiltinMetricResponse结构体当中,用于后续的处理

           


BuiltinMetricResponse结构体包括多个BuiltinMetric结构体,BuiltinMetric保存单个端口或者进程的信息。



          

具体函数源码如下:

Open-falcon Agent模块rpc通信过程_第7张图片         


Agent获取到进程端口信息后,处理操作将在Agent模块通信内容处理说明。

同步监控插件:向HBS服务器发送当前Agent主机名字,等待HBS返回插件列表,并且处理插件列表,此处理过程在Agent模块通信内容处理体现。



注意:这哪即使使用了AgentHeartbeatRequest结构体封装RPC通信请求,但是同步插件的操作没有发送Checksum字符串,区别同步进程端口,具体过程体现在源代码当中。

                   


AgentPluginsResponse结构用于接收保存HBS返回的插件列表,等待后续处理。

具体源代码如下:

                Open-falcon Agent模块rpc通信过程_第8张图片


可信IP地址:此功能暂时不考虑

RPC通信相关结构体

定义一个RPC通信结构体,封装rpcClient客户端结构体

Open-falcon Agent模块rpc通信过程_第9张图片               


sync.MutexGo语言的排它锁,其他开发语言会不一样,根据情况使用。此处目的是实现rpc通信进程与主进程同步,主进程等待rpc通信结束后才继续往下执行任务。

rpcClient :声明一个rpc通信的客户端,用于连接服务端并且发送消息。

RpcServer :声明一个rpc通信的服务端描述,是rpcClient的通信对象,读取配置文件。

Timeout   :rpc连接超时,读取配置文件。

go语言中rpc.Client的原型如下

             Open-falcon Agent模块rpc通信过程_第10张图片


声明两个RPC通信客户端结构体用于HBS、transfer的通信。

             


B、transfer数据传送服务器

Agent发送给transfer服务器的监控数据包括多个项目,每个项目有多个指标。Agent的收集器把监控数据周期性读取到内存中,拼接好之后发送给服务器。在收集器里定义一个用于综合管理主机监控数据的结构体:Mappers

Open-falcon Agent模块rpc通信过程_第11张图片


Interval是从配置文件读过来的超时时间,Fs 是一个函数指针数组,保存多个函数指针。Mappers结构体内初始化了四个方面的监控数据包括:机器负载信息、硬件信息、服务器监控信息、开源软件监控指标。即Mappers中已经规定包含四个FuncsAndInterval,每个FuncsAndInterval中有一个Fs,Fs是一个model.MetricValue结构体数组指针,四个Fs内容如下:

a、AgentMetrics,CpuMetrics,NetMetrics,KernelMetrics,LoadAvgMetrics,MemMetrics,DiskIOMetrics,IOStatsMetrics,NetstatMetrics,ProcMetrics,UdpMetrics

b、DeviceMetrics

c、PortMetrics,SocketStatSummaryMetrics

d、DuMetrics

具体初始化代码如下:

Open-falcon Agent模块rpc通信过程_第12张图片


上面每一个Metrics是一个函数,每个函数读取相关标签的监控信息,读取标签信息重新封装model.MetricValue结构体并且返回。

Open-falcon Agent模块rpc通信过程_第13张图片


如下是MetricValue结构体原型:

Open-falcon Agent模块rpc通信过程_第14张图片


最后,把各个MetricValue结构体返回一个[]*model.MetricValue结构体指针数组。示例:CpuMetrics

Open-falcon Agent模块rpc通信过程_第15张图片


CpuMetrics是Fs: []func() []*model.MetricValue中的成员,同时也是一个函数,收集器循环遍历得到CpuMetrics,执行上面的函数内容,获取相关的监控信息,传递给GaugeValue(),调用NewMetricValue()重新封装MetricValue结构体,在CpuMetrics函数结束返回包含多个MetricValue的[]*model.结构体指针数组。

如下为GaugeValue()函数原型:



metric对应cpu.idle、cpu.busy、cpu.user等标签,val 是当前标签的值,其他的暂时保持默认。至于,标签对应的监控信息获取方法就不再展开解析了。

如下图,是Agent发送数据到transfer服务器的数据结构一个整体概览,Mappers里面四个FuncsAndInterval都有各自的Fs,可以看成Mappers里面有四个Fs,而Fs里面封装了跟多的Metrics,这些Metrics指向一项获取各个监控指标的函数,该函数也是返回[]*model.MetricValue类型,保存着各个model.MetricValue结构体内容。    

Open-falcon Agent模块rpc通信过程_第16张图片


CpuMetrics包括idle, busy, user, nice, system, iowait, irq, softirq, steal, guest,switches等model.MetricValue。

具体采集信息过程源码如下:

Open-falcon Agent模块rpc通信过程_第17张图片      


Collect()为主函数所调用,Collect()为每一个FuncsAndInterval创建一个collect(int64(v.Interval), v.Fs)来处理所对应的Fs,Fs中包含多个model.MetricValue,而这些model.MetricValue又是一个函数,读取相应项目的监控信息,每一个项目里面又有多个标签,每一个标签都是一个model.MetricValue,后来把全部的model.MetricValue都放到了一个[]func() []*model.MetricValue里面发送出去。

      Open-falcon Agent模块rpc通信过程_第18张图片


 

4.Agent模块通信交互过程

该模块主要描述通信过程的概述,包括:监控数据上报的过程、主机存活状态的上报、主机进程端口的同步、主机插件信息的同步。也是划分为两个部分说明。


4.1  模块交互简述

交互过程主要是Agent向服务器发送RPC消息,等待服务器返回结果,并且进行处理,而下面的通信交互过程并不是全部都要处理返回结果。

A、HBS心跳服务器

主机存活信息

使用RPC通信,周期性发送主机存活报告,检验发送消息结果,发送出错完成写日志操作,发送成功与否进入下一次发送周期,等待消息发送。

同步进程端口

使用RPC通信,周期性发送Agent.BuiltinMetricsHBS心跳连接请求,包括主机名与一个校验和。校验和的初始值为”nil”,HBS响应心跳连接,返回进程端口信息、校验和、时间戳。进程端口信息存储起来等待处理,校验和保存起来用于下一次心跳连接请求。时间戳保存起来,用于下一次时间校验,若下一次HBS相应的时间戳比当前时间戳小,直接跳过当前周期,不做进程端口信息处理。同时,校验和没有发生改变是不做任何进程端口信息处理。因为校验和没有发生改变,意味着进程端口信息也没有发生改变。进程端口信息处理结束后,进入下一个通信周期。

同步监控插件

使用RPC通信,周期性发送Agent.MinePluginsHBS心跳连接请求,包含当前主机名。HBS响应心跳连接,返回监控插件信息、时间戳。监控插件信息保存起来等待后续处理,时间戳保存起来,用于下一次时间校验,若下一次HBS相应的时间戳比当前时间戳小,直接跳过当前周期,不做监控插件信息处理。监控插件信息处理结束后,进入下一个通信周期。

B、transfer数据传送服务器

使用RPC通信,周期性发送Transfer.Update信息,包含一个Mapperers中的全部项目中标签的主机监控信息,读到每一个标签重新封装到一个项目当中。然后,发送主机监控信息,检验发送消息结果,发送出错完成写日志操作,发送成功与否进入下一次发送周期,等待消息发送。


4.2 交互数据结构

数据结构统一在Agent模块通信内容采集中体现。


5.Agent模块通信内容处理

Agent模块通信内容的处理主要针对:服务器返回给Agent的响应信息,特别是同步信息的处理。


5.1  内容处理相关项目

A、HBS心跳服务器

主机存活信息

没有相关信息的处理

同步进程端口

处理进程信息或者处理端口信息或者获取某个目录的大小(例如:log目录是否发生改变)。

同步监控插件

处理监控插件的信息,由服务器下发的插件信息与本地的插件信息作比较,然后处理插件信息。

B、 transfer数据传送服务器

没有相关信息的处理

该节建议参考Agent源码以及Agent源码解释视频。


5.2 内容处理数据结构

数据结构统一在Agent模块通信内容采集中体现。具体数据处理过程如下:

A、 HBS心跳服务器

主机存活信息

没有内容处理,只是出错时完成写日志操作。

同步进程端口

a、net.port.listen:端口信息,根据下发的端口号,设置需要监控端口的值。从结构体BuiltinMetricResponseresp中查找是否存在net.port.listen,找到则添加到需要监控端口之中。



b、du.bs:监控目录大小,从结构体BuiltinMetricResponseresp中查找是否存在du.bs,找到则添加到需要监控端口之中。



c、proc.num:进程数量(当前进程名字存在进程的个数),从结构体BuiltinMetricResponseresp中查找是否存在proc.num,找到则添加到需要监控端口之中。



 

resp保存HBS服务器返回的进程端口信息。

           


最后调用函数,更新进程端口信息,代码如下图:

 Open-falcon Agent模块rpc通信过程_第19张图片


更新端口信息:g.SetReportPorts(ports)

更新进程信息:g.SetReportProcs(procs)

更新监控目录:g.SetDuPaths(paths)


其中更新进程信息根据进程名字与进程命令进行分类,两种tag不能同时出现。

 

同步监控插件

如下是插件信息的结构体:这里只关心FilePath 插件的路径

Open-falcon Agent模块rpc通信过程_第20张图片        

      

Agent把HBS服务器返回的插件保存在结构体AgentPluginsResponse  resp里面:



Plugins存放了HBS返回的全部插件路径。然后列出全部插件,获取到真实的插件名字,最后更新插件。

           Open-falcon Agent模块rpc通信过程_第21张图片


 

B、transfer数据传送服务器

没有内容处理,只是出错时完成写日志操作。


6.Agent模块通信过程总结

6.1 通信过程总结

本文档基本完成了Agent模块RPC通信的过程,包括Agent的简单介绍、通信方式、通信内容采集、通信交互过程以及通信内容的处理。不论是否存在疑问,都需要参考官方文档以及Agent源码解读,本文档只是加快了解Agent通信过程的脚步,并不是全部。


6.2 其他补充说明

a、没有处理IP白名单,open-falcon功能已经稳定不再需要后门调试,而且这样做非常不安全。

b、 具体Agent升级相关操作并没有做说明,具体关联应该不大。

b、Agent通信模块之外的内部细节并没有说分,例如:监控信息如何获取、如何设置监听端口、如何设置监听进程、如何设置、获取、更新、启动插件等等。


你可能感兴趣的:(运维之路)