[監控]為opserver新增多組帳號密碼來連線server,並客製化顯示名稱

[監控]為opserver新增多組帳號密碼來連線server,並客製化顯示名稱

前言

Opserver是一套由Stackoverflow所使用監控機器的專案,接著發佈出來原始碼給大家使用的,而為何需要這類型的專案呢?主要是因為當我們的產品日漸久遠之後,機器就會越來越多,所以我需要一台機器來監控所有的機器的狀況,如果cpu或memory過高,甚至使用發信或結合chatbot來做通知,而因為Opserver是一套開放原始碼的專案,所以我們都可以經由客製化來達到我們的需求,有興趣的可以直接去github觀看(https://github.com/opserver/Opserver),Demo效果可以到這邊(https://imgur.com/a/dawwf)觀看,而這篇會介紹最基本的監控server狀況,並且針對原始碼做一些客製化。

導覽

  1. 開始使用Opserver
  2. 客製化把帳號密碼設為可以多組
  3. 客製化把機器名稱改為自訂名稱

開始使用Opserver

首先需要從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