Creating HA cluster
pull/19/head
Aidaho12 2018-04-27 19:34:11 +06:00
parent 97f5781313
commit bc4875e670
13 changed files with 267 additions and 30 deletions

View File

@ -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
<VirtualHost *:8080>
ServerName haproxy-wi
ErrorLog /var/log/httpd/haproxy-wi.error.log

View File

@ -146,7 +146,7 @@ for i in listhap:
print('<option value="%s">%s</option>' % (i[2], i[1]))
print('</select>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure First server, second will reconfigured automatically</div>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure Master server, Slave will reconfigured automatically</div>'
'</td>'
'</tr>'
'<tr>'
@ -159,8 +159,8 @@ print('</select>'
'<td class="addName">IP and Port:</td>'
'<td class="addOption">'
'<input type="text" name="ip" id="ip" title="" size="15" placeholder="172.28.0.1" class="form-control"><b>:</b>'
'<input type="number" name="port" id="listen-port" required title="Port for bind listner" size="5" placeholder="8080" class="form-control">'
'<div class="tooltip tooltipTop">IP for bind listner, <b>if empty will be assignet on all IPs</b>. Start typing ip, or press down.</div>'
'<input type="number" name="port" id="listen-port" required title="Port for bind listen" size="5" placeholder="8080" class="form-control">'
'<div class="tooltip tooltipTop">IP for bind listner, <b>if empty will be assignet on all IPs</b>. Start typing ip, or press down.<br>If you use <b>VRRP keep in blank</b>. If you assign an IP, the slave will not start</div>'
'</td>'
'</tr>'
'<tr>'
@ -266,7 +266,7 @@ for i in listhap:
print('<option value="%s">%s</option>' % (i[2], i[1]))
print('</select>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure First server, second will reconfigured automatically</div>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure Master server, Slave will reconfigured automatically</div>'
'</td>'
'</tr>'
'<tr>'
@ -279,8 +279,8 @@ print('</select>'
'<td class="addName">IP and Port:</td>'
'<td class="addOption">'
'<input type="text" name="ip" id="ip1" size="15" placeholder="172.28.0.1" class="form-control"><b>:</b>'
'<input type="number" name="port" required title="Port for bind listner" placeholder="8080" class="form-control">'
'<div class="tooltip tooltipTop">IP for bind listner, <b>if empty will be assignet on all IPs</b>. Start typing ip, or press down.</div>'
'<input type="number" name="port" required title="Port for bind frontend" placeholder="8080" class="form-control">'
'<div class="tooltip tooltipTop">IP for bind listner, <b>if empty will be assignet on all IPs</b>. Start typing ip, or press down.<br>If you use <b>VRRP keep in blank</b>. If you assign an IP, the slave will not start</div>'
'</td>'
'</tr>'
'<tr>'
@ -350,7 +350,7 @@ for i in listhap:
print('<option value="%s">%s</option>' % (i[2], i[1]))
print('</select>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure First server, second will reconfigured automatically</div>'
'<div class="tooltip tooltipTop"><b>Note:</b> If you reconfigure Master server, Slave will reconfigured automatically</div>'
'</td>'
'</tr>'
'<tr>'

View File

@ -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,6 +45,8 @@ 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"
log_path = config.get('main', 'log_path')
try:
log = open(log_path + "/config_edit-"+get_data('logs')+".log", "a")
log.write(mess)
@ -180,6 +182,8 @@ def links():
'<li><a href=/cgi-bin/add.py#ssl title="Upload SSL cert" class="cert head-submenu">SSL</a></li>'
'<li><a href=/cgi-bin/config.py title="Edit Config" class="edit head-submenu">Edit</a> </li>')
print('</li>')
if is_admin():
print('<li><a title="Create HA cluster" class="ha">HA</a>')
if is_admin(level = 2):
print('<li><a title="Actions with configs" class="version">Versions</a>'
'<li><a href=/cgi-bin/configver.py title="Upload old versions configs" class="upload head-submenu">Upload</a></li>')
@ -189,7 +193,7 @@ def links():
print('</li>')
show_login_links()
if is_admin():
print('<li><a title="Admin area" class="version">Admin area</a>'
print('<li><a title="Admin area" class="admin">Admin area</a>'
'<li><a href=/cgi-bin/users.py#users title="Actions with users" class="users head-submenu">Users</a></li>'
'<li><a href=/cgi-bin/users.py#groups title="Actions with groups" class="group head-submenu">Groups</a></li>'
'<li><a href=/cgi-bin/users.py#servers title="Actions with servers" class="runtime head-submenu">Servers</a></li>'
@ -199,7 +203,7 @@ def links():
'</li>')
print('</ul>'
'</nav>'
'<div class="copyright-menu">HAproxy-WI v2.0.8</div>'
'<div class="copyright-menu">HAproxy-WI v2.1</div>'
'</div>')
def show_login_links():
@ -534,7 +538,7 @@ def ssh_command(serv, commands, **kwargs):
else:
print('<div style="margin: -10px;">'+stdout.read().decode(encoding='UTF-8')+'</div>')
print(stderr.read().decode(encoding='UTF-8'))
print(stderr.read().decode(encoding='UTF-8')+"<br/>")
ssh.close()
@ -578,6 +582,6 @@ def chooseServer(formName, title, note, **kwargs):
print('</p></form>')
if note == "y":
print('<div class="alert alert-info"><b>Note:</b> If you reconfigure First server, second will reconfigured automatically</div>')
print('<div class="alert alert-info"><b>Note:</b> If you reconfigure Master server, Slave will reconfigured automatically</div>')
print('</center>')

55
cgi-bin/ha.py Normal file
View File

@ -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('<script src="/inc/users.js"></script>'
'<h2>Configure HA</h2>'
'<table class="overview">'
'<tr class="overviewHead">'
'<td class="padding10 first-collumn">Master</td>'
'<td>Slave</td>'
'<td>VRRP interface</td>'
'<td>VRRP IP</td>'
'<td>Install HAProxy</td>'
'<td></td>'
'</tr>'
'<tr>'
'<td class="padding10 first-collumn">'
'<select id="master">'
'<option disable selected>Choose master</option>')
funct.choose_only_select(serv)
print('</select>'
'</td>'
'<td>'
'<select id="slave">'
'<option disable selected>Choose master</option>')
funct.choose_only_select(serv)
print('</select>'
'</td>'
'<td>'
'<input type="text" id="interface" class="form-control">'
'</td>'
'<td>'
'<input type="text" id="vrrp-ip" class="form-control">'
'</td>'
'<td>'
'<label for="hap"></label><input type="checkbox" checked id="hap">'
'</td>'
'<td>'
'<a class="ui-button ui-widget ui-corner-all" id="create" title="Create HA configuration">Create</a>'
'</td>'
'</table>'
'<div id="ajax"></div>')

View File

@ -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

View File

@ -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')
try:
log_path = config.get('main', 'log_path')
except:
print('<div class="alert alert-warning">Please check the config for the presence of the parameter - "log_path". </div>')
try:
log = open(log_path + viewlog, "r")
except IOError:
print('<div class="alert alert-danger">Can\'t read import config file</div>')
print('<div class="alert alert-danger">Can\'t read import log file</div>')
sys.exit()
print('<center><h3>Shows log: %s</h3></center><br />' % viewlog)
i = 0
@ -217,3 +223,26 @@ if form.getvalue('viewlogs') is not None:
else:
print('<div class="line">' + line + '</div>')
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)

View File

@ -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

View File

@ -162,6 +162,26 @@ def update_server(hostname, ip, group, typeip, enable, master, id):
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('<span class="alert alert-danger" id="error">An error occurred: ' + e.args[0] + ' <a title="Close" id="errorMess"><b>X</b></a></span>')
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('<span class="alert alert-danger" id="error">An error occurred: ' + e.args[0] + ' <a title="Close" id="errorMess"><b>X</b></a></span>')
con.rollback()
cur.close()
con.close()
def select_users(**kwargs):
con, cur = create_db.get_cur()
sql = """select * from user ORDER BY id"""
@ -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:

View File

@ -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)
try:
if config.get('main', 'log_path'):
log_path = config.get('main', 'log_path')
funct.page_for_admin()
funct.get_auto_refresh("View logs")
except:
print('<center><div class="alert alert-danger">Can not find "log_path" parametr. Check into config</div>')
try:
os.chdir(log_path)
except IOError:

View File

@ -27,7 +27,7 @@ print('<br />'
'<select autofocus required name="serv" id="serv">'
'<option disabled>Choose server</option>')
funct.choose_only_select(serv, virt=1)
funct.choose_only_select(serv, master_slave=1)
print('</select>'
'<a class="ui-button ui-widget ui-corner-all" id="show" title="Show stats" onclick="showStats()">Show</a>'

View File

@ -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";

View File

@ -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('<div class="alert alert-danger">Please fill in all fields</div>')
} else if(! $("#vrrp-ip").val().match(ipformat)) {
$("#ajax").html('<div class="alert alert-danger">Please enter IP in "VRRP IP" field</div>')
} else {
$("#ajax").html('<div class="alert alert-warning">Please don\'t close and don\'t represh page. Wait until the work is completed. This may take some time </div>');
$.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('<div class="alert alert-danger">'+data+'</data>');
} else if (data.indexOf('success') != '-1' ){
$('.alert-danger').remove();
$("#ajax").html('<div class="alert alert-success">All is ready!</data>');
}
}
} );
}
});
$('.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('<div class="alert alert-danger">Something wrong, check and try again</div>');
}

View File

@ -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