Ман анбори PyPI-и худро бо иҷозат ва S3 сохтам. Дар Nginx

Дар ин мақола ман мехоҳам таҷрибаи худро бо NJS, тарҷумони JavaScript барои Nginx, ки аз ҷониби Nginx Inc таҳия шудааст, мубодила кунам, ки қобилиятҳои асосии онро бо истифода аз мисоли воқеӣ тавсиф мекунад. NJS як зермаҷмӯи JavaScript мебошад, ки ба шумо имкон медиҳад, ки функсияҳои Nginx-ро васеъ кунед. Ба савол чаро тарчумони худат??? Дмитрий Волынцев муфассал чавоб дод. Хулоса: NJS nginx-роҳ аст ва JavaScript бар хилофи Луа пешрафтатар, "модарӣ" ва бидуни GC аст.

Хеле пеш ...

Дар кори охирини худ ман gitlab-ро бо як қатор қубурҳои рангоранги CI/CD бо docker-compose, dind ва дигар лаззатҳо мерос гирифтам, ки онҳо ба релсҳои kaniko интиқол дода шуданд. Тасвирҳое, ки қаблан дар CI истифода мешуданд, ба шакли аслии худ интиқол дода шуданд. Онҳо дуруст кор карданд, то рӯзе, ки IP-и gitlab мо тағир ёфт ва CI ба каду табдил ёфт. Мушкилот дар он буд, ки яке аз тасвирҳои докер, ки дар CI иштирок дошт, git дошт, ки модулҳои Python-ро тавассути ssh кашид. Барои ssh ба шумо калиди хусусӣ лозим аст ва... он дар тасвир дар баробари маълум_хостҳо буд. Ва ҳама гуна CI бо хатогии калидии тасдиқкунӣ аз сабаби номувофиқатии байни IP-и воқеӣ ва дар маълум_hosts нишондодашуда ноком шуд. Тасвири нав аз файлҳои мавҷудаи Dockfiles зуд ҷамъ карда шуд ва хосият илова карда шуд StrictHostKeyChecking no. Аммо таъми бад боқӣ монд ва хоҳиши интиқол додани libs ба анбори хусусии PyPI вуҷуд дошт. Бонуси иловагӣ, пас аз гузаштан ба PyPI хусусӣ, қубури соддатар ва тавсифи муқаррарии requirements.txt буд.

Интихоб карда шуд, ҷанобон!

Мо ҳама чизро дар абрҳо ва Кубернетес иҷро мекунем ва дар ниҳоят мо мехостем хидмати хурде ба даст орем, ки як контейнери бидуни шаҳрвандӣ бо нигаҳдории беруна буд. Хуб, азбаски мо S3-ро истифода мебарем, афзалият ба он дода шуд. Ва агар имконпазир бошад, бо аутентификатсия дар gitlab (агар лозим бошад, шумо метавонед онро худатон илова кунед).

Ҷустуҷӯи фаврӣ чанд натиҷа дод: s3pypi, pypicloud ва вариант бо "дастӣ" сохтани файлҳои html барои шалғам. Варианти охирин худ аз худ нопадид шуд.

s3pypi: Ин cli барои истифодаи хостинги S3 аст. Мо файлҳоро бор мекунем, html тавлид мекунем ва онро ба ҳамон сатил бор мекунем. Муносиб барои истифодаи хона.

pypicloud: Ин як лоиҳаи ҷолиб ба назар мерасид, аммо пас аз хондани ҳуҷҷатҳо ман ноумед шудам. Сарфи назар аз ҳуҷҷатҳои хуб ва қобилияти васеъ кардани мувофиқат ба эҳтиёҷоти шумо, дар асл он зиёдатист ва танзим кардан душвор буд. Ислоҳ кардани код мувофиқи вазифаҳои шумо, мувофиқи ҳисобҳои он вақт, 3-5 рӯз лозим буд. Хизматрасонӣ инчунин ба пойгоҳи додаҳо ниёз дорад. Мо онро тарк кардем, агар ягон чизи дигар наёфтем.

Ҷустуҷӯи амиқтар як модул барои Nginx, ngx_aws_auth дод. Натиҷаи санҷиши ӯ дар браузер XML намоиш дода шуд, ки мундариҷаи сатили S3-ро нишон дод. Ухдадории охирин дар замони ҷустуҷӯ як сол пеш буд. Анбор партофташуда ба назар мерасид.

Бо рафтан ба манбаъ ва хондан ПЭП-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, ба тасвири расмии докер дохил карда шудааст. Мо скрипти худро бо истифода аз дастур ворид мекунем js_importба конфигуратсияи Nginx. Функсия тавассути директива даъват карда мешавад js_content. Директива барои муқаррар кардани тағирёбандаҳо истифода мешавад js_set, ки ҳамчун далел танҳо функсияи дар скрипт тавсифшударо мегирад. Аммо мо метавонем зерпурсишҳоро дар NJS танҳо бо истифода аз Nginx иҷро кунем, на ягон XMLHttpRequest. Барои ин, макони мувофиқ бояд ба конфигуратсияи 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>&#34;00000000000000000000000000000000-1&#34;</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>&#34;b2d76df4aeb4493c5456366748218093&#34;</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="/tg/${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 сиёсати системаи файлии решаи танҳо барои хонданро истифода барад, пас ин ҳангоми иваз кардани 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 имконпазир аст. Вобаста ба маълумоти сатҳи муқарраршудаи сабти ном, огоҳӣ ё хатогӣ, мувофиқан 3 усули r.log, r.warn, r.errorро истифода бурдан мумкин аст. Ман кӯшиш мекунам, ки баъзе скриптҳоро дар Chrome (v8) ё абзори консол njs ислоҳ кунам, аммо на ҳама чизро дар он ҷо тафтиш кардан мумкин аст. Ҳангоми ислоҳи код, яъне санҷиши функсионалӣ, таърих чунин ба назар мерасад:

docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx

ва садҳо чунин пайдарпайҳо вуҷуд дошта метавонанд.

Навиштани код бо истифода аз зерпурсишҳо ва тағирёбандаҳо барои онҳо ба печи печида табдил меёбад. Баъзан шумо дар атрофи тирезаҳои гуногуни IDE шитоб мекунед, кӯшиш мекунед, ки пайдарпайии амалҳои рамзи худро муайян кунед. Ин мушкил нест, аммо баъзан хеле дилгиркунанда аст.

Дастгирии пурраи ES6 вуҷуд надорад.

Мумкин аст, ки камбудиҳои дигар ҳам бошад, аммо ман ба ягон чизи дигар дучор нашудаам. Агар шумо таҷрибаи манфии истифодаи NJS дошта бошед, маълумотро мубодила кунед.

хулоса

NJS як тарҷумони сабуки кушода аст, ки ба шумо имкон медиҳад скриптҳои гуногуни JavaScript-ро дар Nginx татбиқ кунед. Дар рафти инкишофи он ба ичрои он диккати калон дода шуд. Албатта, ҳанӯз ҳам бисёр чизҳо намерасанд, аммо лоиҳа аз ҷониби як гурӯҳи хурд таҳия карда мешавад ва онҳо фаъолона хусусиятҳои нав илова мекунанд ва хатогиҳоро ислоҳ мекунанд. Умедворам, ки рӯзе NJS ба шумо имкон медиҳад, ки модулҳои берунаро пайваст кунед, ки функсияи Nginx-ро қариб номаҳдуд месозад. Аммо NGINX Plus вуҷуд дорад ва эҳтимоли зиёд вуҷуд надорад!

Анбор бо рамзи пурраи мақола

njs-pypi бо дастгирии AWS Sign v4

Тавсифи дастурҳои модули ngx_http_js_module

Анбори расмии NJS и ҳуҷҷатҳо

Намунаҳои истифодаи NJS аз Дмитрий Волинцев

njs - скрипти аслии JavaScript дар nginx / Суханронии Дмитрий Волныев дар Saint HighLoad++ 2019

NJS дар истеҳсолот / Суханронии Василий Сошников дар HighLoad++ 2019

Имзо ва тасдиқи дархостҳои REST дар AWS

Манбаъ: will.com