Managing a shared_mysql_farm_phpday2011
-
Upload
combell-group -
Category
Technology
-
view
1.345 -
download
2
description
Transcript of Managing a shared_mysql_farm_phpday2011
Managing a shared MySQL farmThijs FerynEvangelist+32 (0)9 218 79 [email protected]
phpDayFriday May 13th 2011Verona, Italy
About me
I’m an evangelist at Combell
About me
I’m a board member at PHPBenelux
I live in the wonderful city of Bruges
MPBecker -‐ Bruges by Night hYp://www.flickr.com/photos/galverson2/3715965933
Give me feedback: hYp://joind.in/3003
Read my blog: hYp://blog.feryn.eu
Follow me on TwiYer: @ThijsFeryn
Challenges
Challenges
✓Management across mulaple nodes✓Aggregaang data from mulaple nodes✓Name clashes✓Quota management
Soluaons
Soluaons
✓Centralized provisioning database✓GeYers on the provisioning database✓Node mapper for user/db/privilege management✓INFORMATION_SCHEMA for quota management✓Prefixes to avoid name clashes
Provisioning plan
User✓Id✓Prefix✓Username✓Password✓Enabled✓DatabaseId✓Write✓CreateDate✓UpdateDate
Database✓Id✓Node✓Prefix✓Database✓Quota✓Enabled✓Overquota✓CreateDate✓UpdateDate
Mapping uses cases to SQL
MySQL authenacaaon & privilege system
✓Add user✓Delete user✓Reset user password✓Disable user✓Enable user
Add user
INSERT INTO `user`(`prefix`,`username`,`password`,`createdate`) VALUES(‘test’,‘test_user’,‘mypass123’,NOW());
Delete user
DELETE FROM `user` WHERE username=‘test_user’;
DELETE u.*, db.* FROM `mysql`.`user` u LEFT JOIN `mysql`.`db` db ON(db.`User` = u.`User`) WHERE u.`User` = ‘test_user’;
Reset user password
UPDATE `user` SET `password` = ‘newpass123’ WHERE `username` = ‘test_user’;
UPDATE `mysql`.`user` SET `Password` = PASSWORD(‘newpass123’) WHERE `User`= ‘test_user’;
Disable user
UPDATE `user` SET `enabled` = '0' WHERE `username` = ‘test_user’;
UPDATE `mysql`.`user` SET `Host` = ‘localhost’ WHERE `User`= ‘test_user’
Enable user
UPDATE `user` SET `enabled` = '1' WHERE `username` = ‘test_user’;
UPDATE `mysql`.`user` SET `Host` = ‘%’ WHERE `User`= ‘test_user’
✓Add database✓Delete database✓Set database quota✓Disable database✓Enable database
Add database
INSERT INTO `database`(`node`,`prefix`,`database`,`quota`,`createdate`) VALUES(1,‘test’,‘test_db’,10,NOW());
CREATE DATABASE test_db1;
Delete database
DELETE FROM `database` WHERE `database` = ‘test_db’;
Delete database
SELECT u.usernameFROM `user` uWHERE u.databaseId = 123GROUP BY u.username; Find
deletable users to delete from MySQL privileges system
Are linked to this database
Delete database
DELETE u.*, db.* FROM `user` u LEFT JOIN `db` db ON(db.`User` = u.`User`) WHERE u.`User` IN('test_user’);
Deletethese users from MySQL privileges
system
Delete database
DROP DATABASE test_db;
Set database quota
UPDATE `database` SET `quota` = 100WHERE `database` = ‘test_db’;
Disable database
UPDATE `database` SET `enabled` = '0' WHERE `database` = ‘test_db’;
DELETE FROM `db` WHERE db = 'test_db’;
Enable database
UPDATE `database` SET `enabled` = '1' WHERE `database` = ‘test_db’;
Enable database
SELECT u.username, u.writeFROM user uWHERE u.databaseId = 123 Find
user mappings to re-‐enable
Enable database
INSERT INTO `db`(Host,Db,User,Select_priv,Insert_priv, Update_priv,Delete_priv,Create_priv,Drop_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Create_tmp_table_priv,Lock_tables_priv, Create_view_priv,Show_view_priv,Create_routine_priv, Alter_routine_priv,Execute_priv)
Enable database
VALUES(‘%’,‘test_db’,‘test_user’,'Y','Y','Y','Y','Y','Y','N','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
VALUES(‘%’,‘test_db’,‘test_user’,'Y','N','N','N','N','N','N','N','N','N','N','N','N','Y','N','N','Y');
Write permissions
Read-‐only
permissions
✓Grant privilege✓Revoke privilege✓Enable database wriang✓Disable database wriang
Grant privilege
UPDATE `user` SET `databaseId`=123, `write`='1' WHERE `username`= ‘test_user’;
UPDATE `user` SET `databaseId`=123, `write`='0' WHERE `username`= ‘test_user’;
Write permissions
Read-‐only
permissions
Grant privilege
INSERT INTO `user`(Host,User,Password) VALUES(‘%’,‘test_user’,PASSWORD(‘password’));
Try adding user or catch duplicate user error
Grant privilege
INSERT INTO `db`(Host,Db,User,Select_priv,Insert_priv, Update_priv,Delete_priv,Create_priv,Drop_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Create_tmp_table_priv,Lock_tables_priv, Create_view_priv,Show_view_priv,Create_routine_priv, Alter_routine_priv,Execute_priv)
Grant privilege
VALUES(‘%’,‘test_db’,‘test_user’,'Y','Y','Y','Y','Y','Y','N','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y');
VALUES(‘%’,‘test_db’,‘test_user’,'Y','N','N','N','N','N','N','N','N','N','N','N','N','Y','N','N','Y');
Write permissions
Read-‐only
permissions
Revoke privilege
UPDATE `user` SET `databaseId`= NULL, `write`= NULL WHERE `user`= ‘test_user’;
DELETE u.*, db.* FROM `user` u LEFT JOIN `db` db ON(db.`User` = u.`User`) WHERE u.`User` = ‘test_user’;
Enable database wriang
UPDATE `user` SET `write`= '1' WHERE `username` = ‘test_user’;
Enable database wriang
UPDATE `user` SET `write`= '1' WHERE `username` = ‘test_user’;
UPDATE `db` SET`Select_priv` = 'Y',`Insert_priv` = 'Y',`Update_priv` = 'Y',`Delete_priv` = 'Y',`Create_priv` = 'Y',`Drop_priv` = 'Y',`Grant_priv` = 'N',`References_priv` = 'Y',`Index_priv` = 'Y',`Alter_priv` = 'Y',`Create_tmp_table_priv`='Y',`Lock_tables_priv` = 'Y',`Create_view_priv` = 'Y',`Show_view_priv` = 'Y',`Create_routine_priv` = 'Y',`Alter_routine_priv` = 'Y',`Execute_priv` = 'Y'WHERE `db`= ‘test_db’ AND `user` = ‘test_user’;
Disable database wriang
UPDATE `user` SET `write`= '0' WHERE `username` = ‘test_user’;
Disable database wriang
UPDATE `user` SET `write`= '1' WHERE `username` = ‘test_user’;
UPDATE `db` SET`Select_priv` = 'Y',`Insert_priv` = 'N',`Update_priv` = 'N',`Delete_priv` = 'N',`Create_priv` = 'N',`Drop_priv` = 'N',`Grant_priv` = 'N',`References_priv` = 'N',`Index_priv` = 'N',`Alter_priv` = 'N',`Create_tmp_table_priv`='N',`Lock_tables_priv` = 'N',`Create_view_priv` = 'N',`Show_view_priv` = 'Y',`Create_routine_priv` = 'N',`Alter_routine_priv` = 'N',`Execute_priv` = 'Y'WHERE `db`= ‘test_db’ AND `user` = ‘test_user’;
Quota management
Quota management
✓Limits in provisioning database✓Current usage stored in INFORMATION_SCHEMA✓Raao calculated via cron task✓Write permissions disabled while over quota
Quota management
SELECT `database`,`quota` FROM `database`
SELECT TABLE_SCHEMA as `database`,ROUND(SUM(DATA_LENGTH + INDEX_LENGTH)/1048576,2) as `usage`FROM `information_schema`.`TABLES`GROUP BY TABLE_SCHEMA
Quota management
UPDATE `database` SET `overquota` = '1' WHERE `database` = ‘test_db’;
Quota management
UPDATE `db` SET`Select_priv` = 'Y',`Insert_priv` = 'N',`Update_priv` = 'N',`Delete_priv` = 'Y',`Create_priv` = 'N',`Drop_priv` = 'Y',`Grant_priv` = 'N',`References_priv` = 'N',`Index_priv` = 'N',`Alter_priv` = 'N',`Create_tmp_table_priv` = 'N',`Lock_tables_priv` = 'N',`Create_view_priv` = 'N',`Show_view_priv` = 'Y',`Create_routine_priv` = 'N',`Alter_routine_priv` = 'N',`Execute_priv` = 'Y' WHERE `db`= ‘test_database’
Quota management
UPDATE `database` SET `overquota` = '0' WHERE `database` = ‘test_db’;
Quota management
UPDATE `db` SET`Select_priv` = 'Y',`Insert_priv` = 'Y',`Update_priv` = 'Y',`Delete_priv` = 'Y',`Create_priv` = 'Y',`Drop_priv` = 'Y',`Grant_priv` = 'N',`References_priv` = 'Y',`Index_priv` = 'Y',`Alter_priv` = 'Y',`Create_tmp_table_priv`='Y',`Lock_tables_priv` = 'Y',`Create_view_priv` = 'Y',`Show_view_priv` = 'Y',`Create_routine_priv` = 'Y',`Alter_routine_priv` = 'Y',`Execute_priv` = 'Y' WHERE `db`= ‘test_db’
Goals
Single point of management
Single point of connecaon
Replicaaon & loadbalancing
Proxying strategies
Server proxy
Goal
Goal
✓ Accept connecaon using the proxy✓Hook into the authenacaaon✓Match user to the provisioning DB✓Fetch node from provisioning✓Switch to the right node
➡Effecave proxying soluaon
Reality
Reality
✓ Accept connecaon using the proxy✓Hook into the authenacaaon✓Match user to the provisioning DB✓Fetch node from provisioning✓Switch to the right node
➡Effecave proxying soluaon
Reality
Connecaon switching only happens in the connect_server hook
Auth info is only available starang from the read_auth hook
Bridge the gap
Redirect to node based on client IP
Let’s see some code !
Coderequire('luarocks.require')require('md5')require('Memcached')require('luasql.mysql')local memcache = Memcached.Connect()-‐-‐-‐ configlocal mysqlhost = "localhost"local mysqluser = "myUser"local mysqlpassword = "MyPwDsesd"local mysqldatabase = "test"-‐-‐ debuglocal debug = true
Dependencies & config
Code
function error_result (msg) proxy.response = { type = proxy.MYSQLD_PACKET_ERR, errmsg = msg, errcode = 7777, sqlstate = 'X7777', } return proxy.PROXY_SEND_RESULTend
Custom MySQL errors
Codefunction node_get(ip) local node = memcache:get(md5.sumhexa(ip)) if not node == nil then return loadstring('return '..memcache:get(md5.sumhexa(ip)))() end node = sql_get(ip) if node == nil then return nil end
memcache:set(md5.sumhexa(ip), node, 3600) return node
end
Get node from cache or database
Codefunction sql_get(ip) env = assert (luasql.mysql()) con = assert (env:connect(mysqldatabase,mysqluser,mysqlpassword,mysqlhost)) cur = assert (con:execute(string.format("SELECT n.`id` FROM `accesslist` a JOIN `node` n ON(n.id=a.node) WHERE a.`ip` = '%s'",ip))) row = cur:fetch ({}, "a") if cur:numrows() == 0 then return nil end cur:close() con:close() env:close() return row.idend
Get node from provisioning database
Code
function connect_server() selectedNode = node_get(proxy.connection.client.src.address)
if selectedNode == nil then return error_result(string.format("No info found in the cluster for IP '%s'",proxy.connection.client.src.address)) end
proxy.connection.backend_ndx = selectedNode end
Retrieve and switch to node
Reality
MySQL Proxy is not acavely supported
Client proxy
MySQL Naave Driver Plugins
MySQL Naave Driver Plugins
✓ Accept connecaon using the proxy✓Hook into the authenacaaon✓Match user to the provisioning DB✓Fetch node from provisioning✓Switch to the right node✓Doesn’t work for remote connecaons
➡Effecave proxying soluaon
DNS & hostnames
Hostname per account
What about PhpMyAdmin?
What about PhpMyAdmin?
✓Use single signon auth module✓Use customized fallback auth module✓Detect linked database & node✓Switch to node
config.inc.php
<?php$cfg['Servers'][1]['auth_type'] = 'httpsoap';$cfg['Servers'][1]['host'] = '1.2.3.4';$cfg['Servers'][1]['connect_type'] = 'tcp';$cfg['Servers'][1]['compress'] = false;$cfg['Servers'][1]['extension'] = 'mysql';$cfg['Servers'][1]['AllowNoPassword'] = false;$cfg['Servers'][2]['auth_type'] = 'httpsoap';$cfg['Servers'][2]['host'] = '1.2.3.5';$cfg['Servers'][2]['connect_type'] = 'tcp';$cfg['Servers'][2]['compress'] = false;$cfg['Servers'][2]['extension'] = 'mysql';$cfg['Servers'][2]['AllowNoPassword'] = false;$cfg['Servers'][3]['extension'] = 'mysql';$cfg['Servers'][3]['auth_type'] = 'signon';$cfg['Servers'][3]['SignonSession'] = 'SSOSession';$cfg['Servers'][3]['SignonURL'] = 'scripts/signon.php';$cfg['Servers'][3]['LogoutURL'] = 'scripts/signon-logout.php';
scripts/signon.php
<?phpif (isset($_REQUEST['user'])) { try{ $soap = new SoapClient('http://my.soap-webservice.net/?WSDL'); $user = $soap->user_getByUsername($_REQUEST['user']); if(!isset($_REQUEST['hash'])){ die("No hash submitted"); } if(sha1($user->username.$user->password.'azertyuiop') !== $_REQUEST['hash']){ die("Invalid hash"); } } catch (Exception $e){ die("No such user"); }...
scripts/signon.php
session_set_cookie_params(0, '/', '', 0); $session_name = 'SSOSession'; session_name($session_name); session_start(); $_SESSION['PMA_single_signon_user'] = $user->username; $_SESSION['PMA_single_signon_password'] = $user->password; $_SESSION['PMA_single_signon_host'] = $user->node; $_SESSION['PMA_single_signon_port'] = '3306'; $id = session_id(); session_write_close(); header('Location: ../index.php?server=3');} else { header('Location: ../index.php?server=1'); exit();}
scripts/signon-‐logout.php
<?phpsession_set_cookie_params(0, '/', '', 0);$session_name = 'SSOSession';session_name($session_name);session_start();session_destroy();header('Location: ../index.php?server=1');
Customized fallback auth module
✓Copy of ./libraries/auth/h4p.auth.lib.php✓Modify PMA_auth_set_user() funcaon✓Implement detecaon logic✓Communicates with provisioning service✓Retrieves database & node✓Switches to node
libraries/auth/hYpsoap.auth.lib.php<?phpfunction PMA_auth_set_user(){ global $cfg, $server; global $PHP_AUTH_USER, $PHP_AUTH_PW; try{ $soap = new SoapClient('http://my.soap-webservice.net/?WSDL'); $user = $soap->user_getByUsername($PHP_AUTH_USER); $cfg['Server']['host'] = $user->node; } catch (Exception $e){ PMA_auth(); return true; }...
libraries/auth/hYpsoap.auth.lib.phpif ($cfg['Server']['user'] != $PHP_AUTH_USER) { $servers_cnt = count($cfg['Servers']); for ($i = 1; $i <= $servers_cnt; $i++) { if (isset($cfg['Servers'][$i]) && ($cfg['Servers'][$i]['host'] == $cfg['Server']['host'] && $cfg['Servers'][$i]['user'] == $PHP_AUTH_USER)) { $server = $i; $cfg['Server'] = $cfg['Servers'][$i]; break; } } } $cfg['Server']['user'] = $PHP_AUTH_USER; $cfg['Server']['password'] = $PHP_AUTH_PW; return true;}
Q&A