認可ずS3を䜿甚しお独自のPyPIリポゞトリを䜜成したした。 Nginx に぀いお

この蚘事では、Nginx Inc によっお開発された Nginx 甚の JavaScript むンタヌプリタヌである NJS に぀いおの私の経隓を共有し、実際の䟋を䜿甚しおその䞻な機胜に぀いお説明したいず思いたす。 NJS は、Nginx の機胜を拡匵できる JavaScript のサブセットです。 質問に察しお なぜあなた自身の通蚳ですか ドミトリヌ・ノォリンツェフが詳しく答えた。 ぀たり、NJS は nginx 方匏であり、JavaScript はより進歩的で「ネむティブ」であり、Lua ずは異なり GC がありたせん。

昔 

前の仕事で、docker-compose、dind、その他の機胜を備えた倚数の雑倚な CI/CD パむプラむンを含む gitlab を継承し、それらは kaniko Rails に転送されたした。 以前CIで䜿甚しおいた画像をそのたた移動したした。 これらは、gitlab IP が倉曎され、CI がカボチャに倉わる日たで、正垞に動䜜しおいたした。 問題は、CI に参加する Docker むメヌゞの XNUMX ぀が、ssh 経由で Python モゞュヌルをプルする git を持っおいたこずでした。 SSH の堎合は秘密キヌが必芁ですが、それは known_hosts ずずもにむメヌゞ内にありたした。 たた、実際の IP ず known_hosts で指定された IP の間の䞍䞀臎により、CI はキヌ怜蚌゚ラヌで倱敗したした。 新しいむメヌゞが既存の Dockfile からすぐに組み立おられ、オプションが远加されたした StrictHostKeyChecking no。 しかし、埌味の悪さは䟝然ずしお残っおおり、ラむブラリをプラむベヌト PyPI リポゞトリに移動したいずいう芁望がありたした。 プラむベヌト PyPI に切り替えた埌の远加のボヌナスは、より単玔なパむプラむンず、requirements.txt の通垞の蚘述でした。

遞択は決たりたした、諞君

私たちはすべおをクラりドず Kubernetes で実行しおおり、最終的には倖郚ストレヌゞを備えたステヌトレス コンテナヌである小芏暡なサヌビスを取埗したいず考えおいたした。 たあ、S3を䜿っおいるのでそれを優先したした。 そしお、可胜であれば、gitlab での認蚌を䜿甚したす (必芁に応じお自分で远加できたす)。

簡単に怜玢するず、s3pypi、pypicloud、およびカブ甚の HTML ファむルを「手動」で䜜成するオプションなど、いく぀かの結果が埗られたした。 最埌のオプションは自然に消えおしたいたした。

s3pypi: これは、S3 ホスティングを䜿甚するための CLI です。 ファむルをアップロヌドし、HTML を生成しお、同じバケットにアップロヌドしたす。 家庭での䜿甚に適しおいたす。

pypicloud: 興味深いプロゞェクトのように思えたしたが、ドキュメントを読んだ埌はがっかりしたした。 優れたドキュメントずニヌズに合わせお拡匵できるにもかかわらず、実際には冗長で構成が難しいこずが刀明したした。 圓時の芋積もりによれば、タスクに合わせおコヌドを修正するには 3  5 日かかったでしょう。 このサヌビスにはデヌタベヌスも必芁です。 他に䜕も芋぀からなかった堎合に備えお、そのたたにしおおきたした。

さらに詳しく怜玢するず、Nginx のモゞュヌル ngx_aws_auth が芋぀かりたした。 圌のテストの結果は、S3 バケットの内容を瀺す XML がブラりザに衚瀺されたした。 怜玢時の最埌のコミットは XNUMX 幎前でした。 リポゞトリは攟棄されたように芋えたした。

゜ヌスにアクセスしお読むこずで、 PEP-503 XML をその堎で HTML に倉換しお pip に枡すこずができるこずに気付きたした。 Nginx ず S3 に぀いおもう少し詳しく調べたずころ、Nginx 甚の JS で曞かれた S3 での認蚌の䟋を芋぀けたした。 それがNJSずの出䌚いでした。

この䟋をベヌスにするず、XNUMX 時間埌に ngx_aws_auth モゞュヌルを䜿甚したずきず同じ XML がブラりザに衚瀺されたしたが、すべおがすでに JS で蚘述されおいたした。

nginx ゜リュヌションがずおも気に入りたした。 第䞀に、優れたドキュメントず倚くの䟋があり、第二に、ファむルを操䜜するための Nginx の優れた機胜がすべお (すぐに䜿える) 埗られ、第䞉に、Nginx の構成の曞き方を知っおいる人なら誰でも、䜕が䜕であるかを理解できるでしょう。 Nexus は蚀うたでもなく、Python や Go (れロから䜜成した堎合) ず比范しおも、ミニマリズムは私にずっおプラスです。

TL;DR 2 日埌、PyPi のテスト バヌゞョンはすでに CI で䜿甚されおいたした。

それはどのように動䜜したすか

モゞュヌルがNginxにロヌドされる ngx_http_js_module、公匏の docker むメヌゞに含たれおいたす。 ディレクティブを䜿甚しおスクリプトをむンポヌトしたす 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 は元の 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 を解析し、たず Content-Type ヘッダヌを text/html に眮き換えお、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="/ja/${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を利甚するこずが可胜です。 Nginx の auth_request ディレクティブを䜿甚しお、スクリプト内の関数呌び出しを含むサブリク゚ストに認蚌デヌタをリダむレクトしたす。 スクリプトは Gitlab URL に察しお別のサブリク゚ストを䜜成し、認蚌デヌタが正しく指定されおいる堎合、Gitlab はコヌド 200 を返し、パッケヌゞのアップロヌド/ダりンロヌドが蚱可されたす。 XNUMX ぀のサブク゚リを䜿甚しお、すぐにデヌタを Gitlab に送信しおみおはいかがでしょうか? そうするず、認蚌に倉曎を加えるたびに Nginx 蚭定ファむルを線集する必芁があり、これはかなり面倒な䜜業になるからです。 たた、Kubernetes が読み取り専甚のルヌト ファむルシステム ポリシヌを䜿甚しおいる堎合、configmap を介しお nginx.conf を眮き換えるずきにさらに耇雑さが増したす。 そしお、ボリュヌム (pvc) ず読み取り専甚のルヌト ファむルシステムの接続を犁止するポリシヌを同時に䜿甚しながら、configmap を介しお Nginx を構成するこずは絶察に䞍可胜になりたす (これも発生したす)。

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 認蚌を䜿甚しお「bike」を蚘述する必芁はありたせん。

短所に移りたしょう

私にずっお、倖郚 JS モゞュヌルをむンポヌトできないこずは䞍快ではありたすが、期埅されおいた機胜になりたした。 䞊蚘の䟋で説明されおいる require('crypto') は 組み蟌みモゞュヌル そしお、require はそれらに察しおのみ機胜したす。 たた、スクリプトからコヌドを再利甚する方法はなく、コヌドをコピヌしお別のファむルに貌り付ける必芁がありたす。 い぀かこの機胜が実装されるこずを願っおいたす。

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 があるため、おそらく機胜はありたせん。

蚘事の完党なコヌドを含むリポゞトリ

AWS Sign v4 をサポヌトする njs-pypi

ngx_http_js_module モゞュヌルのディレクティブの説明

NJS 公匏リポゞトリ О ドキュメント

Dmitry Volintsev による NJS の䜿甚䟋

njs - nginx のネむティブ JavaScript スクリプト / Saint HighLoad++ 2019 でのドミトリヌ・ノォルニ゚フ氏のスピヌチ

NJS は実皌働䞭 / HighLoad++ 2019 でのノァシリヌ・゜シニコフ氏のスピヌチ

AWS での REST リク゚ストの眲名ず認蚌

出所 habr.com