์Šน์ธ ๋ฐ S3๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‚˜๋งŒ์˜ PyPI ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. Nginx์—์„œ

์ด ๊ธฐ์‚ฌ์—์„œ๋Š” Nginx Inc์—์„œ ๊ฐœ๋ฐœํ•œ Nginx์šฉ JavaScript ์ธํ„ฐํ”„๋ฆฌํ„ฐ์ธ NJS์— ๋Œ€ํ•œ ๋‚ด ๊ฒฝํ—˜์„ ๊ณต์œ ํ•˜๊ณ  ์‹ค์ œ ์˜ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. NJS๋Š” Nginx์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” JavaScript์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. ์งˆ๋ฌธ์— ์™œ ๋‹น์‹ ์˜ ํ†ต์—ญ์‚ฌ์ž…๋‹ˆ๊นŒ??? Dmitry Volyntsev๊ฐ€ ์ž์„ธํžˆ ๋‹ต๋ณ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ, NJS๋Š” nginx ๋ฐฉ์‹์ด๋ฉฐ JavaScript๋Š” Lua์™€ ๋‹ฌ๋ฆฌ GC๊ฐ€ ์—†๋Š” ๋” ์ง„๋ณด์ ์ด๊ณ  "๋„ค์ดํ‹ฐ๋ธŒ"์ž…๋‹ˆ๋‹ค.

์˜›๋‚ ์—โ€ฆ

๋งˆ์ง€๋ง‰ ์ง์žฅ์—์„œ ์ €๋Š” docker-compose, dind ๋ฐ ๊ธฐํƒ€ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋œ ๋‹ค์–‘ํ•œ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์ด ์žˆ๋Š” gitlab์„ ๋ฌผ๋ ค๋ฐ›์•˜์œผ๋ฉฐ ์ด๋Š” kaniko Rails๋กœ ์ด์ „๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด CI์— ์‚ฌ์šฉ๋˜์—ˆ๋˜ ์ด๋ฏธ์ง€๋ฅผ ์›๋ณธ ํ˜•ํƒœ๋กœ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค. gitlab IP๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ณ  CI๊ฐ€ ํ˜ธ๋ฐ•์œผ๋กœ ๋ณ€ํ•˜๋Š” ๋‚ ๊นŒ์ง€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” CI์— ์ฐธ์—ฌํ•œ ๋„์ปค ์ด๋ฏธ์ง€ ์ค‘ ํ•˜๋‚˜์— ssh๋ฅผ ํ†ตํ•ด Python ๋ชจ๋“ˆ์„ ๋Œ์–ด์˜ค๋Š” git์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. SSH์˜ ๊ฒฝ์šฐ ๊ฐœ์ธ ํ‚ค๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ... Known_hosts์™€ ํ•จ๊ป˜ ์ด๋ฏธ์ง€์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ IP์™€ Known_hosts์— ์ง€์ •๋œ IP ๊ฐ„์˜ ๋ถˆ์ผ์น˜๋กœ ์ธํ•ด ํ‚ค ํ™•์ธ ์˜ค๋ฅ˜๋กœ ์ธํ•ด ๋ชจ๋“  CI๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด Dockfile์—์„œ ์ƒˆ ์ด๋ฏธ์ง€๊ฐ€ ๋น ๋ฅด๊ฒŒ ์กฐํ•ฉ๋˜์—ˆ์œผ๋ฉฐ ์˜ต์…˜์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. StrictHostKeyChecking no. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜์œ ์ทจํ–ฅ์€ ๋‚จ์•„ ์žˆ์—ˆ๊ณ  libs๋ฅผ ๊ฐœ์ธ PyPI ์ €์žฅ์†Œ๋กœ ์˜ฎ๊ธฐ๋ ค๋Š” ์š•๊ตฌ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ผ์ด๋น— PyPI๋กœ ์ „ํ™˜ํ•œ ํ›„ ์ถ”๊ฐ€ ๋ณด๋„ˆ์Šค๋Š” ํŒŒ์ดํ”„๋ผ์ธ์ด ๋” ๊ฐ„๋‹จํ•ด์ง€๊ณ  ์š”๊ตฌ์‚ฌํ•ญ.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๋ฅผ ๋งŒ๋‚ฌ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ๋ฅผ ๊ธฐ์ดˆ๋กœ ํ•˜์—ฌ ํ•œ ์‹œ๊ฐ„ ํ›„์— ๋ธŒ๋ผ์šฐ์ €์—์„œ ngx_aws_auth ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๋™์ผํ•œ XML์„ ๋ณด์•˜์ง€๋งŒ ๋ชจ๋“  ๊ฒƒ์ด ์ด๋ฏธ JS๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” nginx ์†”๋ฃจ์…˜์„ ์ •๋ง ์ข‹์•„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ, ์ข‹์€ ๋ฌธ์„œ์™€ ๋งŽ์€ ์˜ˆ์ œ, ๋‘˜์งธ, ํŒŒ์ผ ์ž‘์—…์„ ์œ„ํ•œ Nginx์˜ ๋ชจ๋“  ์žฅ์ ์„ ์ฆ‰์‹œ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์…‹์งธ, Nginx ๊ตฌ์„ฑ์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•„๋Š” ์‚ฌ๋žŒ์€ ๋ˆ„๊ตฌ๋‚˜ ๋ฌด์—‡์ด ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„ฅ์„œ์Šค๋Š” ๋งํ•  ๊ฒƒ๋„ ์—†๊ณ  Python์ด๋‚˜ Go(์ฒ˜์Œ๋ถ€ํ„ฐ ์ž‘์„ฑ๋œ ๊ฒฝ์šฐ)์— ๋น„ํ•ด ๋ฏธ๋‹ˆ๋ฉ€๋ฆฌ์ฆ˜์€ ๋‚˜์—๊ฒŒ ํ”Œ๋Ÿฌ์Šค์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

TL;DR 2์ผ ํ›„ PyPi์˜ ํ…Œ์ŠคํŠธ ๋ฒ„์ „์ด ์ด๋ฏธ CI์—์„œ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ?

๋ชจ๋“ˆ์ด Nginx์— ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ngx_http_js_module, ๊ณต์‹ ๋„์ปค ์ด๋ฏธ์ง€์— ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€์‹œ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. js_importNginx ๊ตฌ์„ฑ์—. ํ•จ์ˆ˜๋Š” ์ง€์‹œ๋ฌธ์— ์˜ํ•ด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. js_content. ์ง€์‹œ๋ฌธ์€ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. js_set, ์Šคํฌ๋ฆฝํŠธ์— ์„ค๋ช…๋œ ํ•จ์ˆ˜๋งŒ ์ธ์ˆ˜๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šฐ๋ฆฌ๋Š” XMLHttpRequest๊ฐ€ ์•„๋‹Œ Nginx๋งŒ์„ ์‚ฌ์šฉํ•˜์—ฌ NJS์—์„œ ํ•˜์œ„ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด 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์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜์—ฌ 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="/ko/${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

์šฐ๋ฆฌ๋Š” 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

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์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ํŒจํ‚ค์ง€ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•˜์œ„ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์ฆ‰์‹œ ๋ฐ์ดํ„ฐ๋ฅผ 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 ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜์—ฌ "์ž์ „๊ฑฐ"๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!

๋‹จ์ ์œผ๋กœ ๋„˜์–ด๊ฐ€์ž

๋‚˜์—๊ฒŒ๋Š” ์™ธ๋ถ€ JS ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด ๋ถˆํŽธํ–ˆ์ง€๋งŒ ์˜ˆ์ƒํ–ˆ๋˜ ๊ธฐ๋Šฅ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์—์„œ ์„ค๋ช…๋œ require('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๊ฐ€ ์žˆ์œผ๋ฉฐ ๊ธฐ๋Šฅ์ด ์—†์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค!

๊ธฐ์‚ฌ์˜ ์ „์ฒด ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ์ €์žฅ์†Œ

AWS Sign v4๋ฅผ ์ง€์›ํ•˜๋Š” njs-pypi

ngx_http_js_module ๋ชจ๋“ˆ์˜ ์ง€์‹œ์–ด์— ๋Œ€ํ•œ ์„ค๋ช…

NJS ๊ณต์‹ ์ €์žฅ์†Œ ะธ ๋ฌธ์„œ

Dmitry Volintsev์˜ NJS ์‚ฌ์šฉ ์˜ˆ

njs - nginx์˜ ๊ธฐ๋ณธ JavaScript ์Šคํฌ๋ฆฝํŒ… / Saint HighLoad++ 2019์—์„œ Dmitry Volnyev์˜ ์—ฐ์„ค

NJS ์ œ์ž‘ ์ค‘ / HighLoad++ 2019์—์„œ Vasily Soshnikov์˜ ์—ฐ์„ค

AWS์—์„œ REST ์š”์ฒญ ์„œ๋ช… ๋ฐ ์ธ์ฆ

์ถœ์ฒ˜ : habr.com