[監控]為opserver新增多組帳號密碼來連線server,並客製化顯示名稱
前言
Opserver是一套由Stackoverflow所使用監控機器的專案,接著發佈出來原始碼給大家使用的,而為何需要這類型的專案呢?主要是因為當我們的產品日漸久遠之後,機器就會越來越多,所以我需要一台機器來監控所有的機器的狀況,如果cpu或memory過高,甚至使用發信或結合chatbot來做通知,而因為Opserver是一套開放原始碼的專案,所以我們都可以經由客製化來達到我們的需求,有興趣的可以直接去github觀看(https://github.com/opserver/Opserver),Demo效果可以到這邊(https://imgur.com/a/dawwf)觀看,而這篇會介紹最基本的監控server狀況,並且針對原始碼做一些客製化。
導覽
首先需要從github下載原始碼專案,你可以直接訪問https://github.com/opserver/Opserver去下載,或者使用git clone的方式,然後開啟專案之後,首先看到web.config有如下配置
<SecuritySettings configSource="Config\SecuritySettings.config"/>
所以我們首先到Config把SecuritySettings.config.example改成SecuritySettings.config
而此配置可以結合ad的登入方式,目前我只需要使用alladmin就好了,所以把內容改為
<?xml version="1.0" encoding="utf-8"?>
<SecuritySettings provider="alladmin" />
在config裡面有很多json配置檔,可以提供你去監控各類型的機器
目前我只需要使用Dashboard,所以把DashboardSettings.example.config改成DashboardSettings.config,然後把wmi的區段反註解,並改成如下
"wmi": {
"nodes": [ "localhost" ], // List of nodes to monitor
"staticDataTimeoutSeconds": 300, // (Optional) How long to cache static data (node name, hardware, etc.) - defaults to 5 minutes
"dynamicDataTimeoutSeconds": 5, // (Optional) How long to cache dynamic data (utilizations, etc.) - defaults to 30 seconds
"historyHours": 2 // (Optional) How long to retain data (in memory) - defaults to 24 hours
}
然後把bosun的區段刪除掉
至此我們已經完成了,可以開啟專案,並輸入預設的帳號密碼皆為admin,就可以看到我們本機的狀況
雖然nodes可以設定多組,但帳號密碼皆只能單一,所以我期望把wmi設定可以吃多組的ip對應帳號密碼,完成之後的json大約會如下方式
"wmi": [
{
"nodes": [ "localhost" ],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "administrator",
"Password": "123"
},
{
"nodes": [ "localhost1" ],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "administrator",
"Password": "123"
},
{
"nodes": [ "localhost2" ],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "admin",
"Password": "123"
}
]
接著就開始來修改程式碼的部份,首先修改Opserver.Core\Data\Dashboard\DashboardModule.cs
foreach (var p in providers.All)
{
p?.Normalize();
}
改成如下
foreach (var p in providers.All)
{
if (p != null)
{
foreach (var item in p)
{
item?.Normalize();
}
}
}
移除底下的程式碼
if (providers.Bosun != null)
Providers.Add(new BosunDataProvider(providers.Bosun));
if (providers.Orion != null)
Providers.Add(new OrionDataProvider(providers.Orion));
接著修改Opserver.Core\Data\Dashboard\Providers\DashboardDataProvider.cs
public TSettings Settings { get; protected set; }
改成
public IEnumerable<TSettings> Settings { get; protected set; }
protected DashboardDataProvider(TSettings settings) : base(settings)
改成
protected DashboardDataProvider(IEnumerable<TSettings> settings) : base(settings)
protected DashboardDataProvider(IProviderSettings settings) : base(settings.Name + "Dashboard")
改成
protected DashboardDataProvider(IEnumerable<IProviderSettings> settings) : base(settings.FirstOrDefault().Name + "Dashboard")
Name = settings.Name;
改成
Name = settings.FirstOrDefault().Name;
接著修改Opserver.Core\Data\Dashboard\Providers\WmiDataProvider.cs
internal partial class WmiDataProvider : DashboardDataProvider<WMISettings>, IServiceControlProvider
{
private readonly IEnumerable<WMISettings> _config;
private readonly List<WmiNode> _wmiNodes = new List<WmiNode>();
private readonly Dictionary<string, WmiNode> _wmiNodeLookup;
public WmiDataProvider(IEnumerable<WMISettings> settings) : base(settings)
{
_config = settings;
_wmiNodes = InitNodeList(_config).OrderBy(x => x.Endpoint).ToList();
// Do this ref cast list once
AllNodes.AddRange(_wmiNodes.Cast<Node>().ToList());
// For fast lookups
_wmiNodeLookup = new Dictionary<string, WmiNode>(_wmiNodes.Count);
foreach (var n in _wmiNodes)
{
_wmiNodeLookup[n.Id] = n;
}
}
/// <summary>
/// Make list of nodes as per configuration.
/// When adding, a node's IP address is resolved via DNS.
/// </summary>
/// <param name="nodeNames">The names of the server nodes to monitor.</param>
private IEnumerable<WmiNode> InitNodeList(IEnumerable<WMISettings> settings)
{
var nodesList = new List<WmiNode>();
var exclude = Current.Settings.Dashboard.ExcludePatternRegex;
foreach (var setting in settings)
{
foreach (var nodeName in setting.Nodes)
{
if (exclude?.IsMatch(nodeName) ?? false) continue;
var node = new WmiNode(nodeName)
{
Config = _config.FirstOrDefault(a => a.Nodes.Contains(nodeName)),
DataProvider = this
};
try
{
var hostEntry = Dns.GetHostEntry(node.Name);
if (hostEntry.AddressList.Any())
{
node.Ip = hostEntry.AddressList[0].ToString();
node.Status = NodeStatus.Active;
}
else
{
node.Status = NodeStatus.Unreachable;
}
}
catch (Exception)
{
node.Status = NodeStatus.Unreachable;
}
node.Caches.Add(ProviderCache(
() => node.PollNodeInfoAsync(),
node.Config.StaticDataTimeoutSeconds.Seconds(),
memberName: node.Name + "-Static"));
node.Caches.Add(ProviderCache(
() => node.PollStats(),
node.Config.DynamicDataTimeoutSeconds.Seconds(),
memberName: node.Name + "-Dynamic"));
nodesList.Add(node);
}
}
return nodesList;
}
private WmiNode GetWmiNodeById(string id) =>
_wmiNodeLookup.TryGetValue(id, out WmiNode n) ? n : null;
public override int MinSecondsBetweenPolls => 10;
public override string NodeType => "WMI";
public override IEnumerable<Cache> DataPollers => _wmiNodes.SelectMany(x => x.Caches);
protected override IEnumerable<MonitorStatus> GetMonitorStatus()
{
yield break;
}
protected override string GetMonitorStatusReason() => null;
public override bool HasData => DataPollers.Any(x => x.ContainsData);
public override List<Node> AllNodes { get; } = new List<Node>();
}
接著修改Opserver.Core\Monitoring\Wmi.cs
把Static Wmi()的區塊改成如下
static Wmi()
{
_localOptions = new ConnectionOptions
{
EnablePrivileges = true
};
_remoteOptions = new ConnectionOptions
{
EnablePrivileges = true,
Authentication = AuthenticationLevel.Packet,
Timeout = TimeSpan.FromSeconds(30)
};
}
接著把WmiQuery的建構子的程式碼改成如下
public WmiQuery(string machineName, string q, string wmiNamespace = @"root\cimv2")
{
_machineName = machineName;
_rawQuery = q;
_wmiNamespace = wmiNamespace;
if (machineName.IsNullOrEmpty())
throw new ArgumentException("machineName should not be empty.");
var connectionOptions = GetConnectOptions(machineName);
string username = Current.Settings.Dashboard.Providers?.WMI?.FirstOrDefault(a => a.Nodes.Contains(machineName))?.Username ??
Current.Settings.Polling.Windows?.AuthUser.IsNullOrEmptyReturn(null),
password = Current.Settings.Dashboard.Providers?.WMI?.FirstOrDefault(a => a.Nodes.Contains(machineName))?.Password ??
Current.Settings.Polling.Windows?.AuthPassword.IsNullOrEmptyReturn(null);
if (username.HasValue() && password.HasValue())
{
_remoteOptions.Username = username;
_remoteOptions.Password = password;
}
var path = $@"\\{machineName}\{wmiNamespace}";
var scope = _scopeCache.GetOrAdd(path, x => new ManagementScope(x, connectionOptions));
_searcher = _searcherCache.GetOrAdd(path + q, x => new ManagementObjectSearcher(scope, new ObjectQuery(q), new EnumerationOptions { Timeout = connectionOptions.Timeout }));
}
接著修改Opserver.Core\Settings\DashboardSettings.Providers.cs
public class ProvidersSettings
{
public BosunSettings Bosun { get; set; }
public OrionSettings Orion { get; set; }
public WMISettings WMI { get; set; }
public bool Any() => All.Any(p => p != null);
public IEnumerable<IProviderSettings> All
{
get
{
yield return Bosun;
yield return Orion;
yield return WMI;
}
}
}
改成
public class ProvidersSettings
{
public List<WMISettings> WMI { get; set; }
public bool Any() => All.Any(p => p != null);
public IEnumerable<IEnumerable<IProviderSettings>> All
{
get
{
yield return WMI;
}
}
}
最後把編譯不過的檔案全部排除掉,因為在wmi裡面不需要用到
Opserver.Core\Data\Dashboard\Providers\OrionDataProvider.cs
Opserver.Core\Data\Dashboard\Providers\BosunDataProvider.cs
Opserver.Core\Data\Dashboard\Providers\BosunDataProvider.Metrics.cs
Opserver.Core\Data\Dashboard\Providers\BosunDataProvider.Nodes.cs
一般來說如果你顯示的機器數量沒幾台,這點並不會是一個困擾,但是如果你監控的機器越來越多的時候,就會有點搞混不清了,所以更好的方法是自己可以為各台機器顯示為自訂的名稱,以便更好的管理眾多機器
找到Opserver.Core\Settings\DashboardSettings.WMI.cs,並加入下面這行,主要是定義屬性能吃到多的屬性值
public List<string> DisplayName { get; set; }
找到Opserver.Core\Data\Dashboard\Providers\WmiDataProvider.cs,在改掉的差異性我加入註解,以便尋查差異之處
private IEnumerable<WmiNode> InitNodeList(IEnumerable<WMISettings> settings)
{
var nodesList = new List<WmiNode>();
var exclude = Current.Settings.Dashboard.ExcludePatternRegex;
foreach (var setting in settings)
{
foreach (var nodeName in setting.Nodes)
{
if (exclude?.IsMatch(nodeName) ?? false) continue;
var displayNameIndex = setting.Nodes.FindIndex(x => x == nodeName); //尋找node的index
var node = new WmiNode(nodeName)
{
Config = _config.FirstOrDefault(a => a.Nodes.Contains(nodeName)),
DataProvider = this,
Name=setting.DisplayName[displayNameIndex]//在此改掉Name的名稱
};
try
{
var hostEntry = Dns.GetHostEntry(node.Name);
if (hostEntry.AddressList.Any())
{
node.Ip = hostEntry.AddressList[0].ToString();
node.Status = NodeStatus.Active;
}
else
{
node.Status = NodeStatus.Unreachable;
}
}
catch (Exception)
{
node.Status = NodeStatus.Unreachable;
}
node.Caches.Add(ProviderCache(
() => node.PollNodeInfoAsync(),
node.Config.StaticDataTimeoutSeconds.Seconds(),
memberName: node.Name + "-Static"));
node.Caches.Add(ProviderCache(
() => node.PollStats(),
node.Config.DynamicDataTimeoutSeconds.Seconds(),
memberName: node.Name + "-Dynamic"));
nodesList.Add(node);
}
}
return nodesList;
}
找到Opserver.Core\Data\Dashboard\Providers\WmiDataProvider.Data.cs
public WmiNode(string endpoint)
{
Endpoint = endpoint;
Id = endpoint.ToLower();
//Name = endpoint.ToLower(); 把此行刪除掉
MachineType = "Windows";
Caches = new List<Cache>(2);
Interfaces = new List<Interface>(2);
Volumes = new List<Volume>(3);
Services = new List<NodeService>();
VMs = new List<Node>();
Apps = new List<Application>();
// TODO: Size for retention / interval and convert to limited list
MemoryHistory = new List<MemoryUtilization>(1024);
CPUHistory = new List<CPUUtilization>(1024);
CombinedNetHistory = new List<Interface.InterfaceUtilization>(1024);
NetHistory = new ConcurrentDictionary<string, List<Interface.InterfaceUtilization>>();
VolumeHistory = new ConcurrentDictionary<string, List<Volume.VolumeUtilization>>();
CombinedVolumePerformanceHistory = new List<Volume.VolumePerformanceUtilization>(1024);
VolumePerformanceHistory = new ConcurrentDictionary<string, List<Volume.VolumePerformanceUtilization>>();
}
最後把DashboardSettings.json的參數加上DisplayName對應了Node的Index,就可以改掉我們想要顯示的名稱了
"wmi": [
{
"nodes": [ "localhost","localhost123" ],
"displayName":["my computer","my computer123"],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "administrator",
"Password": "123"
},
{
"nodes": [ "localhost1" ],
"displayName":["my computer1"],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "administrator",
"Password": "123"
},
{
"nodes": [ "localhost2" ],
"displayName":["my computer"],
"staticDataTimeoutSeconds": 300,
"dynamicDataTimeoutSeconds": 5,
"historyHours": 2,
"Username": "admin",
"Password": "123"
}
]
此文章有很大部份參考來自https://blog.yowko.com/2017/03/opserver-windows-server_30.html