NGINX 5: 프로그래머빌리티 & 자동화
먼저 글의 제목인 프로그래머빌리티와 자동화가 Nginx와 어떤 관련이 있는지 설명합니다.
프로그래머빌리티(Programmability)란 프로그래밍을 통해 상호 작용하는 능력을 말합니다. Nginx Plus는 엔지니어가 Ningx의 설정과 상호작용 하고 동작을 제어할 수 있도록 API를 제공하는데 이것도 프로그래머빌리티의 맥락으로 볼 수 있습니다.
또한 현대의 대규모 웹 애플리케이션을 다루는 엔지니어는 직접 서버 설정을 만지지 않고 설정 관리 도구(Cheif, Ansible, Consul 등..)를 선택해 자동화할 수 있습니다. 도구를 통해 한 번 작성된 설정과 코드를 수많은 서버에 반복적으로 적용할 수 있으며 모듈화된 방식으로 배포할 수 있습니다.
1. Nginx Plus API
Nginx Plus가 제공하는 API를 호출해 서버를 추가하거나 삭제할 수 있습니다.
upstream backend {
zone http_backend 64k;
}
server {
location /api {
api [write= on];
}
location /dashboard.html {
root /usr/share/nginx/html;
}
}
- 공유 메모리 영역을 사용하는 업스트림 서버를 생성합니다.
- /api 경로에 대한 location 블록을 통해 API를 활성화하며 Nginx Plus 대시보드 접근을 위한 location 블록을 구성합니다.
- 설정을 완료하면 다음과 같은 API를 호출할 수 있습니다.
업스트림 서버 추가
- POST /api/{version}/http/upstreams/{httpUpstreamName}/servers
- Request Body: {"server": "[upstream server ip]"}
업스트림 서버 목록 조회
- GET /api/{version}/http/upstreams/{httpUpstreamName}/servers
- URI에 작성된 업스트림 서버 풀에 포함된 모든 서버 목록을 가져옵니다.
업스트림 서버 커넥션 드레이닝
- PATCH /api/{version}/http/upstreams/{httpUpstreamName}/servers/{serverId}
- Request Body: {"drain": true}
- 서버가 제거되려면 해당 서버의 연결이 모두 종료돼야 하기 때문에 drain 옵션을 통해 연결이 점진적으로 감소하도록 한다.
업스트림 서버 제거
- DELETE /api/{version}/http/upstreams/{httpUpstreamName}/servers/{serverId}
2. 키-값 저장소 사용하기 (엔진엑스 플러스)
Nginx는 API 호출을 통해 공유 메모리 영역에 동적으로 데이터를 추가할 수 있습니다. 해당 API를 사용하여 사용자 IP를 차단하는 기능을 만들어 봅니다.
keyval_zone zone=blocklist:1M;
keyval $remote_addr $blocked zone=blocklist;
server {
location / {
if ($blocked) {
return 403 'Forbidden';
}
return 200 'OK';
}
server {
location /api {
api write=on;
}
}
}
- keyval_zone 지시자를 사용해 blocklist라는 공유 메모리 영역을 만들고 용량 제한을 1MB로 제한합니다.
- keyval 지시자는 공유 메모리에 $remote_addr 값과 일치하는 키가 있으면 키값을 $blocked 변수에 저장합니다.
- 값이 할당된 $blocked 변수는 Nginx가 요청에 대해 403을 반환할지 정상적인 리소스를 반환할지 결정하는데 사용됩니다.
blocklist 저장소에 로컬 호스트 주소르 1로 저장
- POST /api/{version}/http/keyvals/{httpKeyvalZoneName}
- Request Body: {"127.0.0.1": "1"}
- 해당 API를 호출한 후에 Nginx에 요청을 보내면 403 Forbidden 응답 코드를 받게됩니다.
blocklist 저장소에 저장된 정보 변경
- PATCH /api/{version}/http/keyvals/{httpKeyvalZoneName}
- Request Body: {"127.0.0.1": null}
- 값을 null로 변경하면 해당 IP의 요청은 200 OK 응답 코드를 받게됩니다.
3. NJS 모듈로 엔진엑스 자바스크립트 기능 활용하기
Nginx는 NJS모듈을 통해 Javascript를 사용해서 사용자의 요청이나 응답을 처리할 때 사용자 정의된 로직을 수행할 수 있습니다.
먼저 NJS 모듈을 설치합니다. (데비안/우분투)
apt-get install nginx-module-njs
Nginx 설정 파일 경로 내에 자바스크립트 리소스를 위한 디렉터리를 생성합니다.
mkdir -p /etc/nginx/njs
만들어진 디렉터리에 jwt.js라는 파일을 생성합니다.
function jwt(data) {
var parts = data.split('.').slice(0, 2)
.map(v=>Buffer.from(v, 'base64url').toString()
.map(JSON.parse);
return { headers:parts[0], payload: parts[1] };
}
function jwt_payload_subject(r) {
return jwt(r.headersIn.Authorization.slice(7)).payload.sub;
}
function jwt_payload_issuer(r) {
return jwt(r.headersIn.Authorization.slice(7)).payload.iss;
}
export default {jwt_payload_subject, jwt_payload_issuer}
- 위 함수는 JWT를 디코딩하는 함수와 디코딩된 JWT payload의 subject와 issuer 값을 획득합니다.
- Nginx에서 두 함수를 사용할 수 있도록 export 명령으로 추출합니다.
Nginx 기본 설정 파일에서 설치한 NJS 모듈을 로드하고 http 블록 내에서 사용할 자바스크립트 파일을 임포트할 수 있습니다.
load_module /etc/nginx/modules/ngx_http_js_module.so;
http {
js_path "/etc/nginx/njs/";
js_import main from jwt.js;
js_set $jwt_payload_subject main.jwt_payload_subject;
js_set $jwt_payload_issuer main.jwt_payload_issuer;
...
}
- js_set 지시자는 자바스크립트 함수가 반환한 값을 Nginx 변수에 설정합니다.
- 이 변수들을 활용해서 자바스크립트 로직을 검증할 수 있습니다.
위 변수들을 응답에 포함하여 반환하는 server 블록을 정의합니다.
server {
listen 80 default_server;
listen [::] 80 default_server;
server_name _;
location / {
return 200 "$jwt_payload_subject $jwt_payload_issuer";
}
}
- 설정이 적용되면 클라이언트가 권한 헤더로 보낸 값에서 추출한 subject와 issuer 값을 응답으로 반환합니다.
JWT를 요청 헤더에 포함시켜 테스트할 수 있습니다.
JWT payload
{
"iss":"nginx",
"sub":"alice",
"foo":123
}
curl 'http://127.0.0.1/' -H \
"Authorizaton: Bearer [JWT]"
alice nginx
- subject인 "alice"와 issuer인 "nginx"가 응답에 포함되어 반환된걸 확인할 수 있습니다.
- NJS 모듈은 요청이 수신됐을 때뿐만 아니라 Nginx가 클라이언트로 응답을 보낼 때도 로직을 주입할 수 있습니다.
4. 상용 프로그래밍 언어로 엔진엑스 확장하기
Nginx가 자바스크립트 기반으로 작성한 NJS 모듈처럼 사용자는 C나 Lua, Perl 언어를 이용해서 커스텀한 모듈을 작성할 수 있습니다.
작성한 모듈을 통해 코드가 작성된 파일을 불러오거나 Nginx 설정 내부에 직접 코드블록을 작성할 수도 있습니다.
load_module modules/ndk_http_modile.so;
load_module modules/ndx_http_lua_module.so;
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("hello, world")
}
}
}
}
- 루아로 만들어진 모듈은 ngx라는 이름으로 제공되는 내장 객체를 가지며, 이를 통해 Nginx API를 다룹니다.
- ngx 객체는 NJS의 요청 객체와 마찬가지로 Nginx가 수신한 요청을 다루는 여러 속성과 메서드가 있고 응답을 만드는데 사용됩니다.
- 모듈을 작성하는 방식으로 Nginx의 기능을 무한대로 확장할 수 있다는 장점을 가집니다.
5. 셰프로 엔진엑스 설치하기
6. 앤서블로 엔진엑스 설치하기
7. 콘술 템플릿 기능으로 설정 자동화하기