doc:n:web_服务器实战笔记_nginx_php-fpm_mariadb_in_jail

Web 服务器实战笔记:Nginx + PHP-FPM + MariaDB in Jail

作者:Siroh siroh@sohu.com

几个月前,曾受托对为某区域性媒体单位的网站原服务器进行漏洞修补和调整 优化,该服务器为CentOS系统,但是设置明显是初级水平,安全漏洞比比皆是。 鉴于网站不能停机时间过长,无法进行彻底修补,同时服务器已经运行6年多未 进行软硬件升级,因此建议其购买新硬件,重新安装、设置,测试成功后,直 接替换原服务器。更换下来的原服务器可以重新配置成为紧急备份服务器,定期 从主服务器接受更新数据,保持与主服务最多相差一天的状态。

要求和思路

要求

运行某基于 php + mysql 的 CMS 系统提供新闻等内容服务,无论坛等互动功能。

思路

  • FreeBSD:笔者熟悉,zfs 提供数据容错和读取并发性能,jial 提供 Web 运行环境隔离,freebsd-update、pkg 方便升级和维护
  • ZFS-Mirror:获得数据的容错性,并利用快照功能进行备份、恢复和回滚
  • Jail:隔离Web服务软件环境
  • sshd:只允许密钥登录
  • sftp:取代ftp,提供安全上传环境
  • ipfw:防火墙提供网络安全
  • nginx + php-fpm + mariadb:提供web服务
  • awstats:定时运行生成静态页面提供访问分析
  • 全部采用 pkg 安装官方包,方便日后维护

硬件条件

  • 四核CPU
  • 2*16G 内存
  • 2*500G 硬盘

宿主系统安装

过程省略,不再详细叙述。安装过程中选择自动设置文件系统,选择两个硬盘做 zfs-mirror。 以下都使用系统安装时默认的存储池名 zroot 和文件系统名称。以下几点建议,供参考。

  • 服务器硬件时钟设置为 UTC 时间,时区设置为服务器所在时区,中国为 CST。
  • DNS服务器设置为 8.8.8.8 和 8.8.4.4,原因嘛,你懂的。
  • 主机名要求区别于运行 Web 服务环境的 Jail 客户机名。假定域名为 domain.com,server.domain.com 是比较好选择,方便配置时区分宿主机和客户机。

安装完成后,运行 freebsd-update fetch && freebsd-update install 命令更新系统。

系统设置

启用内核模块

在 /boot/loader.conf 中添加

aio_load="YES"
accf_data_load="YES"
accf_http_load="YES"

aio 用于提高 zfs 上 nginx 性能,accept filters 对于防范 DoS 攻击有一定帮助。 参看 Nginx Secure Web Server with HTTP, HTTPS SSL and Reverse Proxy Examples

ntpd

在 /etc/rc.conf 中添加

ntpd_enable="YES"
ntpd_sync_on_start="YES"

修改 /etc/ntp.conf 服务器,访问就近 NTP 服务器,提高时间同步精度和速度。

server 0.asia.pool.ntp.org iburst
server 1.asia.pool.ntp.org iburst
server 2.asia.pool.ntp.org iburst
server 3.asia.pool.ntp.org iburst

创建远程管理员用户

一定要加入 wheel 组!否则远程登录后,不能切换至 root 用户

root@server:~ # pw groupadd user
root@server:~ # pw useradd sysadmin -m -g user -G wheel,operator  # 一定要加入 wheel 组!
root@server:~ # su -l sysadmin -c "ssh-keygen -q -N some_password; mv ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys"

下载 id_rsa 后删除

root@server:~ # su -l sysadmin -c "rm ~/.ssh/id_rsa"

下载的私钥可以使用 putty 提供的 puttygen 转换成 ppk 格式密钥,使用 putty 等 ssh 等客户端远程登录

sshd

在 /etc/rc.conf 中添加

sshd_enable="YES"
sshd_flags="-4"        # 可以用 -p 选项指定另外的侦听端口

编辑 /etc/ssh/sshd_config,添加并修改如下参数

# /etc/ssh/sshd_config
AuthenticationMethods publickey     # 指定认证方式为密钥。感谢 alphachi 的指正
                                    # 如果设置为 publickey,keyboard-interactive,则要求密钥和交互式密码双重认证 
UseDNS no                           # 不使用 DNS 反向解析客户端主机名,加快连接速度

配置 Web 服务环境

建立 jail 客户机

创建客户机 zfs 分区

创建 jail 环境所使用的 zfs 分区

root@server:~ # zfs create -o mountpoint=/opt/jail zroot/jail
root@server:~ # zfs create zroot/jail/www  # Web 服务独立环境分区

安装客户机系统

安装 jial 客户机的系统,解压安装光盘或USB映像中的 /usr/freebsd-dist/base.txz,并更新客户机系统

root@server:~ # mount_cd9660 /dev/cd0 /mnt # 或者 mount /dev/da0a /mnt
root@server:~ # tar -xf /mnt/usr/freebsd-dist/base.txz -C /opt/jail/www  # -C /opt/jail/www 指定客户机根目录
root@server:~ # freebsd-update -b /opt/jail/www fetch install            # -b /opt/jail/www 指定客户机根目录

编辑 /etc/jail.conf, 加入如下内容:

# /etc/jail.conf
allow.raw_sockets = 1;   # 允许客户机 ping 外部主机,安装调试时可以打开。正式上线后应当关闭。
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.clean;
mount.devfs;

path = "/opt/jail/$name";

www {   # www 为客户机名称
        host.hostname = "www.domain.com";   # 服务器的主机名,使用DNS注册过的名字
        ip4.addr = "x.x.x.x";               # 采用与宿主机相同的 IP 地址,可无需 NAT,以简化配置
}

在 /etc/rc.conf 添加如下内容,以便系统启动时自动启动 jial 客户机

jail_enable="YES"
jail_list="www"   # 指定启动特定的 jail 客户机,如果只有一个客户机或者启动全部客户机,可以不要这项

客户机配置

客户机 rc.conf 配置

现在 jail 客户机就已经安装好了。因为客户机和宿主机使用相同的 ip 地址,因此在启 动 jail 客户机之前,需要进行一些设置,以免内外服务冲突。在 /opt/jail/www/etc/rc.conf 添加如下内容

# /etc/rc.conf 
sshd_enable="YES"        # 打开客户机 sshd,专门提供 sftp 服务,以便网站管理员上传文件
sshd_flags="-4 -p 9922"  # 修改 sshd 端口为9922或其他端口,避免与宿主机 sshd 冲突。"-4" 仅启用 ipv4 地址上的侦听
syslogd_flags="-ss"      # syslogd 不侦听 ip 端口。因为宿主机 syslogd 运行于 ip 侦听状态
sendmail_enable="NONE"   # 禁用所有 sendmail 进程
客户机计划任务配置

编辑 /opt/jail/www/etc/crontab,禁用 adjkerntz 计划任务

# /etc/crontab
# 1,31    0-5     *       *       *       root    adjkerntz -a
启动客户机

启动 jail 客户机,切换至客户机环境

root@server:~ # service jail start
root@server:~ # jexec www tcsh
设置时区

运行 tzsetup,设置时区。

设置域名解析服务器

编辑 /etc/resolv.conf

# /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4

安装软件包

根据 CMS 和 awstats 的要求,需要安装 php、mysql 和 perl 等软件包

root@www:/ # pkg update    # 更新 pkg 仓库
root@www:/ # pkg install -y awstats p5-Geo-IP p5-Geo-IP-PurePerl \
? mariadb55-server nginx \
? php5-gd php5-mysql php5-session \
? php5-tokenizer pecl-zendopcache

因为考虑到网站属于商业应用,为避免可能风险,采用 MariaDB 代替 Mysql,兼 容性极佳,使用上几乎没有差别。

pecl-zendopcache 是 php 的加速器,是否安装不影响 php 的运行,建议安装。

建立网站和数据库 zfs 分区

退出客户机环境在宿主机环境中,建立数据库和网站所需要的 zfs 分区

root@server:~ # zfs create -o exec=off -o setuid=off zroot/jail/www/opt
root@server:~ # zfs create zroot/jail/www/opt/www                       # 用于保存不同虚拟主机数据
root@server:~ # zfs create zroot/jail/www/opt/www/www.domain.com        # 特定的虚拟主机数据目录
root@server:~ # zfs create zroot/jail/www/opt/www/www.domain.com/html   # 特定的虚拟主机网站根目录
root@server:~ # zfs create zroot/jail/www/opt/data
root@server:~ # zfs create zroot/jail/www/opt/data/mysql                # mariadb 数据库目录
root@server:~ # zfs create zroot/jail/www/opt/log                       # 用于保存日志

完成之后切换至客户机环境。以下除非特别说明,都在客户机环境中操作。

root@server:~ # jexec www tcsh

创建上传用户

创建上传用户组

root@www:/ # pw groupadd vhost -g 5001     # 设置 gid 为 5001,以区别宿主机用户组

创建上传用户,指定 uid 为 5001,加入 vhost 组;指定虚拟主机数据目录为主目录,禁止登录。同时创建项目,设置权限

root@www:/ # pw useradd vhost_www -u 5001 -g vhost -d /opt/www/www.domain.com -s /usr/sbin/nologin -w no
root@www:/ # mkdir /opt/www/www.domain.com/{.ssh,backup,data,tmp}
root@www:/ # chown vhost_www:vhost /opt/www/www.domain.com/*    # /opt/www/www.domain.com 所有者和组必须是 root:wheel
root@www:/ # mkdir /opt/log/www.domain.com                      # 保存虚拟主机日志
root@www:/ # chown www:www /opt/log/www.domain.com
root@www:/ # mkdir /opt/data/ssl                                # 保存虚拟主机证书

创建上传用户 ssh 登录密钥

root@www:/ # ssh-keygen -N "newpassword" -C "vhost_www@www.domain.com" -f /opt/www/www.domain.com/.ssh/id_rsa
root@www:/ # mv /opt/www/www.domain.com/.ssh/id_rsa.pub /opt/www/www.domain.com/.ssh/authorized_keys
root@www:/ # chown vhost_www:vhost /opt/www/www.domain.com/.ssh/*

下载并删除私钥 id_rsa。下载的私钥可以使用 putty 提供 的 puttygen 程序转换成 ppk 格式的私钥,提供给 sftp 客户端使用,推荐 winscp,支持多重验证。

sshd

修改 /etc/ssh/sshd_config,修改或添加一下内容

# /etc/ssh/sshd_config
AuthenticationMethods publickey
UseDNS no
Subsystem sftp internal-sftp        # 使用内建的 sftp

Match Group vhost                   # 匹配用户组为 vhost 的用户
        ChrootDirectory %h          # chroot 至用户主目录
        ForceCommand internal-sftp  # 仅允许 sftp

MariaDB

在 /etc/rc.conf 添加如下内容

# /etc/rc.conf
mysql_enable="YES"
mysql_dbdir="/opt/data/mysql"

本例CMS应用中无需数据库事务功能,故无需使用 innodb 数据库引擎。如果要在 zfs 上使用 innodb 数据库引擎,应当根据 innodb 的要求在创建数据库分区时指定 recordsize 的值。详细介绍,请另行查找。

创建 Mariadb 数据可配置文件 /opt/data/mysql/my.cnf,添加如下内容。注意这些参数适用于 MariaDB 5.5 和 MySQL 5.5,其他版本参数可能不同,请阅读特定版本的文档。

# /opt/data/mysql/my.cnf
[mysqld]
skip-networking                   # 不侦听 ip 端口,仅使用 socket
thread_concurrency = 4            # 并发数,等于或 2 倍 CPU 核心数
character_set_server = utf8       # 默认编码 utf-8
default-storage-engine = MyISAM   # 默认数据库引擎为 MyISAM
innodb = OFF                      # 关闭 innodb 引擎

启动 MariaDB,设置所有用户初始密码,应当在未创建其他用户之前设置。创建网站所需要的数据库和用户

root@www:/ # service mysql-server start
root@www:/ # mysql
MariaDB [(none)]> UPDATE mysql.user set Password=PASSWORD("newpassword");   # 设置所有初始账号的密码为 newpassword
MariaDB [(none)]> CREATE DATABASE vhost_www;                                # 创建一个名为 vhost_www 的数据库
MariaDB [(none)]> GRANT ALL PRIVILEGES ON vhost_www.* TO                    # 创建 vhost_www 用户,并授予 vhost_www
               -> vhost_www@"localhost" IDENTIFIED BY "vhostpassword";      # 设置密码权限,设置密码为 vhostpassword
MariaDB [(none)]> flush privileges;                                         # 刷新权限
MariaDB [(none)]> exit

PHP

在 /etc/rc.conf 添加如下内容

# /etc/rc.conf
php_fpm_enable="YES"
全局配置 php-fpm.conf

创建 /usr/local/etc/php-fpm 目录,编辑 /usr/local/etc/php-fpm.conf 删除 Pool Definitions 中所有内容,添加如下内容

# /usr/local/etc/php-fpm.conf
include = etc/php-fpm/*.conf      # 加载 /usr/local/etc/php-fpm 目录下的配置文件

虚拟机池配置

创建 /usr/local/etc/php-fpm/www.domain.com.conf, 参照 /usr/local/etc/php-fpm.conf-dist, 主 要参数设置如下

; Pool name: www.domain.com
[www.domain.com]
user = www
group = www

listen = /tmp/php-fpm-$pool.sock        # 使用 /tmp/php-fpm.www.domain.com.sock,而不是侦听 ip 端口

listen.owner = www
listen.group = www
listen.mode = 0660

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

php_admin_value['open_basedir'] = /opt/www/$pool   # 限制 php 访问本地磁盘路径,类似的对不同的 Pool 设置不同的值

php.ini

root@www:/ # cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

复制一份 php.ini,根据实际需要修改 safe_mode、disable_functions 等相关设置。建议修改 date 相关的参数

# /usr/local/etc/php.ini
date.timezone = Asia/Shanghai      # 默认时区
date.default_latitude = 32         # 纬度,北纬为正,建议使用服务器所在城市的经纬度
date.default_longitude = 118       # 经度,东经为正

启动 php-fpm

root@www:/ # service php-fpm start

Nginx

启用 nginx

在 /etc/rc.conf 中,添加

nginx_enable="YES"

全局配置 nginx.conf

创建 /usr/local/etc/nginx/vhost 目录,编辑 /usr/local/etc/nginx/nginx.conf 删除 所有 server 定义,相应位置上添加 include vhost/*.conf;

# /usr/local/etc/nginx/nginx.conf
worker_processes  auto;   #  尝试自动检测或者指定具体数值

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    
    aio             on;   # 需要加载aio模块 aio_load="YES"
    sendfile        off;  # 在 ZFS 上应当关闭,参见 https://calomel.org/nginx.html
    #tcp_nopush     on;   # 具体含义见手册,暂不开启

    keepalive_timeout  65;

    #gzip  on;     # 这里用于设置是否开启数据压缩,根据需要设置
    
    open_file_cache          max=1000 inactive=20s;    # 打开缓存
    open_file_cache_valid    30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors   on;
    
    include vhost/*.conf;  # 加载 /usr/local/etc/nginx/vhost 目录下的虚拟主机配置文件
}

虚拟主机配置

创建 /usr/local/etc/nginx/vhost/www.domain.com.conf,定义 server。同时开 启 HTTP 和 HTTPS 服务。HTTPS 主要用于 CMS 后台管理。一般访问仍然使用 HTTP。

# /usr/local/etc/nginx/vhost/www.domain.com.conf
server {
    listen       80 accept_filter=httpready;               # 启用 http accept filters
    listen       443  ssl spdy accept_filter=dataready;    # 在 443 端口上启动 https 侦听,启用 data accept filters,支持 SDPY
    server_name  www.domain.com;
    root /opt/www/$server_name/html;
    index  index.php index.html index.htm;

    #charset utf-8;

    ssl_certificate      /opt/ssl/www.domain.com.crt;
    ssl_certificate_key  /opt/ssl/www.domain.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;   # 禁用 SSLv3
    ssl_prefer_server_ciphers on;
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;

    access_log  /opt/log/www.domain.com/access.log  main;
    error_log   /opt/log/www.domain.com/error.log;

    location /awstats/ {    # http://www.domain.com/awstats 访问 awstats 分析结果
        alias   /opt/awstats/$server_name/html/;
        index awstats.$server_name.html;   # awstats 静态页面默认文件名
        access_log off;
    }

    location /awstatsicons/ {    # http://www.domain.com/awstatsicons  awstats 图标 URL
        alias   /usr/local/www/awstats/icons/;
        access_log off;
    }

    location /admin {            # 强制使用 https 访问 CMS 程序的后台管理目录
        if ( $server_port = 80 ) {
            rewrite ^(/admin.*) https://$http_host$1 permanent;
        }
    }
    
    location ~ \.(gif|jpg|jpeg|png|bmp|ico)$ {
           expires 30d;
    }

    # pass the PHP scripts to FastCGI server via unix socket
    #
    location ~ \.php$ {
       fastcgi_pass   unix:/tmp/php-fpm-$server_name.sock;   # php-fpm socket
       fastcgi_index  index.php;
       fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
       include        fastcgi_params;
    }
}

创建虚拟主机 HTTPS 证书

本例中,HTTPS 只是用于 CMS 后台管理,没有必要使用商业证书。自 己签发证书就可以了。首先建立相应目录和文件,生成根证书

root@www:/ # cd ~
root@www:~ # mkdir -p demoCA/newcerts
root@www:~ # touch demoCA/index.txt
root@www:~ # echo "01" > demoCA/serial
root@www:~ # openssl genrsa -des3 -out domain.com.ca.key 2048            # domain.com 域 CA 私钥,2048 位
root@www:~ # openssl req -new -x509 -days 7305 -key domain.com.ca.key -out domain.com.ca.crt  # # domain.com 域 CA 证书
root@www:~ # openssl genrsa -des3 -out www.domain.com.pem 1024           # www.domain.com 私钥,密码保护
root@www:~ # openssl rsa -in www.domain.com.pem -out www.domain.com.key  # www.domain.com 私钥
root@www:~ # openssl req -new -key www.domain.com.pem -out www.domain.com.csr  # 签名请求,Common Name
root@www:~ # openssl ca -policy policy_anything -days 1460 \             # www.domain.com 证书
? -cert domain.com.ca.crt -keyfile domain.com.ca.key \
? -in www.domain.com.csr -out www.domain.com.crt
root@www:~ # mv www.domain.com.crt www.domain.com.key /opt/data/ssl

启用 nginx,编辑 /etc/rc.conf,添加

# /etc/rc.conf
nginx_enable="YES"

测试配置,如无误,则启动 nginx

root@www:~ # nginx -t 
root@www:~ # service nginx start

日志分割

Nginx 不支持日志分割,为便于日志管理和 awstats 进行分析,要进行日志 分割。可以通过 newsyslog 来实现日志分割。本例中,虚拟主机日志是分离 的,创建一个虚拟主机就需要增加一个条目。反而不如定期运行脚本实现所有 虚拟主机 nginx 日志分割。

创建一个名为 /opt/tool/nginx_logrotate 脚本,赋予运行权限

#!/bin/sh
# nginx_logrotate -- Script to ratate nginx logs

BASE_DIR=/opt/log     # 日志主目录
EXPIRE_TIME=14d            # 日志过期时间,时间参数的表示方式和 date 相同
                           # 14d 表示保留14天日志

ACCESS_LOG=access.log      # 配置文件中的日志名称
ERROR_LOG=error.log
NGINX=/usr/local/sbin/nginx

LOG_DATE=`/bin/date -v-1d "+%Y%m%d"`
EXPIRE_DATE=`/bin/date -v-$EXPIRE_TIME -v-1d "+%Y%m%d"`

ROTATED_ACCESS_LOG=`/usr/bin/basename $ACCESS_LOG .log`.${LOG_DATE}.log  # access.YYYYMMDD.log
ROTATED_ERROR_LOG=`/usr/bin/basename $ERROR_LOG .log`.${LOG_DATE}.log


for dir in `/bin/ls ${BASE_DIR}`
do
    if [ -d ${BASE_DIR}/$dir ]
    then
        if [ -f ${BASE_DIR}/$dir/${ACCESS_LOG} ]
        then
            /bin/mv ${BASE_DIR}/$dir/${ACCESS_LOG} ${BASE_DIR}/$dir/${ROTATED_ACCESS_LOG}
        fi
        if [ -f ${BASE_DIR}/$dir/${ERROR_LOG} ]
        then
            /bin/mv ${BASE_DIR}/$dir/${ERROR_LOG} ${BASE_DIR}/$dir/${ROTATED_ERROR_LOG}
        fi
    fi
    for file in `/bin/ls ${BASE_DIR}/$dir`
    do
        logfile_date=`echo $file | /usr/bin/awk -F"." '{print $2}'`
        if [ $logfile_date -lt $EXPIRE_DATE ]
        then
            /bin/rm ${BASE_DIR}/$dir/$file
        fi
    done
done

${NGINX} -s reopen  # 重新创建日志

运行 crontab -u root -e,添加计划任务条目,定于每日零时进行日志分割

#minute hour    mday    month   wday    command
0       0       *       *       *       /bin/sh /opt/bin/nginx_logrotate

安装 CMS

过程略。主要注意的是,大多数 CMS 需要对某些目录具有写权限。开放写权限的原则是,更改 可写目录的用户和用户组为 php-fpm 池的用户和用户组,例如:www:wwww,而不是设置为 777 权限。

CMS 程序数据库配置时,主机名使用 localhost,而不是 127.0.0.1 的 ip,否则 php 无法 连接 MariaDB 的 socket。

awstats

设置输出 html 编码 UTF-8

转换 /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt 编码

root@www:~ # mv /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt-dist
root@www:~ # iconv -f gbk -t utf-8 /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt-dist \
? > /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt

修改 PageCode 值为 UTF-8

# /usr/local/www/awstats/cgi-bin/lang/awstats-cn.txt
PageCode=UTF-8

获取 ip 地理信息数据库

获取并安装 GeoIP.dat 、GeoLiteCity.dat 和 qqhostinfo 插件

root@www:~ # mkdir /usr/local/www/awstats/data
root@www:~ # fetch http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
root@www:~ # fetch http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
root@www:~ # gunzip GeoIP.dat.gz
root@www:~ # gunzip GeoLiteCity.dat.gz
root@www:~ # cp GeoIP.dat GeoLiteCity.dat /usr/local/www/awstats/data
root@www:~ # fetch http://www.openwares.net/downloads/qqhostinfo.pm
root@www:~ # fetch http://www.openwares.net/downloads/ip_geo_qqwry.zip
root@www:~ # unzip ip_geo_qqwry.zip  # 可能需要安装 unzip,pkg install -y unzip
root@www:~ # cp qqhostinfo.pm ip_geo_qqwry.pl /usr/local/www/awstats/cgi-bin/plugins

修改 /usr/local/www/awstats/cgi-bin/plugins/ip_geo_qqwry.pl

# /usr/local/www/awstats/cgi-bin/plugins/ip_geo_qqwry.pl
      my $qqwry_dat = "/usr/local/www/awstats/data/qqwry.dat";

qqwry.dat 无法直接下载,访问 http://www.cz88.net/down/76250/ 下载“QQ IP数 据库 纯真版”,在 Windows 系统安装后,把安装目录中的 qqwry.dat 复制或上传到客户机 的 /usr/local/www/awstats/data 目录中。

日志分析配置

创建名为 /opt/tool/nginx_awstats 脚本,赋予运行权限

#!/bin/sh
# nginx_awstats: Script to analyze nginx logs and build static pages

LOG_BASE_DIR=/opt/log
AWSTATS_DATA_BASE_DIR=/opt/awstats
AWSTATS_INSTALL_DIR=/usr/local/www/awstats

for dir in `/bin/ls ${LOG_BASE_DIR}`
do
    if [ -d ${LOG_BASE_DIR}/$dir ]
    then
        if [ -f ${LOG_BASE_DIR}/$dir/access.log ]
        then
            if [ ! -d ${AWSTATS_DATA_BASE_DIR}/$dir/data ]
            then
                /bin/mkdir -p ${AWSTATS_DATA_BASE_DIR}/$dir/data
            fi
            if [ ! -d ${AWSTATS_DATA_BASE_DIR}/$dir/html ]
            then
                /bin/mkdir -p ${AWSTATS_DATA_BASE_DIR}/$dir/html
            fi

            ${AWSTATS_INSTALL_DIR}/cgi-bin/awstats.pl -config=$dir -update
            ${AWSTATS_INSTALL_DIR}/tools/awstats_buildstaticpages.pl \
                -config=$dir -lang=cn -dir=${AWSTATS_DATA_BASE_DIR}/$dir/html -staticlinks
        fi
    fi
done

运行 crontab -u root -e,添加计划任务条目,定于每日 1:30 分析日志生成静态结果页面

#minute hour    mday    month   wday    command
30       1       *       *       *      /bin/sh /opt/tool/nginx_awstats

创建虚拟主机配置文件

root@www:/ # mkdir /usr/local/etc/awstats
root@www:/ # cd /usr/local/www/awstats
root@www:/usr/local/www/awstats # tools/awstats_configure.pl

----- AWStats awstats_configure 1.0 (build 20140126) (c) Laurent Destailleur -----
This tool will help you to configure AWStats to analyze statistics for
one web server. You can try to use it to let it do all that is possible
in AWStats setup, however following the step by step manual setup
documentation (docs/index.html) is often a better idea. Above all if:
- You are not an administrator user,
- You want to analyze downloaded log files without web server,
- You want to analyze mail or ftp log files instead of web log files,
- You need to analyze load balanced servers log files,
- You want to 'understand' all possible ways to use AWStats...
Read the AWStats documentation (docs/index.html).

-----> Running OS detected: Linux, BSD or Unix

-----> Check for web server install

Enter full config file path of your Web server.
Example: /etc/httpd/httpd.conf
Example: /usr/local/etc/apache(22/24)/httpd.confExample: /usr/local/apache2/conf/httpd.conf
Example: c:\Program files\apache group\apache\conf\httpd.conf
Config file path ('none' to skip web server setup):
> none # 不是 Apache

Your web server config file(s) could not be found.
You will need to setup your web server manually to declare AWStats
script as a CGI, if you want to build reports dynamically.
See AWStats setup documentation (file docs/index.html)

-----> Update model config file '/usr/local/www/awstats/cgi-bin/awstats.model.conf'
  File awstats.model.conf updated.

-----> Need to create a new config file ?
Do you want me to build a new AWStats config/profile
file (required if first install) [y/N] ? y  # 创建新的配置文件

-----> Define config file name to create
What is the name of your web site or profile analysis ?
Example: www.mysite.com
Example: demo
Your web site, virtual server or profile name:
> www.domain.com   # 主机名

-----> Define config file path
In which directory do you plan to store your config file(s) ?
Default: /etc/awstats
Directory path to store config file(s) (Enter for default):
> /usr/local/etc/awstats  # 配置文件目录

-----> Create config file '/usr/local/etc/awstats/awstats.www.domain.com.conf'
 Config file /usr/local/etc/awstats/awstats.www.domain.com.conf created.

-----> Add update process inside a scheduler
Sorry, configure.pl does not support automatic add to cron yet.
You can do it manually by adding the following command to your cron:
/usr/local/www/awstats/cgi-bin/awstats.pl -update -config=www.domain.com
Or if you have several config files and prefer having only one command:
/usr/local/www/awstats/tools/awstats_updateall.pl now
Press ENTER to continue...


A SIMPLE config file has been created: /usr/local/etc/awstats/awstats.www.domain.com.conf
You should have a look inside to check and change manually main parameters.
You can then manually update your statistics for 'www.domain.com' with command:
> perl awstats.pl -update -config=www.domain.com
You can also build static report pages for 'www.domain.com' with command:
> perl awstats.pl -output=pagetype -config=www.domain.com

Press ENTER to finish...

配置文件 /usr/local/etc/awstats/awstats.www.domain.com.conf 生成,还需要编辑 修改一些参数

# /usr/local/etc/awstats/awstats.www.domain.com.conf
LogFile="/opt/log/www.domain.com/access.%YYYY-24%MM-24%DD-24.log"   # 分析昨天日志
DNSLookup=0              # 不反向解析客户端名称
DirData="/opt/awstats/www.domain.com/data"    # 分析数据存储位置
SkipHosts="localhost REGEX[^127\.0\.0\.] REGEX[^192\.168\.] REGEX[^172\.(1[6-9]|2[0-9]|3[01])\.] REGEX[^10\.]"   # 忽略本地访问
LoadPlugin="geoip GEOIP_STANDARD /usr/local/www/awstats/data/GeoIP.dat"   # 启用 ip 归属地,国家
LoadPlugin="geoip_city_maxmind GEOIP_STANDARD /usr/local/www/awstats/data/GeoLiteCity.dat"  # 启用 ip 归属地,城市
LoadPlugin="qqhostinfo"   # 启用 qqwry.dat 查询 ip 归属地

防火墙设置

特别提示,远程设置防火墙时,要格外小心。不要把自己给拦在外面。 有一个小技巧,在测试阶段,启用或测试防火墙之前,可以使用如下命令,创建 一个任务,在 5 分钟后,卸载 ipfw 内核模块。万一有错误,你还有救。

root@server:~ # at + 5 minutes
kldunload ipfw
^D                  # Ctrl + D

访问控制要求

本例中,选用 ipfw 防火墙,用于保护单机 Web 服务器足够了,配置也非常方便。根据以上 设置,列出以下允许的网络访问:

  • 本机到外部的访问
    • dns - tcp/udp - 53
    • ntp - tcp/udp - 123
    • http/https - tcp - 80/443:需要日志记录
    • ping
  • 外部到本机的访问
    • sshd - tcp - 22
    • sshd(jail) - tcp - 9922:Jail 客户机 sshd 侦听端口
    • http/https - tcp - 80/443

防火墙规则

创建防火墙规则脚本 /etc/ipfw.rules,此部分得到lsstarboy建议改写静态规则,以避免大量访问下防火墙创建过多动态规则。

# /etc/ipfw.rules

dns="8.8.8.8, 8.8.4.4"

IPFW=/sbin/ipfw

cmd="$IPFW -q add"

# Flush out the list before we begin.
$IPFW -q -f flush

# No restrictions on Loopback Interface
$cmd 00010 allow all from any to any via lo0

# Allow access to public DNS
$cmd 00110 allow tcp from me to $dns 53
$cmd 00111 allow tcp from $dns 53 to me
$cmd 00120 allow udp from me to $dns 53
$cmd 00121 allow udp from $dns 53 to me

# Allow access to public NTP
$cmd 00210 allow tcp from me to any 123
$cmd 00211 allow tcp from any 123 to me
$cmd 00220 allow udp from me to any 123
$cmd 00221 allow udp from any 123 to me

# Allow and log outbound HTTP and HTTPS connections
$cmd 00510 allow log logamount 0 tcp from me to any 80
$cmd 00511 allow tcp from any 80 to me
$cmd 00550 allow log logamount 0 tcp from me to any 443
$cmd 00551 allow tcp from any 443 to me

# Allow outbound ping
$cmd 00901 allow icmp from me to any icmptypes 8
$cmd 00902 allow icmp from any to me icmptypes 0

# Allow inbound SSH connections, allow 2 connections of each remote host
$cmd 01410 allow tcp from any to me 22 limit src-addr 2
$cmd 01450 allow tcp from any to me 9922 limit src-addr 2

# Allow HTTP connections to internal web server
$cmd 01510 allow tcp from any to me 80
$cmd 01511 allow tcp from me 80 to any
$cmd 01550 allow tcp from any to me 443
$cmd 01551 allow tcp from me 443 to any

# Deny and log all other outbound connections
$cmd 50000 deny log logamount 0 all from me to any

默认状态下,ipfw 总是添加

65535 deny ip from any to any

阻止一切不符合前面规则的网络包,除非你通过 firewall_type=“open” 或者定制内核 改变了防火墙的默认规则。

启用防火墙,在 /etc/rc.conf 中添加

# /etc/rc.conf
firewall_enable="YES"
firewall_script="/etc/ipfw.rules"

防火墙日志

启用防火墙日志,在 /etc/sysctl.conf 中添加

# /etc/sysctl.conf
net.inet.ip.fw.verbose=1

指定日志文件位置和文件名,在 /etc/syslog.conf 中添加

# /etc/syslog.conf
!ipfw
*.*     /var/log/ipfw.log

每日零时分割防火墙日志,保留 7 天日志,在 /etc/newsyslog.conf 中添加

# /etc/newsyslog.conf
/var/log/ipfw.log   600  7   1000000  @T00  JC  # 1000000,以 kB 为单位,表示日志最大为 1 G

维护

ZFS Snapshot

服务器测试完毕,上线之前,做一次整个快照

root@server:~ # zfs snapshot -r zroot@first

创建脚本 /opt/bin/zfs_snapshot,授予 755 权限。每月 1 日创建整个 zfs 快照,保留 12 个 快照;每周日创建 jail 客户机快照,保留 4 个;每天创建虚拟主机和数据库快照,保留 7 个。

#!/bin/sh
# /opt/bin/zfs_snapshot

ZFS_POOL="zroot"
ZFS_JAIL="$ZPOOL/jail"
ZFS_VHOST="$ZPOOL/jail/www/opt/www"
ZFS_DATABASE="$ZPOOL/jail/www/opt/data"

TODAY=`/bin/date "+%Y%m%d"`
ONE_YEAR_BEFORE=`/bin/date -v-1y "+%Y%m%d"`
ONE_WEEK_BEFORE=`/bin/date -v-1w "+%Y%m%d"`
FOUR_WEEKS_BEFORE=`/bin/date -v-4w "+%Y%m%d"`

[ `/bin/date "+%d"` -eq 1 ] && FISRT_DAY_OF_MONTH="yes"
[ `/bin/date "+%a"` = "Sun" ] && SUNDAY="yes"

snap_pool () {
    if [ $FISRT_DAY_OF_MONTH ]
    then
        /sbin/zfs snapshot -r "$ZFS_POOL@$TODAY"
        /sbin/zfs destroy -r "$ZFS_POOL@$ONE_YEAR_BEFORE" >> /dev/null 2>&1
    fi
}

snap_jail () {
    if [ $SUNDAY ]
    then
        /sbin/zfs snapshot -r "$ZFS_JAIL@$TODAY"
        /sbin/zfs destroy -r "$ZFS_JAIL@$FOUR_WEEKS_BEFORE" >> /dev/null 2>&1
    fi
}

snap_data () {
    /sbin/zfs snapshot -r "$ZFS_VHOST@$TODAY"
    /sbin/zfs destroy -r "$ZFS_VHOST@$ONE_WEEK_BEFORE" >> /dev/null 2>&1
    /sbin/zfs snapshot -r "$ZFS_DATABASE@$TODAY"
    /sbin/zfs destroy -r "$ZFS_DATABASE@$ONE_WEEK_BEFORE" >> /dev/null 2>&1
}

/usr/sbin/service jail stop
snap_pool
snap_jail
snap_data
/usr/sbin/service jail start

建立计划任务,运行 crontab -u root -e,添加

#minute hour    mday    month   wday    command
0       3       *       *       *       /opt/bin/zfs_snapshot

其他设置

ZFS

prefetch

ZFS prefetch 对于大并发量的针对不同文件的读操作来说通常不会带来好处。 而 Web 服务恰恰属于此种情景,编辑 /boot/loader.conf 关闭 zfs 的 prefetch

# /boot/loader.conf
vfs.zfs.prefetch_disable=1

ARC

实际上是 ZFS 的文件系统的cache,可自动伸缩。ZFS 会尽可能多地使用物 理内存作为cache。是否需要限制 ARC 的大小,应当根据实际情况加以决定, 以避免与 nginx、php、mariadb 竞争内存。如要限制,/boot/loader.conf 添加

# /boot/loader.conf
vfs.zfs.arc_max="8G" 

tmpfs

启用 tmpfs 加载到 /tmp,会使临时文件创建生成更快,也会与 zfs 和其他 程序竞争内存,如何取舍应当根据实际情况决定。如要启用,/boot/loader.conf 添加

# /boot/loader.conf
tmpfs_load="YES"

在 /etc/fstab 中,添加

# /etc/fstab
# Device Mountpoint FStype  Options         Dump    Pass#
tmpfs    /tmp       tmpfs   rw,size=512M    0       0      # size 参数限制 tmpfs 的大小

结束语

本文是按照笔者实际安装服务器过程编写的笔记。欢迎指正,交流。

/data/vhosts/wiki-data/pages/doc/n/web_服务器实战笔记_nginx_php-fpm_mariadb_in_jail.txt · 最后更改: 2016/12/02 09:02 由 iheaing