Кеширующий прокси-сервер для geoipupdate
Потребность в актуализации GeoIP-баз, как правило, реализуется через установку на каждом сервере утилиты geoipupdate.
Но оказывается, при таком подходе легко столкнуться с суточными ограничениями на скачивание бесплатной версии GeoLite:
Можно изменить архитектуру, выделив под обновления отдельный сервер и распространять базы с него. Однако, такое решение требует самописной обвязки, для доставки обновлений на конечные хосты.
Обратим внимание, что geoipupdate поддерживает переопределение сервера обновлений:
# The server to use. Defaults to "https://updates.maxmind.com". # Host https://updates.maxmind.com
# The proxy host name or IP address. You may optionally specify a # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080http, # will be used. # Proxy 127.0.0.1:8888
Развёртывание полноценного прокси-сервера (например Squid) затруднительно в Kubernetes, из-за метода CONNECT, которые стандартные Ingress не поддерживают.
Зато, имитация API сервиса обновлений MaxMind, выглядит подходящим решением.
Разберём алгоритм работы geoipupdate:
1. geoipupdate считает контрольную сумму локальной версии базы (допустим Country) и запрашивает у updates.maxmind.com информацию об актуальной версии:
> Host: updates.maxmind.com > GET /geoip/updates/metadata?edition_id=GeoLite2-Country HTTP/2 > Authorization: Basic <лицензионный ключ>
2. updates.maxmind.com отвечает в следующем формате:
{
"databases": [
{
"date": "2025-11-25",
"edition_id": "GeoLite2-Country",
"md5": "7f97170bfe5f384a14ec6cb49a538d33"
}
]
}3. Если хеш не совпадает — требуется обновление, тогда geoipupdate обращается к download.maxmind.com:
> Host: download.maxmind.com > GET /geoip/databases/GeoLite2-Country/download?date=20251125&suffix=tar.gz HTTP/2 > Authorization: Basic <лицензионный ключ>
4. download.maxmind.com отвечает редиректом, на скачивание файла напрямую из S3
5. geoipupdate следует по ссылке и таким образом начинается загрузка файла
Всё что нам нужно, это спроксировать запросы от geoipupdate к сервисам updates и download.maxmind.com и закешировать их ответы.
Решение этой задачи уже реализовано в виде сервиса на Go:
однако оставалось предчувствие, что для реализации похожей функциональности достаточно базового Nginx.
Так получилась следующая конфигурация:
# Указание резолвера является требованием Nginx при использовании
# переменной ($follow_upstream_http_location) в proxy_pass
resolver ${NGINX_LOCAL_RESOLVERS};
# Логируем был ли использован кеш ($upstream_cache_status)
log_format geoipupdate '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$upstream_cache_status"';
proxy_cache_path /var/cache/nginx/geoipupdate levels=1:2
keys_zone=geoipupdate_cache:1m
max_size=500M
inactive=14d
use_temp_path=off;
server {
listen 8080;
server_name _;
access_log /var/log/nginx/access.log geoipupdate;
# По умолчанию, proxy_cache_key основывается на $proxy_host,
# т.е. upstream-домене.
# В результате кеш записывается, но фактически не используется.
# Запросы продолжают доходить до proxy_pass и проксироваться в maxmind.com.
# Поэтому, в составе ключа кеширования используется
# переменная $host (домен самого прокси-сервера).
# Это позволяет проверить кеш на попадание, до обращения к proxy_pass.
proxy_cache_key $host$uri$is_args$args;
# (1) Проксируем запрос на получение метаданных, в сервис updates.maxmind.com.
#
# Кешируем его ответ не непродолжительное время.
location /geoip/updates/ {
proxy_pass https://updates.maxmind.com;
proxy_ssl_server_name on;
proxy_cache geoipupdate_cache;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 30s;
# Игнорируем заголовки запрещающие кеширование.
proxy_ignore_headers Cache-Control Expires Set-Cookie;
add_header X-Proxy-Cache $upstream_cache_status;
}
# (2) Проксируем запрос на скачивание определённой версии базы,
# в сервис download.maxmind.com.
#
# Данный ресурс отвечает 302-редиректом,
# содержащим динамическую ссылку на S3-хранилище.
# Nginx перехватывает редирект и передаёт
# его обработку в internal-location @follow_redirect.
#
# То есть, вместо отдачи редиректа как есть, Nginx переходит по нему
# за нас, чтобы закешировался итоговый файл, а не ссылка на него.
location /geoip/databases/ {
# Отвечаем из кеша при наличии в нём запрашиваемой версии БД.
proxy_cache geoipupdate_cache;
proxy_pass https://download.maxmind.com;
proxy_ssl_server_name on;
# Перехватываем редирект в ответе бекенда и передаём его в internal-location.
proxy_intercept_errors on;
error_page 302 = @follow_redirect;
}
# (3) Обработчик перехода по ссылке из заголовка Location ответа upstream.
location @follow_redirect {
internal;
# При входе в location, сохраняем текущее
# значение переменной $upstream_http_location из шага 2.
# Поскольку после вызова proxy_pass оно будет перезаписано.
set $follow_upstream_http_location $upstream_http_location;
# Переходим в S3 по ссылке из редиректа.
proxy_pass $follow_upstream_http_location;
proxy_ssl_server_name on;
# Исключаем проксирование заголовка Authorization в Amazon S3,
# поскольку в обратном случае он отвечает ошибкой:
# > Missing x-amz-content-sha256
proxy_set_header Authorization "";
# Сохраняем ответ S3 в кеш.
proxy_cache geoipupdate_cache;
proxy_cache_valid 200 7d;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 30s;
proxy_ignore_headers Cache-Control Expires Set-Cookie;
add_header X-Proxy-Cache $upstream_cache_status;
}
}Остаётся прописать URL-нашего прокси, в параметре Host конфигурации GeoIP.conf.