managing a shared_mysql_farm_phpday2011

Post on 04-Dec-2014

1.345 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

Managing  a  shared  MySQL  farmThijs  FerynEvangelist+32  (0)9  218  79  06thijs@combellgroup.com

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

top related