์ด ๊ธฐ์ฌ์์๋ Nginx Inc์์ ๊ฐ๋ฐํ Nginx์ฉ JavaScript ์ธํฐํ๋ฆฌํฐ์ธ NJS์ ๋ํ ๋ด ๊ฒฝํ์ ๊ณต์ ํ๊ณ ์ค์ ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์ ๊ธฐ๋ฅ์ ์ค๋ช
ํ๊ณ ์ถ์ต๋๋ค. NJS๋ Nginx์ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์๋ JavaScript์ ํ์ ์งํฉ์
๋๋ค. ์ง๋ฌธ์
์๋ ์โฆ
๋ง์ง๋ง ์ง์ฅ์์ ์ ๋ 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๋ ์ ์ด์์ต๋๋ค. ์ ์ฅ์๋ ๋ฒ๋ ค์ง ๊ฒ์ฒ๋ผ ๋ณด์์ต๋๋ค.
์ถ์ฒ์ ๊ฐ์ ์ฝ์ด๋ณด๋ฉด
์ด ์๋ฅผ ๊ธฐ์ด๋ก ํ์ฌ ํ ์๊ฐ ํ์ ๋ธ๋ผ์ฐ์ ์์ ngx_aws_auth ๋ชจ๋์ ์ฌ์ฉํ ๋์ ๋์ผํ XML์ ๋ณด์์ง๋ง ๋ชจ๋ ๊ฒ์ด ์ด๋ฏธ JS๋ก ์์ฑ๋์์ต๋๋ค.
๋๋ nginx ์๋ฃจ์ ์ ์ ๋ง ์ข์ํ์ต๋๋ค. ์ฒซ์งธ, ์ข์ ๋ฌธ์์ ๋ง์ ์์ , ๋์งธ, ํ์ผ ์์ ์ ์ํ Nginx์ ๋ชจ๋ ์ฅ์ ์ ์ฆ์ ์ป์ ์ ์์ต๋๋ค. ์ ์งธ, Nginx ๊ตฌ์ฑ์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๋ ์ฌ๋์ ๋๊ตฌ๋ ๋ฌด์์ด ๋ฌด์์ธ์ง ์์๋ผ ์ ์์ต๋๋ค. ๋ฅ์์ค๋ ๋งํ ๊ฒ๋ ์๊ณ Python์ด๋ Go(์ฒ์๋ถํฐ ์์ฑ๋ ๊ฒฝ์ฐ)์ ๋นํด ๋ฏธ๋๋ฉ๋ฆฌ์ฆ์ ๋์๊ฒ ํ๋ฌ์ค์ด๊ธฐ๋ ํฉ๋๋ค.
TL;DR 2์ผ ํ PyPi์ ํ ์คํธ ๋ฒ์ ์ด ์ด๋ฏธ CI์์ ์ฌ์ฉ๋์์ต๋๋ค.
์ด๋ป๊ฒ ์๋ํฉ๋๊น?
๋ชจ๋์ด Nginx์ ๋ก๋๋ฉ๋๋ค. ngx_http_js_module
, ๊ณต์ ๋์ปค ์ด๋ฏธ์ง์ ํฌํจ๋์ด ์์ต๋๋ค. ์ง์๋ฌธ์ ์ฌ์ฉํ์ฌ ์คํฌ๋ฆฝํธ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. js_import
Nginx ๊ตฌ์ฑ์. ํจ์๋ ์ง์๋ฌธ์ ์ํด ํธ์ถ๋ฉ๋๋ค. 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>"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 ํค๋๋ฅผ 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๊ฐ ์์ผ๋ฉฐ ๊ธฐ๋ฅ์ด ์์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค!
์ถ์ฒ : habr.com