第 6-2 章 Linux httpd 服务进阶配置

作者: Brinnatt 分类: ARM64 Linux 进阶架构 发布时间: 2022-01-21 13:55

6.1、httpd 虚拟主机配置

httpd 使用 VirtualHost 指令进行虚拟主机的定义。支持三种虚拟主机:基于 ip,基于端口和基于名称。其中基于端口的虚拟主机在 httpd 的术语上也属于基于 IP 的。

当一个请求到达时,将首先匹配虚拟主机。匹配虚拟主机的规则为最佳匹配法。所谓最佳,是指通配的越少,匹配的优先级越高。

  • 例如 "192.168.0.99:80" 的优先级高于 "*:80"。
  • 如果基于名称的虚拟主机无法匹配上,则采用虚拟主机列表中的第一个虚拟主机作为响应主机。
  • 如果所有虚拟主机都无法匹配上,则采用从主配置段落中的主机,如果主配置段落中注释了 DocumentRoot,则返回对应的错误。

主配置段落的指令基本上都能使用在虚拟主机容器中。至于虚拟主机中必须配有什么指令,这没有规定,因为虚拟主机只是封装一组指令而已,即使其中没有任何指令,它也会从主配置段落中继承。

但是,既然要使用且已经使用了虚拟主机,按照常理来说,至少得提供不同的 ServerName,DocumentRoot 等指令以让它们各自独立。

最后需要说明的是,httpd 的 "-S" 选项在调试虚拟主机配置选项时非常有用。

6.1.1、基于 IP 的虚拟主机

基于 IP 的虚拟主机是在不同的 IP+PORT 上提供不同的站点服务,最常见的是在不同端口上提供不同站点。

如果仅基于 IP,使用不同 IP 地址,那么要求操作系统上有两个或更多 IP 地址,可以提供多个网卡,或者通过网卡别名来实现。

如果基于端口,使用不同端口,则使用相同 IP 或不同 IP 均可,但在 httpd 术语中,基于单个 IP 但不同端口的虚拟主机,也是基于 IP 的虚拟主机。

假设本机为 192.168.0.99:

# 首先设置个虚拟网卡。
[root@aarch64 ~]# ip add add 192.168.0.111 dev eth0 label eth0:0

# 添加基于IP地址的虚拟主机,DocumentRoot使用的相对路径,基于ServerRoot
[root@aarch64 ~]# vim /etc/apache/extra/vhosts.conf

<VirtualHost 192.168.0.99:80>
    ServerName www.a.com
    DocumentRoot htdocs/a.com
</VirtualHost>

<VirtualHost 192.168.0.111:80>
    ServerName www.b.com
    DocumentRoot htdocs/b.com
</VirtualHost>

在主配置文件中,将该虚拟主机配置文件 vhosts.conf 包含进去:

Include /etc/apache/extra/vhosts.conf

再提供 DocumentRoot 和各自的 index.html:

[root@aarch64 ~]# mkdir /usr/local/apache/htdocs/{a.com,b.com}
[root@aarch64 ~]# echo '<h1>a.com<h1>' >/usr/local/apache/htdocs/a.com/index.html
[root@aarch64 ~]# echo '<h1>b.com<h1>' >/usr/local/apache/htdocs/b.com/index.html

使用 httpd -S 查看配置文件加载过程:

[root@aarch64 ~]# /usr/local/apache/bin/httpd -S -f /etc/apache/httpd.conf
VirtualHost configuration:
192.168.0.111:80       www.b.com (/etc/apache/extra/vhosts.conf:6)
192.168.0.99:80        www.a.com (/etc/apache/extra/vhosts.conf:1)
ServerRoot: "/usr/local/apache"
Main DocumentRoot: "/usr/local/apache/htdocs"
Main ErrorLog: "/usr/local/apache/logs/error_log"
Mutex default: dir="/usr/local/apache/logs/" mechanism=default 
PidFile: "/usr/local/apache/logs/httpd.pid"
Define: DUMP_VHOSTS
Define: DUMP_RUN_CFG
User: name="daemon" id=2
Group: name="daemon" id=2
[root@aarch64 ~]#

重启 httpd:

service httpd restart

测试:

[root@aarch64 ~]# curl 192.168.0.99:80
<h1>a.com<h1>
[root@aarch64 ~]# 
[root@aarch64 ~]# curl 192.168.0.111:80
<h1>b.com<h1>
[root@aarch64 ~]#

注意:以上的实验是基于 httpd 源码编译实验后,进行的虚拟主机实验。httpd 源码编译后还有一些配置需要完善,请参见 httpd 源码编译

6.1.2、基于端口的虚拟主机

基于端口的虚拟主机需要监听两个套接字。

首先在配置文件中使用 Listen 指令修改监听套接字,这里假设只基于端口,所以只需修改端口号即可。

listen 80
listen 8080

修改虚拟主机配置文件 vhosts.conf 文件如下:

[root@aarch64 ~]# cat /etc/apache/extra/vhosts.conf 
<VirtualHost 192.168.0.99:80>
    ServerName www.a.com
    DocumentRoot htdocs/a.com
</VirtualHost>

<VirtualHost 192.168.0.99:8080>
    ServerName www.b.com
    DocumentRoot htdocs/b.com
</VirtualHost>
[root@aarch64 ~]#

重启httpd,测试 www.a.com 和 www.b.com 能否显示:

[root@aarch64 ~]# systemctl daemon-reload 
[root@aarch64 ~]# systemctl restart httpd
[root@aarch64 ~]# curl 192.168.0.99:80
<h1>a.com<h1>
[root@aarch64 ~]# curl 192.168.0.99:8080
<h1>b.com<h1>
[root@aarch64 ~]#

6.1.3、基于名称的虚拟主机

请求报文中获取资源时包含了两部分资源定位的格式:TCP/IP 协议和 HTTP 协议,虽然 TCP/IP 部分相同,但是 HTTP 协议的请求报文中指定了 HOST,这就是基于域名的虚拟主机能实现的原因。也因此,基于名称的虚拟主机必须指定 ServerName 指令,否则它将会继承操作系统的 FQDN。

[root@aarch64 ~]# cat /etc/apache/extra/vhosts.conf
<VirtualHost 192.168.0.99:80>
    ServerName www.a.com
    DocumentRoot htdocs/a.com
</VirtualHost>

<VirtualHost 192.168.0.99:80>
    ServerName www.b.com
    DocumentRoot htdocs/b.com
</VirtualHost>
[root@aarch64 ~]#

测试:为了方便测试,在没有 DNS 服务的前提下,需要在 hosts 文件中配置临时域名解析,然后重启服务,测试域名请求。

[root@aarch64 ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.0.99 www.a.com
192.168.0.99 www.b.com
[root@aarch64 ~]#
[root@aarch64 ~]# service httpd restart
Restarting httpd (via systemctl):                          [  OK  ]
[root@aarch64 ~]# 
[root@aarch64 ~]# curl www.a.com
<h1>a.com<h1>
[root@aarch64 ~]# curl www.b.com
<h1>b.com<h1>
[root@aarch64 ~]#
  • 注意:对于基于名称的虚拟主机,当使用 IP 地址请求(例如浏览器中输入的是 IP 地址),或者无法匹配到任何虚拟主机时,将采用第一个虚拟主机作为默认虚拟主机。
[root@aarch64 ~]# curl 192.168.0.99
<h1>a.com<h1>
[root@aarch64 ~]# echo "192.168.0.99 www.c.com" >> /etc/hosts   # 没有配置对应虚拟主机
[root@aarch64 ~]# curl www.c.com
<h1>a.com<h1>
  • 注意:ServerName 用于唯一标识提供 web 服务的主机名,只有在基于名称的虚拟主机中该指令才是必须提供的。

    • 如果不是在基于名称的虚拟主机中,可以任意指定该指令的值,只要你认为它能唯一标识你的主机。
    • 但如果不设置该指令,那么 httpd 在启动时,将会反解操作系统的 IP 地址。

6.2、httpd 网页身份认证

httpd 对 web 身份认证的支持很丰富,提供的控制也非常细致。无疑,功能丰富意味着模块多。关于完整的模块,见 https://httpd.apache.org/docs/2.4/mod/ ,其中 mod_authX_XXX 都是和认证有关的模块。

要实现最基本的帐户认证访问控制,只需几个常见的模块即可:mod_authz_core,mod_authz_user,mod_authz_host ... 。

6.2.1、htpasswd 命令

htpasswd 用于为指定用户生成基于网页用户身份认证的密码,由 httpd-tools 软件包提供。支持 3 种加密算法:MD5、SHA 和系统上的 crypt() 函数,不指定算法时,默认为 md5。

htpasswd [ -c ] [ -m ] [ -D ] passwdfile username
htpasswd -b [ -c ] [ -m | -d | -p | -s ] [ -D ] passwdfile username password
htpasswd -n [ -m | -d | -s | -p ] username
htpasswd -nb [ -m | -d | -s | -p ] username password
选项说明:
passwdfile:包含用户名及其密码的用户密码文件。如果使用了"-c"选项,则会创建或覆盖文件。不使用"-n"选项时必须指定passwdfile参数。
username:为指定的用户名创建密码。如果该用户记录已存在,则更新。
-c:创建用户密码文件passwdfile,如果文件已经存在则会覆盖已存在的文件。不能和"-n"一起使用。
-n:在标准输出中输出结果,而不是将其写入到用户密码文件中。该选项会忽略用户密码文件passwdfile参数。不能和"-c"选项一起使用。
-m:使用MD5加密算法。默认。
-d:使用crypt()函数计算密码,不安全。
-s:使用SHA加密算法。安全。
-P:强制不加密密码,保持明文状态,不安全。
-B:强制bcrypt加密密码,非常安全。
-D:从用户密码文件中删除指定的用户及其密码。
-b:使用批处理模式,即非交互模式,可以直接待加密的传递明文密码。
password:指定要输入的明文密码。只能在批处理模式中使用,即和"-b"一起使用。
  1. 使用 "-n" 选项直接将结果输出到标准输出而不创建 passwdfile。

    [root@aarch64 ~]# htpasswd -n brinnatt
    New password: 
    Re-type new password: 
    brinnatt:$apr1$LchJiq9J$JpMVs4SxFvXP.Wrt5/ETU1
    
    [root@aarch64 ~]#
  2. 使用批处理模式直接传递密码。

    [root@aarch64 ~]# htpasswd -nb brinnatt Firewall; htpasswd -nb brinnatt Firewall
    brinnatt:$apr1$f5FXdjZ2$hK3MzwAs2HV.oX9Kw5Evi0
    
    brinnatt:$apr1$ay7Tf9Wt$Sjp3FekQyNqOgwZwNNED5/
    
    [root@aarch64 ~]#
    • 发现密码完全是随机的。
  3. 创建用户密码文件 passwdfile。

    [root@aarch64 ~]# htpasswd -cb usercryfile brinnatt Firewall
    Adding password for user brinnatt
    [root@aarch64 ~]# cat usercryfile 
    brinnatt:$apr1$0E6btbxL$NuKbnLvfu.277/a8R5aMw1
    [root@aarch64 ~]#
  4. 删除用户文件中的某用户。

    [root@aarch64 ~]# htpasswd -D usercryfile brinnatt
    Deleting password for user brinnatt
    [root@aarch64 ~]# cat usercryfile 
    [root@aarch64 ~]#
  5. 使用 sha 和 md5 加密算法计算密码。

    [root@aarch64 ~]# htpasswd -sb usercryfile brinnatt Firewall
    Adding password for user brinnatt
    [root@aarch64 ~]# cat usercryfile 
    brinnatt:{SHA}SZotReXw4wRZa3tm9mgzEk8zAFg=
    [root@aarch64 ~]# 
    [root@aarch64 ~]# htpasswd -mb usercryfile Tina Passkey
    Adding password for user Tina
    [root@aarch64 ~]# cat usercryfile 
    brinnatt:{SHA}SZotReXw4wRZa3tm9mgzEk8zAFg=
    Tina:$apr1$mFxN/NhQ$VYNBh1D/1O2S2R/Q4ndGn0
    [root@aarch64 ~]#

6.2.2、身份认证类基本指令

AuthType:指定 web 身份认证的类型。有效值为 none、basic、digest 以及 form。通常最基本的认证使用的是文件认证,所以通常使用 basic。

AuthName:设置身份认证时的提示信息。

AuthUserFile file-path:指定 web 用户认证列表。由 htpasswd 命令生成。

AuthGroupFile file-path:指定组认证文件,文件中分组格式为 "mygroup: Jim Bob Alice"。如果文件路径为相对路径,则相对于 ServerRoot。

基于 basic 类型的认证就这么几个指令,最主要的还是 require 指令的使用。更多的认证方法见官方手册的 auth 类模块。

6.2.3、Require 指令

该指令只能放在 Directory 容器中,用于控制对目录的访问权限。它的主要功能是由 mod_authz_core 模块提供,但有些身份认证类模块也提供它额外的功能,这时它可以放在 < Directory >、< Files > 或 < Location > 容器中。

主要功能:

  • Require all granted:无条件允许所有人访问该目录
  • Require all denied:无条件拒绝所有人访问该目录
  • Require env env-var [env-var] ...:只有给定的环境变量 var-env 已经定义才允许访问该目录
  • Require method http-method [http-method] ...:只有给定的 HTTP 请求方法才允许访问该目录,如只允许 GET 才能访问
  • Require expr expression:只有给定的表达式为 true 才允许访问该目录

身份认证类模块提供的 require 指令功能包括:

  • mod_authz_user 为 require 指令提供的功能:
    • Require user userid [userid] ...:认证列表中只有指定的 userid 才能访问
    • Require valid-user:认证列表中的所有用户都可以访问
  • mod_authz_groupfile 为 require 指令提供的功能:
    • Require group group1 [group2] ...:指定组内的用户都可以访问
  • 本地文件系统身份参考类:
    • Require file-owner:要求 web 用户名必须和请求文件的 uid 对应的 username 完全相同
    • Require file-group:要求 web 用户名必须为请求文件的 gid 组中的一员
  • mod_authz_host 为 require 指令提供的 ip 和 host 功能:
    • Require ip 192.168.1.104 192.168.1.205
    • Require ip 10.1
    • Require ip 10 172.20 192.168.2
    • Require ip 10.1.0.0/255.255.0.0
    • Require ip 10.1.0.0/16
    • Require host www.example.org
    • Require host example.org
    • Require host .net example.edu
    • Require local

可以在 require 指令后紧跟 not 关键字,表示取反。例如 "require not group group1"、"require not local" 等。

还支持 require 条件容器,包括 < RequireAll >、< RequireAny > 和 < RequireNone >,当 require 指令没有写在任何 Require 容器中时,它们隐式包含在一个 < RequireAny > 容器中。

  • < RequireAll >:其内封装的 Require 指令必须全都不能失败,且至少有一个成功时,该容器成功。如果其内所有指令既不成功又不失败,则该容器中立。其余所有情况都会导致该容器失败。
  • < RequireAny >:其内封装的 Require 指令只要有一个成功,该容器就成功。如果其内所有指令既不成功又不失败,则该容器中立。其余所有情况(即全部失败时)都会导致该容器失败。
  • < RequireNone >:其内封装的 Require 指令只要有一个成功时该容器就失败,否则就中立。

6.2.4、web 身份认证示例

以最常见的 Basic 认证方式为例。支持基于用户的认证和基于组的认证。

6.2.4.1、基于用户的认证

先创建一个 web 用户及其密码列表文件。其内有 4 个用户:East、South、West 和 North。

[root@aarch64 ~]# htpasswd -cb /usr/local/apache/a_com.pass East EpassKey
Adding password for user East
[root@aarch64 ~]# htpasswd -b /usr/local/apache/a_com.pass South SpassKey
Adding password for user South
[root@aarch64 ~]# htpasswd -b /usr/local/apache/a_com.pass West WpassKey
Adding password for user West
[root@aarch64 ~]# htpasswd -b /usr/local/apache/a_com.pass North NpassKey
Adding password for user North
[root@aarch64 ~]# 
[root@aarch64 ~]# cat /usr/local/apache/a_com.pass 
East:$apr1$SnPs00ty$u/5llOVRdnmJ/fM8XAPBF1
South:$apr1$AxSf3CFN$PjbY2TSWe.lDHpGF7y.Dq.
West:$apr1$B5A7z4yL$MOQD1DGWkecdMCBfQ43eV0
North:$apr1$Ois62HWt$fFiXEzVcqf/q2mVim1AGL1
[root@aarch64 ~]#

修改 httpd 配置文件,假设只有 www.a.com 中的 a.com 目录才需要认证且只有 East 和 West 可以认证,而其他目录以及 www.b.com 不需要认证,其他用户认证不通过。

[root@aarch64 ~]# cat /etc/apache/extra/vhosts.conf
<VirtualHost 192.168.0.99:80>
    ServerName www.a.com
    DocumentRoot /usr/local/apache/htdocs/a.com
    <Directory /usr/local/apache/htdocs/a.com>
        AllowOverride Authconfig
        AuthType Basic
        AuthName "please enter your name & passwd"
        AuthUserFile a_com.pass
        Require user East West 
    </Directory>
</VirtualHost>

<VirtualHost 192.168.0.99:80>
    ServerName www.b.com
    DocumentRoot /usr/local/apache/htdocs/b.com
</VirtualHost>
[root@aarch64 ~]#
  • 此处 AuthUserFile 使用的相对路径,所以该文件必须放在 ServerRoot(我的测试环境 ServerRoot 为 /usr/local/apache)下。且 Require user 行可以替换为 "Require valid-user" 表示 a_com.pass 中的所有用户都允许认证。

然后重启 httpd,并修改客户端 hosts 文件:

[root@aarch64 ~]# service httpd restart
Restarting httpd (via systemctl):                          [  OK  ]
[root@aarch64 ~]#

编辑 windows C:\Windows\System32\Drivers\etc\hosts
192.168.0.99 www.a.com www.b.com

windows 测试访问:

httpd auth

6.2.4.2、基于组的认证

基于组的认证只需创建一个组文件,文件中包含的是组名和组中用户成员。

例如,将 South 和 North 加入到 allow 组,使它们也可以访问 a.com 目录。

[root@aarch64 ~]# echo 'allow:North South' > /usr/local/apache/auth_group

修改配置文件,例如:

[root@aarch64 ~]# cat /etc/apache/extra/vhosts.conf
<VirtualHost 192.168.0.99:80>
    ServerName www.a.com
    DocumentRoot /usr/local/apache/htdocs/a.com
    <Directory /usr/local/apache/htdocs/a.com>
        AllowOverride Authconfig
        AuthType Basic
        AuthName "please enter your name & passwd"
        AuthUserFile a_com.pass
        AuthGroupFile auth_group
        Require user East West 
        Require group allow
    </Directory>
</VirtualHost>

<VirtualHost 192.168.0.99:80>
    ServerName www.b.com
    DocumentRoot /usr/local/apache/htdocs/b.com
</VirtualHost>
[root@aarch64 ~]#

再重启 httpd 服务进行测试。

6.3、httpd 日志和日志轮替工具

需要记录的日志类型有:错误日志 ErrorLog、访问日志 CustomLog。错误日志一般采用默认即可,最多改下错误日志的存放路径,而 CustomLog 因为量比较多,很可能需要定制。

事实上,ErrorLog 由 httpd 的核心模块提供,而 CustomLog 却提供了专门的模块 mod_log_config 来处理,该模块还支持 TransferLog 指令,该指令和 CustomLog 作用和用法基本类似,如有需要可查官方手册,本文略过。

最后介绍两种日志轮替工具:apache httpd 自带的 rotatelogs 工具和 cronolog 工具。

6.3.1、错误日志 ErrorLog

错误日志的级别 LogLevel:debug, info, notice, warn(默认), error, crit, alert, emerg。

错误日志的定义语法:

ErrorLog file-path|syslog[:[facility][:tag]]

如果使用 syslog 替换 file-path,则表示采用系统自带的 syslog 日志工具,facility 是记录 syslog 日志的设施类型。一般来说,不会采用这种方法记录日志。

如果采用 file-path,则有两种方式:直接指定文件路径;前面使用管道,表示将输出的日志作为标准输入传递给管道后的日志处理程序,例如传递给 apache 自带的 rotatelogs 工具。例如:

ErrorLog "logs/error_log"
ErrorLog "|/usr/local/apache/bin/rotatelogs /var/log/error_log 86400" common

当然,对于 ErrorLog 来说,数据量并不会太多,一般直接使用文件记录即可。对于 CustomLog,则可以考虑使用日志切割工具进行分割、轮替等行为。

错误日志的记录格式由 ErrorLogFormat 指令控制,例如以下是 worker 和 event 模式下错误日志的默认记录格式,其中各参数代表的意义见官方手册 ErrorLogFormat

ErrorLogFormat "[%{u}t] [%-m:%l] [pid %P:tid %T] %7F: %E: [client\ %a] %M% ,\ referer\ %{Referer}i"

6.3.2、访问日志 CustomLog

使用 CustomLog 指令指定访问日志记录的位置,可以在同一个 host 下多次使用该指令表示同一条日志记录到多个位置。语法格式为:

CustomLog file|pipe format|nickname [env=[!]environment-variable| expr=expression]

使用 LogFormat 可以指定日志记录的事项,例如是否要记录客户端 IP 地址,是否要记录请求方法等。使用 LogFormat 还可以定义日志分类(在 httpd 术语中称为 nickname),例如 common 类、combined 类、combinedio 类。它支持非常弹性化的记录事项,具体的见官方手册 LogFormat

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common

<IfModule logio_module>
  # You need to enable mod_logio.c to use %I and %O
  LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>

然后使用 CustomLog 指令就可以使用已经定义的日志类。例如:

CustomLog "logs/access_log" combined

当然,使用 LogFormat 定义分类只是为了方便,CustomLog 指令完全可以不使用 nickname 而是直接定义要记录的事项。例如:

CustomLog "logs/access_log" "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""

如果提供 CustomLog 的第三个参数,即 env 或者 expr,则表示满足条件的请求才会记录日志。例如,当请求的是 gif 文件时将独立记录该日志,而其他请求则记录在另一个日志中。

SetEnvIf Request_URI \.gif$ gif-image
CustomLog "gif-requests.log" common env=gif-image
CustomLog "nongif-requests.log" common env=!gif-image

通常来说,除了特殊需求,一般都只需使用 LogFormat 提供的各个事项即可。

6.3.3、日志轮替 rotatelogs 工具

首先说明 rotatelogs 工具,它是 apache httpd 提供的一个基于时间间隔、大小切分日志的简单工具。

默认,它在启动时不会立即创建日志文件,而是在有请求到达时才会创建。同理在轮替时也如此,如果到达了轮替的时间间隔,如果没有新请求到达,则暂时不会创建新的日志文件。

如果使用 "-f" 选项,则在启动时,不管是否有没有请求,都立即创建日志文件。如果使用 "-c" 选项,则在每次轮替时无论是否有新请求到达,都创建新日志文件。

rotatelogs [ -l ] [ -f ] [ -t ] [ -v ] [ -e ] [ -c ] [ -n number-of-files ] logfile rotationtime|filesize(B|K|M|G)

选项说明:

  • logfile:指定日志记录路径。可以加上一些修饰符,例如时间类的 a.log_%Y.%m.%d 将在 a.log 后追加年、月、日的后缀,如果没有给定任何后缀修饰,则自动加上 ".nnnnnnnn",表示创建时的时间点换算成距离 1970-01-01 00:00:00 的总秒数,也就是 "date +%s",每次轮替时也会加上该后缀。如果使用相对路径,则相对于 ServerRoot。
  • -n:使用数字作为后缀,且表示轮替的循环列表,例如 "-n 3" 表示一直在 log.1、log.2、log.3 这三个文件中记录,不会再创建任何新的日志文件。
  • rotationtime:指定轮替的时间间隔。将初始化为对应时间格式的起始值。例如,设置为 3600 时,将表示在每个小时的开头进行轮替,尽管当前时间点进入下一个小时可能只有 5 分钟,在 5 分钟之后也会进行一次轮替。
  • filesize:以大小方式轮替日志。
  • -l:使用本地时间计算时间间隔。默认使用的是 UTC 时间。
  • -f:强制立即打开日志文件。因为有些时候在启动 httpd 时,可能一小段时间内没有任何请求到达,因为没有日志需要记录,所以暂时不会创建日志文件。使用该选项可立即创建文件。
  • -t:截断日志而不再是轮替日志。这种情况下,不会再添加任何文件后缀。
  • -v:详细记录轮替或截断时的信息。
  • -e:将日志也输出到标准错误输出中。当日志还需要被其他工具处理时,该选项有用。
  • -c:每个时间间隔到了都创建新文件,尽管没有日志到达。而默认的情况下,在轮替时间间隔到达时,如果没有日志到达,将暂时不会创建日志,而是等待第一个请求到达后才创建。

注意,如果出现了轮替时文件名重复的情况,例如按照 5M 大小进行轮替,但文件名的格式为 "a.log_%Y.%m.%d",如果同一天内出现了多次轮替,由于文件名相同,则会覆盖旧文件进行记录。

另外,rotatelogs 只能对日志文件名本身使用时间类修饰符,无法将其设置在目录上,否则启动 httpd 时会因为无法打开日志而报错。

示例:以下使用了日志管道 "|",表示记录的日志传递给后面的程序(此处为 rotatelogs) 进行处理。

每天轮替一次 $ServerRoot/logs/mylog.nnnnnnnn,其中 nnnnnnnn 是当前时间换算成秒的数值。且在启动时不立即创建日志文件,轮替时也不立即创建新日志文件。

CustomLog "|/usr/local/apache/bin/rotatelogs logs/mylog 86400" common

按照文件大小轮替。

CustomLog "|bin/rotatelogs /var/log/logfile 5M" common

轮替错误日志,达到 5M 时就轮替。

ErrorLog "|bin/rotatelogs /var/log/errorlog.%Y-%m-%d-%H_%M_%S 5M"

截断日志而非轮替日志。

CustomLog "|bin/rotatelogs -t /var/log/logfile 86400" common

以下是日期类的修饰符。下面的 cronolog 工具也采用这同一套修饰符。

%    %字符
n    换行
t    水平制表符

时间类:  
H    小时(00..23)
I    小时(01..12)
p    该locale下的AM或PM标识
M    分钟(00..59)
S    秒 (00..61, which allows for leap seconds)
X    该locale下时间表示符(e.g.: "15:12:47")
Z    时区。若时区不能确定,则无意义

日期类:  
a    该locale下的工作日简名(e.g.: Sun..Sat)
A    该locale下的工作日全名(e.g.: Sunday ..  Satur-ay)
b    该locale下的月份简称(e.g.: Jan .. Dec)
B    该locale下的月份全称(e.g.:  January .. December)
c    该locale下的日期和时间(e.g.: "Sun Dec 15  14:12:47 GMT 1996")
d    当月中的天数 (01 .. 31)
j    当年中的天数 (001 .. 366)
m    月数 (01 .. 12)
U    当年中的星期数,以周日作为一周开始,其中第一周为首个含星期天的星期(00..53)
W    当年中的星期数,以星期一作为一周的开始,其中第一周为首个含星期天的星期(00..53)
w    工作日数(0 .. 6, 0表示星期天)
x    该locale下的日期表示(e.g. "13/04/97")
y    两位数的年份(00 .. 99)
Y    四位数的年份(1970 .. 2038)

6.3.4、日志轮替 cronolog 工具

通过 epel 源安装:

[root@arm64v8 ~]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
[root@arm64v8 ~]# yum install cronolog -y
[root@arm64v8 ~]# cronolog -h
usage: cronolog [OPTIONS] logfile-spec

   -H NAME,   --hardlink=NAME maintain a hard link from NAME to current log
   -S NAME,   --symlink=NAME  maintain a symbolic link from NAME to current log
   -P NAME,   --prev-symlink=NAME  maintain a symbolic link from NAME to previous log
   -l NAME,   --link=NAME     same as -S/--symlink
   -h,        --help          print this help, then exit
   -p PERIOD, --period=PERIOD set the rotation period explicitly
   -d DELAY,  --delay=DELAY   set the rotation period delay
   -o,        --once-only     create single output log from template (not rotated)
   -x FILE,   --debug=FILE    write debug messages to FILE
                              ( or to standard error if FILE is "-")
   -a,        --american         American date formats
   -e,        --european         European date formats (default)
   -s,    --start-time=TIME   starting time
   -z TZ, --time-zone=TZ      use TZ for timezone
   -V,      --version         print version number, then exit
[root@arm64v8 ~]#

这个工具的使用非常简单,但必须得搞懂它的轮替原理。以下面设置为例:

CustomLog "|/usr/local/sbin/cronolog logs/%Y/%m/%d/access.log" combined
  • 由于使用的是相对路径,所以它相对于 ServerRoot。假设 ServerRoot 为 /usr/local/apache,则这里的日志将创建在 /usr/local/apache/logs/%Y/%m/%d/access.log,其中 %Y、%m、%d 分别表示的是年、月、日。这些修饰符组成的文件名或目录被称为模板。

cronolog 的轮替原理是:

  • 根据当前日志文件模板的时间点,和当前时间进行比较,如果模板中的某个部分和当前时间点不同,则需要进行轮替,轮替时会自动创建缺失的目录,并且计算下一次进行轮替的时间点。
  • 在轮替时,先关闭当前日志文件,再根据当前时间点创建新日志文件,并打开新日志文件。
  • 但注意,cronolog 工具在创建或轮替时,如果没有请求到达,则不会立即创建日志文件,这和 rotatelogs 工具的默认情况是一样的。

看上去很简单,确实很简单,它只需要根据指定的时间修饰符和当前时间点进行比较就可以了。

例如上面的配置,如果当前时间点为 2021-10-01,则按照以下顺序进行创建,注意,只有在请求到达时才会创建,没有请求时是不会创建的。

  1. Period 1:在第一个请求到达时创建
    • logs/2021 目录是否存在,不存在则创建。
    • logs/2021/10 目录是否存在,不存在则创建。
    • logs/2021/10/01 目录是否存在,不存在则创建。
    • 创建 logs/2021/10/01/access.log 文件,并写日志。
  2. Period 2:在 2021-10-02 凌晨第一个请求到达时创建
    • logs/2021 目录已存在,不创建。
    • logs/2021/10 目录已存在,不创建。
    • logs/2021/10/02 目录不存在,创建。
    • 创建 logs/2021/10/02/access.log 文件,并写日志。
  3. 之后将继续按照此规则不断进行下去 ......

也就是说,cronolog 默认将按照最小单位的时间修饰符为轮替时间间隔。例如,最小单位为 %d 时,将按照天轮替,最小单位为 %W 将按周轮替,最小单位为 %S,将按秒轮替。但仍然需要说明的是,只有请求到达时,才会按照当前时间点创建新的日志文件。

  • 例如,如果按秒轮替,第 10 秒时创建了一个文件 10.log,第 11 秒将关闭该日志文件,但是不会立即创建新日志文件,假设在第 15 秒时新的请求到达,则创建一个 15.log,而不是 11.log。

此外,cronolog 可使用 "-p N UNITs" 选项显式指定轮替时间间隔,有效单位 UNITs 为:seconds, minutes, hours,days, weeks 和 months。

  • 例如 "-p 5 minutes" 表示每 5 分钟轮替一次。
  • 但需要注意,N 的值必须为更高一级 UNITs 的公因数(不包括最大公因数),例如小时的长度为 60 分钟,在指定 minutes 单位时,可以指定 "1,2,3,4,5,6,10,15,20,30 minutes",但不能指定为 "7,9,11,12,13,14" 等 minutes。

如果不想轮替,有两种方法:不要使用时间类的修饰符;使用 "-o" 选项。这时候会一直写同一个文件。

cronolog 还支持动态符号链接功能,例如下面的配置,这样就使得每次访问 /usr/local/apache/logs/access_log 都能访问到最新的日志文件。

CustomLog "|/path/to/cronolog --symlink=/usr/local/apache/logs/access_log /usr/local/apache/logs/%Y/%m/access_log" combined

最后,cronolog 不支持日志截断。

6.3.5、rotatelogs 和 cronolog 的比较

这两个工具各有优缺点:

  1. 在日志文件路径上:rotatelogs 无法将时间类修饰符作为目录,只能使用在日志文件名上。而 cronolog 可以。例如 logs/%Y%m/%d/access.log
  2. 在日志控制力度上:rotatelogs 对日志的控制力更强,它可以控制启动时、轮替时是否立即创建日志文件。而 cronolog 仅只能在有新请求到达时才创建日志文件。
  3. 在特性上:rotatelogs 更丰富一些,它支持日志截断,支持在一定数量内循环轮替。而 cronolog 只能轮替,且只能按照时间不断向后轮替。
  4. 在日志访问方便性上:cronolog 通过它的动态符号链接功能使得这一点比 rotatelogs 方便的多。

6.4、httpd 路径映射和重定向

主要包括 Alias 指令、AliasMatch、Redirect、RedirectMatch、ScriptAlias 和 ScriptAliasMatch。它们由 mod_alias 模块提供,该模块用于提供简单的路径映射和重定向需求,更复杂的内容见 URL重写

其中 ScriptAlias 用法和 Alias 完全一样,唯一不同的是 ScriptAlias 映射的目标路径被认为是 CGI 文件或包含 CGI 文件的目录,它们将会被 cgi 处理器处理执行。

6.4.1、处理顺序

Redirect、RedirectMatch 指令优先于 Alias、AliasMatch 进行处理;同类型的 Redirect 或 Alias 按照出现顺序进行匹配,匹配到了立即停止。

例如,如果几个指令按照下面的顺序出现:

Redirect1
Alias1
AliasMatch2
Alias3
RedirectMatch2
Redirect3
  • 那么它们匹配的顺序为:Redirect1 > RedirectMatch2 > Redirect3 > Alias1 > AliasMatch2 > Alias3。且一被匹配立即生效,后面的将被忽略。

6.4.2、Alias 和 AliasMatch

Alias 指令用于将 URL 路径映射到本地文件系统的路径,且本地路径不受 DocumentRoot 的限制。AliasMatch 是正则版本的 Alias。

Alias "/image" "/ftp/pub/image"

这将使得 http://myserver/image/foo.gif 的请求得到 /ftp/pub/image/foo.gif,由于是精确匹配的,所以 http://myserver/image/foo.gif 不会返回该路径下的文件。但 Alias 有支持正则匹配的指令 AliasMatch。

AliasMatch "(?i)^/image(.*)" "/ftp/pub/image$1"

Alias 中如果 URL 部分包含了尾随斜线,则映射路径也应该包含斜线。例如下面的例子,这时如果请求 http://myserver/icons ,将不会得到 /usr/local/apache/icons/$DirectoryIndex。

Alias "/icons/" "/usr/local/apache/icons/"

设置了 Alias 后,需要考虑映射到的本地路径的权限是否放行。特别是对于 DocumentRoot 外的路径,通常会因为 < Directory "/"> 的拒绝而导致这些映射目标不能访问。所以,对于这样的映射路径应该还需要为 Alias 指令配一个 < Directory > 容器放行。例如:

Alias "/image" "/ftp/pub/image"
<Directory "/ftp/pub/image">
    Require all granted
</Directory>

如果 Alias 或 AliasMatch 出现在 < Location > 容器中,则它们只需要一个映射路径作为参数即可,因为 URL 已经包含在容器路径中。例如:

<Location "/image">
    Alias "/ftp/pub/image"
</Location>

6.4.3、Redirect 和 RedirectMatch 指令

它们告知客户端资源已经转移,让它们重新发送对新路径资源的 HTTP 请求。

Redirect [status] old-URL new-URL

旧的 URL 必须是以斜线 "/" 开头的绝对路径,新 URL 也必须是绝对路径,但可以是包含了 scheme://serverhost 的绝对路径,也可以是使用斜线开头的绝对路径。例如:

# 重定向到不同主机上
Redirect "/service" "http://foo2.example.com/service"

# 重定向到相同主机上
Redirect "/one" "/two"

如果没有指定 status 参数,则重定向是临时重定向(HTTP status 302)。对客户端来说,此资源的路径变动是临时性的。以下几个 status 参数可以返回各自不同的 HTTP 状态码:

  • permanent:返回一个永久性重定向状态码(301),表示此资源的位置变动是永久性的。
  • temp:返回一个临时性重定向状态码(302),这是默认值。
  • seeother:返回一个 "参见" 状态码(303),表示此资源已经被替代。
  • gone:返回一个 "已废弃" 状态码(410),表示此资源已经被永久性地删除了。如果指定了这个状态码,则 new-URL 参数将被忽略。

status 可以被指定为某个数值以返回其他指定状态码。如果此值在 300-399 之间,则必须提供 new-URL 参数,否则必须忽略 new-URL,但不管如何,值必须是有效的 http 状态码值。

和 alias 指令一样,如果写在 Location 容器中,则 old-URL 忽略,因为它已经包含在 location 容器的路径中。例如:

<Location "/one">
    Redirect permanent "http://example.com/two"
</Location>
<Location "/three">
    Redirect 303 "http://example.com/other"
</Location>
<LocationMatch "/error/(?<NUMBER>[0-9]+)">
    Redirect permanent "http://example.com/errors/%{env:MATCH_NUMBER}.html"
</LocationMatch>

6.5、httpd 添加新模块

有些时候因为特殊需求,需要在已安装的 httpd 上添加一些额外的模块,这时候要使用 httpd-devel 中的 apxs 工具。要使用这个扩展机制,你的平台必须支持 DSO 特性,即要求 Apache httpd 必须内建了 mod_so 模块。

apxs 添加模块的方法很简单,以下是几个可能用上的选项:

  • -c mod_foo.c:将 c 文件编译为 .so 文件。
  • -i:表示安装一个或多个模块到 apache 服务所在目录的 modules 目录中。
  • -a:表示自动在 httpd.conf 中加入 LoadModule 行。
  • -A:表示自动在 httpd.conf 中加入 #LoadModule 行,也就是说安装了模块但是不启用。
  • -n:显式指定 -i 需要安装的模块名。

例如:添加 mod_proxy.so 模块。

cd httpd-2.4.51/modules  # 进入到httpd源码的解压目录
apxs -c -i -A proxy/mod_proxy.c proxy/proxy_util.c

安装成功后的最后几行会告诉你要去修改配置文件,看情况是否要修改 LoadModule 指令来加载模块。

有时候添加一个模块后重启失败了并提示 undefined Symbol,说明还需添加另外相关的所需模块。

例如,上面如果只安装 apxs -c -i -a proxy/mod_proxy.c 时重启 httpd。

Starting httpd: httpd: Syntax error on line 117 of /etc/apache/httpd.conf: Cannot load modules/mod_proxy.so into server: /usr/local/apache/modules/mod_proxy.so: undefined symbol: ap_proxy_strmatch_domain

这说明还需要装相关的 devel 或者 util 对应的模块。

标签云