后端nginx使用set_real_ip_from获取用户真实IP

随着nginx的迅速崛起,越来越多公司将apache更换成nginx. 同时也越来越多人使用nginx作为负载均衡, 并且代理前面可能还加上了CDN加速,但是随之也遇到一个问题:nginx如何获取用户的真实IP地址.
实例环境:

  • 用户IP 120.22.11.11
  • CDN前端 61.22.22.22
  • CDN中转 121.207.33.33
  • 公司NGINX前端代理 192.168.50.121(外网121.207.231.22)

1、使用CDN自定义IP头来获取

假如说你的CDN厂商使用nginx,那么在nginx上将$remote_addr赋值给你指定的头,方法如下:

proxy_set_header remote-user-ip $remote_addr;

后端PHP代码getRemoteUserIP.php

<?php
    $ip = getenv("HTTP_REMOTE_USER_IP");
    echo $ip;    
?>

访问getRemoteUserIP.php,结果如下:

120.22.11.11 //取到了真实的用户IP,如果CDN能给定义这个头的话,那这个方法最佳

2、通过HTTP_X_FORWARDED_FOR获取IP地址

一般情况下CDN服务器都会传送HTTP_X_FORWARDED_FOR头,这是一个ip串,后端的真实服务器获取HTTP_X_FORWARDED_FOR头,截取字符串第一个不为unkown的IP作为用户真实IP地址, 例如:
120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121(用户IP,CDN前端IP,CDN中转,公司NGINX代理)
getFor.php

<?php
    $ip = getenv("HTTP_X_FORWARDED_FOR");
    echo $ip;
?>

访问getFor.php结果如下:

120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121

如果你是php程序员,你获取第一个不为unknow的ip地址,这边就是120.22.11.11.

3.使用nginx自带模块realip获取用户IP地址

安装nginx之时加上realip模块,我的参数如下:

    ./configure --prefix=/usr/local/nginx-1.4.1 --with-http_realip_module

真实服务器nginx配置

    server {
        listen       80;
        server_name  www.ttlsa.com;
        access_log  /data/logs/nginx/www.ttlsa.com.access.log  main;

        index index.php index.html index.html;
        root /data/site/www.ttlsa.com;

        location /
        {
                root /data/site/www.ttlsa.com;
        }
        location = /getRealip.php
        {
                set_real_ip_from  192.168.50.0/24;
                set_real_ip_from  61.22.22.22;
                set_real_ip_from  121.207.33.33;
                set_real_ip_from 127.0.0.1;
                real_ip_header    X-Forwarded-For;
                real_ip_recursive on;
                fastcgi_pass  unix:/var/run/phpfpm.sock;
                fastcgi_index index.php;
                include fastcgi.conf;
        }
    }

getRealip.php内容

    <?php
        $ip =  $_SERVER['REMOTE_ADDR'];
        echo $ip;    
    ?>

访问www.ttlsa.com/getRealip.php,返回:

    120.22.11.11

如果注释 real_ip_recursive on或者 real_ip_recursive off
访问www.ttlsa.com/getRealip.php,返回:

121.207.33.33

很不幸,获取到了中继的IP,real_ip_recursive的效果看明白了吧.

  • set_real_ip_from:真实服务器上一级代理的IP地址或者IP段,可以写多行
  • real_ip_header:从哪个header头检索出要的IP地址
  • real_ip_recursive:递归排除IP地址,ip串从右到左开始排除set_real_ip_from里面出现的IP,如果出现了未出现这些ip段的IP,那么这个IP将被认为是用户的IP。例如我这边的例子,真实服务器获取到的IP地址串如下:
120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121

在real_ip_recursive on的情况下
61.22.22.22,121.207.33.33,192.168.50.121都出现在set_real_ip_from中,仅仅120.22.11.11没出现,那么他就被认为是用户的ip地址,并且赋值到remote_addr变量
在real_ip_recursive off或者不设置的情况下
192.168.50.121出现在set_real_ip_from中,排除掉,接下来的ip地址便认为是用户的ip地址
如果仅仅如下配置:

    set_real_ip_from   192.168.50.0/24;
    set_real_ip_from 127.0.0.1;
    real_ip_header    X-Forwarded-For;
    real_ip_recursive on;

访问结果如下:

   121.207.33.33

4、三种在CDN环境下获取用户IP方法总结

4.1 CDN自定义header头

  • 优点:获取到最真实的用户IP地址,用户绝对不可能伪装IP
  • 缺点:需要CDN厂商提供

4.2 获取forwarded-for头

  • 优点:可以获取到用户的IP地址
  • 缺点:程序需要改动,以及用户IP有可能是伪装的

4.3 使用realip获取

  • 优点:程序不需要改动,直接使用remote_addr即可获取IP地址
  • 缺点:ip地址有可能被伪装,而且需要知道所有CDN节点的ip地址或者ip段

使用nginx ngx_http_memcached_module及memcached实现页面缓存

页面静态化是前端优化的一个重要方法,一般采用生成静态文件的方式实现。这里我尝试采用另外一种方式去实现,就是直接把页面用Memcached进行缓存,然后通过Nginx直接去访问。

采用Memcached缓存页面的好处是什么呢?

  1. 由于页面是缓存在内存里,所以减少了系统的I/O操作。
  2. 可以直接利用Memcached的分布式特性。
  3. 可以直接利用缓存的过期时间,方便对页面的过期时间进行处理。
  4. 部署简单,生成静态文件还需要考虑文件系统的问题。
    当然缺点也很明显,首先是对内存的性能依赖很大,其次由于页面直接放内存里,一旦Memcached挂掉或者服务器重启,内存里存储的页面就会全部消失。

问题来了

Nginx内置了Memcached模块ngx_http_memcached_module,可以很轻松的实现对Memcached的访问。我这里做一个示例,通过PHP缓存我们邮轮网站的首页,然后通过URLhttp://dev.hwtrip.com/cache/index.html去访问这个页面。

首先,我们对Nginx进行配置:

server {
    listen       80;
    server_name  dev.hwtrip.com;

    location ^~ /cache/ {
        set            $memcached_key $request_uri;
        memcached_pass 127.0.0.1:11211;
    }

    error_page     404 502 504 = @fallback;
}

location @fallback {
    proxy_pass     http://backend;
}

这个配置把所有请求URI前缀为/cache/的访问用Memcached模块进行内容的读取,同时使用请求URI作为Memcached的key。当缓存没有命中或者出错时,我们使用@fallback进行处理(例如访问实际的应用并重新写入缓存),这个不在这里展开了。

然后,我们用简单的代码把页面写进Memcached里:


$htmlContent = file_get_contents('http://youlun.hwtrip.com'); $memcached = new Memcache(); $memcached->addServer('127.0.0.1', 11211); $memcached->set('/cache/index.html', $htmlContent);

注意写缓存时的key,由于我们访问的URL是http://dev.hwtrip.com/cache/index.html,所以写进Memcached的key就是URI/cache/index.html。

执行完代码后,我们访问下http://dev.hwtrip.com/cache/index.html:
Nginx

可以看到,通过nginx很容易实现对Memcached进行访问,但是这离我们缓存页面的目标还差很多,因为有两个大问题还没有解决。

  • 首先,我们没有用到Memcached的分布式,我们上面的示例只是对一个Memcached的节点进行访问。
  • 其次,通过这种方式返回的页面的没有携带浏览器缓存相关的响应头。没有这些响应头,页面就不能缓存在浏览器了,会导致每次访问都会去请求服务器。

使Ngxin支持Memcached的分布式访问

Nginx可以通过upstream支持访问多个Memcached服务节点:


upstream memcached { server 127.0.0.1:11211; server 127.0.0.1:11212; server 127.0.0.1:11213; server 127.0.0.1:11214; } server { listen 80; server_name dev.hwtrip.com; location ^~ /cache/ { set $memcached_key $request_uri; memcached_pass memcached; } error_page 404 502 504 = @fallback; } ......

但是,upstream采用的是round-robin的轮询方式,而我们知道PHP的php_memcache扩展使用的是一致性哈希算法进行Memcached服务节点的选择。于是乎,我们在前端用PHP缓存的页面,通过nginx不一定能访问到。所以我们必须让Nginx也能通过一致性哈希算法去选择节点。

这里我们用到了ngx_http_upstream_consistent_hash这个第三方模块,这个模块实现了跟php_memcache这个PHP扩展一样的一致性哈希算法。

重新编译Nginx,添加好这个模块,我们修改下Nginx的配置文件的upstream部分:

upstream memcached {
    consistent_hash $request_uri;
    server 127.0.0.1:11211;
    server 127.0.0.1:11212;
    server 127.0.0.1:11213;
    server 127.0.0.1:11214;
}

......

我们修改下缓存页面的示例PHP代码:

$htmlContent = file_get_contents('http://youlun.hwtrip.com');

$memcached = new Memcache();
$memcached->addServer('127.0.0.1', 11211);
$memcached->addServer('127.0.0.1', 11212);
$memcached->addServer('127.0.0.1', 11213);
$memcached->addServer('127.0.0.1', 11214);

for ($i = 1; $i < 10; $i ++) { 
    $cacheIns->set("/cache/index$i.html", $htmlContent);
}

通过设置不同的key,我们测试下Nginx是否能获取到正确的内容。经测试,PHP设置的缓存,用Nginx都能正常访问到。

返回浏览器可缓存的页面

这是第一个例子里Nginx返回的页面的响应头:

Nginx

可以看到没有返回任何缓存相关的响应头,这样每次访问,浏览器都会去请求服务器,虽然服务器有缓存,但明显不符合我们对性能优化的追求。不过就算Nginx返回了相关的响应头,然后我们请求的时候包含了If-Modified-Since这个请求头,Ngxin的memcached模块也不会去判断这个请求有没有过期以及返回304 Not Modified。所以我们需要实现两件事,第一是能让Ngxin返回正确的响应头,第二是能让Nginx判断请求的资源是否过期,并正确返回响应码。

这里,我们借助另外一个Nginx的第三方模块:gx_http_enhanced_memcached_module。这个模块提供了很多功能,大家可以到它的github页面上了解。这里我们主要用到它的两个功能:

  • Send custom http headers, like Content-Type, Last-Modified. Http headers are stored in memcached, with your body data.
  • Reply 304 Not Modified for request with If-Modified-Since headers and content with Last-Modified in cache
    再次重新编译安装Nginx,添加好gx_http_enhanced_memcached_module模块,我们再次对Nginx的配置文件进行修改:
upstream memcached {
    consistent_hash $request_uri;
    server 127.0.0.1:11211;
    server 127.0.0.1:11212;
    server 127.0.0.1:11213;
    server 127.0.0.1:11214;
}

server {
    listen       80;
    server_name  dev.hwtrip.com;

    location ^~ /cache/ {
        set                     $enhanced_memcached_key $request_uri;
        enhanced_memcached_pass memcached;
    }

    error_page     404 502 504 = @fallback;
}

......

我们再次修改示例PHP文件:

$htmlContent = file_get_contents('http://youlun.hwtrip.com');

// 页面过期时间
$expiresTime = 60 * 5;

// Last-Modified头设置的时间
$lastModified = gmdate('D, d M Y H:i:s GMT', time());

// Expires头设置的时间
$expires = gmdate('D, d M Y H:i:s GMT', time() + $expiresTime);

// 最终缓存的内容
$cacheContent = "EXTRACT_HEADERS
Content-Type: text/html
Cache-Control:max-age=$expiresTime
Expires:$expires
Last-Modified:$lastModified

$htmlContent";

// 获取memcache实例
$memcached = new Memcache();
$memcached->addServer('127.0.0.1', 11211);
$memcached->addServer('127.0.0.1', 11212);
$memcached->addServer('127.0.0.1', 11213);
$memcached->addServer('127.0.0.1', 11214);

// 写入缓存
$memcached->set('/cache/index.html', $cacheContent, $expiresTime);

这次我们设置了缓存的过期时间,并在缓存内容前面添加了一些响应头。ngx_http_enhanced_memcached_module模块EXTRACT_HEADERS这个标记去识别并记录响应头,详情请看github页面的说明。

重新写入缓存后,我们再次访问页面:

Nginx

可以看到缓存相关的响应头都已正确返回。

后话

至此,我们已经简单的完成了使用Nginx和Memcached对缓存页面的访问,但这只是后端的简单实现,在前端还需要实现对页面缓存的管理等等的工作。

使用nginx limit_req限制用户请求速率

在《Nginx使用limit_rate limit_conn限制文件下载速度》我们说到了ngx_http_limit_conn_module 模块,来限制连接数。那么请求数的限制该怎么做呢?这就需要通过ngx_http_limit_req_module 模块来实现,该模块可以通过定义的 键值来限制请求处理的频率。特别的,可以限制来自单个IP地址的请求处理频率。 限制的方法如同漏斗,每秒固定处理请求数,推迟过多请求。

ngx_http_limit_req_module模块指令

limit_req_zone

  • 语法: limit_req_zone $variable zone=name:size rate=rate;
  • 默认值: none
  • 配置段: http
    设置一块共享内存限制域用来保存键值的状态参数。 特别是保存了当前超出请求的数量。 键的值就是指定的变量(空值不会被计算)。如
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

说明:区域名称为one,大小为10m,平均处理的请求频率不能超过每秒一次。
键值是客户端IP。
使用$binary_remote_addr变量, 可以将每条状态记录的大小减少到64个字节,这样1M的内存可以保存大约1万6千个64字节的记录。
如果限制域的存储空间耗尽了,对于后续所有请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。
速度可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,所以如果你需要指定每秒处理少于1个的请求,2秒处理一个请求,可以使用 “30r/m”。

limit_req_log_level

  • 语法: limit_req_log_level info | notice | warn | error;
  • 默认值: limit_req_log_level error;
  • 配置段: http, server, location
    设置你所希望的日志级别,当服务器因为频率过高拒绝或者延迟处理请求时可以记下相应级别的日志。 延迟记录的日志级别比拒绝的低一个级别;比如, 如果设置“limit_req_log_level notice”, 延迟的日志就是info级别。

limit_req_status

  • 语法: limit_req_status code;
  • 默认值: limit_req_status 503;
  • 配置段: http, server, location
    该指令在1.3.15版本引入。设置拒绝请求的响应状态码。

limit_req

  • 语法: limit_req zone=name [burst=number] [nodelay];
  • 默认值: —
  • 配置段: http, server, location
    设置对应的共享内存限制域和允许被处理的最大请求数阈值。 如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以所有的请求都是以定义的频率被处理的。 超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阈值,这时,这个请求会被终止,并返回503 (Service Temporarily Unavailable) 错误。这个阈值的默认值为0。如:
limit_req_zone $binary_remote_addr zone=ttlsa_com:10m rate=1r/s;
server {
    location /www.ttlsa.com/ {
        limit_req zone=ttlsa_com burst=5;
    }
}

限制平均每秒不超过一个请求,同时允许超过频率限制的请求数不多于5个。
如果不希望超过的请求被延迟,可以用nodelay参数,如:

limit_req zone=ttlsa_com burst=5 nodelay;

完整实例配置

http {
    limit_req_zone $binary_remote_addr zone=ttlsa_com:10m rate=1r/s;

    server {
        location  ^~ /download/ {  
            limit_req zone=ttlsa_com burst=5;
            alias /data/www.ttlsa.com/download/;
        }
    }
}

可能要对某些IP不做限制,需要使用到白名单。名单设置参见后续的文档,我会整理一份以供读者参考。请专注。

使用nginx image_filter 缩放,裁剪和生成缩略图

背景

项目中有个地方需要根据客户端的要求缩放图片。最开始想用PHP来实现这个功能。设想中如果已经存在图片a.jpg,则可以通过类似a_400x400.jpg的方式来获取图片特定尺寸的缩略图。

要实现此功能可以在图片上传的时候就事先裁好指定尺寸的图片,或者在获取的时候拦截请求来实现。

如果使用第一种方法,则只能实现裁剪好预设尺寸的图片,而且会影响到上传图片的效率,如果裁剪失败,也无法后续处理。

使用第二种方式的问题是图片资源存储在一个静态资源的目录,需要在没有图片的情况下将请求转发给PHP去处理。

于是我设想能否在Nginx这一层去做这件事情,恰好Nginx有一个image filter的模块,只不过在编译的时候默认没有编译进去。

手动添加参数编译此模块,开始修改nginx的配置文件。

配置

第一个版本的配置如下:

# 我使用16进制数的方式给图片重命名
location ~* /(.*)/([0-9a-f]+)_(d+)x(d+).(jpg|png|jpeg|gif)$ {
    # 如果存在文件就终止规则
    if (-f $request_filename) {
        break;
    }

    # 设定一些参数
    set $filepath $1;
    set $filename "$2.$5";
    set $thumb    "$2_$3x$4.$5";
    set $width    $3;
    set $height   $4;

    # 如果原文件不存在可以直接返回404
    if (!-f $document_root/$filepath/$filename) {
        return 404;
    }

    # 重写URL
    rewrite /(.*)/([0-9a-f]+)_([0-9x]+).(jpg|png|jpeg|gif) /$1/$2.$4 break;
    # 执行图片缩放
    image_filter test;
    image_filter resize $width $height;
    image_filter_jpeg_quality 75;
}

但是在这个版本的配置中,如果配置原文件不存在,实际上没法正确返回404,而是返回415。过滤还是执行了。

还有一个问题就是在每次访问缩略图的时候都会重新生成,如果访问量比较大的情况下,效率并不高。

进过一系列的实践后,我又改好了一个版本:

location ~* /(.*)/([0-9a-f]+)_(d+)x(d+).(jpg|png|jpeg|gif)$ {
    if (-f $request_filename) {
        break;
    }

    set $filepath $1;
    set $filename "$2.$5";
    set $thumb    "$2_$3x$4.$5";
    set $width    $3;
    set $height   $4;

    if (!-f $document_root/$filepath/$filename) {
        return 404;
    }

    rewrite /(.*)/([0-9a-fx_]+).(.*) /imgcache/$2.$3;

    if (!-f $request_filename) {
        proxy_pass http://127.0.0.1:$server_port/image-resize/$filepath/$filename?width=$width&height=$height;
        break;
    }

    proxy_store          $document_root/imgcache/$thumb;
    proxy_store_access   user:rw  group:rw  all:r;
    proxy_set_header     Host $host;
}

location /image-resize {
    rewrite /(image-resize)/(.*) /$2 break;

    image_filter resize $arg_width $arg_height;
    image_filter_jpeg_quality 75;

    allow 127.0.0.0/8;
    deny all;
}

通过proxy_pass的方式解决415的问题,并通过proxy_store的方式将图片存到指定的目录(imgcache),在每次访问之前先进行判断。

开启nginx的gzip压缩功能,节省流量

开启网站的 gzip 压缩功能,通常可以高达70%,也就是说,如果你的网页有30K,压缩之后就变成9K, 对于大部分网站,显然可以明显提高浏览速度(注:需要浏览器支持)。

nodejs + express

对于 nodejs + express 框架,启用方法非常简单,启用 compress() 中间件即可, 通过 gzip / deflate 压缩响应数据,这个中间件应该放置在所有的中间件最前面以保证所有的返回都是被压缩的。

代码如下:

app.use(express.logger());
app.use(express.compress());
app.use(express.static(__dirname + '/html'));
app.use(express.methodOverride());
app.use(express.bodyParser());

nginx

编辑 nginx 的配置文件

sudo vi /etc/nginx/nginx.conf

在 Gzip Settings 中加入如下设置:

##
# Gzip Settings
##

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;

1) gzip

  • 语法:gzip on/off
  • 默认值:off
  • 作用域:http, server, location
  • 说明:开启或者关闭 gzip 模块,这里使用 on 表示启动

2) gzip_min_length

  • 语法:gzip_min_length length
  • 默认值:gzip_min_length 0
  • 作用域:http, server, location
  • 说明:设置允许压缩的页面最小字节数,页面字节数从header头中的Content-Length中进行获取。默认值是0,不管页面多大都压缩。建议设置成大于1k的字节数,小于1k可能会越压越大。|

3) gzip_buffers

  • 语法: gzip_buffers number size
  • 默认值: gzip_buffers 4 4k/8k
  • 作用域: http, server, location
  • 说明:设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。4 16k 代表以 16k 为单位,按照原始数据大小以 16k 为单位的4倍申请内存。

4) gzip_comp_level

  • 语法: gzip_comp_level 1..9
  • 默认值: gzip_comp_level 1
  • 作用域: http, server, location
  • 说明:gzip压缩比,1 压缩比最小处理速度最快,9 压缩比最大但处理最慢(传输快但比较消耗cpu)。这里设置为 5。

5) gzip_types

  • 语法: gzip_types mime-type [mime-type …]
  • 默认值: gzip_types text/html
  • 作用域: http, server, location
  • 说明:匹配MIME类型进行压缩,(无论是否指定)”text/html” 类型总是会被压缩的。这里设置为 text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php。

centos 7 yum安装配置apache 2.4

本文介绍在centos 7 yum安装apache 2.4,即通过yum仓库来快速安装已经编译好的版本,这样比源码编译省下不少时间。

安装和配置apache

1.安装apache 2.4

sudo yum install httpd

2.更新httpd.conf中的网站根目录配置。添加区块来配置资源使用。
文件片断:/etc/httpd/conf/httpd.conf

DocumentRoot "/var/www/html/example.com/public_html"

...

<IfModule prefork.c>
    StartServers        5
    MinSpareServers     20
    MaxSpareServers     40
    MaxRequestWorkers   256
    MaxConnectionsPerChild 5500
</IfModule>

配置基于名称的虚拟主机

1.在conf.d目录创建vhost.conf来存储你的虚拟主机配置文件。下面是example.com网站的模板;根据你的需要来更改:
文件片断:/etc/httpd/conf.d/vhost.conf

NameVirtualHost *:80

<VirtualHost *:80>
    ServerAdmin [email protected]
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/html/example.com/public_html/
    ErrorLog /var/www/html/example.com/logs/error.log
    CustomLog /var/www/html/example.com/logs/access.log combined
</VirtualHost>

其它额外的域名也同样添加到vhost.conf文件。要添加域名,复制上面的VirtualHost区块并更改。当收到来自互联网的一个新的请求,apache将在vhost.conf来检查域名匹配的VirtualHost区块:
Apache

2.创建与上面相关的目录

sudo mkdir -p /var/www/html/example.com/{public_html,logs}

3.设置apache开机启动并重启服务:

sudo systemctl enable httpd.service
sudo systemctl restart httpd.service

现在可以访问你的域名来测试apache server了。如果在你的网站根目录没有找到index文件,将显示默认的页面
Apache

配置firewalld来允许web流量

CentOS 7内置的防火墙默认设置阻止web流量。执行如下命令来允许web流量:

sudo firewall-cmd --add-service=http --permanent && sudo firewall-cmd --add-service=https --permanent 
sudo systemctl restart firewalld

使用Nginx geoip_country GeoIP模块为不同的国家显示不同的内容

如果想屏蔽某个地区的 IP 访问的话,用 iptables 把来自某个国家的 IP 重定向到预定页面不是特别灵活的办法,如果只有一个 IP 可用而有多个网站在同一 VPS 上怎么办?用 iptable 屏蔽某个网站的话也会屏蔽同一 VPS 上的其他网站的访问。所以正统的办法还是用 GeoIP 配合对应的 web 服务器模块,比如:apache + mod_geoip 或者 nginx + http_geoip_module 等。

安装 Nginx

因为要用到 http_geoip_module 模块,系统自带的 nginx 一般不带这个模块,所以要下载 nginx 源代码后自行编译:

# wget http://nginx.org/download/nginx-0.9.6.tar.gz
# tar zxvf nginx-0.9.6.tar.gz
# cd nginx-0.9.6
# ./configure --without-http_empty_gif_module --with-poll_module 
--with-http_stub_status_module --with-http_ssl_module 
--with-http_geoip_module
# make; make install

安装 MaxMind 的 GeoIP 库

MaxMind 提供了免费的 IP 地域数据库(GeoIP.dat),不过这个数据库文件是二进制的,需要用 GeoIP 库来读取,所以除了要下载 GeoIP.dat 文件外(见下一步),还需要安装能读取这个文件的库。

# wget http://geolite.maxmind.com/download/geoip/api/c/GeoIP.tar.gz
# tar -zxvf GeoIP.tar.gz
# cd GeoIP-1.4.6
# ./configure
# make; make install

刚才安装的库自动安装到 /usr/local/lib 下,所以这个目录需要加到动态链接配置里面以便运行相关程序的时候能自动绑定到这个 GeoIP 库:

# echo '/usr/local/lib' > /etc/ld.so.conf.d/geoip.conf
# ldconfig

下载 IP 数据库

MaxMind 提供了免费的 IP 地域数据库,这个数据库是二进制的,不能用文本编辑器打开,需要上面的 GeoIP 库来读取:

# wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
# gunzip GeoIP.dat.gz

配置 Nginx

最后是配置 nginx,在相关地方加上如下的配置就可以了:

# vi /etc/nginx/nginx.conf

http {
...
geoip_country /home/vpsee/GeoIP.dat;
fastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code;
fastcgi_param GEOIP_COUNTRY_CODE3 $geoip_country_code3;
fastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;
...
}

server {
...
        location / {
            root   /home/vpsee/www;
            if ($geoip_country_code = CN) {
                root /home/vpsee/cn;
            }
            ...
        }
...
}

这样,当来自中国的 IP 访问网站后就自动访问到预定的 /home/vpsee/cn 页面。关于 Nginx + GeoIP 还有很多有用的用法,比如做个简单的 CDN,来自中国的访问自动解析到国内服务器、来自美国的访问自动转向到美国服务器等。MaxMind 还提供了全球各个城市的 IP 信息,还可以下载城市 IP 数据库来针对不同城市做处理。

nginx geo根据客户端IP创建变量

geo指令使用ngx_http_geo_module模块提供的。默认情况下,nginx有加载这个模块,除非人为的 –without-http_geo_module。
ngx_http_geo_module模块可以用来创建变量,其值依赖于客户端IP地址。

geo指令

语法: geo [$address] $variable { … }
默认值: —
配置段: http
定义从指定的变量获取客户端的IP地址。默认情况下,nginx从$remote_addr变量取得客户端IP地址,但也可以从其他变量获得。如

geo $remote_addr $geo {
        default 0;
        127.0.0.1 1;
}
geo $arg_ttlsa_com $geo {
        default 0;
        127.0.0.1 1;
}

如果该变量的值不能代表一个合法的IP地址,那么nginx将使用地址“255.255.255.255”。
nginx通过CIDR或者地址段来描述地址,支持下面几个参数:
– delete:删除指定的网络
– default:如果客户端地址不能匹配任意一个定义的地址,nginx将使用此值。 如果使用CIDR,可以用“0.0.0.0/0”代替default。
– include: 包含一个定义地址和值的文件,可以包含多个。
– proxy:定义可信地址。 如果请求来自可信地址,nginx将使用其“X-Forwarded-For”头来获得地址。 相对于普通地址,可信地址是顺序检测的。
– proxy_recursive:开启递归查找地址。 如果关闭递归查找,在客户端地址与某个可信地址匹配时,nginx将使用“X-Forwarded-For”中的最后一个地址来代替原始客户端地址。如果开启递归查找,在客户端地址与某个可信地址匹配时,nginx将使用“X-Forwarded-For”中最后一个与所有可信地址都不匹配的地址来代替原始客户端地址。
– ranges:使用以地址段的形式定义地址,这个参数必须放在首位。为了加速装载地址库,地址应按升序定义。

geo $country {
    default        ZZ;
    include        conf/geo.conf;
    delete         127.0.0.0/16;
    proxy          192.168.100.0/24;
    proxy          2001:0db8::/32;

    127.0.0.0/24   US;
    127.0.0.1/32   RU;
    10.1.0.0/16    RU;
    192.168.1.0/24 UK;
}
vim conf/geo.conf
10.2.0.0/16    RU;
192.168.2.0/24 RU;

地址段例子:

geo $country {
    ranges;
    default                   ZZ;
    127.0.0.0-127.0.0.0       US;
    127.0.0.1-127.0.0.1       RU;
    127.0.0.1-127.0.0.255     US;
    10.1.0.0-10.1.255.255     RU;
    192.168.1.0-192.168.1.255 UK;
}

[warning]遵循最精确匹配原则,即nginx使用能最精确匹配客户端地址的值。[/warning]

适用实例

上面的例子几乎都是官网说明例子。下面举例说明便于理解该指令的用法。

1. 使用默认变量也就是$remote_addr

http {
  #geo $remote_addr $ttlsa_com {
  geo $ttlsa_com {
        default 0;
        127.0.0.1 1;
  }
  server {
        listen       8080;
        server_name  test.ttlsa.com;

        location /hello {
      default_type text/plain;
      echo $ttlsa_com;
      echo $arg_boy;
    }
  }
}
# curl 127.0.0.1:8080/hello?boy=默北
1
默北

2. 使用指定变量

http {
  geo $arg_boy $ttlsa_com {
        default 0;
        127.0.0.1 1;
        8.8.8.8 2;
}
  server {
        listen       8080;
        server_name  test.ttlsa.com;

        location /hello {
      default_type text/plain;
      echo $ttlsa_com;
      echo $arg_boy;
    }
  }
}
# curl 127.0.0.1:8080/hello?boy=8.8.8.8
2
8.8.8.8

3. 匹配原则

http {
  geo $arg_boy $ttlsa_com {
        default 0;
        127.0.0.1/24 24;
        127.0.0.1/32 32;
        8.8.8.8 2;
}
  server {
        listen       8080;
        server_name  test.ttlsa.com;

        location /hello {
      default_type text/plain;
      echo $ttlsa_com;
      echo $arg_boy;
    }
  }
}
# curl 127.0.0.1:8080/hello?boy=127.0.0.1
32
127.0.0.1
# curl 127.0.0.1:8080/hello?boy=127.0.0.12
24
127.0.0.12

geo指令主要是根据IP来对变量进行赋值的。因此geo块下只能定义IP或网络段,否则会报错“nginx: [emerg] invalid network”。

Ubuntu 16.04源码编译安装Nginx 1.10.3

在Ubuntu 16.04源码编译安装Nginx 1.10.3 过程记录。

一、下载相关的依赖库

  • pcre 下载地址 http://120.52.73.43/jaist.dl.sourceforge.net/project/pcre/pcre/8.38/pcre-8.38.tar.gz
  • openssl 下载地址 https://www.openssl.org/source/openssl-1.0.2h.tar.gz
  • zlib 下载地址 http://zlib.net/zlib-1.2.8.tar.gz
  • nginx 下载地址 http://nginx.org/download/nginx-1.10.3.tar.gz

二、解压相关依赖库源码包

cd /tmp
tar -zxf nginx-1.10.3.tar.gz 
tar -zxf pcre-8.38.tar.gz
tar -zxf zlib-1.2.8.tar.gz
tar -zxf openssl-1.0.2h.tar.gz

三、编译安装Nginx

cd /tmp/nginx-1.10.3
./configure --prefix=/usr/local/nginx --pid-path=/usr/local/nginx/logs/nginx.pid --error-log-path=/usr/local/nginx/logs/error.log --http-log-path=/usr/local/nginx/logs/access.log --with-http_ssl_module --with-pcre=/tmp/pcre-8.38 --with-zlib=/tmp/zlib-1.2.8 --with-openssl=/tmp/openssl-1.0.2h

四、执行make和make install 安装

make && sudo make install

五、验证nginx是否安装成功

/usr/local/nginx/sbin/nginx -v

执行结果:nginx version: nginx/1.10.3

六、启动nginx

/usr/local/nginx/sbin/nginx

浏览器访问localhost:
Nginx

centos7源码编译安装apache2.4

本该将介绍在centos 7系统上源码编译apache 2.4。这个也适用于centos 6。你可能会疑问,为什么不直接rpm安装,这可能有几个原因:
– 从仓库中安装的apache默认会安装大量地模板,这些模板可能会占用比较多的资源,而生产环境可能只需要这些模块的30%。
– 假设你只有一个512MB内存的VPS,而且选择了CentOS发行版本,从仓库安装apache,如果不优化的话,将有可能内存不足

现在开始安装。

1) 安装一些依赖包

yum install wget gcc pcre-devel openssl-devel

2) 下载apache,apr和apr-util

apache 2.4版本使用apache 2.4.12,apr-1.5.2和 apr-util-1.5.4

cd ~
mkdir sources
cd sources
wget http://ftp.piotrkosoft.net/pub/mirrors/ftp.apache.org//httpd/httpd-2.4.12.tar.bz2
wget http://ftp.ps.pl/pub/apache//apr/apr-1.5.2.tar.bz2
wget http://ftp.ps.pl/pub/apache//apr/apr-util-1.5.4.tar.bz2
tar -xvf httpd-2.4.12.tar.bz2
tar -xvf apr-1.5.2.tar.bz2
tar -xvf apr-util-1.5.4.tar.bz2
cp -r apr-1.5.2 httpd-2.4.12/srclib/apr
cp -r apr-util-1.5.4 httpd-2.4.12/srclib/apr-util
cd httpd-2.4.12
./configure --prefix=/etc/apache2 --enable-ssl --enable-so --with-included-apr --with-mpm=event
make
make install

需要注意的是由于./configure中的–prefix=/etc/apache2,apache将安装在/etc/apache2,同样的会启用ssl支持,so(动态模块支持),包括apr和event mpm。

3) 启动前一些基本的配置

需要确保/etc/apache2的所有者为apache用户:

chown -R apache.root /etc/apache2

需要对httpd.conf作一些更改

cd /etc/apache2/conf
cp httpd.conf httpd.conf.bak
vi httpd.conf

在文件内,设置用户和用户组为apache

User apache
Group apache

设置一个ServerName,这可以避免apache启动时的warning

ServerName example.com

取消Server-pool管理的注释

Include conf/extra/httpd-mpm.conf

在上面的行添加如下行:

Include conf/vhosts/*.conf

也取消server默认配置的注释

Include conf/extra/httpd-default.conf

4) 启动apache

检查启用的模板

/etc/apache2/bin/apachectl -M

检查配置文件是否有错误

/etc/apache2/bin/apachectl -t

重启apache server

/etc/apache2/bin/apachectl -k graceful

确保apache开机启动

echo '/etc/apache2/bin/apachectl start' >> /etc/rc.local

在centos 7开机80 443端口

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

如果是centos 6,使用iptables开启端口

iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -p tcp --dport 443 -j ACCEPT
service iptables save