在高性能 Web 服务器领域,NGINX 凭借轻量、高效的架构,成为承载大量流量的常见选择。NGINX JavaScript 模块(njs)引入共享字典功能后,又进一步扩展了其能力。
本文介绍 njs 共享字典的功能和优势,以及如何配置 NGINX 开源版,在轮换 SSL/TLS 证书时无需重启服务。
共享字典的基础与优势
新的 js_shared_dict_zone 指令允许 NGINX 开源版用户启用共享内存区域,在工作进程之间高效交换数据。这些区域充当键值字典,存储可实时读取和修改的动态配置。
共享字典主要有以下优势:
- 开销小,容易使用:直接内置于 njs,API 直观,实现简单,配置和使用都很方便,也简化了工作进程之间的数据管理和共享。
- 轻量高效:与 NGINX 无缝集成,利用事件驱动、非阻塞 I/O 模型降低内存占用、提高并发能力,让 NGINX 高效处理大量并发连接。
- 可扩展:利用 NGINX 多工作进程的横向扩展能力,在进程之间共享、同步数据,无需复杂的进程间通信。生存时间(TTL)设置可让共享字典条目因不活动而被移除;
evict参数会移除最旧的键值对,为新条目腾出空间。
使用共享字典轮换 SSL 证书
SSL/TLS 轮换是共享字典最有价值的用途之一。使用 js_shared_dict_zone 后,更新 SSL/TLS 证书或密钥时无需重启 NGINX,同时还能为证书管理提供类似 REST 的 API。
下面的 NGINX 配置使用 js_set 和 ssl_certificate 设置 HTTPS 服务器。JavaScript 处理函数通过 js_set 从文件读取证书或密钥。
配置片段把证书和密钥存入共享字典,作为共享内存缓存。如果缓存里没有对应键,就从磁盘读取并写入缓存。
还可以暴露一个清空缓存的 location。磁盘文件更新后,例如完成证书与密钥续期,清空共享字典会使下一次读取重新从磁盘加载。这样便能在不重启 NGINX 进程的情况下轮换证书和密钥。
http {
...
js_shared_dict_zone zone=kv:1m;
server {
…
# Sets an njs function for the variable. Returns a value of cert/key
js_set $dynamic_ssl_cert main.js_cert;
js_set $dynamic_ssl_key main.js_key;
# use variable's data
ssl_certificate data:$dynamic_ssl_cert;
ssl_certificate_key data:$dynamic_ssl_key;
# a location to clear cache
location = /clear {
js_content main.clear_cache;
# allow 127.0.0.1;
# deny all;
}
...
}
下面是使用 js_shared_dict_zone 轮换 SSL/TLS 证书和密钥的 JavaScript 实现:
function js_cert(r) {
if (r.variables['ssl_server_name']) {
return read_cert_or_key(r, '.cert.pem');
} else {
return '';
}
}
function js_key(r) {
if (r.variables['ssl_server_name']) {
return read_cert_or_key(r, '.key.pem');
} else {
return '';
}
}
/**
* Retrieves the key/cert value from Shared memory or fallback to disk
*/
function read_cert_or_key(r, fileExtension) {
let data = '';
let path = '';
const zone = 'kv';
let certName = r.variables.ssl_server_name;
let prefix = '/etc/nginx/certs/';
path = prefix + certName + fileExtension;
r.log('Resolving ${path}');
const key = ['certs', path].join(':');
const cache = zone && ngx.shared && ngx.shared[zone];
if (cache) {
data = cache.get(key) || '';
if (data) {
r.log(`Read ${key} from cache`);
return data;
}
}
try {
data = fs.readFileSync(path, 'utf8');
r.log('Read from cache');
} catch (e) {
data = '';
r.log(`Error reading from file:${path}. Error=${e}`);
}
if (cache && data) {
try {
cache.set(key, data);
r.log('Persisted in cache');
} catch (e) {
const errMsg = `Error writing to shared dict zone: ${zone}. Error=${e}`;
r.log(errMsg);
}
}
return data
}
发送 /clear 请求会使缓存失效,NGINX 在下一次 SSL/TLS 握手中从磁盘读取证书或密钥。还可以实现一个 js_content 处理函数,从请求中接受证书或密钥,在持久化的同时更新缓存。
本例完整代码见 njs 官方 GitHub 示例仓库。
开始使用
共享字典是增强应用可编程能力的工具,在简化实现和扩展方面有明显优势。利用 js_shared_dict_zone,可以拓展新的使用场景,更高效地应对不断增长的流量需求。
可以升级 NGINX 部署以使用 js_shared_dict_zone,并通过官方示例文档了解这项功能。原文还介绍了当时新发布的 njs-acme 项目,它提供共享字典的完整示例,让 njs 模块运行时能够与 ACME 服务提供方交互。











暂无评论内容