在本文中,我想分享我使用NJS的经验,NJS是Nginx Inc.开发的Nginx的JavaScript解释器,使用一个真实的示例来描述其主要功能。NJS是JavaScript的子集,扩展了Nginx的功能。当被问及为什么要自己的口译员???德米特里·沃林佐夫(Dmitry Volyntsev)详细回答。简而言之:与Lua不同,NJS是nginx方式,而JavaScript是更具进步性,“本机”且没有GC的。
很久以前 ...
在我的上一份工作中,我继承了gitlab,其中包含许多motley CI / CD管道,并将docker-compose,dind和其他乐趣转移到kaniko轨道上。 CI中先前使用的图像已以其原始形式移动。他们工作正常,直到gitlab更改IP和CI变成南瓜的那一天。问题是参与CI的docker映像之一包含git,该git通过ssh提取了Python模块。 Ssh需要一个私钥,并且...与已知的主机一起在映像中。而且,由于实际IP与known_hosts中指定的IP不匹配,任何CI都无法验证密钥。从现有的Dockfiles快速构建了一个新映像,并添加了一个选项StrictHostKeyChecking no
... 但是,令人不愉快的余味仍然存在,并且有将其转移到私有PyPI存储库的愿望。切换到私有PyPI之后,额外的好处变成了更简单的管道和对requirements.txt的常规描述。
做出选择了,先生们!
我们将所有内容都旋转到云和Kubernetes中,因此,我们希望获得一个小型服务,该服务是具有外部存储的无状态容器。好吧,因为我们使用S3,所以它是优先考虑的事情。并且,如果可能的话,在gitlab中进行身份验证(如果需要,您可以自己添加)。
快速搜索产生了s3pypi,pypicloud的多个结果,并提供了“手动”为萝卜生成html文件的选项。最后一个选项本身消失了。
s3pypi:这是使用S3托管的cli。我们上传文件,生成html并填写相同的存储桶。适合家庭使用。
pypicloud:似乎是一个有趣的项目,但是在阅读了码头之后,我很失望。尽管有很好的文档说明和可以扩展您的任务的能力,但事实证明,它是多余的且难以配置。要纠正您的任务代码,根据当时的估计,这需要3-5天的时间。该服务还需要一个数据库。我们留下了它,以防万一。
更深入的搜索产生了Nginx的模块ngx_aws_auth。测试的结果是XML显示在浏览器中,该XML显示了S3存储桶的内容。搜索时的最后一次提交是在一年前。该存储库看起来已废弃。
PEP-503 , XML HTML pip. Nginx S3 S3 JS Nginx. NJS.
, XML, ngx_aws_auth, JS.
nginx . - , - Nginx ( ), - Nginx, . , Python Go ( ), nexus.
TL;DR 2 PyPi CI.
?
Nginx ngx_http_js_module
, docker-. c js_import
Nginx. js_content
. js_set
, . NJS Nginx, XMLHttpRequest. Nginx . (subrequest) . Nginx, export default
.
nginx.conf
load_module modules/ngx_http_js_module.so;
http {
js_import imported_name from script.js;
server {
listen 8080;
...
location = /sub-query {
internal;
proxy_pass http://upstream;
}
location / {
js_content imported_name.request;
}
}
script.js
function request(r) {
function call_back(resp) {
// handler's code
r.return(resp.status, resp.responseBody);
}
r.subrequest('/sub-query', { method: r.method }, call_back);
}
export default {request}
http://localhost:8080/
location /
js_content
request
script.js
. request
location = /sub-query
, ( GET) (r)
, . call_back
.
S3
S3-, :
ACCESS_KEY
SECRET_KEY
S3_BUCKET
http-, /, S3_NAME URI , (HMAC_SHA1) SECRET_KEY. , AWS $ACCESS_KEY:$HASH
, . /, , X-amz-date
. :
nginx.conf
load_module modules/ngx_http_js_module.so;
http {
js_import s3 from s3.js;
js_set $s3_datetime s3.date_now;
js_set $s3_auth s3.s3_sign;
server {
listen 8080;
...
location ~* /s3-query/(?<s3_path>.*) {
internal;
proxy_set_header X-amz-date $s3_datetime;
proxy_set_header Authorization $s3_auth;
proxy_pass $s3_endpoint/$s3_path;
}
location ~ "^/(?<prefix>[\w-]*)[/]?(?<postfix>[\w-\.]*)$" {
js_content s3.request;
}
}
s3.js
( AWS Sign v2, deprecated)
var crypt = require('crypto');
var s3_bucket = process.env.S3_BUCKET;
var s3_access_key = process.env.S3_ACCESS_KEY;
var s3_secret_key = process.env.S3_SECRET_KEY;
var _datetime = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '');
function date_now() {
return _datetime
}
function s3_sign(r) {
var s2s = r.method + '\n\n\n\n';
s2s += `x-amz-date:${date_now()}\n`;
s2s += '/' + s3_bucket;
s2s += r.uri.endsWith('/') ? '/' : r.variables.s3_path;
return `AWS ${s3_access_key}:${crypt.createHmac('sha1', s3_secret_key).update(s2s).digest('base64')}`;
}
function request(r) {
var v = r.variables;
function call_back(resp) {
r.return(resp.status, resp.responseBody);
}
var _subrequest_uri = r.uri;
if (r.uri === '/') {
// root
_subrequest_uri = '/?delimiter=/';
} else if (v.prefix !== '' && v.postfix === '') {
// directory
var slash = v.prefix.endsWith('/') ? '' : '/';
_subrequest_uri = '/?prefix=' + v.prefix + slash;
}
r.subrequest(`/s3-query${_subrequest_uri}`, { method: r.method }, call_back);
}
export default {request, s3_sign, date_now}
_subrequest_uri
: uri S3. «», uri- delimiter
, xml- CommonPrefixes, ( PyPI, ). ( ), uri- prefix () /. , . aiohttp-request aiohttp-requests /?prefix=aiohttp-request
, . , /?prefix=aiohttp-request/
, . , uri .
, Nginx. Nginx, XML, :
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>myback-space</Name>
<Prefix></Prefix>
<Marker></Marker>
<MaxKeys>10000</MaxKeys>
<Delimiter>/</Delimiter>
<IsTruncated>false</IsTruncated>
<CommonPrefixes>
<Prefix>new/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>old/</Prefix>
</CommonPrefixes>
</ListBucketResult>
CommonPrefixes
.
, , , XML:
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name> myback-space</Name>
<Prefix>old/</Prefix>
<Marker></Marker>
<MaxKeys>10000</MaxKeys>
<Delimiter></Delimiter>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>old/giphy.mp4</Key>
<LastModified>2020-08-21T20:27:46.000Z</LastModified>
<ETag>"00000000000000000000000000000000-1"</ETag>
<Size>1350084</Size>
<Owner>
<ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>old/hsd-k8s.jpg</Key>
<LastModified>2020-08-31T16:40:01.000Z</LastModified>
<ETag>"b2d76df4aeb4493c5456366748218093"</ETag>
<Size>93183</Size>
<Owner>
<ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
Key
.
XML HTML, Content-Type text/html.
function request(r) {
var v = r.variables;
function call_back(resp) {
var body = resp.responseBody;
if (r.method !== 'PUT' && resp.status < 400 && v.postfix === '') {
r.headersOut['Content-Type'] = "text/html; charset=utf-8";
body = toHTML(body);
}
r.return(resp.status, body);
}
var _subrequest_uri = r.uri;
...
}
function toHTML(xml_str) {
var keysMap = {
'CommonPrefixes': 'Prefix',
'Contents': 'Key',
};
var pattern = `<k>(?<v>.*?)<\/k>`;
var out = [];
for(var group_key in keysMap) {
var reS;
var reGroup = new RegExp(pattern.replace(/k/g, group_key), 'g');
while(reS = reGroup.exec(xml_str)) {
var data = new RegExp(pattern.replace(/k/g, keysMap[group_key]), 'g');
var reValue = data.exec(reS);
var a_text = '';
if (group_key === 'CommonPrefixes') {
a_text = reValue.groups.v.replace(/\//g, '');
} else {
a_text = reValue.groups.v.split('/').slice(-1);
}
out.push(`<a href="/${reValue.groups.v}">${a_text}</a>`);
}
}
return '<html><body>\n' + out.join('</br>\n') + '\n</html></body>'
}
PyPI
, .
#
python3 -m venv venv
. ./venv/bin/activate
# .
pip download aiohttp
#
for wheel in *.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done
rm -f *.whl
#
pip install aiohttp -i http://localhost:8080
.
#
python3 -m venv venv
. ./venv/bin/activate
pip install setuptools wheel
python setup.py bdist_wheel
for wheel in dist/*.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done
pip install our_pkg --extra-index-url http://localhost:8080
CI, :
pip install setuptools wheel
python setup.py bdist_wheel
curl -sSfT dist/*.whl -u "gitlab-ci-token:${CI_JOB_TOKEN}" "https://pypi.our-domain.com/${CI_PROJECT_NAME}"
Gitlab JWT / . auth_request Nginx, . url Gitlab- , Gitlab 200 / . Gitlab? , Nginx , - , . , Kubernetes read-only root filesystem, nginx.conf configmap. Nginx configmap (pvc) read-only root filesystem ( ).
NJS, nginx - (, URL).
nginx.conf
location = /auth-provider {
internal;
proxy_pass $auth_url;
}
location = /auth {
internal;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
js_content auth.auth;
}
location ~ "^/(?<prefix>[\w-]*)[/]?(?<postfix>[\w-\.]*)$" {
auth_request /auth;
js_content s3.request;
}
s3.js
var env = process.env;
var env_bool = new RegExp(/[Tt]rue|[Yy]es|[Oo]n|[TtYy]|1/);
var auth_disabled = env_bool.test(env.DISABLE_AUTH);
var gitlab_url = env.AUTH_URL;
function url() {
return `${gitlab_url}/jwt/auth?service=container_registry`
}
function auth(r) {
if (auth_disabled) {
r.return(202, '{"auth": "disabled"}');
return null
}
r.subrequest('/auth-provider',
{method: 'GET', body: ''},
function(res) {
r.return(res.status, "");
});
}
export default {auth, url}
: - ? ! , var AWS = require('aws-sdk') "" S3-!
, JS-, , . require('crypto'), build-in- require . - . , - .
Nginx gzip off;
, gzip- NJS , . , . , . , .
«» error.log. info, warn error 3 r.log, r.warn, r.error . Chrome (v8) njs, . , , history :
docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx
.
, . IDE . , .
ES6.
- , . NJS.
NJS - open-source , Nginx JavaScript. . , . , - NJS , Nginx . NGINX Plus - !
njs-Nginx中的本机JavaScript脚本/ Dmitry Volnyev在Saint HighLoad ++ 2019上的演讲
正在生产中的NJS / Vasily Soshnikov在HighLoad ++ 2019上的演讲