From bc4875e670d9e7ea33e33f860ac44c3f376da6a1 Mon Sep 17 00:00:00 2001 From: Aidaho12 Date: Fri, 27 Apr 2018 19:34:11 +0600 Subject: [PATCH] v2.1 Creating HA cluster --- README.md | 2 + cgi-bin/add.py | 14 ++--- cgi-bin/funct.py | 16 +++--- cgi-bin/ha.py | 55 ++++++++++++++++++++ cgi-bin/haproxy-webintarface.config | 2 +- cgi-bin/options.py | 37 +++++++++++-- cgi-bin/scripts/install_keepalived.sh | 75 +++++++++++++++++++++++++++ cgi-bin/sql.py | 24 ++++++++- cgi-bin/viewlogs.py | 14 ++--- cgi-bin/viewsttats.py | 2 +- inc/awesome.css | 10 ++++ inc/users.js | 45 ++++++++++++++-- install.sh | 1 + 13 files changed, 267 insertions(+), 30 deletions(-) create mode 100644 cgi-bin/ha.py create mode 100644 cgi-bin/scripts/install_keepalived.sh diff --git a/README.md b/README.md index c2e1ca41..90cc32e0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ A simple web interface(user-frendly web GUI) for managing Haproxy servers. Leave 13. Users roles: admin, editor, viewer 14. Server groups 15. Telegram notification +16. Creating HA HAProxy cluster # Install The installer will ask you a few questions @@ -40,6 +41,7 @@ $ chmod +x haproxy-wi/cgi-bin/*.py For Apache do virtualhost with cgi-bin. Like this: ``` +# vi /etc/httpd/conf.d/haproxy-wi.conf ServerName haproxy-wi ErrorLog /var/log/httpd/haproxy-wi.error.log diff --git a/cgi-bin/add.py b/cgi-bin/add.py index 9cfa11b8..3460eb57 100644 --- a/cgi-bin/add.py +++ b/cgi-bin/add.py @@ -146,7 +146,7 @@ for i in listhap: print('' % (i[2], i[1])) print('' - '
Note: If you reconfigure First server, second will reconfigured automatically
' + '
Note: If you reconfigure Master server, Slave will reconfigured automatically
' '' '' '' @@ -159,8 +159,8 @@ print('' 'IP and Port:' '' ':' - '' - '
IP for bind listner, if empty will be assignet on all IPs. Start typing ip, or press down.
' + '' + '
IP for bind listner, if empty will be assignet on all IPs. Start typing ip, or press down.
If you use VRRP keep in blank. If you assign an IP, the slave will not start
' '' '' '' @@ -266,7 +266,7 @@ for i in listhap: print('' % (i[2], i[1])) print('' - '
Note: If you reconfigure First server, second will reconfigured automatically
' + '
Note: If you reconfigure Master server, Slave will reconfigured automatically
' '' '' '' @@ -279,8 +279,8 @@ print('' 'IP and Port:' '' ':' - '' - '
IP for bind listner, if empty will be assignet on all IPs. Start typing ip, or press down.
' + '' + '
IP for bind listner, if empty will be assignet on all IPs. Start typing ip, or press down.
If you use VRRP keep in blank. If you assign an IP, the slave will not start
' '' '' '' @@ -350,7 +350,7 @@ for i in listhap: print('' % (i[2], i[1])) print('' - '
Note: If you reconfigure First server, second will reconfigured automatically
' + '
Note: If you reconfigure Master server, Slave will reconfigured automatically
' '' '' '' diff --git a/cgi-bin/funct.py b/cgi-bin/funct.py index af766769..a2a119da 100644 --- a/cgi-bin/funct.py +++ b/cgi-bin/funct.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*-" import cgi import os import paramiko @@ -15,7 +16,6 @@ config.read(path_config) form = cgi.FieldStorage() serv = form.getvalue('serv') fullpath = config.get('main', 'fullpath') -log_path = config.get('main', 'log_path') time_zone = config.get('main', 'time_zone') ssh_keys = config.get('ssh', 'ssh_keys') ssh_user_name = config.get('ssh', 'ssh_user_name') @@ -45,7 +45,9 @@ def logging(serv, action): cookie = http.cookies.SimpleCookie(os.environ.get("HTTP_COOKIE")) login = cookie.get('login') mess = now_utc.strftime(dateFormat) + " from " + IP + " user: " + login.value + " " + action + " for: " + serv + "\n" - try: + log_path = config.get('main', 'log_path') + + try: log = open(log_path + "/config_edit-"+get_data('logs')+".log", "a") log.write(mess) log.close @@ -180,6 +182,8 @@ def links(): '
  • SSL
  • ' '
  • Edit
  • ') print('') + if is_admin(): + print('
  • HA') if is_admin(level = 2): print('
  • Versions' '
  • Upload
  • ') @@ -189,7 +193,7 @@ def links(): print('') show_login_links() if is_admin(): - print('
  • Admin area' + print('
  • Admin area' '
  • Users
  • ' '
  • Groups
  • ' '
  • Servers
  • ' @@ -199,7 +203,7 @@ def links(): '') print('' '' - '' + '' '') def show_login_links(): @@ -534,7 +538,7 @@ def ssh_command(serv, commands, **kwargs): else: print('
    '+stdout.read().decode(encoding='UTF-8')+'
    ') - print(stderr.read().decode(encoding='UTF-8')) + print(stderr.read().decode(encoding='UTF-8')+"
    ") ssh.close() @@ -578,6 +582,6 @@ def chooseServer(formName, title, note, **kwargs): print('

    ') if note == "y": - print('
    Note: If you reconfigure First server, second will reconfigured automatically
    ') + print('
    Note: If you reconfigure Master server, Slave will reconfigured automatically
    ') print('') diff --git a/cgi-bin/ha.py b/cgi-bin/ha.py new file mode 100644 index 00000000..0f9396e0 --- /dev/null +++ b/cgi-bin/ha.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import html +import cgi +import funct +import sql +from configparser import ConfigParser, ExtendedInterpolation + +funct.head("HA") +funct.check_login() +funct.page_for_admin() + +path_config = "haproxy-webintarface.config" +config = ConfigParser(interpolation=ExtendedInterpolation()) +config.read(path_config) +serv = "" + +print('' + '

    Configure HA

    ' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
    MasterSlaveVRRP interfaceVRRP IPInstall HAProxy
    ' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + 'Create' + '
    ' + '
    ') + diff --git a/cgi-bin/haproxy-webintarface.config b/cgi-bin/haproxy-webintarface.config index a9046d87..c0272248 100644 --- a/cgi-bin/haproxy-webintarface.config +++ b/cgi-bin/haproxy-webintarface.config @@ -58,7 +58,7 @@ haproxy_config_path = ${haproxy_dir}/haproxy.cfg server_state_file = ${haproxy_dir}/haproxy.state haproxy_sock = /var/run/haproxy.sock #Temp store configs, for haproxy check -tmp_config_path = /tmp +tmp_config_path = /tmp/ cert_path = /etc/ssl/certs/ #If enable this option Haproxy-wi will be configure firewalld based on config port firewall_enable = 1 \ No newline at end of file diff --git a/cgi-bin/options.py b/cgi-bin/options.py index aa987bfe..ae154856 100644 --- a/cgi-bin/options.py +++ b/cgi-bin/options.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 +# -*- coding: utf-8 -*-" import html import cgi -import os +import os, sys import json import subprocess import funct @@ -202,11 +203,16 @@ if serv is not None and act == "configShow": if form.getvalue('viewlogs') is not None: viewlog = form.getvalue('viewlogs') - log_path = config.get('main', 'log_path') + try: + log_path = config.get('main', 'log_path') + except: + print('
    Please check the config for the presence of the parameter - "log_path".
    ') + try: log = open(log_path + viewlog, "r") except IOError: - print('
    Can\'t read import config file
    ') + print('
    Can\'t read import log file
    ') + sys.exit() print('

    Shows log: %s


    ' % viewlog) i = 0 @@ -216,4 +222,27 @@ if form.getvalue('viewlogs') is not None: print('
    ' + line + '
    ') else: print('
    ' + line + '
    ') - \ No newline at end of file + +if form.getvalue('master'): + master = form.getvalue('master') + slave = form.getvalue('slave') + interface = form.getvalue('interface') + vrrpip = form.getvalue('vrrpip') + hap = form.getvalue('hap') + tmp_config_path = config.get('haproxy', 'tmp_config_path') + script = "install_keepalived.sh" + + os.system("cp scripts/%s ." % script) + + + funct.upload(master, tmp_config_path, script) + funct.upload(slave, tmp_config_path, script) + + commands = [ "chmod +x "+tmp_config_path+script, tmp_config_path+script+" MASTER "+interface+" "+vrrpip+" "+hap ] + funct.ssh_command(master, commands) + + commands = [ "chmod +x "+tmp_config_path+script, tmp_config_path+script+" BACKUP "+interface+" "+vrrpip+" "+hap ] + funct.ssh_command(slave, commands) + + os.system("rm -f %s" % script) + sql.update_server_master(master, slave) \ No newline at end of file diff --git a/cgi-bin/scripts/install_keepalived.sh b/cgi-bin/scripts/install_keepalived.sh new file mode 100644 index 00000000..a9bdccb5 --- /dev/null +++ b/cgi-bin/scripts/install_keepalived.sh @@ -0,0 +1,75 @@ +#!/bin/bash +CONF=/etc/keepalived/keepalived.conf + +if [[ $4 == 1 ]];then + yum install haproxy -y > /dev/null + systemctl enable haproxy + systemctl restart haproxy +fi + +yum install keepalived -y > /dev/null +if [ $? -eq 1 ] +then + exit 1 +fi +echo "" > $CONF + +cat << EOF > $CONF +global_defs { + router_id LVS_DEVEL +} + +#health-check for keepalive +vrrp_script chk_haproxy { # Requires keepalived-1.1.13 + script "pidof haproxy" + interval 2 # check every 2 seconds + weight 3 # addA 3 points of prio if OK +} + +vrrp_instance VI_1 { + state MASTER + interface eth0 + virtual_router_id 100 + priority 102 + + #check if we are still running + track_script { + chk_haproxy + } + + advert_int 1 + authentication { + auth_type PASS + auth_pass VerySecretPass + } + virtual_ipaddress { + 0.0.0.0 + } +} +EOF +if [ $? -eq 1 ] +then + echo "Can't read keepalived config" + exit 1 +fi +sed -i "s/MASTER/$1/g" $CONF +sed -i "s/eth0/$2/g" $CONF +sed -i "s/0.0.0.0/$3/g" $CONF + +if [[ $1 == "BACKUP" ]];then + sed -i "s/102/103/g" $CONF +fi + +systemctl enable keepalived +systemctl restart keepalived +echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf +sysctl -p +firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 --in-interface enp0s8 --destination 224.0.0.18 --protocol vrrp -j ACCEPT +firewall-cmd --direct --permanent --add-rule ipv4 filter OUTPUT 0 --out-interface enp0s8 --destination 224.0.0.18 --protocol vrrp -j ACCEPT +firewall-cmd --reload + +if [ $? -eq 1 ] +then + echo "Can't start keepalived" + exit 1 +fi \ No newline at end of file diff --git a/cgi-bin/sql.py b/cgi-bin/sql.py index e1fbf47c..ea8cce79 100644 --- a/cgi-bin/sql.py +++ b/cgi-bin/sql.py @@ -161,6 +161,26 @@ def update_server(hostname, ip, group, typeip, enable, master, id): con.rollback() cur.close() con.close() + +def update_server_master(master, slave): + con, cur = create_db.get_cur() + sql = """ select id from servers where ip = '%s' """ % master + try: + cur.execute(sql) + con.commit() + except sqltool.Error as e: + print('An error occurred: ' + e.args[0] + ' X') + con.rollback() + for id in cur.fetchall(): + sql = """ update servers set master = '%s' where ip = '%s' """ % (id[0], slave) + try: + cur.execute(sql) + con.commit() + except sqltool.Error as e: + print('An error occurred: ' + e.args[0] + ' X') + con.rollback() + cur.close() + con.close() def select_users(**kwargs): con, cur = create_db.get_cur() @@ -301,9 +321,11 @@ def get_dick_permit(**kwargs): cur.close() con.close() -def is_master(ip): +def is_master(ip, **kwargs): con, cur = create_db.get_cur() sql = """ select slave.ip from servers left join servers as slave on servers.id = slave.master where servers.ip = '%s' """ % ip + if kwargs.get('master_slave'): + sql = """ select master.hostname, master.ip, slave.hostname, slave.ip from servers as master left join servers as slave on master.id = slave.master where slave.master > 0 """ try: cur.execute(sql) except sqltool.Error as e: diff --git a/cgi-bin/viewlogs.py b/cgi-bin/viewlogs.py index 88b73820..bf20d815 100644 --- a/cgi-bin/viewlogs.py +++ b/cgi-bin/viewlogs.py @@ -10,17 +10,19 @@ form = cgi.FieldStorage() viewlog = form.getvalue('viewlogs') funct.head("View logs") -funct.check_config() funct.check_login() +funct.page_for_admin() +funct.get_auto_refresh("View logs") path_config = "haproxy-webintarface.config" config = ConfigParser(interpolation=ExtendedInterpolation()) -config.read(path_config) +config.read(path_config) -log_path = config.get('main', 'log_path') - -funct.page_for_admin() -funct.get_auto_refresh("View logs") +try: + if config.get('main', 'log_path'): + log_path = config.get('main', 'log_path') +except: + print('
    Can not find "log_path" parametr. Check into config
    ') try: os.chdir(log_path) except IOError: diff --git a/cgi-bin/viewsttats.py b/cgi-bin/viewsttats.py index 81b88842..42ab7165 100644 --- a/cgi-bin/viewsttats.py +++ b/cgi-bin/viewsttats.py @@ -27,7 +27,7 @@ print('
    ' '' 'Show' diff --git a/inc/awesome.css b/inc/awesome.css index 90e8493b..f9dbc11f 100644 --- a/inc/awesome.css +++ b/inc/awesome.css @@ -66,6 +66,11 @@ font-family: "Font Awesome 5 Solid"; content: "\f044"; } +.ha::before { + display: none; + font-family: "Font Awesome 5 Solid"; + content: "\f0c2"; +} .version::before { display: none; font-family: "Font Awesome 5 Solid"; @@ -91,6 +96,11 @@ font-family: "Font Awesome 5 Solid"; content: "\f01e"; } +.admin::before { + display: none; + font-family: "Font Awesome 5 Solid"; + content: "\f21b"; +} .users::before { display: none; font-family: "Font Awesome 5 Solid"; diff --git a/inc/users.js b/inc/users.js index b10bcef6..a661a602 100644 --- a/inc/users.js +++ b/inc/users.js @@ -15,6 +15,43 @@ jQuery.expr[':'].regex = function(elem, index, match) { } $( function() { + $('#create').click(function() { + var ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + var hap = 0; + if ($('#hap').is(':checked')) { + hap = '1'; + } + $("#ajax").html('') + if( $("#master").val() == "" || $("#slave").val() == "" || $("#interface").val() == "" || + $("#vrrp-ip").val() == "") { + $("#ajax").html('
    Please fill in all fields
    ') + } else if(! $("#vrrp-ip").val().match(ipformat)) { + $("#ajax").html('
    Please enter IP in "VRRP IP" field
    ') + } else { + $("#ajax").html('
    Please don\'t close and don\'t represh page. Wait until the work is completed. This may take some time
    '); + $.ajax( { + url: "options.py", + data: { + master: $('#master').val(), + slave: $('#slave').val(), + interface: $("#interface").val(), + vrrpip: $('#vrrp-ip').val(), + hap: hap + }, + type: "GET", + success: function( data ) { + data = data.replace(/\s+/g,' '); + if (data.indexOf('error') != '-1' || data.indexOf('alert') != '-1' || data.indexOf('Failed') != '-1') { + $("#ajax").html('
    '+data+''); + } else if (data.indexOf('success') != '-1' ){ + $('.alert-danger').remove(); + $("#ajax").html('
    All is ready!'); + } + } + } ); + } + }); + $('.alert-danger').remove(); $('#add-user').click(function() { @@ -316,11 +353,11 @@ function uploadSsh() { $("#ajax-ssh").html(data); } else if (data.indexOf('success') != '-1') { $('.alert-danger').remove(); - $("#ssh").addClass( "update", 1000 ); - setTimeout(function() { - $( "#ssh").removeClass( "update" ); - }, 2500 ); $("#ajax-ssh").html(data); + setTimeout(function() { + $( "#ajax-ssh").html( "" ); + }, 2500 ); + } else { $("#ajax-ssh").html('
    Something wrong, check and try again
    '); } diff --git a/install.sh b/install.sh index 4b399111..452e5b07 100644 --- a/install.sh +++ b/install.sh @@ -284,6 +284,7 @@ firewall-cmd --reload chmod +x /var/www/$HOME_HAPROXY_WI/cgi-bin/*.py chown -R apache:apache /var/www/$HOME_HAPROXY_WI/ +rm -f /var/www/$HOME_HAPROXY_WI/log/config_edit.log setenforce 0 sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config