ในการตรวจสอบการทำงาน (Monitoring) บนระบบ Production ในหลายๆ ครั้งนั้น สิ่งที่ผู้ดูแลระบบมักจะกลัว และไม่ค่อยมักจะอยากให้ทำบนระบบคือ การติดตั้งโปรแกรมใดๆ ลงบนเครื่องนั้น โดยเฉพาะกับเครื่องที่มีการทำงานโหลดสูงๆ (High utilization) มีการทำงานที่สำคัญ (Critical function) รวมไปถึงขอปิดระบบยากๆ (Less down time)
ดังนั้นทางเลือกหนึ่งที่จะสามารถทำการตรวจสอบการทำงานเกี่ยวกับการให้บริการแต่ละเซอร์วิสผ่านทางพอร์ท (Port) ได้ คือการ connect เข้าไปผ่านช่องทางของพอร์ทการทำงานในเซอร์วิสเหล่านั้น
ข้อดี:
สิ่งที่ต้องเตรียม
ทำความรู้จักกับ Zabbix Item
สำหรับ Item ใน Zabbix นั้น คือการสร้างการ monitor หนึ่งอย่าง เช่นต้องการสร้างการ monitor ระบบ MSSQL เราสามารถสร้าง Item ที่ Host ได้ดังนี้ (ปกติควรสร้าง Item ที่ Template แต่เพื่อความเข้าใจที่ง่าน และลดขั้นตอน จึงของแสดงตัวอย่างในการสร้าง Item ที่ Host)
Configuration -> Hosts -> Items -> Create item
โดยจะปรากฏหน้าจอ Create item โดยใส่ข้อมูลดังนี้
ทำความรู้จักกับ Macro
สำหรับ Macro ใน Zabbix นั้นคือตัวแปรที่สามารถเก็บค่าต่างๆ ได้ ซึ่ง macro นั้นมี 3 รุปแบบคือ
Configuration -> Hosts -> [SELECTED HOST] -> Macro
โดยจะปรากฏหน้าจอ Create item โดยใส่ข้อมูลดังนี้
*** โดยปกติจะไม่มีการใส่รหัสผ่าน (Password) ใน macro เพราะจะทำให้เห็นรหัสผ่าน วิธีการที่แนะนำคือการทำ mapping ระหว่าง host และรหัสผ่านใน script แทน***
ทำความรู้จักกับ Zabbix External Script
Zabbix External Script คือการเขียน script เพื่อเติมเพื่อให้สามารถทำการ monitor ได้ตามที่ต้องการอย่างไม่มีขอบเขต เนื่องจากเป็นการเขียนโปรแกรมเพื่อทำการ monitor ซึ่งสามารถเขียนได้ทั้งจาก Shell Script, ภาษา Python, ภาษา Perl และภาษาอื่นๆ ที่เครื่อง Zabbix สามารถ execute ได้
ซึ่งความคิดเป็นส่วนตัว การใช้งาน external script นั้นมีแนวทางการใช้งานดังนี้
สำหรับโปรแกรม script นั้น โดยปกติจะอยู่ที่ไดเร็กทอรี /etc/zabbix/externalscripts ซึ่งจากตัวอย่างการกำหนดใน Item ในส่วนของ Key มีรายละเอียดดังนี้
mssqlchk.py["{HOST.CONN}","{DBQUERY}","{DBUSER}","{DBPASS}","{DBNAME}"]
การ mapping parameters
และในไฟล์ สามารถเขียนโปรแกรมให้ทำงานได้ดังนี้ (ตัวอย่างภาษา Python)
จากนั้นสามารถทดสอบค่าที่ได้จาก script โดยการตรวจสอบที่ Latest Data เมื่อการทำงานถูกต้องสามารถดำเนินการสร้าง Graph และ Trigger เพื่อใช้งานได้ต่อไป รวมไปถึงสามารถเขียนโปรแกรมดึงข้อมูลตัวเลขอื่นๆ ตามที่เซอร์วิสนั้นมีให้ดำเนินการได้
ตัวอย่าง script อื่นๆ
Script สำหรับตรวจสอบ Website
Script สำหรับตรวจสอบ DNS
Script สำหรับตรวจสอบ Oracle
Script สำหรับตรวจสอบ LDAP
Script สำหรับตรวจสอบ TACACS
Script สำหรับตรวจสอบ Radius
ดังนั้นทางเลือกหนึ่งที่จะสามารถทำการตรวจสอบการทำงานเกี่ยวกับการให้บริการแต่ละเซอร์วิสผ่านทางพอร์ท (Port) ได้ คือการ connect เข้าไปผ่านช่องทางของพอร์ทการทำงานในเซอร์วิสเหล่านั้น
ข้อดี:
- ไม่ต้องติดตั้งโปรแกรมใดๆ ลงบนเครื่องนั้นๆ ใช้ช่องทางปกติที่เซอร์วิสนั้นเปิดให้บริการอยู่แล้ว
- สามารถทำงานได้เกือบเทียบเท่า Agent โดยสามารถทำได้เท่าที่พอร์ทนั้นให้บริการ
- เวลาระบบมีปัญหา เราจะไม่เป็น แพะ!!!
ข้อเสีย:
- อาจจะทำงานได้ไม่เทียบเท่ากับการติดตั้ง Agent ในบางกรณีที่รันบางคำสั่งไม่สามารถใช้งานผ่านพอร์ทนั้นได้
- ต้องเขียนโปรแกรมนึดนึง
- อาจจะมีการติดตั้งบาง Module บน Zabbix Server(Proxy) ในกรณีที่เป็นเซอร์วิสแปลกๆ
สิ่งที่ต้องเตรียม
- การกำหนด Zabbix Item, Macro และ External Script
- การเขียนโปรแกรม (Python, Shell Script)
ทำความรู้จักกับ Zabbix Item
สำหรับ Item ใน Zabbix นั้น คือการสร้างการ monitor หนึ่งอย่าง เช่นต้องการสร้างการ monitor ระบบ MSSQL เราสามารถสร้าง Item ที่ Host ได้ดังนี้ (ปกติควรสร้าง Item ที่ Template แต่เพื่อความเข้าใจที่ง่าน และลดขั้นตอน จึงของแสดงตัวอย่างในการสร้าง Item ที่ Host)
Configuration -> Hosts -> Items -> Create item
โดยจะปรากฏหน้าจอ Create item โดยใส่ข้อมูลดังนี้
- Name: MSSQL_mon => คือชื่อของ Item นี้
- Type: External check => คือการเลือกรูปแบบในการตรวจสอบ ในที่นี้เลือก External check เพื่อให้ Zabbix ใช้ script ที่เราจะสร้างขึ้นในขั้นตอนต่อไปมาใช้งาน
- *Key: mssqlchk.py["{HOST.CONN}","{DBQUERY}","{DBUSER}","{DBPASS}","{DBNAME}"] => คือการกำหนด script ที่จะใช้งานในการตรวจสอบ โดยมีพารามิเตอร์ภายใน [] ซึ่งพารามิเตอร์นั้นจะใช้การนำ Macro มาใช้งานเพื่อความสะดวกในการกำหนดค่าปรับแต่ง (Configuration)
- รูปแบบของ script คือ scriptname[<parameter1>,<parameter2>,...] => โดยที่ script อยู่ที่ /etc/zabbix/externalscripts ใน Zabbix Server/Proxy
- *Macro คือสิ่งที่เปรียนเสมือนตัวแปรที่สามารถกำหนดได้ในระบบ Zabbix ซึ่งมี macro อยู่สามแบบคือ System (supported) macro, Function macro และ User macro (ดูรายละเอียดเพิ่มเติมที่ https://www.zabbix.com/documentation/4.0/manual/config/macros)
- Host interface: IPADDR => คือหมายเลขไอพีของเครื่องที่จะต้องการตรวจสอบการทำงาน
- Type of information: Numeric (float) => คือชนิดข้อมูลที่จะต้องการเก็บ กำหนดเป็น Numeric (float) คือทศนิยม ที่เป็นได้ทั้งค่าบวก และลบ (พิจารณาสิ่งที่ script ได้ return ผลมา ณ ที่นี้คือ เวลา response time เป็นวินาที)
- Units: s => คือหน่วยของค่าที่ต้องการกำหนดของ Item นี้ เช่น ms คือ มิลลิวินาที
- Update interval: 300s => คือเวลาทุกๆ ที่ต้องการให้ตรวจสอบ Item นี้ เช่น 300s คือ 300 วินาที
- History storage period: 90d => คือระยะเวลาที่ให้ระบบเก็บค่าทุกๆ ครั้งที่มีการตรวจสอบ Item นี้ เช่น 90d คือ 90 วัน
- Trend storage period: 365d => คือระยะเวลาที่ให้ระบบเก็บค่าที่ถูกประมวลผลแล้วเช่น Min,Max,Avg
- Show value: As is => คือรูปแบบการเก็บค่า Item สำหรับ As is คือได้ค่ามายังไงก็เก็บค่านั้น
ทำความรู้จักกับ Macro
สำหรับ Macro ใน Zabbix นั้นคือตัวแปรที่สามารถเก็บค่าต่างๆ ได้ ซึ่ง macro นั้นมี 3 รุปแบบคือ
- System (supported) macro คือ macro ของระบบเช่น หมายเลขไอพีของ host นั้น
- Function macro คือการกำหนด macro ที่สามารถคำนวนค่าได้จาก function
- User marco คือ macro ที่สามารถกำหนดชื่อ และค่าได้จากผู้ใช้งาน ซึ่งจะมีระดับในการกำหนดค่า (Level) ดังต่อไปนี้
- Host level macro (ใช้งานก่อน)
- Macro ที่ถูกำหนดไว้ใน 1'st level templates ของ host (พิจารณาใช้งานตามลำดับ link template), จากนั้นเรียงการใช้งานตามหมายเลข ID ของ template
- Macros ที่ถูกำหนดไว้ใน 2'nd level templates ของ host, เรียงการใช้งานตามหมายเลข ID ของ template
- ที่ถูกำหนดไว้ใน 3'rd level templates ของ host, รียงการใช้งานตามหมายเลข ID ของ template
- Global macros (ใช้งานลำดับสุดท้าย)
Configuration -> Hosts -> [SELECTED HOST] -> Macro
โดยจะปรากฏหน้าจอ Create item โดยใส่ข้อมูลดังนี้
- Macro: QUERY => คือชื่อของ macro (ใส่ให้ตรงกับพารามิเตอร์)
- Value: SELECT COUNT(ID) FROM Product => คือค่าของ macro นั้น ยกตัวอย่างต้องการทดสอบการ query ฐานข้อมูล จึงทำการใส่ SQL เข้าไป
*** โดยปกติจะไม่มีการใส่รหัสผ่าน (Password) ใน macro เพราะจะทำให้เห็นรหัสผ่าน วิธีการที่แนะนำคือการทำ mapping ระหว่าง host และรหัสผ่านใน script แทน***
ทำความรู้จักกับ Zabbix External Script
Zabbix External Script คือการเขียน script เพื่อเติมเพื่อให้สามารถทำการ monitor ได้ตามที่ต้องการอย่างไม่มีขอบเขต เนื่องจากเป็นการเขียนโปรแกรมเพื่อทำการ monitor ซึ่งสามารถเขียนได้ทั้งจาก Shell Script, ภาษา Python, ภาษา Perl และภาษาอื่นๆ ที่เครื่อง Zabbix สามารถ execute ได้
ซึ่งความคิดเป็นส่วนตัว การใช้งาน external script นั้นมีแนวทางการใช้งานดังนี้
- ข้อมูล Input กำหนด (ได้มาจากการสั่งจาก Zabbix ที่มี macros)
- การดำเนินการใน Script (จะดูรายละเอียดในตัวอย่างโปรแกรม)
- ผลที่ได้จากการตรวจสอบ return value โดยมีตัวเลขในการส่งผลดังนี้
- ถ้า Return Response time ที่ได้จากการทำงาน ตั่งแต่ 0 เป็นในทางบวก นั่นหมายถึงระบบทำงานได้ (ขึ้นอยู่ว่าจะทำงานได้เร็วหรือช้า)
- ถ้า Return เป็น -1 จะหมายถึงระบบทำงานไม่ได้/มีปัญหา
สำหรับโปรแกรม script นั้น โดยปกติจะอยู่ที่ไดเร็กทอรี /etc/zabbix/externalscripts ซึ่งจากตัวอย่างการกำหนดใน Item ในส่วนของ Key มีรายละเอียดดังนี้
mssqlchk.py["{HOST.CONN}","{DBQUERY}","{DBUSER}","{DBPASS}","{DBNAME}"]
การ mapping parameters
- {HOST.CONN} <--> sys.argv[1]
- {DBQUERY} <--> sys.argv[2]
- {DBUSER} <--> sys.argv[3]
- {DBPASS} <--> sys.argv[4]
- {DBNAME} <--> sys.argv[5]
- สร้างไฟล์ script ชื่อ mssqlchk.py (**ต้องกำหนด permission ใน OS ให้ผู้ใช้งานชื่อ zabbix สามารถ execute ไฟล์นี้ได้, chown zabbix mssqlchk.py; chmod 500 mssqlchk.py **
และในไฟล์ สามารถเขียนโปรแกรมให้ทำงานได้ดังนี้ (ตัวอย่างภาษา Python)
#!/usr/bin/pythonimport pymssql #import module pymssql เพื่อ connection ไปยังฐานข้อมูล
import sys #import module sys เพื่อรับ parameter จาก command line
import time #import module time เพื่อใช้จับเวลาtry: #ขึ้นตอนคำสั่ง exceptstart = int(round(time.time() * 1000)) #ดูเวลาเริ่ม scriptdb = pymssql.connect(sys.argv[1], sys.argv[3], sys.argv[4], sys.argv[1]) #connection to databasecursor = db.cursor()sql = sys.argv[2] #คำสั่ง SQLcursor.execute(sql) #ทำการ Query databaseresults = cursor.fetchall()db.close()end = int(round(time.time() * 1000)) #ดูเวลาจบการรัน scripttime = end-start #ให้ time=end-start แล้วส่งเป็นค่า time เวลาหน่วยเป็น msprint int(time)except pymssql.OperationalError: #ถ้าเกิด Error หรือรัน Script ไม่ผ่านให้ส่ง -1print int(-1)
จากนั้นสามารถทดสอบค่าที่ได้จาก script โดยการตรวจสอบที่ Latest Data เมื่อการทำงานถูกต้องสามารถดำเนินการสร้าง Graph และ Trigger เพื่อใช้งานได้ต่อไป รวมไปถึงสามารถเขียนโปรแกรมดึงข้อมูลตัวเลขอื่นๆ ตามที่เซอร์วิสนั้นมีให้ดำเนินการได้
ตัวอย่าง script อื่นๆ
Script สำหรับตรวจสอบ Website
#!/usr/bin/python
import sys #import module sys เพื่อใช้รับข้อมูลจาก Parameter
import time #import module time เพื่อใช้นับเวลา
import urllib2 #import module urllib2 ใช้ในการเรียกค่าจากหน้าเว็บ
try: #ขึ้นตอนคำสั่ง except
s = int(round(time.time() * 1000)) #ดูเวลาเริ่ม script
url = sys.argv[1] #รับค่าจาก parameter ที่1 เข้าตัวแปร url
response = urllib2.urlopen(url) #เปิดหน้าเว็บ
webContent = response.read() #อ่านค่าจะหน้าเว็บ
e = int(round(time.time() * 1000)) #ดูเวลาจบการรัน script
find = webContent.find(sys.argv[2]) #ค้นหา คำตาม Key ที่ใส่มาใน Parameter ที่ 2
if find < 0: #ถ้าคนหาได้ น้อยกว่า 0 คำ ให้ส่งเป็น -1
t = -1
print int(t)
else: #นอกนั้นให้ t=e-s แล้วส่งเป็นค่า t เวลาหน่วยเป็น ms
t = e-s
print int(t)
except urllib2.URLError: #ถ้าเกิด Error หรือรัน Script ไม่ผ่านให้ส่ง -1
print int(-1)
Script สำหรับตรวจสอบ DNS
#!/usr/bin/python
import time #import module time เพื่อใช้นับเวลา
import sys
import socket #import module socket ตรวจสอบ DNS
start = int(round(time.time()*1000)) #ดูเวลาเริ่ม script
name = sys.argv[1] #ชื่อ DNS
try: #ขึ้นตอนคำสั่ง except
host = socket.gethostbyname(name) #ตรวจสอบชื่อ DNS ว่าเป็น IP อะไรถ้า DNS ทราบถือว่าใช้ได้
end = int(round(time.time()*1000)) #ดูเวลาจบ script
time = end-start #ให้ time=end-start แล้วส่งเป็นค่า time เวลาหน่วยเป็น ms
print int(time)
except socket.gaierror, err: #ถ้าไม่สามารถถาม IP จาก DNS ได้จะเกิด socket.gaierror จะส่งค่าเป็น -1
print int(-1)
Script สำหรับตรวจสอบ Oracle
#!/usr/bin/python
import cx_Oracle #import module cx_Oracle เพื่อ connection ไปหา database
import sys
import time #import module time เพื่อใช้นับเวลา
try: #ขึ้นตอนคำสั่ง except
start = int(round(time.time() * 1000)) #ดูเวลาเริ่ม script
connection = cx_Oracle.connect(sys.argv[1]) #connection string to database
cursor = connection.cursor()
sql = sys.argv[2] #คำสั่ง SQL
cursor.execute(sql) #ทำการ Query database
count = cursor.fetchall()
connection.close()
end = int(round(time.time() * 1000)) #ดูเวลาจบการรัน script
time = end-start #ให้ time=end-start แล้วส่งเป็นค่า time เวลาหน่วยเป็น ms
print int(time)
except pymssql.OperationalError: #ถ้าเกิด Error หรือรัน Script ไม่ผ่านให้ส่ง -1
print int(-1)
Script สำหรับตรวจสอบ LDAP
#!/usr/bin/perl
use Net::LDAP;
use Time::HiRes qw( gettimeofday tv_interval );
my $chkSuccess = 0;
my $ldapserver = "LDAPSERVERIP";
my $ldapversion = 2;
my $ldapuser = "cn=ldadm,o=test";
my $ldapbase = "uniqueId=lduser1,ou=local,deptName=it,o=users,o=TESTER";
my $ldappass = "pass123";
my $searchstring = "objectclass=*";
my @attribs = ('uniqueID');
sub LDAPsearch {
my ($ldap,$searchString,$attrs,$base) = @_;
my $result = $ldap->search ( base => "$base",
scope => "base",
filter => "$searchString",
attrs => $attrs
);
}
my $t0 = [gettimeofday];
my $ldap = Net::LDAP->new ($ldapserver) or die ("-1\n");
$ldap->bind ($ldapuser, password => $ldappass, version => $ldapversion) or die ("-1\n");
my $search = LDAPsearch ($ldap, $searchstring, \@attribs, $ldapbase ) or die ("-1\n");
my @entries = $search->entries;
my ($uniqueID);
if (@entries) {
$uniqueID=$entries[0]->get_value('uniqueID');
if ($uniqueID=="lduser1") {
$chkSuccess = 1;
} else {
$chkSuccess = 0;
} } else {
$chkSuccess = 0;
}
$ldap->unbind;
my $elapsed = tv_interval ($t0, [gettimeofday]);
if($chkSuccess==1) {
print $elapsed;
} else {
print "-1";
}
Script สำหรับตรวจสอบ TACACS
#!/usr/bin/perl
use Net::TacacsPlus::Client;
use Net::TacacsPlus::Constants;
use Time::HiRes qw( gettimeofday tv_interval );
my $chkSuccess = 0;
my $tac = new Net::TacacsPlus::Client(
host => 'TACACSSERVERIP',
key => 'tacacskey');
my $t0 = [gettimeofday];
if ($tac->authenticate('tuser1', 'tuser1passwd', TAC_PLUS_AUTHEN_TYPE_PAP)){
#print "Authentication successful.\n";
$chkSuccess = 1;
} else {
#print "Authentication failed: ".$tac->errmsg()."\n";
$chkSuccess = 0;
}
my @args = ('service=junos-exec', 'local-user-name=staff');
my @args_response;
if($tac->authorize('tuser1', \@args, \@args_response))
{
#print "Authorization successful.\n";
#print "Arguments received from server:\n";
#print join("\n", @args_response);
$chkSuccess = 1;
} else {
#print "Authorization failed: " . $tac->errmsg() . "\n";
$chkSuccess = 0;
}
my $elapsed = tv_interval ($t0, [gettimeofday]);
if($chkSuccess==1) {
print $elapsed;
} else {
print "-1";
}
Script สำหรับตรวจสอบ Radius
#!/usr/bin/perl
use Authen::Radius;
use Time::HiRes qw( gettimeofday tv_interval );
my $chkSuccess = 0;
my $host = "RADUISSERVERIP";
my $port = 1812;
my $username = "ruser1";
my $password = "ruser1passwd";
my $secret = "secret";
my $nas = "192.168.99.100";
my $timeout = 5;
my $t0 = [gettimeofday];
$host = "$host:$port" if $port;
if (!(my $radius = Authen::Radius->new(Host => $host, Secret => $secret, TimeOut => $timeout))){
my $err = "Could not connect to $host";
$err .= ": " . Authen::Radius::strerror if (Authen::Radius::strerror);
print $err, "\n";
$status = &set_status(0);
}else{
my $answer = $radius->check_pwd($username, $password, $nas);
$status = &set_status($answer);
print $status;
}
my $elapsed = tv_interval ($t0, [gettimeofday]);
if($chkSuccess==1) {
print $elapsed;
} else {
print "-1";
}
sub set_status {
my $res = shift;
my $status;
if ($res) {
$status = "OK";
}else {
$status = "CRITICAL";
}
print "$status\n";
return($status);
}