18.13 页面自动化安装LAMP环境
LAMP环境是Linux+Apache+MySQL+PHP的经典组合,常用来搭建动态网站或者服务器的开源软件。它们本身都是各自独立的程序,但是因为常被放在一起使用,拥有了越来越高的兼容度,因而共同组成了一个强大的Web应用程序平台。随着开源潮流的蓬勃发展,开放源代码的LAMP已经与J2EE和.Net商业软件形成了三足鼎立之势,并且该软件开发的项目在软件方面的投资成本较低,因此受到了整个IT界的关注。从网站的流量上来说,70%以上的访问流量由LAMP提供,所以LAMP是最强大的网站解决方案,很多知名网站都采用了该环境,如表18-1所示。
表18-1 部分采用LAMP环境的著名站点
由于LAMP环境的流行度非常高,所以LAMP环境的安装也成了很多网站运维人员的基本技能和日常工作内容。笔者曾为国内某著名云计算公司开发了一套可为Linux主机自动安装软件的系统,对外提供相关API调用方法,其中就包含了安装LAMP环境的功能。本节节选了该系统中的部分代码作为示例,带领读者开发一套可完成LAMP环境安装的系统。读者只需要通过在网页中填写要安装的服务器的IP地址以及该服务器的密码,然后单击按钮,就可以实现LAMP环境的部署。在实际工作时可根据需求再做修改,以实现更多功能。
实验环境:两台预装好Apache、PHP环境的Linux服务器,服务器B(IP:172.16.5.20)模拟前端页面服务器,用于页面化的用户交互;服务器A(IP:192.168.61.130)作为软件安装API服务器,在接到前端服务器安装需求时进行实际的软件安装。在CentOS下只需要一条命令就可以安装完成,如果是没有RHN支持的RedHat系统,则只能采用RPM安装或者按照8.3.3节介绍的方法让RedHat支持yum安装,然后使用yum方式安装。采用yum方式安装的命令如下:
- [root@localhost ~]# yum -y install httpd php
服务器A:提供软件安装API。
#
启动httpd
服务
[root@localhost ~]# service httpd start #
在varwww/html
目录中创建文件,内容如下所示
#
主程序,用于接收处理前端安装需求
[root@localhost html]# cat install.php <?php
include_once "crypt.php";
$USER = $_REQUEST['user'];
$CRYPT_PASS = $_REQUEST['pass']; $PASS = DeCode($CRYPT_PASS,'D',$key); $IP = $_REQUEST['ip'];
$OS = $_REQUEST['os'];
$SOFT = $_REQUEST['soft'];
$ID = $_REQUEST['id'];
if ($PASS == "") {
print "Error:Password encrypt fail"; $fp = fopen("log/error.log","a"); $time = date("D M j G:i:s T Y"); $fileData = "$time Error:$PASS $IP $OS $SOFT Password encrypt fail\n"; fwrite($fp,$fileData);
fclose($fp);
exit();
}
if ($USER != "root") {
print "Error:User Must be root"; $fp = fopen("log/error.log","a"); $time = date("D M j G:i:s T Y"); $fileData = "$time Not root:$PASS $IP $OS $SOFT\n"; fwrite($fp,$fileData);
fclose($fp);
exit();
}
if (($OS == "centos") && ($SOFT == "lamp")) {
exec("usrbin/sudo varwww/html/nmap_port.sh $IP",$out_1,$status_1); if ($status_1 != 0) {
$fp = fopen("log/error.log","a"); $time = date("D M j G:i:s T Y"); $fileData = "$time Error:$PASS $IP $OS $SOFT Nmap exit code $status_1\n"; fwrite($fp,$fileData); fclose($fp);
}
if ($status_1 == 1) {
print "Error:Host Unreachable"; exit(1);
}
if ($status_1 == 2) {
print "Error:Port 80 Running"; exit(1);
}
if ($status_1 == 3) {
print "Error:Port 3306 Running"; exit(1);
}
exec("usrbin/sudo varwww/html/expect.sh $PASS $IP",$out_2,$status_2); if ($status_2 == 4) {
$fp = fopen("log/error.log","a"); $time = date("D M j G:i:s T Y"); $fileData = "$time Error:$PASS $IP $OS $SOFT Expect exit code $status_2, password not correct\n"; fwrite($fp,$fileData); fclose($fp);
print "Error:IP and password not match"; exit(1);
}
$fp = fopen("log/process.log","a"); $time = date("D M j G:i:s T Y"); $fileData = "$time START:$USER $PASS $IP $OS $SOFT $ID\n"; fwrite($fp,$fileData);
fclose($fp);
$exec_install="usrbin/sudo varwww/html/install_lamp.sh $IP $SOFT $OS"; system("{$exec_install} > devnull &"); print "Sucess";
}
else
print "Error:No such soft or OS not support"; ?>
install_lamp.sh
用于安装并启动相关服务
[root@localhost html]# cat install_lamp.sh #!binbash
IP=$1
SOFT=$2
OS=$3
ssh root@$IP "yum -y install httpd mysql mysql-server php php-mysql php-mbstring php-mcrypt php-xml php-xmlrpc php-gd"
ssh root@$IP "sbinchkconfig httpd on; binsleep 1 && sbinchkconfig mysqld on; binsleep 1 && sbinservice httpd start; binsleep 1 && sbinservice mysqld start"
crypt.php
为加密解密算法,用于对接收到的密码字段进行解密
[root@localhost html]# cat crypt.php <?php
$key = "!hT3^pDs";
function DeCode($string,$operation,$key='') {
$key=md5($key);
$key_length=strlen($key); $string=$operation=='D'?base64_decode($string):substr(md5($string.$key),0,8).$string; $string_length=strlen($string); $rndkey=$box=array();
$result='';
for($i=0;$i<=255;$i++) {
$rndkey[$i]=ord($key[$i%$key_length]); $box[$i]=$i;
}
for($j=$i=0;$i<256;$i++) {
$j=($j+$box[$i]+$rndkey[$i])%256; $tmp=$box[$i];
$box[$i]=$box[$j];
$box[$j]=$tmp;
}
for($a=$j=$i=0;$i<$string_length;$i++) {
$a=($a+1)%256;
$j=($j+$box[$a])%256; $tmp=$box[$a];
$box[$a]=$box[$j];
$box[$j]=$tmp;
$result.=chr(ord($string[$i])^($box[($box[$a]+$box[$j])%256])); }
if($operation=='D')
{
if(substr($result,0,8)==substr(md5(substr($result,8).$key),0,8)) {
return substr($result,8); }
else
{
return'';
}
}
else
{
return str_replace('=','',base64_encode($result)); }
}
?>
expect.sh
用于自动复制公钥,请确保已经生成了root.ssh/id_rsa.pub [root@localhost html]# cat expect.sh #!binbash
PASS=$1
IP=$2
auto_ssh_copy_id () {
expect -c "set timeout -1;
spawn usrbin/ssh-copy-id -i root.ssh/id_rsa.pub root@$2; expect {
(yes/no) {send — yes\r;exp_continue;}
assword: {send — $1\r;exp_continue;}
ssword). {exit 4;}
eof {exit 0;}
}";
}
auto_ssh_copy_id $PASS $IP
nmap_port.sh
用于在安装前测试是否指定服务器已经有相关组件在运行
#
如果是则会导致主程序在运行中途退出,以免发生重新安装造成主机问题
[root@localhost html]# cat nmap_port.sh #!binbash
IP=$1
SOFT=$2
OS=$3
if [ -z <em>usr</em>bin/nmap -p 22 $IP | grep open | awk '{print $1}'
]; then exit 1
fi
if [ ! -z <em>usr</em>bin/nmap -p 80 $IP | grep open | awk '{print $1}'
]; then exit 2
fi
if [ ! -z <em>usr</em>bin/nmap -p 3306 $IP | grep open | awk '{print $1}'
]; then exit 3
fi
exit 0
#
安装完成后检测应该正常运行的端口,返回主程序特定的退出码
[root@localhost html]# cat last_check.sh #!binbash
IP=$1
SOFT=$2
OS=$3
COUNT=2
if [ ! -z <em>usr</em>bin/nmap -p 80 $IP | grep open | awk '{print $1}'
]; then COUNT=$[$COUNT - 1]
fi
if [ ! -z <em>usr</em>bin/nmap -p 3306 $IP | grep open | awk '{print $1}'
]; then COUNT=$[$COUNT - 1]
fi
exit $COUNT
#
创建日志目录log
文件
[root@localhost html]# mkdir log [root@localhost html]# touch log/error.log [root@localhost html]# touch log/process.log
服务器B:提供用户界面。
#
启动httpd
服务
[root@localhost ~]# service httpd start #
在varwww/html
目录中创建两个文件,内容如下所示
index.html
,软件安装的用户页面
[root@localhost html]# cat index.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"> <html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>InstallSoft</title> </head>
<body>
<form action="action.php" method="get"> IpAdd:<input type="text" name="ip" /> Pass:<input type="text" name="pass"/> <select name="os"> <option value="centos">centos</option> </select>
<select name="soft"> <option value="lamp">lamp</option> </select>
<input type="submit" value="INSTALL" /> </form>
</body>
</html>
action.php
,用于加密传输过程中的用户密码,以及调用API
的安装方法
HTTP
包在网络中传输时使用明文传输,所以需要将用户密码进行加密
[root@localhost html] cat action.php <?php
$key = "!hT3^pDs";
function DeCode($string,$operation,$key='') {
$key=md5($key);
$key_length=strlen($key); $string=$operation=='D'?base64_decode($string):substr(md5($string.$key),0,8).$string; $string_length=strlen($string); $rndkey=$box=array();
$result='';
for($i=0;$i<=255;$i++) {
$rndkey[$i]=ord($key[$i%$key_length]); $box[$i]=$i;
}
for($j=$i=0;$i<256;$i++) {
$j=($j+$box[$i]+$rndkey[$i])%256; $tmp=$box[$i];
$box[$i]=$box[$j];
$box[$j]=$tmp;
}
for($a=$j=$i=0;$i<$string_length;$i++) {
$a=($a+1)%256;
$j=($j+$box[$a])%256; $tmp=$box[$a];
$box[$a]=$box[$j];
$box[$j]=$tmp;
$result.=chr(ord($string[$i])^($box[($box[$a]+$box[$j])%256])); }
if($operation=='D')
{
if(substr($result,0,8)==substr(md5(substr($result,8).$key),0,8)) {
return substr($result,8); }
else
{
return'';
}
}
else
{
return str_replace('=','',base64_encode($result)); }
}
$password = $_GET["pass"];
$pass = DeCode($password,'E',$key); $os = $_GET["os"];
$soft = $_GET["soft"];
$ip = $_GET["ip"];
$id = rand();
echo
file_get_contents("http://192.168.61.130/install.php?user=root&pass=
$pass&ip=$ip&os=$os&soft=$soft&id=$id"); ?>
创建完成后,使用浏览器访问http://172.16.5.20,可以看到如图18-6所示页面。
图18-6 软件安装工具前端
只要输入需要安装LAMP环境的主机的IP和root密码,并单击INSTALL按钮,很快该页面就会显示安装结果,如果安装正常则显示Success。常见的几种安装失败场景如下所示:
- Error:Host Unreachable #
主机不可达
Error:Port 80 Running #
检测到80
端口已被占用(已有Web
服务)
Error:Port 3306 Running #
检测到3306
端口已被占用(已有MySQL
服务)
Error:IP and password not match #
提供的密码不正确,无法登录远程系统
实测时,该系统在CentOS release 5.5(Final)发行版中能正常运行,但可能会由于系统配置参数等环境差异,而无法保证部署到其他环境中不出现任何问题。如遇运行异常,请查看相关日志(包括但不限于varlog/message、varlog/httpd/error_log)。常见如下两个问题,需要对服务器A系统参数进行相关调整。
第一,由于install.php中使用了sudo命令,而Apache服务在运行时使用的是Apache用户,所以默认情况下Apache用户并没有sudo权限,需要通过/etc/sudoers文件给该用户适当的权限,否则PHP脚本会因为权限问题而无法运行。示例如下:
- apache ALL=(ALL) NOPASSWD:varwww/html/nmap_port.sh apache ALL=(ALL) NOPASSWD:varwww/html/expect.sh apache ALL=(ALL) NOPASSWD:varwww/html/install_lamp.sh #
或者为测试期间方便起见使用下面的设置,但是不建议在生产环境中使用
#apache ALL=(ALL) NOPASSWD:ALL
第二,由于Apache用户并不是一个登录用户,所以即便按照上述方法赋予了正确的权限,也可能会因为没有获得tty而无法运行。在这种情况下在Apache的错误日志中会看到如下所示的报错:
- sudo: sorry, you must have a tty to run sudo
解决办法是,注销/erc/sudoers中的Defaults requiretty,表示用户在没有tty的情况下也可以运行命令。命令如下:
- #Defaults requiretty