Rinne mé mo stór PyPI féin le húdarú agus S3. Ar Nginx

San Airteagal seo ba mhaith liom mo thaithí a roinnt le bheith ag obair le NJS, ateangaire JavaScript do Nginx arna fhorbairt ag Nginx Inc, ag cur síos ar a phríomhchumais ag baint úsáide as sampla fíor. Is fothacar de JavaScript é NJS a ligeann duit feidhmiúlacht Nginx a leathnú. Chun an cheist cén fáth a bhfuil do ateangaire féin??? D'fhreagair Dmitry Volyntsev go mion. I mbeagán focal: tá NJS nginx-way, agus JavaScript níos forásaí, “dúchais” agus gan GC, murab ionann agus Lua.

Fadó fadó…

Ag mo phost deiridh, fuair mé oidhreacht gitlab le roinnt píblínte CI/CD motley le cumadóireacht docker, dind agus aoibhnis eile, a aistríodh go ráillí kaniko. Bogadh na híomhánna a úsáideadh roimhe seo in CI ina mbunfhoirm. D'oibrigh siad i gceart go dtí an lá nuair a d'athraigh ár gitlab IP agus d'iompaigh CI ina phumpkin. Ba í an fhadhb ná go raibh git ag ceann de na híomhánna docker a ghlac páirt i CI, a tharraing modúil Python trí ssh. Teastaíonn eochair phríobháideach le haghaidh ssh agus... bhí sé san íomhá in éineacht le known_hosts. Agus theip ar aon CI le heochairearráid fíoraithe mar gheall ar neamhréir idir an fíor-IP agus an ceann a shonraítear in known_hosts. Cuireadh íomhá nua le chéile go tapa ó na Dockfiles a bhí ann cheana féin agus cuireadh an rogha leis StrictHostKeyChecking no. Ach d'fhan an droch-blas agus bhí fonn ar na libs a aistriú chuig stór príobháideach PyPI. Bónas breise a bhí ann, tar éis athrú go PyPI príobháideach, píblíne níos simplí agus gnáththuairisc ar requirements.txt

Tá an rogha déanta, a dhaoine uaisle!

Ritheann muid gach rud sna scamaill agus Kubernetes, agus sa deireadh theastaigh uainn seirbhís bheag a fháil a bhí ina choimeádán gan stát le stóráil sheachtrach. Bhuel, ós rud é go n-úsáidimid S3, tugadh tosaíocht dó. Agus, más féidir, le fíordheimhniú i gitlab (is féidir leat é féin a chur leis más gá).

Fuarthas roinnt torthaí as cuardach tapa: s3pypi, pypicloud agus rogha le cruthú “láimhe” de chomhaid html le haghaidh tornapaí. An rogha dheireanach imithe leis féin.

s3pypi: Is é seo an cli chun óstáil S3 a úsáid. Déanaimid na comhaid a uaslódáil, an html a ghiniúint agus a uaslódáil chuig an buicéad céanna. Oiriúnach le húsáid sa bhaile.

pypicloud: Ba chosúil gur tionscadal suimiúil é, ach tar éis dom an doiciméadú a léamh bhí díomá orm. In ainneoin doiciméadú maith agus an cumas chun leathnú chun freastal ar do riachtanais, i ndáiríre d'éirigh sé amach a bheith iomarcach agus deacair a chumrú. Bheadh ​​3-5 lá glactha ag ceartú an chóid a oireann do do thascanna, de réir meastachán ag an am sin. Teastaíonn bunachar sonraí ón tseirbhís freisin. D’fhágamar é ar eagla nach bhfaighimis aon rud eile.

Fuarthas modúl do Nginx, ngx_aws_auth as cuardach níos doimhne. Ba é toradh a thástála ná XML ar taispeáint sa bhrabhsálaí, a léirigh a bhfuil sa bhuicéad S3. Ba é an gealltanas deireanach ag am an chuardaigh bliain ó shin. D'fhéach an stór tréigthe.

Trí dul go dtí an fhoinse agus léamh PEP-503 Thuig mé gur féidir XML a thiontú go HTML ar an eitilt agus a thabhairt do pip. Tar éis googling beagán níos mó faoi Nginx agus S3, tháinig mé trasna ar shampla de fhíordheimhniú i S3 scríofa i JS do Nginx. Sin mar a bhuail mé le NJS.

Ag glacadh leis an sampla seo mar bhunús, uair an chloig ina dhiaidh sin chonaic mé i mo bhrabhsálaí an XML céanna agus nuair a bhí mé ag baint úsáide as an modúl ngx_aws_auth, ach bhí gach rud scríofa cheana féin i JS.

Thaitin an réiteach nginx go mór liom. Ar an gcéad dul síos, doiciméadú maith agus go leor samplaí, ar an dara dul síos, faigheann muid gach maitheas ó Nginx as oibriú le comhaid (as an mbosca), ar an tríú dul síos, beidh aon duine a bhfuil a fhios aige conas cumraíochtaí a scríobh do Nginx in ann a dhéanamh amach cad atá ann. Buntáiste freisin domsa is ea minimalism, i gcomparáid le Python or Go (má scríobhann tú ón tús), gan trácht ar nexus.

TL;DR Tar éis 2 lá, baineadh úsáid as an leagan tástála de PyPi cheana féin in CI.

Conas a oibríonn sé?

Tá an modúl luchtaithe i Nginx ngx_http_js_module, san áireamh san íomhá oifigiúil docker. Déanaimid ár script a allmhairiú ag baint úsáide as an treoir js_importchuig cumraíocht Nginx. Tugtar treoir ar an bhfeidhm js_content. Úsáidtear an treoir chun athróga a shocrú js_set, a thógann mar argóint ach an fheidhm a thuairiscítear sa script. Ach ní féidir linn subqueries a fhorghníomhú i NJS ach amháin ag baint úsáide as Nginx, ní aon XMLHttpRequest. Chun seo a dhéanamh, ní mór an suíomh comhfhreagrach a chur le cumraíocht Nginx. Agus caithfidh an script cur síos a dhéanamh ar fho-iarraidh chuig an suíomh seo. Chun a bheith in ann feidhm a rochtain ó chumraíocht Nginx, ní mór ainm na feidhme a onnmhairiú sa script féin 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}

Nuair a iarrtar é sa bhrabhsálaí http://localhost:8080/ cuirimid isteach location /ina bhfuil an treoir js_content glaonna feidhm request cur síos air inár script script.js. Ina dhiaidh sin, sa fheidhm request déantar subquery do location = /sub-query, le modh (sa sampla reatha GET) a fuarthas ón argóint (r), a ritheadh ​​go hintuigthe nuair a thugtar an fheidhm seo. Déanfar an freagra fo-iarraidh a phróiseáil san fheidhm call_back.

Déanaimis iarracht S3

Chun iarratas a dhéanamh chuig stóras príobháideach S3, ní mór dúinn:

ACCESS_KEY

SECRET_KEY

S3_BUCKET

Ón modh http a úsáidtear, an dáta/am reatha, S3_NAME agus URI, gintear cineál áirithe teaghrán, atá sínithe (HMAC_SHA1) ag baint úsáide as SECRET_KEY. Ar aghaidh tá líne cosúil le AWS $ACCESS_KEY:$HASH, is féidir é a úsáid sa cheanntásc údaraithe. Ní mór an dáta/am céanna a úsáideadh chun an teaghrán a ghiniúint sa chéim roimhe seo a chur leis an gceanntásc X-amz-date. Breathnaíonn sé mar seo i gcód:

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(Sampla údaraithe AWS Sign v2, athraithe go stádas dímheasta)

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}

Míniú beag faoi _subrequest_uri: is athróg é seo a fhoirmíonn, ag brath ar an urú tosaigh, iarratas chuig S3. Más gá duit inneachar an “fhréamh” a fháil, ní mór duit iarratas uri a chruthú a léiríonn an teorannóir delimiter, a chuirfidh ar ais liosta de na heilimintí xml CommonPrefixes go léir, a fhreagraíonn do eolairí (i gcás PyPI, liosta de na pacáistí go léir). Más gá duit liosta ábhar a fháil in eolaire ar leith (liosta de gach leagan pacáiste), caithfidh réimse réimír a bheith san iarratas uri agus ainm an eolaire (pacáiste) a chríochnaíonn le slais /. Seachas sin, is féidir imbhuailtí a dhéanamh nuair a iarrtar ábhar eolaire, mar shampla. Tá eolairí aiohttp-request agus aiohttp-iarratais agus má shonraíonn an t-iarratas /?prefix=aiohttp-request, ansin beidh inneachar an dá chomhadlann sa fhreagra. Má tá slais ag an deireadh, /?prefix=aiohttp-request/, ansin ní bheidh sa fhreagra ach an t-eolaire riachtanach. Agus má iarraimid comhad, ansin níor cheart go mbeadh difríocht idir an uri mar thoradh air agus an ceann bunaidh.

Sábháil agus atosú Nginx. Sa bhrabhsálaí a chuirimid isteach seoladh ár Nginx, is é XML toradh an iarratais, mar shampla:

Liosta eolaire

<?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>

Ó liosta na n-eolairí ní bheidh uait ach na heilimintí CommonPrefixes.

Tríd an eolaire a theastaíonn uainn a chur lenár seoladh sa bhrabhsálaí, gheobhaidh muid a bhfuil ann i bhfoirm XML freisin:

Liosta de na comhaid i eolaire

<?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>

Ón liosta comhad ní ghlacfaimid ach eilimintí Key.

Níl fágtha ach an XML a bheidh mar thoradh air a pharsáil agus é a sheoladh amach mar HTML, tar éis téacs/html a chur in ionad an cheanntásc Ábhar-Cineál ar dtús.

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="/ga/${reValue.groups.v}">${a_text}</a>`);
    }
  }

  return '<html><body>n' + out.join('</br>n') + 'n</html></body>'
}

Ag iarraidh PyPI

Seiceálaimid nach mbriseann aon rud áit ar bith ar phacáistí ar eol go n-oibríonn siad.

# Создаем для тестов новое окружение
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

Déanaimid arís lenár libs.

# Создаем для тестов новое окружение
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

In CI, tá cuma mar seo ar chruthú agus ar lódáil pacáiste:

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}"

Fíordheimhniú

In Gitlab is féidir JWT a úsáid chun seirbhísí seachtracha a fhíordheimhniú/údarú. Ag baint úsáide as an treoir auth_request i Nginx, déanfaimid na sonraí fíordheimhnithe a atreorú chuig fo-iarraidh ina bhfuil glao feidhme sa script. Déanfaidh an script fo-iarraidh eile chuig an url Gitlab agus má sonraíodh na sonraí fíordheimhnithe i gceart, ansin seolfaidh Gitlab cód 200 ar ais agus ceadófar uaslódáil/íoslódáil an phacáiste. Cén fáth nach n-úsáidfeá focheist amháin agus na sonraí a sheoladh láithreach chuig Gitlab? Mar gheall air sin beidh orainn an comhad cumraíochta Nginx a chur in eagar gach uair a dhéanaimid aon athruithe ar údarú, agus is tasc sách tedious é seo. Chomh maith leis sin, má úsáideann Kubernetes polasaí córas fréimhe inléite amháin, cuireann sé seo níos mó castachta fós nuair a dhéantar ionadú nginx.conf trí configmap. Agus bíonn sé dodhéanta Nginx a chumrú trí configmap agus beartais á n-úsáid ag an am céanna a chuireann cosc ​​​​ar nascadh toirteanna (pvc) agus córas fréimhe inléite amháin (tarlaíonn sé seo freisin).

Ag baint úsáide as an NJS idirmheánach, faigheann muid an deis a athrú ar na paraiméadair sonraithe sa nginx config ag baint úsáide as athróga timpeallachta agus a dhéanamh ar roinnt seiceálacha sa script (mar shampla, URL sonraithe go mícheart).

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}

Is dócha gurb í an cheist ghrúdaireachta: -Cén fáth nach n-úsáidfeá modúil réamhdhéanta? Tá gach rud déanta ansin cheana féin! Mar shampla, var AWS = éilíonn(‘aws-sdk’) agus ní gá “rothar” a scríobh le fíordheimhniú S3!

A ligean ar bogadh ar aghaidh go dtí na míbhuntáistí

Maidir liom féin, bhí an neamhábaltacht chun modúil JS seachtracha a allmhairiú ina ghné mhíthaitneamhach, ach bhíothas ag súil leis. Cur síos air sa sampla thuas, tá need(‘crypto’). modúil ionsuite agus a cheangal ar oibreacha amháin dóibh. Níl aon bhealach ann freisin cód ó scripteanna a athúsáid agus caithfidh tú é a chóipeáil agus a ghreamú isteach i gcomhaid éagsúla. Tá súil agam go gcuirfear an fheidhmiúlacht seo i bhfeidhm lá éigin.

Ní mór comhbhrúite a dhíchumasú freisin don tionscadal reatha i Nginx gzip off;

Toisc nach bhfuil modúl gzip sa NJS agus nach féidir é a nascadh; mar sin, níl aon bhealach ann oibriú le sonraí comhbhrúite. Fíor, ní lúide an cás seo i ndáiríre. Níl go leor téacs ann, agus tá na comhaid aistrithe comhbhrúite cheana féin agus ní chuideoidh comhbhrú breise leo i bhfad. Chomh maith leis sin, ní seirbhís chomh luchtaithe nó ríthábhachtach í seo go gcaithfidh tú a bheith bodhraigh le hábhar a sheachadadh cúpla milleasoicind níos tapúla.

Tógann sé tamall fada an script a dhífhabhtú agus ní féidir é a dhéanamh ach trí “phriontaí” in error.log. Ag brath ar an leibhéal logála atá leagtha eolas, rabhadh nó earráid, is féidir úsáid a bhaint as 3 modhanna r.log, r.warn, r.error faoi seach. Déanaim iarracht roinnt scripteanna a dhífhabhtú i Chrome (v8) nó an uirlis consól njs, ach ní féidir gach rud a sheiceáil ann. Nuair a bhíonn cód dífhabhtaithe, nó tástáil fheidhmiúil, feictear rud éigin mar seo sa stair:

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

agus is féidir na céadta seicheamh den sórt sin a bheith ann.

Nuair a scríobhtar cód trí úsáid a bhaint as subqueries agus athróga dóibh, is amhlaidh a bhíonn sé ina tang tangled. Uaireanta, tosaíonn tú ag sracadh timpeall fuinneoga IDE éagsúla ag iarraidh seicheamh gníomhartha do chód a dhéanamh amach. Níl sé deacair, ach uaireanta tá sé an-annoying.

Níl aon tacaíocht iomlán ann do ES6.

Seans go bhfuil roinnt easnaimh eile ann, ach níor tháinig mé ar aon rud eile. Roinn faisnéis má tá taithí dhiúltach agat ar NJS.

Conclúid

Is ateangaire foinse oscailte éadrom é NJS a ligeann duit scripteanna JavaScript éagsúla a chur i bhfeidhm i Nginx. Le linn a fhorbairt, tugadh aird mhór ar fheidhmíocht. Ar ndóigh, tá go leor fós ar iarraidh, ach tá an tionscadal á fhorbairt ag foireann bheag agus tá siad ag cur gnéithe nua go gníomhach agus ag socrú bugs. Tá súil agam go dtabharfaidh NJS deis duit modúil sheachtracha a nascadh, rud a fhágann go mbeidh feidhmiúlacht Nginx beagnach gan teorainn. Ach tá NGINX Plus ann agus is dócha nach mbeidh aon ghnéithe ann!

Stór le cód iomlán don alt

njs-pypi le tacaíocht AWS Sign v4

Cur síos ar threoracha an mhodúil ngx_http_js_module

Stór oifigiúil NJS и an doiciméadú

Samplaí de úsáid NJS ó Dmitry Volintsev saor in aisce,

njs - scriptithe JavaScript dúchais i nginx / Óráid le Dmitry Volnyev ag Saint HighLoad++ 2019

NJS i dtáirgeadh / Óráid le Vasily Soshnikov ag HighLoad++ 2019

Iarratais REST a Shíniú agus a Fhíordheimhniú in AWS

Foinse: will.com