本节书摘来自华章出版社《构建高可用Linux服务器 第3版》一 书中的第3章,第3.1节,作者:余洪春 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.6 生产环境下的Shell脚本分类
生产环境下的Shell脚本作用还是挺多的,这里根据3.1节所介绍的日常工作中Shell脚本的作用,将生产环境下的Shell脚本分为备份类、监控类、统计类、开发类和自动化类。前面3类从字面意义上看比较容易理解,后面的我稍微解释一下:开发类脚本是用Shell来配合PHP做一些非系统类的管理工作,比如SVN的发布程序等;而自动化类脚本则利用Shell自动来替我们做一些繁琐的工作,比如自动生成及分配密码给开发组的用户或自动安装LNMP环境等。下面我会就这些分类举一些具体实例,便于大家理解。另外值得说明的一点是,这些实例都源自于我的线上环境,大家拿过来稍微改动一下IP或备份目录基本上就可以使用了。
3.6.1 生产环境下的Shell脚本备份类
俗话说得好,备份是救命的稻草。特别是重要的数据和代码,这些都是公司的重要资产,所以备份是必需的。备份能在我们执行了一些毁灭性的工作(比如不小心删除了数据)之后,进行恢复工作。我在实施项目时,发现有些有实力的公司在国内几个地方都有灾备机房,而且用的都是价格不菲的EMC高端存储。有朋友可能会问:如果我们没有存储怎么办?这可以参考一下我们公司的备份策略,即在执行本地备份的同时,让Shell脚本自动上传数据到另一台FTP备份服务器中。这种备份策略成本比较小,无需存储。此双备策略的具体步骤如下所示。
首先,做好准备工作。我们先要安装一台CentOS 5.8备份服务器,并安装vsftpd服务,稍微改动一下配置后启动。另外,关于vsftpd的备份目录,我们可以选择做RAID1或RAID5的分区或存储。没条件的朋友也可以像我们一样,用硬盘底座(HARD DRIVE DOCK)挂接一块2TB的SATA3硬盘(如果追求速度的话大家可以选择e-SATA插口),然后直接将其挂载到这台服务器的/mnt分区上。经过长时间测试发现这也非常稳定,备份工作进行得很顺利。
vsftpd服务的安装如下,CentOS 5.8下自带的yum极为方便。
yum -y install vsftpd
service vsftpd start && chkconfig vsftpd on
关于vsftpd配置前面已经讲过,这里给出配置文件,我们可以通过组合使用如下命令直接得出vsftpd.conf中有效的文件内容:
grep -v "^#" /etc/vsftpd/vsftpd.conf | grep -v '^$'
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=YES
chroot_local_user=YES
pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
chroot_local_user=YES这句话的作用我要重点强调一下。它的作用是限制所有本地用户在登录vsftpd服务器时只能在自己的家目录下,这是基于安全的考虑。在编写脚本的过程中也考虑到了这点,如果大家要移植此脚本到自己的工作环境中,不要忘了这句语法,否则异地备份极有可能失效。
另外,下例中的CVS目录备份脚本说明一种情况,我们在备份服务器(即IP为192.168.4.45的机器)上应该建立备份用户CVS,并分配其密码,还应该将其家目录更改为备份目录,即/data/backup/cvs-backup,这样的话更方便备份工作,以下备份脚本以此类推。FreeBSD 8.1的用户密码配置文件为/etc/master.passwd,CentOS 5.8为/etc/passwd,两者是不一样的。由于这里用的FTP服务器是CentOS 5.8,所以我们只需要修改/etc/passwd文件即可。
注意 下面的脚本源自于我的生产环境,并没有做任何变动,我对每个脚本运行的环境都进行了说明,大家也可以尝试将在FreeBSD 8.1下运行的Shell脚本移植到CentOS 5.8下面。注意FreeBSD 8.1和CentOS 5.8下的data用法是不一样的,改编脚本时请注意!
1.CVS目录的备份脚本
由于CVS服务器仅保留以前网站的代码和CVS日志,我们现不提交任何内容,只是以读为准,所以仅仅做目录级的备份即可。考虑到目录的备份不是太大,我就没有做轮询处理(即只备份某一周期的文件,例如前30天),准备等备份文件过多时再考虑手动删除。应该在vsftpd服务器上建立相应的备份用户cvs_user,脚本内容如下所示(此脚本在FreeBSD 8.1 x86_64下已通过):
#!/bin/sh
# CVS backup for FreeBSD 8.1
# 2010-04-23
CVSDIR=/home/cvsroot/project
DATE='date +%Y-%m-%d'
OLDDATE='date -v -10d +%Y-%m-%d'
BACKDIR=/data/backup/cvs-backup
FILENAME=cvsbackup_'date +%Y-%m-%d'
if[! -d ${BACKDIR}/${DATE} ]; then
mkdir ${BACKDIR}/${DATE}
fi
if [-d ${BACKDIR}/${OLDDATE} ]; then
rm -rf ${BACKDIR}/${OLDDATE}
fi
HOST=192.168.4.45
FTP_USERNAME=cvs_user
FTP_PASSWORD=cvs101
cd $CVSDIR
tar zcvf $FILENAME.tar.gz $CVSDIR
ftp -i -n -v << !
open ${HOST}
user ${FTP_USERNAME} ${FTP_PASSWORD}
bin
rmdir ${OLDDATE}
mkdir ${DATE}
cd ${DATE}
mput *
bye
!
2.版本控制软件SVN的代码库的备份脚本
版本控制软件SVN的重要性这里就不多说了,现在很多公司基本还是利用SVN作为提交代码集中管理的工具,所以做好其备份工作的重要性不言而喻。这里的轮询周期为30天一次,Shell会自动删除30天前的文件。应该在vsftpd服务器上建立相应的备份用户svn_user,脚本内容如下(此脚本在FreeBSD 8.1 x86_64下已通过):
#!/bin/sh
# subversion backup for FreeBSD 8.1
# Created by ritto.zhao
# 2010-04-23
SVNDIR=/data/svn
SVNADMIN=/usr/local/bin/svnadmin
DATE='date +%Y-%m-%d'
OLDDATE='date -v -30d +%Y-%m-%d'
BACKDIR=/data/backup/svn-backup
[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}
LogFile=${BACKDIR}/svnbak.log
[ -f ${LogFile} ] || touch ${LogFile}
mkdir ${BACKDIR}/${DATE}
if [-d ${BACKDIR}/${OLDDATE} ]; then
rm -rf ${BACKDIR}/${OLDDATE}
echo " " >> ${LogFile}
echo " " >> ${LogFile}
echo "-----------------------------" >> ${LogFile}
echo 'date +"%Y-%m-%d %H:%M:%S"' >> ${LogFile}
echo "-----------------------------" >> ${LogFile}
echo "***Subversion Backup Notification***" >> ${LogFile}
/usr/bin/printf "Host: 'hostname'\ nAddress: ${IP}\ nDate:${DATE}\ n"
>> ${LogFile}
fi
for PROJECT in superbiiz ewizweb rest report ewiz
do
cd ${SVNDIR}
${SVNADMIN} hotcopy ${PROJECT} ${BACKDIR}/${DATE}/${PROJECT} --clean-logs
cd ${BACKDIR}/${DATE}
tar zcvf ${PROJECT}_svn_${DATE}.tar.gz ${PROJECT} > /dev/null
rm -rf ${PROJECT}
echo "Repository: ${PROJECT} backup done into ${BACKDIR}/${DATE}/ Successful!" >> ${LogFile}
echo "-----------------------------" >> ${LogFile}
echo "***Subversion Backup Notification***" >> ${LogFile}
/usr/bin/printf "Host: 'hostname'\ nAddress: ${IP}\ nDate: ${DATE}\ n"
>> ${LogFile}
done
for PROJECT in httpd web rest report test
do
cd ${SVNDIR}
${SVNADMIN} hotcopy ${PROJECT} ${BACKDIR}/${DATE}/${PROJECT} --clean-logs
cd ${BACKDIR}/${DATE}
tar zcvf ${PROJECT}_svn_${DATE}.tar.gz ${PROJECT} > /dev/null
rm -rf ${PROJECT}
echo "Repository: ${PROJECT} backup done into ${BACKDIR}/${DATE}/ Successful!" >> ${LogFile}
/bin/sleep 2
done
HOST=192.168.4.45
FTP_USERNAME=svn_user
FTP_PASSWORD=svn101
cd ${BACKDIR}/${DATE}
ftp -i -n -v << !
open ${HOST}
user ${FTP_USERNAME} ${FTP_PASSWORD}
bin
cd ${OLDDATE}
mdelete *
cd ..
rmdir ${OLDDATE}
mkdir ${DATE}
cd ${DATE}
mput *
bye
!
另附上CentOS下SVN代码库备份脚本(此脚本在CentOS 5.6|5.8 x86_64下通过):
#!/bin/bash
SVNDIR=/data/svn
SVNADMIN=/usr/bin/svnadmin
DATE='date +%Y-%m-%d'
OLDDATE='date +%Y-%m-%d -d '30 days''
BACKDIR=/data/svnbackup
[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}
LogFile=${BACKDIR}/svnbak.log
[ -f ${LogFile} ] || touch ${LogFile}
mkdir $BACKDIR/$DATE
for PROJECT in myproject official analysis mypharma
do
cd $SVNDIR
$SVNADMIN hotcopy $PROJECT $BACKDIR/$DATE/$PROJECT --clean-logs
cd $BACKDIR/$DATE
tar zcvf $PROJECT-svn-$DATE.tar.gz $PROJECT > /dev/null
rm -rf $PROJECT
sleep 2
done
HOST=192.168.11.29
FTP_USERNAME=svn
FTP_PASSWORD=svn101
cd ${BACKDIR}/${DATE}
ftp -i -n -v << !
open ${HOST}
user ${FTP_USERNAME} ${FTP_PASSWORD}
bin
cd ${OLDDATE}
mdelete *
cd ..
rmdir ${OLDDATE}
mkdir ${DATE}
cd ${DATE}
mput *
bye
!
3.MySQL从数据库备份脚本
本着简单高效,不影响网站正常运营的原则,我们采用暂停从MySQL数据库的方式备份。这里要说明的是,本地的轮询周期为20天,vsftpd的轮询周期为60天,备份后就算是用gzip压缩,MySQL的数据库还是很大,大家可以根据实际需求来更改这个时间,注意不要让数据库撑爆你的备份服务器。由于是内部开发环境,密码设置得比较简单,如果要将其移植到自己的公网服务器上,还要在安全方面注意一下。另外附带说明一下,--opt只是一个快捷选项,等同于同时添加--add-drop-tables--add-locking --create-option --disable-keys --extended-insert --lock-tables --quick --set-charset选项。本选项能让mysqldump很快地导出数据,并且导出的数据可以很快导回。该选项默认是开启的,但可以用--skip-opt禁用。注意,如果运行mysqldump没有指定--quick或--opt选项,则会将整个结果集放在内存中。如果导出的是大数据库则可能会出现问题。脚本内容如下(此脚本在CentOS 5.6|5.8 x86_64下已通过):
#!/bin/bash
USERNAME=mysqlbackup
PASSWORD=mysqlbackup
DATE='date +%Y-%m-%d'
OLDDATE='date +%Y-%m-%d -d '-20 days''
FTPOLDDATE='date +%Y-%m-%d -d '-60 days''
MYSQL=/usr/local/mysql/bin/mysql
MYSQLDUMP=/usr/local/mysql/bin/mysqldump
MYSQLADMIN=/usr/local/mysql/bin/mysqladmin
SOCKET=/tmp/mysql.sock
BACKDIR=/data/backup/db
[-d ${BACKDIR} ] || mkdir -p ${BACKDIR}
[-d ${BACKDIR}/${DATE} ] || mkdir ${BACKDIR}/${DATE}
[! -d ${BACKDIR}/${OLDDATE} ] || rm -rf ${BACKDIR}/${OLDDATE}
for DBNAME in mysql test report
do
${MYSQLDUMP} --opt -u${USERNAME} -p${PASSWORD} -S${SOCKET} ${DBNAME} | gzip > ${BACKDIR}/${DATE}/${DBNAME}-backup-${DATE}.sql.gz
echo "${DBNAME} has been backup successful"
/bin/sleep 5
done
HOST=192.168.4.45
FTP_USERNAME=dbmysql
FTP_PASSWORD=dbmysql
cd ${BACKDIR}/${DATE}
ftp -i -n -v << !
open ${HOST}
user ${FTP_USERNAME} ${FTP_PASSWORD}
bin
cd ${FTPOLDDATE}
mdelete *
cd ..
rmdir ${FTPOLDDATE}
mkdir ${DATE}
cd ${DATE}
mput *
bye
!
4.mysqlhotcopy热备份数据库脚本
mysqlhotcopy是一个Perl脚本,最初由Tim Bunce编写并提供。它的主要实现原理是先锁住(LOCK)表,然后执行FLUSH TABLES动作,该正常关闭的表正常关闭,该进行flush同步的数据也进行同步,然后通过执行系统级别的复制(cp等)命令,将需要备份的表或数据库的所有物理文件都复制到指定的备份集位置上。它主要用于备份MyISAM存储引擎的数据库。脚本内容如下(此脚本在CentOS 5.6|5.8 x86_64下已通过):
#!/usr/bin/env bash
#author:coralzd powered by www.freebsdsystem.org
#description: mysqlhotcopy backup script
DATE='date+%y%m%d.%H'
DATADIR="/data0/mysql/data" #MySQL数据库目录
BACKDIR_DAILY="/data0/local/mysqld/daily" #每天备份数据库的目录
BACKDIR_HOURLY="/data0/local/mysqld/hourly"#每小时备份数据库的目录
INTERVAL="$1"
TMPDIR="/var/tmp/" #临时目录
TMPDIRH="$TMPDIR"hourly #临时每小时数据库目录
TMPDIRD="$TMPDIR"daily #临时每小时备份数据库的目录
LOGDIR="/data0/log/dbbackup/" #备份数据输出日志路径
KEEPH_LOCAL=1
USER=hotcopy #mysqlhotcopy用到的用户,必须要有select、reload、lock_tables 权限
PASSWD=hotcopy # 密码
HOST='hostname -s'
MYVERSION="5.1"
mkdir -p $LOGDIR
case $INTERVAL in
hourly | HOURLY | Hourly | 1 )
echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Performing HOURLY level backup -- 'date +$m-$d.%H:%M:%S'" && echo
""
echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
>>"$LOGDIR"hourly.log
echo "Performing HOURLY level backup -- 'date +$m-$d.%H:%M:%S'"
>>"$LOGDIR"hourly.log
mkdir -p $TMPDIRH # 创建临时目录
mkdir -p $BACKDIR_HOURLY #创建每小时备份目录
/usr/local/mysql/bin/mysqlhotcopy --allowold test -u $USER -p $PASSWD
$TMPDIRH > /dev/null
echo "" && echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Compressing -- 'date +$m-$d.%H:%M:%S'"
echo "Compressing -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"hourly.log
cd $TMPDIRH #进入临时目录
find . -maxdepth 1 -type d -user mysql -exec tar czf {}-"$DATE".tar.gz '{}' \ ;
# 查找是mysql用户的数据库,然后进行打包
find . -maxdepth 1 -type d -user mysql -exec rm -rf {} \ ; # 查找并删除用户名为mysql的文件
echo ""
echo "Copying Local... -- 'date +$m-$d.%H:%M:%S'"
echo "Copying Local... -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"hourly.log
chattr -i $BACKDIR_HOURLY # 移除备份目录的保护属性
find $BACKDIR_HOURLY -type f -mtime +7 -exec rm {} \ ;#查找并删除在7天以外的备份数据库
mv * $BACKDIR_HOURLY #将打包好的文件移到每小时备份目录
chattr +i $BACKDIR_HOURLY # 对备份目录加保护
echo ""
echo "Ending -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"hourly.log
exit 0
;;
daily | DAILY | Daily | 2)
echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Performing DAILY level backup -- 'date +$m-$d.%H:%M:%S'" && echo
""
echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
>>"$LOGDIR"daily.log
echo "Performing DAILY level backup -- 'date +$m-$d.%H:%M:%S'"
>>"$LOGDIR"daily.log
mkdir -p $TMPDIRD #进入临时目录
mkdir -p $BACKDIR_DAILY #创建每天备份目录
/usr/local/mysql/bin/mysqlhotcopy --allowold test -u $USER -p $PASSWD
$TMPDIRD > /dev/null
echo "" && echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Compressing -- 'date +$m-$d.%H:%M:%S'"
echo "Compressing -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"daily.log
cd $TMPDIRD #进入临时目录
find . -maxdepth 1 -type d -user mysql -exec tar czf {}-"$DATE".tar.gz '{}' \ ;
#查找是mysql用户的数据库,然后进行打包
find . -maxdepth 1 -type d -user mysql -exec rm -rf {} \ ; #查找备份数据库,并将在1天以外的备份删除
echo "Copying Local... -- 'date +$m-$d.%H:%M:%S'"
echo "Copying Local... -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"daily.log
chattr -i $BACKDIR_DAILY #移除备份目录的保护属性
find $BACKDIR_DAILY -type f -mtime +$KEEPD_LOCAL -exec rm -rf '{}' \ ; #查找备份数据库,并将在1天以外的备份删除
mv * $BACKDIR_DAILY #将打包好的文件移到每小时备份目录
chattr +i $BACKDIR_DAILY #对备份目录加保护
echo "Ending -- 'date +$m-$d.%H:%M:%S'" >>"$LOGDIR"daily.log
exit 0
;;
* )
echo ""
echo
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "Invalid Selection" 1>&2
exit 1
esac
3.6.2 生产环境下的开发类Shell脚本
Shell作为系统管理脚本,在开发功能上一直有所欠缺,但我们可以将其作为PHP或Python开发语言的辅助语言,配合作开发工具,提高我们的工作效率。
1.配合PHP作为SVN的发布程序
公司的SVN发布程序是这样的工作流程:把将要发布的程序打上publish please:,后面接上SVN日志(开发人员提交的代码日志,这比较重要,是后来更改代码的条件依据),然后PHP程序会检索出这些打上标的代码,并发布到线上环境中。这样一来就带来了一个问题:在按照原先的程序发布到线上后,会将publish please后面的开发人员写的SVN log清除,给开发造成了不便。我在接手此工作后将此Shell脚本重写,解决了这个Bug。由于此脚本牵涉的内容比较多,功能也比较繁琐,所以这里只给大家演示一下,并不要求掌握。实现功能的代码如下所示(此脚本在FreeBSD 8.1 x86_64下已通过):
#!/bin/sh
# SVN PUBLISH TOOL
# 2010-07-23
SVN=/usr/local/bin/svn
cd /var/www/html/svn_log/files/svn/
svn up --username web --password web101
cat /var/www/html/svn_log/files/svntest/file.log | svn log $2 | head -n5 | sed -n "/publish/p" > svnlog
svnlog_change='sed -i "s@publish please@already published@" svnlog'
cat /var/www/html/svn_log/files/svntest/file.log | awk -F ',' '{ print "/usr/local/bin/svn propset --username web --password web101 -r"$1" --revprop svn:log "$svnlog_change" " $2 }' | /bin/sh
[! -d /var/www/html/svn_log/files/svntest ] || mv /var/www/html/svn_log/files/svntest /var/www/html
/old-svntest/html-${COMMIT}
mkdir /var/www/html/svn_log/files/svntest
chmod -R 777 /var/www/html/svn_log/files/svntest
chown -R www:www /var/www/html/svn_log/files/svntest
cd /var/www/html/svn_log/files/svn/
svn up --username web --password web101
此脚本在线上已稳定运行半年,基本能实现我们的要求。美中不足的是,我们发现还存在着一个Bug,即如果有大量脚本要同时提交,此脚本会失效,这个Bug目前也只能在后续版本中改进了。
2.利用Shell安全方便地删除指定的zone
我维护的DNS服务器主要有3个,即一主一从一备。由于公司的架构采用了CDN方案,所以namd.conf针对“okspace.com”的出现位置就有3处,即电信、网通及其他,加上3个服务器,每次手动用Vim删除okspace.com时就必须修改9处,维护起来很麻烦。更棘手的是,有些zone经常需要删除,特别麻烦。所以我写了个Shell脚本以减轻自己的工作量,且达到安全删除的目的。
此脚本中变量domain中的文件内容可以自己定义,鉴于生产环境下bind都是源码安装的,所以就以named.conf文件为主了。代码虽然短小,但操作起来确实很方便,后期我想跟Python或PHP结合起来使用,做成图形化界面,并加入了更多功能,使其更完善。代码如下(此脚本在CentOS 5.6|5.8 x86_64下已通过):
#!/bin/bash
domain='zone\ "okspace.cn"'
if [-e /var/named/chroot/etc/named.conf ];then
sed -i "/$domain/,/};/d" /var/named/chroot/etc/named.conf
else
sed -i "/$domain/,/};/d" /var/named/chroot/var/named/named.rfc1912.zones
fi```
在实际工作中我们了解了Shell脚本强大的系统管理功能,也意识到了它在开发上的先天不足,这里我建议大家有时间可以关注和学习一门开发语言,PHP或Python均可。大家可以根据自己的兴趣和职业规划,有重点地选择学习。
####3.6.3 生产环境下的统计类Shell脚本
统计工作一直是Shell脚本的强项,我们完全可以利用Sed、Awk再加上正则表达式,写出强大的统计脚本来分析系统日志、安全日志及服务器应用日志等。
1.Nginx负载均衡器日志汇总脚本 此脚本同样适用于分析Nginx或Apache Web日志。
以下脚本是用来分析Nginx负载均衡器的日志,作为Awstats的补充,它可以快速得出排名最前的网站和IP等。脚本内容如下所示(此脚本在CentOS 5.8 x86_64下已测试通过):
!/bin/bash
if [$# -eq 0 ]; then
echo "Error: please specify logfile."
exit 0
else
LOG=$1
fi
if [! -f $1 ]; then
echo "Sorry, sir, I can't find this nginx log file, pls try again!"
exit 0
fi
echo "Most of the ip:"
echo "-------------------------------------------"
awk '{ print $1 }' $LOG | sort | uniq -c | sort -nr | head -10
echo
echo
echo "Most of the time:"
echo "--------------------------------------------"
awk '{ print $4 }' $LOG | cut -c 14-18 | sort | uniq -c | sort -nr | head -10
echo
echo
echo "Most of the page:"
echo "--------------------------------------------"
awk '{print $11}' $LOG | sed 's/^.\ (.cn\ )\"/\ 1/g' | sort | uniq -c | sort -rn | head -10
echo
echo
echo "Most of the time / Most of the ip:"
echo "--------------------------------------------"
awk '{ print $4 }' $LOG | cut -c 14-18 | sort -n | uniq -c | sort -nr | head -10 > timelog
for i in 'awk '{ print $2 }' timelog'
do
num='grep $i timelog | awk '{ print $1 }''
echo " $i $num"
ip='grep $i $LOG | awk '{ print $1}' | sort -n | uniq -c | sort -nr | head -10'
echo "$ip"
echo
done
rm -f timelog
2.Nginx Web服务器日志切割脚本
为什么我们要对日志进行切割呢?这是因为在生产环境下,我们的Web服务器是365×24小时不间断地提供服务的,它的日志也是不间断产生的。而我们分析它的日志时,可能需要以天为单位来查看,所以考虑以天为单位来切割,可以利用Crontab来完成这项工作,切割后的日志可以用Awstats来分析。脚本内容如下所示(此脚本在CentOS 5.5 x86_64下已通过):
!/bin/bash
The Nginx logs path
logs_path="/data0/logs"
logs_dir=${logs_path}/$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")
logs_file=$(date -d "yesterday" +"%Y%m%d")
mkdir -p /data0/backuplogs/$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")
tar -czf ${logs_path}/${logs_file}.tar.gz ${logs_path}/*.log
rm -rf ${logs_path}/*.log
mv ${logs_path}/${logs_file}.tar.gz /data0/backuplogs/$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")
/usr/local/nginx/sbin/nginx -s reload
for oldfiles in 'find /data0/backuplogs/$(date -d "30 days ago" +"%Y")/$(date -d "30 days ago" +"%m")/ -type f -mtime +30'
do
rm -f $oldfiles
done
for oldfiles in 'find/data0/backuplogs/$(date -d"30 days ago"+"%Y")/$(date -d"30 days ago"+"%m")/-type f -mtime+30'这句代码的作用是自动删除一个月前的打包日志文件。Nginx处理日志的脚本比较简单,大家没必要像我一样写得这么复杂,还可以简化一些。
3.测试局域网内主机依然存活的小脚本
我们在对局域网的网络情况进行维护时,经常有这样的情况,需要收集网络中存活的IP,这个时候可以写一个Shell脚本,自动收集某一网段的IP。现在的IT技术型公司一般都比较大,网络工程师一般会规划几个VLAN,即几个网段,我们可以多设几个变量参数来自动收集所有网段的IP。脚本如下所示(此脚本在CentOS 5.8 x86_64下已通过):
!/bin/bash
for n in {100..200}; do
host=192.168.4.$n
ping -c2 $host &>/dev/null
if [$? = 0 ]; then
echo "$host is UP"
echo "$host" >> /root/alive.txt
else
echo "$host is DOWN"
fi
done`
程序会自动记录存活的IP,我们用cat命令来查看/root/alive.txt文件,如下所示:
cat /root/alive.txt
192.168.4.100
192.168.4.101
192.168.4.102
192.168.4.108
192.168.4.109
192.168.4.111
192.168.4.113
192.168.4.121
192.168.4.123
192.168.4.125
192.168.4.133
192.168.4.138
192.168.4.142
192.168.4.143
脚本虽然短小,但却非常实用,免得又要到Windows XP下去下载局域网检测工具。我们平时在工作中也应该注意多收集多写一些这样的脚本,达到简化我们工作的目的。
3.6.4 生产环境下的监控类Shell脚本
在生产环境下,服务器的稳定情况会直接影响公司的生意和信誉,可见其有多重要。所以,我们需要即时掌握服务器的状态,一般我们会在机房部署Nagios作为监控程序,然后用Shell作为补充脚本,实时监控我们的服务器。
1.自动监控ADSL状态并重启的脚本
故障分析:公司的办公环境中以一台Ubuntu 8.04作NAT路由器,但它的ADSL总是掉线,一掉的话网关的Gateway就没有了,直接影响就是所有同事都上不了网。所以我针对这种情况写了一个监控脚本,测试了很长时间,效果不错。执行脚本方法如下所示:
nohup sh route.sh &
注意前面要用上nohup,这样可避免root用户logout时此脚本退出。脚本代码如下(此脚本在Ubuntu 8.04 i386下已通过):
#!/bin/bash
#Created By Andrew.Yu
while :
do
if route | tail -l | grep "0.0.0.0"
then
&>/dev/null
else
adsl-stop
adsl-start
fi
sleep 10
done```
2.Nginx负载均衡服务器上监控Nginx进程的脚本
由于我们的电子商务前端的负载均衡层用到了Nginx+Keepalived架构,而Keepalived无法进行Nginx服务的实时切换,所以这里用了一个监控脚本nginx_pid.sh,每隔5秒就监控一次Nginx的运行状态,如果发现有问题就关闭本机的Keepalived程序,让VIP切换到从Nginx负载均衡器上。在对线上环境进行操作的时候,我人为地重启了主Master的Nginx机器,从Nginx机器在很短的时间内就接管了VIP地址,即网站的实际内网地址(此内网地址能过防火墙映射为公网IP),进一步证实了此脚本的有效性。脚本内容如下(此脚本已在CentOS 5.8 x86_64下测试通过):
!/bin/bash
Created By Andrew.Yu
while :
do
nginxpid='ps -C nginx --no-header | wc -l'
if [$nginxpid -eq 0 ];then
ulimit -SHn 65535
/usr/local/nginx/sbin/nginx
sleep 5
nginxpid='ps -C nginx --no-header | wc -l'
if [$nginxpid -eq 0 ];then
/etc/init.d/keepalived stop
fi
fi
sleep 5
done`
3.系统文件打开数监测脚本
这个脚本比较方便,可用来查看Nginx进程下最大文件打开数。脚本代码如下(此脚本在CentOS 5.8 x86_64下已通过):
#!/bin/bash
for pid in 'ps aux | grep nginx | grep -v grep|awk '{print $2}''
do
cat /proc/${pid}/limits | grep 'Max open files'
done```
脚本的运行结果如下所示:
Max open files 1024 1024 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
Max open files 65535 65535 files
4.Web网站即时监控与报警程序
虽然现在规模较大的公司可以购买AlertBot即时监控服务,并配合Nagios来监控网站,但很多时候客户的公司可能没这个需求,所以我们可以写一个Shell程序监控我们的脚本,网站出故障后就向我们的邮箱或手机发送信息报警。如果飞信接口不是太稳定的话,可以向139的邮箱发报警邮件,它会自动绑定到我们的移动手机上。脚本代码如下(此脚本在CentOS 5.6|5.8 x86_64下已通过):
!/bin/sh
Created by kerryhu
MAIL:king_819@163.com
BLOG:http://kerry.blog.51cto.com
LANG=C
被监控服务器、端口列表
server_all_list=(\
125.252.87.171:80 \
58.64.157.166:80 \
58.64.157.174:80 \
219.87.10.1:80 \
)
telnum=152XXXXXXXX
passwd=12345
date=$(date -d "today" +"%Y-%m-%d_%H:%M:%S")
采用HTTP POST方式发送检测信息给接口程序interface.php,接口程序负责分析信息,决定是否发送报警MSN消息、手机短信、电子邮件
send_msg_to_interface()
{
if [[$2 = "0" ]] || [[$2 = "2" ]]; then
#开始发送警报邮件,135XXXXXXXX@139.com既是发送方也是接收方
sendEmail -f 135XXXXXXXX@139.com -t 135XXXXXXXX@139.com -s smtp.139.com -u "from http_monitor" -xu 135XXXXXXXX@139.com -xp james -o message-charset=utf-8 -m $1
#发送MSN警报消息(如果不需要MSN警报可以注释这行)
curl -m 600 -d menu=http -d date=$date -d ip=$server_ip -d port=$server_port -d status=$status http://127.0.0.1/monitor/interface.php
fi;
}
server_all_len=${#server_all_list[*]}
i=0
while [$i -lt $server_all_len ]
do
server_ip=$(echo ${server_all_list[$i]} | awk -F ':' '{print $1}')
server_port=$(echo ${server_all_list[$i]} | awk -F ':' '{print $2}')
server_message=" "
if curl -m 10 -G http://${server_all_list[$i]}/ > /dev/null 2>&1
then
#status: 0,http down 1,http ok 2,http down but ping ok
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!";
server_message="服务器${server_ip},端口${server_port}能够正常访问!";
else
if curl -m 30 -G http://${server_all_list[$i]}/ > /dev/null 2>&1
then
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!"
server_message="服务器${server_ip},端口${server_port}能够正常访问!";
else
if ping -c 1 $server_ip > /dev/null 2>&1
then
status=2
echo "服务器${server_ip},端口${server_port}无法访问,但是能够Ping通!";
server_message="服务器${server_ip},端口${server_port}无法访问,但是能够Ping通!";
else
status=0
echo "服务器${server_ip},端口${server_port}无法访问,并且无法Ping通!";
server_message="服务器${server_ip},端口${server_port}无法访问,并且无法Ping通!";
fi
fi
fi
send_msg_to_interface "${server_message}" "${status}";
let i++
don
5.MySQL主从监控邮件报警脚本
脚本设计思路:
1)此脚本应该能适应各种各样不同的内外网环境,即IP不同的环境。
2)让脚本也顺便监控下MySQL是否正常运行。
3)Slave机器的IO和SQL状态都必须为YES,缺一不可,这里用到了多重条件判断-a。
脚本产生的背景环境:
我有不少基于公网类型的网站(没有硬件防火墙,直接置于IDC机房)做的都是MySQL一主一从架构,从机主要起备份数据库和冷备份的作用,虽然从机宕机了问题不大,但也影响数据的备份工作;这样的网站有数十个,如果一个一个手动检查,每天都要浪费不少时间,所以用了脚本监控,设计了如下脚本(以下脚本在CentOS 5.8 x86_64下已通过):
!/bin/bash
check MySQL_Slave Status
crontab time 00:10
MYSQLPORT='netstat -na|grep "LISTEN"|grep "3306"|awk -F[:" "]+ '{print $4}''
MYSQLIP='ifconfig eth0|grep "inet addr" | awk -F[:" "]+ '{print $4}''
STATUS=$(/usr/local/webserver/mysql/bin/mysql -u yuhongchun -pyuhongchun101 -S /tmp/mysql.sock -e "show slave status\G" | grep -i "running")
IO_env='echo $STATUS | grep IO | awk ' {print $2}''
SQL_env='echo $STATUS | grep SQL | awk '{print $2}''
if [ "$MYSQLPORT" == "3306" ]
then
echo "mysql is running"
else
mail -s "warn!server: $MYSQLIP mysql is down" yuhongchun027@163.com
fi
if [ "$IO_env" = "Yes" -a "$SQL_env" = "Yes" ]
then
echo "Slave is running!"
else
echo "####### $date #########">> /data/data/check_mysql_slave.log
echo "Slave is not running!" >> /data/data/check_mysql_slave.log
mail -s "warn! $MySQLIP_replicate_error" yuhongchun027@163.com << /data/data/check_mysql_slave.log
fi
建议每10分钟运行一次:
/10 * root /bin/sh /root/mysql_slave.sh
记得在每台MySQL从机上分配一个yuhongchun的用户,权限大些也没关系,只限定在本地运行,如下所示:
grant all privileges on . to "yuhongchun"@"127.0.0.1" identified by "yuhongchun101";
grant all privileges on . to "yuhongchun"@"localhost" identified by "yuhongchun101";`
脚本实践:
此脚本我已用于生产环境,大家可以放在从MySQL机器上,用来监控。
后期应用:
后期公司的MySQL数据库准备由一主一从架构升级成一主多从,读写分离的架构,LVS作从数据库的负载均衡器,此脚本自动监控从MySQL的replication状态,如果不能同步则自动关闭本机的MySQL服务,免得影响整个网站的正常业务访问。
3.6.5 生产环境下的自动化类Shell脚本
1.批量生成账户脚本
在内网开发环境中,有时需要为开发组的同事批量生成账户,如果手动添加会非常麻烦,这个时候我们可以写一段Shell脚本来自动完成这项工作。在首次登录时密码均是统一的,在移交给开发人使用时让他们自行更改。脚本代码如下(此脚本在CentOS 5.8 x86_64下已通过):
#!/bin/bash
#此脚本应用于开发环境下生成批量用户账户
for name in tom jerry joe jane andrewy brain
do
useradd $name
echo redhat | passwd --stdin $name
don```
passwd --stdin的作用是将前面的输入通过管道命令作为自己的输出,从而避免脚本交互,达到自动化的目的。
2.系统初始化脚本
此脚本用于新装Linux的相关配置工作,比如禁掉iptable和SELinux及ipv6、优化系统内核、停掉一些没必要启动的系统服务等。此脚本尤其适合大批新安装的CentOS系列的服务器。脚本代码如下所示(此脚本在CentOS 5.5|5.6 x86_64下已通过):
!/bin/bash
Created by kerryhu
MAIL:king_819@163.com
BLOG:http://kerry.blog.51cto.com
cat << EOF |
---|
=== Welcome to CentOS System init === |
+--------------------------by kerry----------------------------+
EOF
set ntp
yum -y install ntp
echo " 3 /usr/sbin/ntpdate 210.72.145.44 > /dev/null 2>&1" >> /etc/crontab
service crond restart
set ulimit
echo "ulimit -SHn 102400" >> /etc/rc.local
set locale
true > /etc/sysconfig/i18n
cat >>/etc/sysconfig/i18n<
LANG="zh_CN.GB18030"
SUPPORTED="zh_CN.GB18030:zh_CN:zh:en_US.UTF-8:en_US:en"
SYSFONT="latarcyrheb-sun16"
EOF
set sysctl
true > /etc/sysctl.conf
cat >> /etc/sysctl.conf << EOF
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 262144
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 1024 65535
EOF
/sbin/sysctl -p
echo "sysctl set OK!!"
close ctrl+alt+del
sed -i "s/ca::ctrlaltdel:\/sbin\/shutdown -t3 -r now/#ca::ctrlaltdel:\/sbin\/shutdown -t3 -r now/" /etc/inittab
set purview
chmod 600 /etc/passwd
chmod 600 /etc/shadow
chmod 600 /etc/group
chmod 600 /etc/gshadow
disable ipv6
cat << EOF |
---|
=== Welcome to Disable IPV6 === |
EOF
echo "alias net-pf-10 off" >> /etc/modprobe.conf
echo "alias ipv6 off" >> /etc/modprobe.conf
/sbin/chkconfig --level 35 ip6tables off
echo "ipv6 is disabled!"
disable selinux
sed -i '/SELINUX/s/enforcing/disabled/' /etc/selinux/config
echo "selinux is disabled,you must reboot!"
vim
sed -i "8 s/^/alias vi='vim'/" /root/.bashrc
echo 'syntax on' > /root/.vimrc
zh_cn
sed -i -e 's/^LANG=.*/LANG="en"/' /etc/sysconfig/i18n
init_ssh
ssh_cf="/etc/ssh/sshd_config"
sed -i -e '74 s/^/#/' -i -e '76 s/^/#/' $ssh_cf
sed -i "s/#Port 22/Port 65535/" $ssh_cf
sed -i "s/#UseDNS yes/UseDNS no/" $ssh_cf
client
sed -i -e '44 s/^/#/' -i -e '48 s/^/#/' $ssh_cf
service sshd restart
echo "ssh is init is ok.............."
chkser
tunoff services
---------------------------------------------------------------------------
cat << EOF |
---|
=== Welcome to Tunoff services === |
EOF
---------------------------------------------------------------------------
for i in 'ls /etc/rc3.d/S*'
do
CURSRV='echo $i|cut -c 15-'
echo $CURSRV
case $CURSRV in
crond | irqbalance | microcode_ctl | network | random | sshd | syslog | local )
echo "Base services, Skip!"
;;
*)
echo "change $CURSRV to off"
chkconfig --level 235 $CURSRV off
service $CURSRV stop
;;
esac
done
echo "service is init is ok.............."