# 大批量虚拟主机的动态配置
本文档描述如何使用Apache有效的架设大批量虚拟主机。
## 动机
如果你的配置文件`httpd.conf`中包含类似下面的许多`<VirtualHost>`段,并且其中的内容都大致相同的话,你应该会对这里所讲的技术感兴趣。比如:
```
NameVirtualHost 111.22.33.44
<VirtualHost 111.22.33.44>
ServerName www.customer-1.com
DocumentRoot /www/hosts/www.customer-1.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-1.com/cgi-bin
</VirtualHost>
<VirtualHost 111.22.33.44>
ServerName www.customer-2.com
DocumentRoot /www/hosts/www.customer-2.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-2.com/cgi-bin
</VirtualHost>
# 等 等 等 。。。
<VirtualHost 111.22.33.44>
ServerName www.customer-N.com
DocumentRoot /www/hosts/www.customer-N.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-N.com/cgi-bin
</VirtualHost>
```
最基本的思想是用动态的机制来实现所有这些静态的`<VirtualHost>`配置段。这样做有许多优点:
1. 配置文件变小,使得Apache可以更快的启动,同时消耗更少的内存。
2. 添加一个虚拟主机,应该只是简单的在文件系统中创建合适的目录,以及配置相关的DNS信息,且无需重新启动Apache 。
主要的缺点是你无法针对每个虚拟主机使用不同的日志文件。然而,如果真的在配置有大量虚拟主机的服务器上记录不同的日志文件的话,很有可能会达到操作系统所允许的最大文件描述符的数量。更好的办法是把日志写到管道或者先入先出的栈,并启用其他的进程来分拣所得到的日志信息(同时也可以做一些历史纪录的统计等等)。
## 概述
一个虚拟主机由两部分来定义:一个是它的IP地址,还有一个是HTTP的"`Host:`"请求头。动态大量虚拟主机的技术,是基于自动在所要返回的文件路径中插入相关信息的想法实现的。使用`mod_vhost_alias`可以很容易的实现,但如果你的Apache版本低于1.3.6 ,则你必须使用`mod_rewrite` 。两者在默认情况下都不启用;要使用他们,必须在配置和编译Apache的阶段启用。
我们需要做很多"伪装",才能使动态虚拟主机看起来像普通主机。最重要的一点是Apache使用虚拟主机名(ServerName)来生成自引用(self-referential)URL等信息。这是用`ServerName`指令来配置的,并且可以通过环境变量`SERVER_NAME`传递给CGI脚本。运行时实际使用的值是由`UseCanonicalName`指令的设置来控制的。当 `UseCanonicalName Off` 时,虚拟主机名(ServerName)取自请求中的"`Host:`"头。当 `UseCanonicalName DNS` 时,则通过DNS反解析虚拟主机的IP地址得到主机名。以前的做法是基于名称的动态虚拟主机,现在常用基于IP地址的虚拟主机。如果Apache无法判断虚拟主机名,则可能是没有"`Host:`"头或是DNS解析失败,这样种情况下,Apache将使用配置`ServerName`时所填写的主机名。
另一件需要"伪装"的事情是文档根目录(由`DocumentRoot`配置并可以通过`DOCUMENT_ROOT`环境变量为CGI脚本所使用)。在通常的配置方式下,这些设置信息由核心(core)模块在将URI映射到文件系统的时候使用,但是如果使用动态虚拟主机配置,这些信息将由另外一个使用不同于核心(core)模块将URI映射到文件系统的方式的模块(`mod_vhost_alias`或`mod_rewrite`)使用。这两个模块都不负责设置`DOCUMENT_ROOT`环境变量,所以如果CGI或SSI程序使用了`DOCUMENT_ROOT`环境变量,那么将得到错误的值。
## 简单的动态虚拟主机
这是`httpd.conf`文件中,完成和上文[动机](#calibre_link-660)部分所提到的虚拟主机一样效果的配置方法,但这里采用了`mod_vhost_alias`模块:
```
# 从"Host:"头中取得主机名
UseCanonicalName Off
# 这种日志格式可以从第一个字段中提取出主机名
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
# 在返回请求的文件名路径中包含主机名
VirtualDocumentRoot /www/hosts/%0/docs
VirtualScriptAlias /www/hosts/%0/cgi-bin
```
将 `UseCanonicalName Off` 的配置改为 `UseCanonicalName DNS` 即可实现基于IP地址的虚拟主机。而在文件路径中所要插入的服务器名则通过虚拟主机的IP地址解析得到。
## 一个实际的个人主页系统
这里对上面的系统作了一点调整,便可作为ISP的个人主页服务器。我们使用了略微复杂的方法,从主机名(ServerName)中提取子字符串,并插入到文件路径中。在这个例子中`www.user.isp.com`的文档将在`/home/user/`中定位。并对所有虚拟主机使用单个`cgi-bin`目录。
```
# 所有之前的准备事项和上面一样,然后在文件路径中包含主机名
VirtualDocumentRoot /www/hosts/%2/docs
# 单个cgi-bin目录
ScriptAlias /cgi-bin/ /www/std-cgi/
```
更复杂的关于`VirtualDocumentRoot`的设置,可以查阅`mod_vhost_alias`文档。
## 在同一个服务器上架设多个主机的虚拟系统
更复杂的设置,应该使用Apache的`<VirtualHost>`容器来管理各种虚拟主机配置的作用域。例如,你可以用一个IP地址来给个人主页客户使用,同时用下面的配置提供给商业客户使用。自然的,这两者通过运用`<VirtualHost>`结合到一起。
```
UseCanonicalName Off
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
<Directory /www/commercial>
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /www/homepages>
Options FollowSymLinks
AllowOverride None
</Directory>
<VirtualHost 111.22.33.44>
ServerName www.commercial.isp.com
CustomLog logs/access_log.commercial vcommon
VirtualDocumentRoot /www/commercial/%0/docs
VirtualScriptAlias /www/commercial/%0/cgi-bin
</VirtualHost>
<VirtualHost 111.22.33.45>
ServerName www.homepages.isp.com
CustomLog logs/access_log.homepages vcommon
VirtualDocumentRoot /www/homepages/%0/docs
ScriptAlias /cgi-bin/ /www/std-cgi/
</VirtualHost>
```
## 更为有效的基于IP地址的虚拟主机
在[第一个例子](#calibre_link-661)中说过,转为基于IP地址的虚拟主机设置很容易做到。但不幸的是,那种做法并不高效,因为这样会在每次处理请求时,需要查询DNS。通过在文件系统中包含IP地址的做法可以避免这样的问题。这样一来,免去了和主机名的关联,在日志记录中也一样可以用IP来分离不同日志。Apache将不会为了确定主机名(ServerName)而去做DNS查询。
```
# 从IP地址反解析得到主机名
UseCanonicalName DNS
# 在日志中包含IP地址,便于以后分拣
LogFormat "%A %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
# 在文件路径中包含IP地址
VirtualDocumentRootIP /www/hosts/%0/docs
VirtualScriptAliasIP /www/hosts/%0/cgi-bin
```
## 使用老版本的Apache
上面的例子基于`mod_vhost_alias` ,但它是在版本1.3.6之后才出现的。如果你的版本比较老,可以通过使用`mod_rewrite`来达到相同的目的,如下所示。但只能是基于"Host:"头方式的虚拟主机。
此外还须注意日志方面的问题。Apache1.3.6是第一个支持"`%V`"日志格式指令的版本,在版本1.3.0-1.3.3中,"`%v`"选项做和"`%V`"一样的事情;而在版本1.3.4中没有等价指令。在所有的这些版本中,指令`UseCanonicalName`可以出现在`.htaccess`文件中,这意味着客户的设置可能会导致日志记录紊乱。所以最好的做法是使用"`%{Host}i`"指令,它可以直接记录"`Host:`"头;注意,这样可能在末尾包含"`:port`",而使用"`%V`"则不会这样。
## 使用`mod_rewrite`实现简单的动态虚拟主机
这里的例子摘自`httpd.conf` ,效果等同于[第一个例子](#calibre_link-661)中的情况。前半部分和上面的例子大致相似,只是为了向后兼容`mod_rewrite`作了适当修改;后半部分配置`mod_rewrite`来做实际的工作。
有些特别的地方需要注意:默认情况下,`mod_rewrite`在所有其他URI转换模块(`mod_alias`等)之前运行,所以如果使用这些模块的话,`mod_rewrite`必须作相应的调整。同时,我们还要为每个动态虚拟主机变些戏法,使之等效于`ScriptAlias`
```
# 从"Host:"头获取主机名
UseCanonicalName Off
# 可分拣的日志
LogFormat "%{Host}i %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
<Directory /www/hosts>
# 这里需要ExecCGI ,因为我们不能强制CGI以与ScriptAlias相同的方式执行
Options FollowSymLinks ExecCGI
</Directory>
# 接下来是关键部分
RewriteEngine On
# 来自"Host:"头的ServerName ,可能大小写混杂
RewriteMap lowercase int:tolower
## 首先处理普通文档
# 允许变名/icons/起作用,其他变名类同
RewriteCond %{REQUEST_URI} !^/icons/
# 允许CGI
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 开始"变戏法"
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/docs/$1
## 现在处理CGI(我们需要强制使用一个MIME类型)
RewriteCond %{REQUEST_URI} ^/cgi-bin/
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/cgi-bin/$1 [T=application/x-httpd-cgi]
# ok 了!
```
## 使用`mod_rewrite`的个人主页系统
这里的配置完成和[第二个例子](#calibre_link-662)相同的工作。
```
RewriteEngine on
RewriteMap lowercase int:tolower
# 允许CGI工作
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 检查hostname正确与否,之后才能使RewriteRule起作用
RewriteCond ${lowercase:%{SERVER_NAME}} ^www\.[a-z-]+\.isp\.com$
# 将虚拟主机名字连接到URI的开头
# [C]表明本次重写的结果将在下一个rewrite规则中使用
RewriteRule ^(.+) ${lowercase:%{SERVER_NAME}}$1 [C]
# 现在创建实际的文件名
RewriteRule ^www\.([a-z-]+)\.isp\.com/(.*) /home/$1/$2
# 定义全局CGI目录
ScriptAlias /cgi-bin/ /www/std-cgi/
```
## 使用独立的虚拟主机配置文件
这样的布局利用了`mod_rewrite`的高级特性,在独立的虚拟主机配置文件中转换。如此可以更为灵活,但需要较为复杂的设置。
`vhost.map`文件包含了类似下面的内容:
```
www.customer-1.com /www/customers/1
www.customer-2.com /www/customers/2
# ...
www.customer-N.com /www/customers/N
```
`http.conf`包含了:
```
RewriteEngine on
RewriteMap lowercase int:tolower
# 定义映射文件
RewriteMap vhost txt:/www/conf/vhost.map
# 和上面的例子一样,处理别名
RewriteCond %{REQUEST_URI} !^/icons/
RewriteCond %{REQUEST_URI} !^/cgi-bin/
RewriteCond ${lowercase:%{SERVER_NAME}} ^(.+)$
# 这里做基于文件的重新映射
RewriteCond ${vhost:%1} ^(/.*)$
RewriteRule ^/(.*)$ %1/docs/$1
RewriteCond %{REQUEST_URI} ^/cgi-bin/
RewriteCond ${lowercase:%{SERVER_NAME}} ^(.+)$
RewriteCond ${vhost:%1} ^(/.*)$
RewriteRule ^/(.*)$ %1/cgi-bin/$1
```
- Apache HTTP Server Version 2.2 文档 [最后更新:2006年3月21日]
- 版本说明
- 从1.3升级到2.0
- 从2.0升级到2.2
- Apache 2.2 新特性概述
- Apache 2.0 新特性概述
- The Apache License, Version 2.0
- 参考手册
- 编译与安装
- 启动Apache
- 停止和重启
- 配置文件
- 配置段(容器)
- 缓冲指南
- 服务器全局配置
- 日志文件
- 从URL到文件系统的映射
- 安全方面的提示
- 动态共享对象(DSO)支持
- 内容协商
- 自定义错误响应
- 地址和端口的绑定(Binding)
- 多路处理模块
- Apache的环境变量
- Apache处理器的使用
- 过滤器(Filter)
- suEXEC支持
- 性能方面的提示
- URL重写指南
- Apache虚拟主机文档
- 基于主机名的虚拟主机
- 基于IP地址的虚拟主机
- 大批量虚拟主机的动态配置
- 虚拟主机示例
- 深入研究虚拟主机的匹配
- 文件描述符限制
- 关于DNS和Apache
- 常见问题
- 经常问到的问题
- Apache的SSL/TLS加密
- SSL/TLS高强度加密:绪论
- SSL/TLS高强度加密:兼容性
- SSL/TLS高强度加密:如何...?
- SSL/TLS Strong Encryption: FAQ
- 如何.../指南
- 认证、授权、访问控制
- CGI动态页面
- 服务器端包含入门
- .htaccess文件
- 用户网站目录
- 针对特定平台的说明
- 在Microsoft Windows中使用Apache
- 在Microsoft Windows上编译Apache
- Using Apache With Novell NetWare
- Running a High-Performance Web Server on HPUX
- The Apache EBCDIC Port
- 服务器和支持程序
- httpd - Apache超文本传输协议服务器
- ab - Apache HTTP服务器性能测试工具
- apachectl - Apache HTTP服务器控制接口
- apxs - Apache 扩展工具
- configure - 配置源代码树
- dbmmanage - 管理DBM格式的用户认证文件
- htcacheclean - 清理磁盘缓冲区
- htdbm - 操作DBM密码数据库
- htdigest - 管理用于摘要认证的用户文件
- httxt2dbm - 生成RewriteMap指令使用的dbm文件
- htpasswd - 管理用于基本认证的用户文件
- logresolve - 解析Apache日志中的IP地址为主机名
- rotatelogs - 滚动Apache日志的管道日志程序
- suexec - 在执行外部程序之前切换用户
- 其他程序
- 杂项文档
- 与Apache相关的标准
- Apache模块
- 描述模块的术语
- 描述指令的术语
- Apache核心(Core)特性
- Apache MPM 公共指令
- Apache MPM beos
- Apache MPM event
- Apache MPM netware
- Apache MPM os2
- Apache MPM prefork
- Apache MPM winnt
- Apache MPM worker
- Apache模块 mod_actions
- Apache模块 mod_alias
- Apache模块 mod_asis
- Apache模块 mod_auth_basic
- Apache模块 mod_auth_digest
- Apache模块 mod_authn_alias
- Apache模块 mod_authn_anon
- Apache模块 mod_authn_dbd
- Apache模块 mod_authn_dbm
- Apache模块 mod_authn_default
- Apache模块 mod_authn_file
- Apache模块 mod_authnz_ldap
- Apache模块 mod_authz_dbm
- Apache模块 mod_authz_default
- Apache模块 mod_authz_groupfile
- Apache模块 mod_authz_host
- Apache模块 mod_authz_owner
- Apache模块 mod_authz_user
- Apache模块 mod_autoindex
- Apache模块 mod_cache
- Apache模块 mod_cern_meta
- Apache模块 mod_cgi
- Apache模块 mod_cgid
- Apache模块 mod_charset_lite
- Apache模块 mod_dav
- Apache模块 mod_dav_fs
- Apache模块 mod_dav_lock
- Apache模块 mod_dbd
- Apache模块 mod_deflate
- Apache模块 mod_dir
- Apache模块 mod_disk_cache
- Apache模块 mod_dumpio
- Apache模块 mod_echo
- Apache模块 mod_env
- Apache模块 mod_example
- Apache模块 mod_expires
- Apache模块 mod_ext_filter
- Apache模块 mod_file_cache
- Apache模块 mod_filter
- Apache模块 mod_headers
- Apache模块 mod_ident
- Apache模块 mod_imagemap
- Apache模块 mod_include
- Apache模块 mod_info
- Apache模块 mod_isapi
- Apache模块 mod_ldap
- Apache模块 mod_log_config
- Apache模块 mod_log_forensic
- Apache模块 mod_logio
- Apache模块 mod_mem_cache
- Apache模块 mod_mime
- Apache模块 mod_mime_magic
- Apache模块 mod_negotiation
- Apache模块 mod_nw_ssl
- Apache模块 mod_proxy
- Apache模块 mod_proxy_ajp
- Apache模块 mod_proxy_balancer
- Apache模块 mod_proxy_connect
- Apache模块 mod_proxy_ftp
- Apache模块 mod_proxy_http
- Apache模块 mod_rewrite
- Apache模块 mod_setenvif
- Apache模块 mod_so
- Apache模块 mod_speling
- Apache模块 mod_ssl
- Apache模块 mod_status
- Apache模块 mod_suexec
- Apache模块 mod_unique_id
- Apache模块 mod_userdir
- Apache模块 mod_usertrack
- Apache模块 mod_version
- Apache模块 mod_vhost_alias
- Developer Documentation for Apache 2.0
- Apache 1.3 API notes
- Debugging Memory Allocation in APR
- Documenting Apache 2.0
- Apache 2.0 Hook Functions
- Converting Modules from Apache 1.3 to Apache 2.0
- Request Processing in Apache 2.0
- How filters work in Apache 2.0
- Apache 2.0 Thread Safety Issues
- 词汇和索引
- 词汇表
- 指令索引
- 指令速查
- 模块索引
- 站点导航