为nginx提供GeoIP2支持

  1. 1. 背景提要
  2. 2. 准备代码
    1. 2.1. 准备一个工作目录
    2. 2.2. 安装libmaxminddb
    3. 2.3. 下载 pcre 、 zlib 和 openssl
    4. 2.4. 下载 nginx 源码
    5. 2.5. 下载 ngx_http_geoip2_module
  3. 3. 开始编译
    1. 3.1. 进入 nginx 源码目录
    2. 3.2. 配置构建参数
    3. 3.3. 构建模块
  4. 4. 加载模块
    1. 4.1. 建立动态模块目录
    2. 4.2. 编写模块加载配置
    3. 4.3. 编写模块调用配置
    4. 4.4. 编写工作规则

最近在处理站点的连接优化问题,需要通过检测访问者的 IP ,来选择是否经由 CloudFlare 代理,还是通过一个优化的直连地址进行体验优化。经过一些资料的比对,最终决定使用 GeoIP2 分辨来访者 IP 归属地,并通过 nginx 设置对应的规则来跳转访问。

背景提要

GeoIP2 是 MaxMind 公司推出的知名 IP 定位数据库,有提供免费的 Lite 版本可供下载使用(需要注册),数据库每周更新。

请注意如果您使用了免费下载的 GeoLite2 数据库,您需要遵循 Creative Commons 4.0 的授权使用原则,在您使用到了该数据库的项目/页面上标注 MaxMind 的宣传(可在下载页面看到),自觉遵守使用协议从我做起。

nginx的版本比较重要,低于1.9.11版本的nginx无法使用动态模块,低于1.11.5版本的nginx编译动态模块时不支持--with-compat兼容参数,所以请保证您的nginx版本不低于1.11.5

比较推荐的做法是使用稳定的主要版本,可以在nginx的官方网站上找到对应Linux系统的包管理软件安装教程

本文中使用的下载方式是 wget ,您也可以选择使用其他的下载方案。

准备代码

准备一个工作目录

一个工作目录有助于保持环境的干净,防止其他项目的干扰,并且在不再需要时移除也比较方便。

假设我们是 root 用户,登入VPS后没有做什么其他的工作,那么我们当前的目录是 /root/
我们创建一个名为 nginx 的工作目录,并进入,那么我们现在的目录应当就是 /root/nginx/

可以稍微留一个心眼,之后编译构建的时候,我使用到的是绝对路径。

安装libmaxminddb

这是一个用于 C 语言读取 MaxMind 数据库的资源包,编译插件时需要使用。这里只介绍最简单的安装方式,如有出现任何问题您可以查询原始页面的说明,或是在评论区回复,一起定位出错点。

  1. Release下载libmaxminddb-*.tar.gz的文件,例如我使用的是libmaxminddb-1.5.2.tar.gz

    1
    wget https://github.com/maxmind/libmaxminddb/releases/download/1.5.2/libmaxminddb-1.5.2.tar.gz
  2. 解压文件,进入目录

    1
    2
    tar zxvf libmaxminddb-1.5.2.tar.gz
    cd libmaxminddb-1.5.2/
  3. 编译安装

    1
    2
    3
    4
    5
    ./configure
    make
    make check
    make install
    ldconfig
  4. 退出目录

    1
    cd ../

    下载 pcre 、 zlib 和 openssl

nginx 的编译需要这三大模块的参与,缺少会导致在配置阶段就报错,因而建议分别下载并解压。

请注意 nginx 使用的是 openssl 1 的头文件,似乎还不支持 openssl 3 ,请还是选择 1.1.1j 版本吧。

名称 官网地址 下载地址 截稿时的最新软件包
pcre pcre pcre-dl pcre-8.44
zlib zlib zlib-dl zlib-1.2.11
openssl openssl openssl-dl openssl-1.1.1j

同样,下载+解压缩:

1
2
3
4
5
6
wget https://ftp.pcre.org/pub/pcre/pcre-8.44.tar.gz
wget https://zlib.net/zlib-1.2.11.tar.gz
wget https://www.openssl.org/source/openssl-1.1.1j.tar.gz
tar zxvf pcre-8.44.tar.gz
tar zxvf zlib-1.2.11.tar.gz
tar zxvf openssl-1.1.1j.tar.gz

下载 nginx 源码

请根据您的 nginx 版本,选择对应版本的源码进行下载,跨越版本会导致插件无法正常工作,可能会需要您重新编译;当然如果您的 nginx 版本太过拉跨的话,不如就趁此机会顺带升级一下吧 (雾)

例如我使用的是 nginx/1.19.7 ,您可以使用 nginx -v 指令查询您的 nginx 版本。

名称 官网地址 下载地址 截稿时的最新软件包
nginx nginx nginx-dl nginx-1.19.7

下载完成,解压备用

1
2
wget https://nginx.org/download/nginx-1.19.7.tar.gz
tar zxvf nginx-1.19.7.tar.gz

下载 ngx_http_geoip2_module

这个模块就是用来让 nginx 读取 MaxMind GeoIP2 数据库的模块。

1
git clone https://github.com/leev/ngx_http_geoip2_module.git

如果没有安装 git ,也可以下载 Release 包使用:

1
2
wget https://github.com/leev/ngx_http_geoip2_module/archive/3.3.tar.gz
tar zxvf 3.3.tar.gz

开始编译

编译使用的应该是 gcc 编译器,并且会使用到 make 相关的工具,我使用的系统中已经提供了这些工具,因而没有出现报错;如果您的系统在编译的过程中出现报错,那么可能是您缺少对应的工具,您可以自行搜索。

进入 nginx 源码目录

1
cd nginx-1.19.7/

配置构建参数

1
2
3
4
5
6
7
./configure                                             \
--with-pcre=/root/nginx/pcre-8.44 \
--with-zlib=/root/nginx/zlib-1.2.11 \
--with-openssl=/root/nginx/openssl-1.1.1j \
--add-dynamic-module=/root/nginx/ngx_http_geoip2_module \
--with-stream \
--with-compat

请注意,如果使用的是不是 git clone 的方式获取 ngx_http_geoip2_module ,您可能需要修改目录的位置为 ngx_http_geoip2_module-3.3 (以3.3版本为例)

ngx_http_geoip2_module 里 ReadMe.md 的方法是错误的,不附带 --with-compat 参数会让构建出来的模块无法被使用。

构建完成后,您应当可以看见如下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Configuration summary
+ using PCRE library: /root/nginx/pcre-8.44
+ using OpenSSL library: /root/nginx/openssl-1.1.1j
+ using zlib library: /root/nginx/zlib-1.2.11

nginx path prefix: "/usr/local/nginx"
nginx binary file: "/usr/local/nginx/sbin/nginx"
nginx modules path: "/usr/local/nginx/modules"
nginx configuration prefix: "/usr/local/nginx/conf"
nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
nginx pid file: "/usr/local/nginx/logs/nginx.pid"
nginx error log file: "/usr/local/nginx/logs/error.log"
nginx http access log file: "/usr/local/nginx/logs/access.log"
nginx http client request body temporary files: "client_body_temp"
nginx http proxy temporary files: "proxy_temp"
nginx http fastcgi temporary files: "fastcgi_temp"
nginx http uwsgi temporary files: "uwsgi_temp"
nginx http scgi temporary files: "scgi_temp"

构建模块

构建时候单纯构建模块就足够了,完整构建浪费资源和时间。

1
make modules

构建完成后您可以看见类似如下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objs/addon/ngx_http_geoip2_module/ngx_http_geoip2_module.o \
objs/ngx_http_geoip2_module_modules.o \
-lmaxminddb \
-shared
cc -c -fPIC -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I /root/nginx/pcre-8.44 -I /root/nginx/zlib-1.2.11 -I objs -I src/http -I src/http/modules -I src/stream \
-o objs/addon/ngx_http_geoip2_module/ngx_stream_geoip2_module.o \
/root/nginx/ngx_http_geoip2_module/ngx_stream_geoip2_module.c
cc -c -fPIC -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I /root/nginx/pcre-8.44 -I /root/nginx/zlib-1.2.11 -I objs -I src/http -I src/http/modules -I src/stream \
-o objs/ngx_stream_geoip2_module_modules.o \
objs/ngx_stream_geoip2_module_modules.c
cc -o objs/ngx_stream_geoip2_module.so \
objs/addon/ngx_http_geoip2_module/ngx_stream_geoip2_module.o \
objs/ngx_stream_geoip2_module_modules.o \
-lmaxminddb \
-shared
make[1]: Leaving directory '/root/nginx/nginx-1.19.7'

到此,模块就已经编译完成,可以准备动态加载了。

加载模块

建立动态模块目录

构建完成的模块在 objs/ 目录下,为了避免后续清理工作目录的操作可能会导致误删,此处建议将这些模块移动到一个比较安全的地方,例如 /etc/nginx 中。为了和静态模块 modules 区分,我们新建一个 dynamic-modules 目录。

1
mkdir /etc/nginx/dynamic-modules

然后就可以快乐地迁移模块啦。

1
2
mv objs/*.so /etc/nginx/dynamic-modules/
cd /etc/nginx

编写模块加载配置

出于管理上的方便考虑,我们可以在 /etc/nginx 下新建一个模块配置文件 (如 modules.conf),专门用于模块的加载管理,其中写入我们的模块加载语句:

1
2
3
# Maxmind GeoIP2
load_module dynamic-modules/ngx_http_geoip2_module.so;
load_module dynamic-modules/ngx_stream_geoip2_module.so;

并在 nginx.conf 中加载这个配置文件:

1
2
3
4
5
6
7
user  nginx;
worker_processes 1;

+include modules.conf;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

这样就能完成模块的加载啦。

编写模块调用配置

光有模块加载还不足够,还需要让模块中的数据能为我们所用。而方法其实也很简单,以国家码为例,在 http / stream 块中按照如下的方式配置即可:

1
2
3
geoip2 /path/to/your/GeoLite2-Country.mmdb {
$geoip2_country_code default=- source=$remote_addr country iso_code;
}

$geoip2_country_code 会被设置成 ISO 3166 规定的国家码。

详细的使用规则可以参见 模块的使用样例 ,此处不再赘述。

编写工作规则

获得了请求地址的国家地区码后,就可以进行匹配了。例如对于加速站设置一条优化,将所有非中国的访客全部禁止访问:

1
2
3
4
## Country Restrict
if ($geoip2_country_code !~ CN) {
return 403;
}

以避免其他请求消耗加速站的流量吧。毕竟在现代世界,使用 CloudFlare 获得的体验比直连好上不少呀。