手把手教你学网站服务器常用脚本

bt面板简介

在解释bt宝塔面板是什么之前,先简单说说一直以来建网站方法

  1. 比较久远的时候,以前都是在Linux环境下,在黑洞洞的命令行窗口,安装建立网站常用的软件。建立网站肯定离不开三件套嘛——http服务器、数据库、服务器运行语言(俗称后台)。
  2. 安装这三件套有各种各样的组合。后来逐步形成LAMP(Linux+Apache+MySQL+PHP)和LNMP(Linux+Nginx+MySQL+PHP)几个组合嘛。
  3. 这三件套一个个安装需要逐个输入命令,还有软件版本对应和依赖问题,以及权限及相关配置,很麻烦。后来就有人就编写了LAMP或者LNMP一键安装命令脚本。比如以下是LAMP的一键安装脚本。
wget -c ftp://soft.vpser.net/lnmp/lnmp1.3-full.tar.gz && tar zxf lnmp1.3-full.tar.gz && cd lnmp1.3-full && ./install.sh lamp

上面只是解决了安装的问题,关于网站后面还有很多维护工作需要——网站监控、数据库备份、日志记录等等。 如果有专门的服务器管理软件就省事多了。因此服务器管理软件(俗称面板)就出现了。

面板产品对比

面板的出现已经有很长历史了,bt面板、wdcp面板、plesk面板、amh面板、c panel。国外用C panel比较多,国内用BT宝塔面板。

C panel面板这个比较古老了,国外VPS厂商用的多。多数是英文版,并且操作起来不太符合国人操作习惯。

BT面板是国产的,原生支持中文,官网介绍“提升运维效率的服务器管理软件,支持一键LAMP/LNMP/集群/监控/网站/FTP/数据库/JAVA等100多项服务器管理功能。”还有他是免费的,收费功能针对技术支持等业务。

所以直接用bt面板来建网站和管理网站了。


bt面板官方网站

[bt面板官网] (https://www.bt.cn/?invite_code=MV9lcWR5emw=)。安装bt面板之后,注册账号,网站后台可以跟微信绑定,方便网站管理等。

具体安装过程可以查看 [阿里云服务器快速建网站_安装BT宝塔面板和wordpress] (https://zhuanlan.zhihu.com/p/137713506)

作为新手来说,免费版bt可满足大部分网站管理需求——网站搭建、运维监控、维护、备份数据库、备份网站、内存清理等等。



关于面板和原生Linux争议

有人说面板的出现,“简直是开历史倒车,把Linux的B格大大降低,一点都没有极客风,面板小白都可以操作”,因此对面板深恶痛绝。对此我就呵呵了。 现代社会节奏那么快,不是那么多人有大把时间能沉下心思来把Linux系统从头到家系统学习一遍的。还怀念以前读书时候,还专门花上几个月来把Linux的系统学习。像《鸟哥的 Linux 私房菜-基础篇·第三版》、《The Linux Command Line》(William E. Shotts Jr)、《深入理解 Linux 内核》( Daniel P.Bovet / Marco Cesati)。 以前年轻时候,也不怎么做笔记,都是背命令。但是尴尬的地方在于,等到年纪大了,发现以前的命令记得不太多了,毕竟老话说得好“好记性不如烂笔头”。另外不是专门干Linux运维,如果不怎么用,这些命令很快就会遗漏。对这个没体会的,可以试试会议一下以前你的小学同学、中学同学,还能记起几个人名字?自然就有体会了。

算了,不叨叨那么多了。

当然也可以学习一下bt面板的脚本,学习为自己所用嘛,这就是这篇文章的由来。

所以,面板的出现,还是降低了不少小白上手Linux门槛的,所以面板的历史很早就出现了。而且正因为bt面板符合国人习惯,并且是免费的,集成了一大堆网站维护工具,另外还有专门的收费类服务解决个人和企业难题,bt面板从2014年开始到现在那么流行。



对了,在使用bt面板之前得要有个云服务器吧。这里推荐阿里云的云服务器(Elastic Compute Service,简称ECS)。根据其官网介绍是阿里云提供的性能卓越、稳定可靠、弹性扩展的IaaS(Infrastructure as a Service)级别云计算服务。

ECS即类似于国外的VPS。(不要好奇那么多人买国外VPS干嘛用)。架设个人博客网站、企业门户都可以使用ECS。如果其网站如个人博客主要面向国内用户访问,为加快速度还是建议选用国内的服务器商。

[阿里云域名] (https://wanwang.aliyun.com/domain/com/?userCode=yos4xyvp)连接,

[阿里云服务器ECS] (https://www.aliyun.com/minisite/goods?userCode=yos4xyvp) 链接

为什么选择阿里云

  1. 以前域名注册一般选老牌域名服务商,其中万网已经被阿里收购到旗下了。
  2. 提供域名备案服务。备案业务还是很贴心的。提交完备案信息之后,第二天阿里云小姐姐会帮你免费形式审查一下,还会主动打电话过来帮助校正。如果是自己动手提交备案信息给ICP备案机构,万一因为低级错误被驳回就浪费了十天左右时间。
  3. 域名ICP备案需要服务器,阿里云也提供服务器购买,一条龙服务嘛。服务器在阿里云毕竟服务器才是支出大头,域名什么的都是小意思了。而且购买完服务器之后,还会有客服主动打电话过来询问使用情况,需不需要技术支持,还是挺不错的。
  4. 阿里云服务器购买新用户有优惠,最基础的话一年下来不超过100元。如果是本科生的话免费使用的。
  5. 备案完成之前,服务器不算租赁时间。比如说3月1日我购买了一年的服务器,域名提交备案。18日域名备案审核通过。服务器租赁时间重新按18日算起,即可以用到第二年3月18日。相当于免费多用几天。占了一点小便宜。


bt面板脚本

它的脚本有以下:

  1. 释放内存
  2. 日志分割
  3. 备份网站


一、释放内存

#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="释放内存!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'

for phpV in 52 53 54 56 70 71 72
do
    if [ -f /etc/init.d/php-fpm-${phpV} ];then
            /etc/init.d/php-fpm-${phpV} reload
    fi
done

if [ -f "/etc/init.d/mysqld" ];then
    /etc/init.d/mysqld reload
fi

if [ -f "/etc/init.d/nginx" ];then
    /etc/init.d/nginx reload
fi

if [ -f "/etc/init.d/httpd" ];then
    /etc/init.d/httpd graceful
fi
# httpd重新加载
​
if [ -f "/etc/init.d/pure-ftpd" ];then
    pkill -9 pure-ftpd
    sleep 0.3
    /etc/init.d/pure-ftpd start 2>/dev/null
fi
# pkill -9 接进程名,杀死进程。
# 2>/dev/null的意思就是将标准错误stderr删掉。
​
​
sync
# sync 强制把文件系统buff写入磁盘,确保文件系统完整性。
sleep 2
sync
echo 3 > /proc/sys/vm/drop_caches
# 释放内存之前需要执行sync命令。sync 强制把文件系统buff写入磁盘,确保文件系统完整性。
# /proc是一个虚拟文件系统。
# 调整/proc/sys/vm/drop_caches来释放内存。
# 其默认值为0,值为1时表示可以释放pagecache缓存,值为2时可以释放pagecache和inode缓存,值为3时可以释放pagecache(页面缓存,磁盘块的任何内存映射)), dentries(目录的数据结构)和inodes(文件的数据结构)缓存。
# echo 3 > /proc/sys/vm/drop_caches,即调整其数值为3来释放内存(包括页面缓存、目录数据结构、文件数据结构等)。
echo '----------------------------------------------------------------------------'
#exit 0


CentOS 6.x 以前的版本中,系统的服务 (services) 启动的接口是在/etc/init.d/ 这个目录下,目录下的所有文件都是 scripts。

虽然/etc/init.d/* 这个脚本启动的方式 (systemV) 已经被新一代的 systemd 所取代 (从 CentOS 7 开始), 但是很多的个别服务在管理他们的服务启动方面,还是使用 shell script 的机制。

下面有nginx(nginx发音:恩静埃克斯 = Engine X)、mysqld、php-fpm-72等文件。


日期显示

endDate=`date +"%Y-%m-%d %H:%M:%S"` 
  1. 注意bash脚本赋值中,变量后紧跟等于号,等于号之后不能有空格,否则会报错。因为shell会误以为变量是命令,而不是赋值。
  2. 单引号是全引用,被单引号括起的内容不管是常量还是变量都不会发生替换;双引号会替换表达式内变量。
  3. `反引号括起来的字符串被shell解释为命令行,在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个反引号(包括两个反引号)部分。如date +"%Y-%m-%d %H:%M:%S"就是把这条命令的结果赋给变量endDATE。
  4. date +"%Y-%m-%d %H:%M:%S"能显示 2019-12-12 15:32:59


mysqld重新加载

if [ -f "/etc/init.d/mysqld" ];then
    /etc/init.d/mysqld reload
fi

通过reload命令,将其清理内存。

reload (重新加载),reload会重新加载配置文件,服务不会中断。而且reload时会测试conf语法等,如果出错会rollback用上一次正确配置文件保持正常运行。也叫平滑重启,不会对已经连接的服务造成影响。

httpd重新加载

if [ -f "/etc/init.d/httpd" ];then
    /etc/init.d/httpd graceful
fi

要在重启 Apache 服务器时不中断当前的连接,则应运行:/usr/local/sbin/apachectl graceful

ftpd重新加载

if [ -f "/etc/init.d/pure-ftpd" ];then
    pkill -9 pure-ftpd
    sleep 0.3
    /etc/init.d/pure-ftpd start 2>/dev/null
fi
​

pkill -9 接进程名,杀死进程。

睡眠0.3秒后,重新启动ftpd,并且2>/dev/null的意思就是将标准错误stderr删掉。

释放内存

sync
echo 3 > /proc/sys/vm/drop_caches
​
  1. 释放内存之前需要执行sync命令。sync 强制把文件系统buff写入磁盘,确保文件系统完整性。
  2. /proc是一个虚拟文件系统,一般可以通过调整/proc/sys/vm/drop_caches来释放内存。 drop_caches其默认值为0,值为1时表示可以释放pagecache缓存(页面缓存,磁盘块对应的内存映射),值为2时可以释放pagecache和inode缓存(文件的数据结构),值为3时可以释放pagecache, dentries(目录的数据结构)和inodes缓存。 echo 3 > /proc/sys/vm/drop_caches,即调整其数值为3来释放内存。


二、日志分割

#!/usr/bin/python
#coding: utf-8

import sys
import os
import shutil
import time
import glob
print '=================================================================='
print '★['+time.strftime("%Y/%m/%d %H:%M:%S")+'],切割日志'
print '=================================================================='
​
print '|--当前保留最新的['+sys.argv[2]+']份'

​
logsPath = '/www/wwwlogs/'
oldFileName = logsPath+sys.argv[1]
if not os.path.exists(oldFileName):
    print '|---'+sys.argv[1]+'文件不存在!'
    exit()
​
​
logs=sorted(glob.glob(oldFileName+"_*"))
count=len(logs)
num=count - int(sys.argv[2])
​
for i in range(count):
    if i>num:
        break;
    os.remove(logs[i])
    print '|---多余日志['+logs[i]+']已删除!'
​
newFileName=oldFileName+'_'+time.strftime("%Y-%m-%d_%H%M%S")+'.log'
# time.strftime("%Y/%m/%d %H:%M:%S")生成为 '2019-12-12_200605'
​
shutil.move(oldFileName,newFileName)
if os.path.exists('/www/server/nginx/logs/nginx.pid'):
    os.system("kill -USR1 `cat /www/server/nginx/logs/nginx.pid`");
​
# cat /www/server/nginx/logs/nginx.pid 显示nginx的pid进程号。因为nginx启动会在该路径下生成nginx.pid文件。
# `反引号括起来的字符串被shell解释为命令行,在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个反引号(包括两个反引号)部分。
# kill -USR1 中,信号量USR1代表重新打开文件。其在core/ngx_config.h中定义
    #if (NGX_LINUXTHREADS)
    #define NGX_REOPEN_SIGNAL        INFO
    #define NGX_CHANGEBIN_SIGNAL     XCPU
    #else
    #define NGX_REOPEN_SIGNAL        USR1
    #define NGX_CHANGEBIN_SIGNAL     USR2
    #endif
​
​
else:
    os.system('/etc/init.d/httpd reload');
print '|---已切割日志到:'+newFileName

这个脚本里面核心部分是

if os.path.exists('/www/server/nginx/logs/nginx.pid'):
    os.system("kill -USR1 `cat /www/server/nginx/logs/nginx.pid`");

cat /www/server/nginx/logs/nginx.pid 显示nginx的pid进程号。因为nginx启动会在该路径下生成nginx.pid文件。 `反引号括起来的字符串被shell解释为命令行,在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个反引号(包括两个反引号)部分。

kill -USR1 中,信号量USR1代表重新打开文件。其在core/ngx_config.h中定义

#if (NGX_LINUXTHREADS)
#define NGX_REOPEN_SIGNAL        INFO
#define NGX_CHANGEBIN_SIGNAL     XCPU
#else
#define NGX_REOPEN_SIGNAL        USR1
#define NGX_CHANGEBIN_SIGNAL     USR2
#endif


备份网站

通过Python运行/www/server/panel/script/目录下的backup.py脚本,将网站www.xxx.com保存。保存备份数为3。

#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
python /www/server/panel/script/backup.py site www.xxx.com 3

其调用的backup.py文件如下

#!/usr/bin/python
#coding: utf-8
#-----------------------------
# 宝塔Linux面板网站备份工具
#-----------------------------
​
import sys,os
reload(sys)
sys.setdefaultencoding('utf-8')
#Python2.x中由于str和byte之间没有明显区别,经常要依赖于defaultencoding来做转换。 
# 在python3中有了明确的str和byte类型区别,从一种类型转换成另一种类型要显式指定encoding。
# 但是仍然可以使用这个方法代替 
# import importlib,sys 
# importlib.reload(sys)
​
os.chdir('/www/server/panel');
sys.path.append("class/")
# /www/server/panel/class 里有大量的模块文件,public.py, db.py。
# 通过改变路径方便下面导入模块。
import public,db,time
# 是class文件夹的模块文件
​
class backupTools:
​
    def backupSite(self,name,count):
        sql = db.Sql();
        path = sql.table('sites').where('name=?',(name,)).getField('path');
        #sql.table(self,table)设置表名
​
        #def where(self,where,param):
        #    self.__OPT_WHERE = " WHERE " + where
        #    self.__OPT_PARAM = param
        # WHERE name=? 
​
        #getField(self,keyName):#取回指定字段
        #result = self.field(keyName).select();
​
        startTime = time.time();
        if not path:
            endDate = time.strftime('%Y/%m/%d %X',time.localtime())
            #'2019/12/12 20:41:01'
            # %Y 年份2019  %m 月份  %d天  %X 标准的时间串
​
            log = "网站["+name+"]不存在!"
            print "★["+endDate+"] "+log
            print "----------------------------------------------------------------------------"
            return;
​
        backup_path = sql.table('config').where("id=?",(1,)).getField('backup_path') + '/site';
        if not os.path.exists(backup_path): public.ExecShell("mkdir -p " + backup_path);
​
        filename= backup_path + "/Web_" + name + "_" + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.tar.gz'
        public.ExecShell("cd " + os.path.dirname(path) + " && tar zcvf '" + filename + "' '" + os.path.basename(path) + "' > /dev/null")
​
​
        endDate = time.strftime('%Y/%m/%d %X',time.localtime())
​
        if not os.path.exists(filename):
            log = "网站["+name+"]备份失败!"
            print "★["+endDate+"] "+log
            print "----------------------------------------------------------------------------"
            return;
​
        outTime = time.time() - startTime
        pid = sql.table('sites').where('name=?',(name,)).getField('id');
        sql.table('backup').add('type,name,pid,filename,addtime,size',('0',os.path.basename(filename),pid,filename,endDate,os.path.getsize(filename)))
        log = "网站["+name+"]备份成功,用时["+str(round(outTime,2))+"]秒";
        public.WriteLog('计划任务',log)
        print "★["+endDate+"] " + log
        print "|---保留最新的["+count+"]份备份"
        print "|---文件名:"+filename
​
        #清理多余备份     
        backups = sql.table('backup').where('type=? and pid=?',('0',pid)).field('id,filename').select();
​
        num = len(backups) - int(count)
        if  num > 0:
            for backup in backups:
                public.ExecShell("rm -f " + backup['filename']);
                sql.table('backup').where('id=?',(backup['id'],)).delete();
                num -= 1;
                print "|---已清理过期备份文件:" + backup['filename']
                if num < 1: break;

    def backupDatabase(self,name,count):
        sql = db.Sql();
        path = sql.table('databases').where('name=?',(name,)).getField('path');
        startTime = time.time();
        if not path:
            endDate = time.strftime('%Y/%m/%d %X',time.localtime())
            log = "数据库["+name+"]不存在!"
            print "★["+endDate+"] "+log
            print "----------------------------------------------------------------------------"
            return;
​
        backup_path = sql.table('config').where("id=?",(1,)).getField('backup_path') + '/database';
        if not os.path.exists(backup_path): public.ExecShell("mkdir -p " + backup_path);
​
        filename = backup_path + "/Db_" + name + "_" + time.strftime('%Y%m%d_%H%M%S',time.localtime())+".sql.gz"
​
        import re
        mysql_root = sql.table('config').where("id=?",(1,)).getField('mysql_root')
​
        mycnf = public.readFile('/etc/my.cnf');
        rep = "\[mysqldump\]\nuser=root"
        sea = "[mysqldump]\n"
        subStr = sea + "user=root\npassword=" + mysql_root+"\n";
        mycnf = mycnf.replace(sea,subStr)
        if len(mycnf) > 100:
            public.writeFile('/etc/my.cnf',mycnf);
​
        public.ExecShell("/www/server/mysql/bin/mysqldump --opt --default-character-set=utf8 " + name + " | gzip > " + filename)
​
        if not os.path.exists(filename):
            endDate = time.strftime('%Y/%m/%d %X',time.localtime())
            log = "数据库["+name+"]备份失败!"
            print "★["+endDate+"] "+log
            print "----------------------------------------------------------------------------"
            return;
​
        mycnf = public.readFile('/etc/my.cnf');
        mycnf = mycnf.replace(subStr,sea)
        if len(mycnf) > 100:
            public.writeFile('/etc/my.cnf',mycnf);
​
        endDate = time.strftime('%Y/%m/%d %X',time.localtime())
        outTime = time.time() - startTime
        pid = sql.table('databases').where('name=?',(name,)).getField('id');
​
        sql.table('backup').add('type,name,pid,filename,addtime,size',(1,os.path.basename(filename),pid,filename,endDate,os.path.getsize(filename)))
        log = "数据库["+name+"]备份成功,用时["+str(round(outTime,2))+"]秒";
        public.WriteLog('计划任务',log)
        print "★["+endDate+"] " + log
        print "|---保留最新的["+count+"]份备份"
        print "|---文件名:"+filename
​
        #清理多余备份     
        backups = sql.table('backup').where('type=? and pid=?',('1',pid)).field('id,filename').select();
​
        num = len(backups) - int(count)
        if  num > 0:
            for backup in backups:
                public.ExecShell("rm -f " + backup['filename']);
                sql.table('backup').where('id=?',(backup['id'],)).delete();
                num -= 1;
                print "|---已清理过期备份文件:" + backup['filename']
                if num < 1: break;
​
    #连接FTP
    def connentFtp(self):
        from ftplib import FTP
        ftp=FTP() 
        ftp.set_debuglevel(0)
        ftp.connect('192.168.1.245','21')
        ftp.login('uptest','admin')
        ftp.cwd('test')
        bufsize = 1024
        return ftp;
​
    #上传文件
    def updateFtp(self,filename):
        ftp = self.connentFtp();
        file_handler = open(filename,'rb')
        ftp.storbinary('STOR %s' % os.path.basename(filename),file_handler,bufsize)
        ftp.set_debuglevel(0) 
        file_handler.close() 
        ftp.quit()
​
    #从FTP删除文件
    def deleteFtp(self,filename):
        ftp = self.connentFtp();
        ftp.delete(filename);
        return True;
​
    #获取列表
    def getList(self):
        ftp = self.connentFtp();
        return ftp.nlst();
​
​
​
if __name__ == "__main__":
    backup = backupTools()
# python /www/server/panel/script/backup.py site www.pythondaquan.com 3
    type = sys.argv[1];
    if type == 'site':
        backup.backupSite(sys.argv[2], sys.argv[3])
    else:
        backup.backupDatabase(sys.argv[2], sys.argv[3])



作者的其他回答:

[自己拥有一台服务器可以做哪些很酷的事情]

[阿里云服务器快速建网站 (安装BT宝塔面板和wordpress)]、

阿里云域名注册与备案、服务器ECS购买与登录

[七牛图床添加阿里云域名]、

[markdown多平台发布及七牛图床使用]

举报
评论 0