R·ex / Zeng


音游狗、安全狗、攻城狮、业余设计师、段子手、苦学日语的少年。 MUGer, hacker, developer, amateur UI designer, punster, Japanese learner.

纸飞机服务器环境搭建笔记

注意:本文发布于 1510 天前,文章中的一些内容可能已经过时。

大概去年这个时候,某只三三打算把纸飞机服务器重新搞一下,当然结果是比以前好多了。嗯,以前的架构基本没法看……然而当时三三的文件已经不知道去哪里了,而且我在这一年折腾的过程中,又发现了一些别的问题。

纸飞机服务器是几年前买的小主机,配置挺低,尤其是硬盘太小。其次,学校的网络环境实在太差,像微信后台这样的项目如果放到我们自己的服务器中,延迟可以达到五分钟,不知道是什么原因,所以只能放到SAE上了。纸飞机只有一个域名:my.nuaa.edu.cn,信息中心给开放的端口只有两个:2280,而前者用来做校内ssh用,也就是说其实只有一个80端口可用。

前辈们写的代码大多数停留在“可用”的阶段,没有太多重构,静态数据没有分离,甚至有时会出现两个项目相互影响的情况。备份脚本已经好久没人动过了,而且纸飞机是七年多之前从论坛起家,针对discuz的备份脚本已经无法适应目前的需求。因此,结合实际情况,我又自己YY出了一套东西,应该比现在的好一点。

好了言归正传。

这次我们的主角是CentOS7,其实本来去年就打算换7的,然而当时我们比较保守,认为7相对于6的变化实在太大(例如连ifconfigiptables都换掉了)因此没有实现。这次我准备好好研究一下。


1. 安装CentOS7 Minimal

这个就不多说了,官网下镜像然后安装。

2. 配置网络

有了网络,什么都好说!首先先看一下服务器网卡的名称。由于CentOS7改了命名机制,有线网卡并不是用eth0eth1作为名称了。

ip addr show

看到下面有那么一行:

2: eno16777736: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000

心里想,虽然我用的是虚拟机,但是这网卡名称eno16777736也太特么长了吧!我一边说着卧槽,一边输入了如下指令:

vi /etc/sysconfig/network-scripts/ifcfg-eno16777736

首先ONBOOT=yes设置开机自动配置,然后将BOOTPROTO=dhcp等号右边换成static,然后在下面添加如下几句话(xxx是我故意隐去的数字):

IPADDR=222.192.100.21
PREFIX=xxx
GATEWAY=222.192.xxx.xxx
DNS1=218.104.80.77
DNS2=222.119.64.123

然后用一句ifup eno16777736把网卡带起来,一句systemctl restart network,于是就可以上网啦!

3. 设置hostname

这个没什么好说的,直接执行如下指令:

echo "my.nuaa.edu.cn" > /etc/hostname

4. 升级系统

这个也没什么好说的,毕竟CentOS7已经出了快半年了,肯定有一些包被更新了。执行如下指令:

yum -y upgrade

当然,一般不要用-y选项,有个确认的步骤心里还是比较舒坦的。

5. 添加第三方软件源

其实下载源码编译安装也可以,但是我比较懒,因此喜欢用yum来装软件。这里需要添加一些额外的软件源,例如epelcommunityreminginx等。

# epel
yum install epel-release
# community
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
# remi
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
# nginx
rpm --import http://nginx.org/keys/nginx_signing.key
rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

6. 禁用selinux,安装firewall

selinux这玩意从一开始就一直妨碍我,如果启用,访问/var/www以外的文件都会提示403错误,于是就把它禁用掉好了,不知道禁用之后会有什么风险。

vi /etc/sysconfig/selinux

将其中的SELINUX=enforcing等号右边改为disabled即可。

然后总得有点什么防护措施吧,之前用的是iptables,在CentOS7中推荐使用firewall,但是我下载的镜像中好像没有这个程序,于是就通过yum来装啦!

yum -y install firewalld
systemctl enable firewalld
systemctl start firewalld

firewall默认的zonepublic,也就是说,除了允许的服务和端口以外,丢弃掉其它的连接。看了一下配置文件,发现ssh服务默认是允许通过的,于是就放心了。

7. 安装nginx并配置防火墙规则

直接输入如下命令:

yum install nginx
systemctl enable nginx

装完之后需要在firewall中开启对应的端口。由于纸飞机对外只有80端口,所以直接添加一个规则即可:

firewall-cmd --zone=public --add-port=80/tcp --permanent

8. 安装/配置MariaDB

MariaDB是一个新的数据库程序,但是操作起来跟MySQL几乎完全一样。在几年前Archlinux就用MariaDB替代了MySQL作为默认的数据库程序。

yum install mariadb-server
systemctl enable mariadb
systemctl start mariadb

然后设置一下MariaDB的安全配置。

mysql_secure_installation

在这个步骤中,我们会设置root账户的密码、删除匿名用户、锁定远程访问、删除test表等操作。

9. 安装/配置PHP

CentOS下面的PHP一直是个难题,因为目前PHP的版本是7.0,上一个稳定版是5.6,然而CentOS7下面的官方版本还是5.4,CentOS6下面的甚至是5.3!后者早就过期了,前者也快要停止支持了。还好有一个叫remi的软件源帮了大忙。remi源在刚才的步骤中已经添加过了,因此我们可以直接用yum来安装5.6版的PHP

yum -y install php56 php56-php-fpm php56-php-gd php56-php-mbstring php56-php-mcrypt php56-php-pdo php56-php-xml php56-php-pecl-memcache

当然,有的项目还需要其它PHP的扩展,例如如果需要tidy扩展的话,可以直接输入:

yum -y install php56-php-tidy

然后设置php-fpm服务为开机自动启动,并启动它:

systemctl enable php-fpm
systemctl start php-fpm

顺便提一下,nginxPHP的支持跟apache是有区别的,前者是两个服务(nginxphp-fpm),任务基本对半分,后者基本上apache占了大头(mod_php模块),PHP只有做解析的份儿。

然后对PHP做一下设置,在/etc/php.ini中添加如下两行(有的服务器可能配置文件不在这里,可以用php56 --ini命令来查看,第一行就有):

cgi.fix_pathinfo=0
date.timezone="Asia/Shanghai"

其中第一行是避免所谓的“nginx文件名解析漏洞”——事实上,这跟nginx并没毛线关系,是php-fpm的一个特性:例如在解析http://my.nuaa.edu.cn/foo.jpg/a.php/b.php/c.php的时候,nginx只是老老实实将请求转发到php-fpm中,然后php-fpm会依次尝试解析以下文件:

/foo.jpg/a.php/b.php/c.php
/foo.jpg/a.php/b.php
/foo.jpg/a.php
/foo.jpg

如果都没有,则返回404错误。为何上文强调“解析”两个字?因为如果foo.jpg是一段恶意代码,那么就会被php-fpm解析到,就会发生一些不好的事情。(好像比较新的php-fpm已经把它禁掉了。)

第二行则是设置默认时区,没什么好说的。

10. 配置服务器

由于CentOS7在用yumPHP的时候会附带上apache,于是先把后者禁掉:

systemctl disable httpd

然后配置nginx的设置文件。纸飞机目前有discuzevaluationmallssowiki等十六个项目,由于历史原因,discuz必须放在网站根目录/data/www中;由于只有一个域名和一个端口,所以mall等项目的网址必须是http://my.nuaa.edu.cn/mall/

当前纸飞机的服务器软件用的是apache,解决方案是在配置文件里设置DocumentRoot/data/httpd/discuz,然后给每一个项目设置一个Alias,例如Alias /mall /data/httpd/mall,然而这样太麻烦了。

折腾了好久,最终我写出了这样一份配置文件:

这是/etc/nginx/nginx.conf

http {
    include       /etc/nginx/mime.types;
    default_type  text/html;
    # 防止暴露服务器软件信息
    server_tokens off;
    log_format    main    '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    # 开启压缩
    gzip  on;
    include /etc/nginx/conf.d/*.conf;
    # 默认只能上传1M的文件,这里改成30M
    client_max_body_size 30m;
}

这是/etc/nginx/conf.d/mynuaa.conf

server {
    listen 80;
    server_name my.nuaa.edu.cn;
    root /data/www;
    index index.php index.html index.htm;
    # 允许解析符号链接
    disable_symlinks off;
    # GZip相关设置,防止有的文件压缩之后比原来还大
    gzip_min_length 1k;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;

    location /office {
        # 办公系统的网址,被我隐藏掉了
        proxy_pass http://xxx.sinaapp.com/;
    }

    # 禁止访问所有隐藏文件
    location ~ /\. {
        deny all;
    }

    location / {
        if (-e /data/www/discuz$uri) {
            root /data/www/discuz;
        }
        # Discuz!的重写规则
        rewrite ^([^\.]*)/topic-(.+)\.html$ $1/portal.php?mod=topic&topic=$2 last;
        rewrite ^([^\.]*)/article-([0-9]+)-([0-9]+)\.html$ $1/portal.php?mod=view&aid=$2&page=$3 last;
        rewrite ^([^\.]*)/forum-(\w+)-([0-9]+)\.html$ $1/forum.php?mod=forumdisplay&fid=$2&page=$3 last;
        rewrite ^([^\.]*)/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=viewthread&tid=$2&extra=page%3D$4&page=$3 last;
        rewrite ^([^\.]*)/group-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=group&fid=$2&page=$3 last;
        rewrite ^([^\.]*)/space-(username|uid)-(.+)\.html$ $1/home.php?mod=space&$2=$3 last;
        rewrite ^([^\.]*)/blog-([0-9]+)-([0-9]+)\.html$ $1/home.php?mod=space&uid=$2&do=blog&id=$3 last;
        rewrite ^([^\.]*)/(fid|tid)-([0-9]+)\.html$ $1/index.php?action=$2&value=$3 last;
        rewrite ^([^\.]*)/([a-z]+[a-z0-9_]*)-([a-z0-9_\-]+)\.html$ $1/plugin.php?id=$2:$3 last;
        location ~ \.php {
            set $my_root "/data/www";
            if (-e /data/www/discuz$uri) {
                set $my_root "/data/www/discuz";
            }
            try_files $uri =404;
            fastcgi_pass unix:/dev/shm/php-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $my_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    location /evaluation {
        alias /data/www/evaluation/public/;
        # Laravel的重写规则
        if (!-f $request_filename) {
            rewrite ^(.*)$ /evaluation/index.php?$query_string;
        }
        location ~ \.php {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/dev/shm/php-fpm.sock;
            fastcgi_param SCRIPT_FILENAME /data/www/evaluation/public/index.php;
            include fastcgi_params;
        }
    }

    access_log /var/log/nginx/access.log main;
    error_page 404 /404.html;
    error_page 500 502 503 504  /50x.html;
}

不知道有没有什么潜在的问题,还请大神们轻喷…… Update 1.6:果然有问题……已修改。

通过如下命令来确定服务器的CPU核数:

grep processor /proc/cpuinfo | wc -l

结果是8核,于是在/etc/nginx/nginx.conf中修改:

worker_processes 8;

最后当然是启动nginx服务:

systemctl start nginx

至此,运行环境已经配置完毕。接下来就是配置纸飞机的各个应用了。

11. 安装Git/配置webhook/部署应用

Git用来管理代码。

yum -y install git

由于我们的代码都托管在Coding.net上,之前在上面设置了webhook以及部署公钥,因此需要将原来服务器的~/.ssh/id_rsa~/.ssh/id_rsa.pub两个文件复制过来,这样就可以继续使用webhook功能啦!如何复制就不贴代码了。

然后将之前写好的webhook项目手动clone到/data/www目录下:

cd /data/www && sudo -u nginx git clone [email protected]:Click_04/webhooks.git

这是一个私有项目,如果之前的id_rsa文件没问题的话,此时应该成功clone下来了。然后我们将其中的config.sample.php复制一份,并重命名为config.php

cd webhooks && sudo -u nginx cp config.sample.php config.php

然后按照项目说明中的描述,输入网址,就可以clone项目以及pull更新啦!纸飞机有那么多项目,都clone下来还是挺麻烦的呢!

最后是每个项目的构建,例如五四评优用到了url rewrite的规则,需要在nginx的站点配置文件中添加如下几句:

location /evaluation {
    try_files $uri $uri/ /index.php?$query_string;
}

当然,有的项目也需要用到Composer,可以通过如下方式安装:

cd /tmp && wget http://install.phpcomposer.com/installer
php56 ./installer -- --install-dir=/usr/bin --filename=composer

顺便吐槽一句:这下载速度可真够慢的。

12. 导入应用数据

刚才只是把代码clone下来了,现在需要将应用的数据导入进来。安全起见,每个应用都是独立的数据库,并分配独立的账号来管理,保持每个账号权限最小化。在数据库中折腾一番之后终于好了。然后将服务器关停之前最后一次mysqldump生成的数据文件导入即可。

至于与代码无关的静态文件,在之前我已经将它们与代码分离开了,所以并不用担心。所有应用的静态文件都储存在/storage/app目录中。同时在/storage/lib目录中还有一个所有项目公用的静态库集合,例如jquery、mui等,这个也可以通过git仓库来随时更新。

13. 安装OneAPM监控

这个没啥好说的,按照OneAPM的操作来就是了。不过值得注意的一点是,需要先将/usr/bin/php替换为php56,然后填写PHP配置文件路径的时候填写/opt/remi/php56/root/etc/php.ini,否则会导致安装不成功。

14. 计划任务:数据库备份

目前的想法是:每周完整备份,每小时增量备份。使用Xtrabackup作为备份程序。如果服务器性能不太好的话,就需要添加对硬盘的IO限制。首先安装Xtrabackup

yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
yum install percona-xtrabackup-22

然后编写备份脚本:

#!/bin/bash

# database full backup
# author: rex
# using xtrabackup

USERNAME="root"
PASSWORD="root"
STORPATH="/backup/mysql"

# full backup
innobackupex --user=$USERNAME --password=$PASSWORD --no-timestamp $STORPATH/$(date +%Y%m%d)-full --throttle=80

# first incremental backup
LASTBACKUPDIR=$(date +%Y%m%d%H%M%S)
innobackupex --incremental $STORPATH/$LASTBACKUPDIR --no-timestamp --incremental-basedir=$STORPATH/$(date +%Y%m%d)-full --user=$USERNAME --password=$PASSWORD --defaults-file=/etc/my.cnf --throttle=80

# print to $STORPATH/lastbackupdir
echo $LASTBACKUPDIR > $STORPATH/lastbackupdir

这是增量备份脚本:

#!/bin/bash

# database incremental backup
# author: rex
# using xtrabackup

USERNAME="root"
PASSWORD="root"
STORPATH="/backup/mysql"

if [ ! -e $STORPATH/lastbackupdir ]; then
    exit 0
fi

LASTBACKUPDIR=$(date +%Y%m%d%H%M%S)
innobackupex --incremental $STORPATH/$LASTBACKUPDIR --no-timestamp --incremental-basedir=$STORPATH/$(cat $STORPATH/lastbackupdir) --user=$USERNAME --password=$PASSWORD --defaults-file=/etc/my.cnf

echo $LASTBACKUPDIR > $STORPATH/lastbackupdir

我把这两个脚本放到了/maintain/crontan文件夹下,系统升级等计划任务也可以写成.sh文件存放到这里,这样可以达到定期更新系统的效果。

关于计划任务,配置在/etc/crontab这个文件中。

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed
 30  *  *  *  * root       /maintain/crontab/dbincbackup
  0  0  *  *  1 root       /maintain/crontab/dbfullbackup
  *  *  *  *  * root       /maintain/crontab/dellocalroute


参考文章:

http://www.tecmint.com/things-to-do-after-minimal-rhel-centos-7-installation/

https://www.linux.cn/article-4314-1.html

https://www.percona.com/doc/percona-xtrabackup/2.2/installation/yum_repo.html

https://www.centos.bz/2013/09/percona-xtrabackup-mysql-backup/

版权声明:除文章开头有特殊声明的情况外,所有文章均可在遵从 CC BY 4.0 协议的情况下转载。
上一篇: JCTF2015非官方writeup
下一篇: 只是一个快乐的程序员罢了

这是我们共同度过的

第 1553 天