Бул макалада мен Nginx Inc тарабынан иштелип чыккан Nginx үчүн JavaScript котормочу NJS менен болгон тажрыйбамды бөлүшкүм келет, анын негизги мүмкүнчүлүктөрүн чыныгы мисал аркылуу сүрөттөп бергим келет. NJS бул Nginxтин функцияларын кеңейтүүгө мүмкүндүк берген JavaScript'тин чакан жыйындысы. деген суроого Дмитрий Волынцев толук жооп берди. Кыскача айтканда: NJS nginx жолу, ал эми JavaScript Луадан айырмаланып прогрессивдүү, "түпкүлүктүү" жана GCсиз.
Көп убакыт мурун…
Акыркы жумушумда мен канико рельстерине которулган docker-compose, dind жана башка ырахаттары бар бир катар түстүү CI/CD түтүктөрү менен gitlabти мурастап алдым. Мурда CIде колдонулган сүрөттөр баштапкы түрүндө жылдырылды. Биздин gitlab IP өзгөрүп, CI ашкабакка айланган күнгө чейин алар туура иштешти. Көйгөй CIге катышкан докер сүрөттөрүнүн биринде Python модулдарын ssh аркылуу тарткан git камтылган. Ssh үчүн сизге купуя ачкыч керек жана... ал белгилүү_hosts менен бирге сүрөттө болгон. Жана ар кандай CI чыныгы IP менен белгилүү_hosts ичинде көрсөтүлгөндүн дал келбегендигинен улам ачкыч текшерүү катасы менен ишке ашпай калды. Жаңы сүрөт тез арада учурдагы Dockfiles файлдарынан чогултулуп, опция кошулду StrictHostKeyChecking no. Бирок жаман даам сакталып калды жана либерлерди жеке PyPI репозиторийине көчүрүү каалоосу бар. Кошумча бонус, жеке PyPIге өткөндөн кийин, жөнөкөй түтүк жана талаптар.txt файлынын кадимки сүрөттөлүшү болду.
Тандоо жасалды, мырзалар!
Биз бардыгын булуттарда жана Кубернеттерде иштетебиз, акырында тышкы сактагычы бар жарандыгы жок контейнер болгон чакан кызматты алгыбыз келди. Ооба, биз S3 колдонгондуктан, ага артыкчылык берилген. Жана, мүмкүн болсо, gitlabда аутентификация менен (зарыл болсо, аны өзүңүз кошо аласыз).
Ыкчам издөө бир нече натыйжаларды берди: s3pypi, pypicloud жана шалкан үчүн html файлдарын "кол менен" түзүү опциясы. Акыркы вариант өзүнөн өзү жок болуп кетти.
s3pypi: Бул S3 хостингди колдонуу үчүн cli. Биз файлдарды жүктөйбүз, html түзөбүз жана аны ошол эле чакага жүктөйбүз. Үйдө колдонууга ылайыктуу.
pypicloud: Бул кызыктуу долбоор сыяктуу көрүндү, бирок документтерди окугандан кийин көңүлүм калды. Жакшы документтерге жана муктаждыктарыңызга ылайык кеңейтүү мүмкүнчүлүгүнө карабастан, чындыгында ал ашыкча жана конфигурациялоо кыйын болуп чыкты. Кодду өз милдеттериңизге ылайыкташтыруу үчүн, ошол кездеги эсептөөлөр боюнча, 3-5 күн талап кылынмак. Кызматка маалымат базасы да керек. Башка эч нерсе таппай калсак деп таштап койдук.
Тереңирээк издөө Nginx, ngx_aws_auth модулун берди. Анын тестирлөөнүн натыйжасы S3 чакасынын мазмунун көрсөткөн браузерде XML көрсөтүлдү. Издөө учурундагы акыркы кылмыш иши бир жыл мурун болгон. Репозиторий кароосуз калгандай көрүндү.
Булакка барып окуу менен Мен XMLди тез эле HTMLге айландырууга жана пипке берүүгө болорун түшүндүм. Nginx жана S3 жөнүндө бир аз көбүрөөк издегенден кийин, мен Nginx үчүн JSде жазылган S3деги аутентификациянын мисалын көрдүм. Мен NJS менен ошентип тааныштым.
Бул мисалды негиз катары алып, бир сааттан кийин мен браузеримде ngx_aws_auth модулун колдонуудагы XMLди көрдүм, бирок баары JSде жазылган.
Мага nginx чечими абдан жакты. Биринчиден, жакшы документтер жана көптөгөн мисалдар, экинчиден, биз файлдар менен иштөө үчүн Nginxтин бардык жакшы жактарын алабыз (кутудан тышкары), үчүнчүдөн, Nginx үчүн конфигурацияларды кантип жазууну билген ар бир адам эмне экенин түшүнө алат. Минимализм дагы Python же Go менен салыштырганда мен үчүн плюс (эгер нөлдөн баштап жазылган болсо), nexus жөнүндө айтпаганда да.
TL;DR 2 күндөн кийин, PyPi'нин сыноо версиясы CIде мурунтан эле колдонулган.
Бул кандай иштейт?
Модуль Nginxке жүктөлөт ngx_http_js_module, расмий докер сүрөтүнө киргизилген. Директиваны колдонуу менен скриптибизди импорттойбуз js_importNginx конфигурациясына. Функция директива аркылуу чакырылат js_content. Директива өзгөрмөлөрдү коюу үчүн колдонулат js_set, ал аргумент катары сценарийде сүрөттөлгөн функцияны гана алат. Бирок биз NJSдеги подсуроолорду эч кандай XMLHttpRequest эмес, Nginx аркылуу гана аткара алабыз. Бул үчүн, тиешелүү жер Nginx конфигурациясына кошулушу керек. Жана скрипт бул жерге субсуранды сүрөттөшү керек. 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, SECRET_KEY аркылуу кол коюлган (HMAC_SHA1) саптын белгилүү бир түрү түзүлөт. Кийинки сыяктуу сызык 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 авторизациясынын мисалы, эскирген статуска өзгөртүлдү)
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 + 'nnnn';
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, каталогдорго туура келген бардык CommonPrefixes xml элементтеринин тизмесин кайтарат (PyPI учурда, бардык пакеттердин тизмеси). Эгер сизге белгилүү бир каталогдогу мазмундардын тизмесин алуу керек болсо (баардык пакет версияларынын тизмеси), анда uri сурамында сөзсүз түрдө / менен аяктаган каталогдун (пакеттин) аталышы менен префикс талаасы болушу керек. Болбосо, мисалы, каталогдун мазмунун сурап жатканда кагылышуулар болушу мүмкүн. 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 башын текст/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="/ky/${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:8080CIде пакетти түзүү жана жүктөө төмөнкүдөй көрүнөт:
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 колдонсо болот. Nginx'те auth_request директивасын колдонуу менен, биз аутентификация маалыматтарын скрипттеги функция чакырыгын камтыган субсуроого багыттайбыз. Скрипт Gitlab url дарегине дагы бир кошумча суроо-талапты жасайт жана аутентификация маалыматтары туура көрсөтүлгөн болсо, анда Gitlab 200 кодун кайтарып берет жана пакетти жүктөө/жүктөп алууга уруксат берилет. Эмне үчүн бир подсуроону колдонбой, маалыматтарды дароо Gitlabге жөнөтүүгө болбойт? Анткени анда биз авторизацияга кандайдыр бир өзгөртүүлөрдү киргизген сайын Nginx конфигурация файлын түзөтүшүбүз керек болот жана бул өтө түйшүктүү иш. Ошондой эле, эгерде Kubernetes окуу үчүн гана түпкү файл тутумунун саясатын колдонсо, анда бул nginx.conf файлын конфигмап аркылуу алмаштырууда дагы татаалдаштырат. Жана Nginxти конфигмап аркылуу конфигурациялоо таптакыр мүмкүн эмес болуп калат, ошол эле учурда көлөмдөрдү (pvc) жана окуу үчүн гана тамыр файл тутумун туташтырууга тыюу салган саясаттарды колдонуу (бул да болот).
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 модулдарын импорттоо мүмкүн эместиги жагымсыз, бирок күтүлгөн өзгөчөлүк болуп калды. Жогорудагы мисалда сүрөттөлгөн талап('crypto') болуп саналат жана алар үчүн гана иштерди талап кылат. Ошондой эле скрипттерден кодду кайра колдонуунун эч кандай жолу жок жана сиз аны ар кандай файлдарга көчүрүп, чапташыңыз керек. Качандыр бир күнү бул функция ишке ашат деп үмүттөнөм.
Nginxтеги учурдагы долбоор үчүн кысуу да өчүрүлүшү керек gzip off;
NJSде gzip модулу жок жана аны туташтыруу мүмкүн эмес болгондуктан, кысылган маалыматтар менен иштөөгө эч кандай мүмкүнчүлүк жок; Ырас, бул иш үчүн чындап эле минус эмес. Текст көп эмес жана өткөрүлүп берилген файлдар кысылып калган жана кошумча кысуу аларга көп жардам бербейт. Ошондой эле, бул сиз мазмунду бир нече миллисекундга тезирээк жеткирүү менен убара боло тургандай жүктөлгөн же критикалык кызмат эмес.
Скрипттин мүчүлүштүктөрүн оңдоо көп убакытты талап кылат жана error.log ичиндеги "басмалар" аркылуу гана мүмкүн. Белгиленген журнал деңгээлиндеги маалыматка, эскертүү же катага жараша r.log, r.warn, r.error 3 методун колдонсо болот. Мен кээ бир скрипттерди Chrome (v8) же njs консол куралында оңдоого аракет кылам, бирок ал жерде бардыгын текшерүү мүмкүн эмес. Кодду оңдоодо, башкача айтканда функционалдык тестирлөө, тарых мындай көрүнөт:
docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginxжана мындай катарлар жүздөгөн болушу мүмкүн.
Алар үчүн подсуроолорду жана өзгөрмөлөрдү колдонуу менен код жазуу чаташкан чаташууга айланат. Кээде сиз кодуңуздун иш-аракеттеринин ырааттуулугун аныктоого аракет кылып, ар кандай IDE терезелерин айланып чыга баштайсыз. Бул кыйын эмес, бирок кээде абдан тажатып жиберет.
ES6 үчүн толук колдоо жок.
Башка кемчиликтер да болушу мүмкүн, бирок мен башка эч нерсеге туш болгон жокмун. NJS колдонууда терс тажрыйбаңыз болсо, маалымат бөлүшүңүз.
жыйынтыктоо
NJS - бул Nginxте ар кандай JavaScript скрипттерин ишке ашырууга мүмкүндүк берген жеңил ачык котормочу. Аны иштеп чыгуу учурунда аткарууга чоң көңүл бурулган. Албетте, дагы эле көп нерсе жетишпейт, бирок долбоор чакан топ тарабынан иштелип чыгууда жана алар жаңы функцияларды активдүү кошуп, мүчүлүштүктөрдү оңдоп жатышат. Мен качандыр бир күнү NJS сизге тышкы модулдарды туташтырууга мүмкүндүк берет деп үмүттөнөм, бул Nginx функционалдуулугун дээрлик чексиз кылат. Бирок NGINX Plus бар жана эч кандай өзгөчөлүктөр болбойт!
и
/ Дмитрий Волныевдин Saint HighLoad++ 2019дагы сөзү
/ Василий Сошниковдун HighLoad++ 2019 программасында сүйлөгөн сөзү
Source: www.habr.com
