[Nginx] 05. 프로그래머빌리티와 자동화 - 1

Date:     Updated:

카테고리:

태그:


01. 서론


프로그래머블리티(Programmability)는 프로그래밍을 통해 시스템과 상호작용하는 능력으로, NGINX Plus의 API를 통해 HTTP 요청으로 서버의 설정을 조정할 수 있다. 이를 통해 서버를 쉽게 추가하거나 제거할 수 있으며, 동적으로 트래픽을 제어하는 기능도 제공한다.

구성 관리 도구(Configuration management tools)는 서버 설치와 설정을 자동화하여 엔지니어들이 더 효율적으로 작업할 수 있도록 도와준다. 이러한 도구를 사용하면 동일한 구성을 가진 여러 서버를 신속하게 생성할 수 있으므로, 대규모 웹 애플리케이션을 운영하는 데 큰 도움이 된다.

예를 들어, AWS에서 NGINX를 설치할 때 Ansible 같은 구성 관리 도구를 사용하여 서버를 자동으로 설정할 수 있다. 이 과정에서 한 번의 설정으로 여러 서버를 빠르게 만들 수 있어 시간과 노력을 절약할 수 있다.



02. NGINX Plus API


NGINX Plus API를 사용하면 동적 애플리케이션 서버가 자동으로 NGINX 구성에 추가되거나 제거될 수 있다. 서버가 온라인 상태가 되면 자신을 등록하고, 필요한 경우 연결을 드레인하여 부드럽게 제거할 수 있어 인력 개입 없이 인프라의 확장 및 축소가 가능하다.


■ API 설정

이 NGINX Plus 설정은 공유 메모리 영역을 가진 업스트림 서버를 생성하고, /api 위치 블록에서 API를 활성화하며, NGINX Plus 대시보드를 위한 위치를 제공한다.

upstream backend {
    zone http_backend 64k;
}
server {
    # ...
    location /api {
        api [write=on];
        # API 접근 제한 지시어
        # 7장 참고
    }
    location = /dashboard.html {
        root /usr/share/nginx/html;
    }
}


■ 서버 추가

# NGINX Plus에 새로운 서버를 백엔드 업스트림 구성에 추가해 달라는 요청 전덜

$ curl -X POST -d '{"server":"172.17.0.3"}' \
'http://nginx.local/api/3/http/upstreams/backend/servers/'
# 응답으로 JSON이 반환되며, 서버 객체의 설정을 보여준다. 

{
 "id":0,
 "server":"172.17.0.3:80",
 "weight":1,
 "max_conns":0,
 "max_fails":1,
 "fail_timeout":"10s",
 "slow_start":"0s",
 "route":"",
 "backup":false,
 "down":false
}


■ 서버 목록 조회

# NGINX Plus API는 RESTful이므로 요청 URI에 파라미터가 포함되며, URI 형식은 다음과 같다.

/api/{version}/http/upstreams/{httpUpstreamName}/servers/
# NGINX Plus API를 사용하여 업스트림 풀에 있는 서버 목록을 조회 요청 전달

$ curl 'http://nginx.local/api/3/http/upstreams/backend/servers/'
# 응답으로 JSON이 반환되며, 서버 목록을 보여준다.

[
 {
 "id":0,
 "server":"172.17.0.3:80",
 "weight":1,
 "max_conns":0,
 "max_fails":1,
 "fail_timeout":"10s",
 "slow_start":"0s",
 "route":"",
 "backup":false,
 "down":false
 }
]


■ 연결 드레인

“연결 드레인”은 서비스가 종료되거나 재시작될 때 기존에 연결된 클라이언트의 요청을 부드럽게 처리하기 위한 방법을 말한다. 이 과정에서 서버는 새로운 연결을 받지 않고, 이미 연결된 요청은 처리한 후 안전하게 종료된다. 이를 통해 서버 재시작 시 연결 중단이나 데이터 손실을 방지할 수 있다.

# 이 curl 호출에서는 요청 메소드를 PATCH로 지정하고, 서버의 연결을 드레인하라는 요청 전달 
# 이때, URI에 서버 ID를 추가하여 특정 서버를 지정

$ curl -X PATCH -d '{"drain":true}' \
'http://nginx.local/api/3/http/upstreams/backend/servers/0'
{
 "id":0,
 "server":"172.17.0.3:80",
 "weight":1,
 "max_conns":0,
 "max_fails":1,
 "fail_timeout":
 "10s","slow_start":
 "0s",
 "route":"",
 "backup":false,
 "down":false,
 "drain":true
}
# 드레인 중인 서버가 제공하는 활성 연결 수를 확인하려면 다음 호출을 사용하고, 드레인 중인 서버의 active 속성을 확인

$ curl 'http://nginx.local/api/3/http/upstreams/backend'
{
 "zone" : "http_backend",
 "keepalive" : 0,
 "peers" : [
 {
 "backup" : false,
 "id" : 0,
 "unavail" : 0,
 "name" : "172.17.0.3",
 "requests" : 0,
 "received" : 0,
 "state" : "draining",
 "server" : "172.17.0.3:80",
 "active" : 0,
 "weight" : 1,
 "fails" : 0,
 "sent" : 0,
 "responses" : {
 "4xx" : 0,
 "total" : 0,
 "3xx" : 0,
 "5xx" : 0,
 "2xx" : 0,
 "1xx" : 0
 },
 "health_checks" : {
 "checks" : 0,
 "unhealthy" : 0,
 "fails" : 0
 },
 "downtime" : 0
 }
 ],
 "zombies" : 0
}


■ 서버 제거

# 모든 연결이 드레인된 후 서버를 완전히 제거함

$ curl -X DELETE \
'http://nginx.local/api/3/http/upstreams/backend/servers/0'



03. 키-값 저장소 사용하기 - Nginx Plus


NGINX Plus의 키-값 저장소는 애플리케이션이 정보를 주입할 수 있는 기능을 제공한다. 이 예제에서는 IP 주소를 사용해 동적인 차단 목록을 만들어 보았다.

  • NGINX Plus R16부터는 키-값 저장소가 클러스터 인식이 되어, 키-값 업데이트를 하나의 NGINX Plus 서버에만 제공하면 모든 서버가 정보를 받게 된다.

  • R19에서는 키 타입 매개변수가 추가되어 특정 키에 대한 인덱싱이 가능해졌다. 예를 들어, CIDR 표기법을 사용하여 특정 IP 범위를 차단할 수 있다.


■ 키-값 저장소 설정

# keyval_zone 디렉토리를 사용하여 blocklist라는 이름의 공유 메모리 영역을 생성하고, 메모리 한도를 1MB로 설정
keyval_zone zone=blocklist:1M;
# keyval 지시어는 첫 번째 매개변수인 $remote_addr의 값을 $blocked라는 새로운 변수로 매핑
# 이 변수는 NGINX Plus가 요청을 처리할지, 아니면 403 Forbidden 코드를 반환할지를 결정하는 데 사용된다.
keyval $remote_addr $blocked zone=blocklist;
server {
    # ...
    location / {
        if ($blocked) {
            return 403 'Forbidden';
        }
        return 200 'OK';
    }
}
server {
    # ...
    # API 접근 제한 지시어
    # 6장 참고
    location /api {
        api write=on;
    }
}


■ 차단 목록 추가

# 키-값 저장소 API URI 형식은 다음과 같다.

/api/{version}/http/keyvals/{httpKeyvalZoneName}
# 로컬 머신에 요청을 보내면 200 OK 응답을 받을 수 있다.

$ curl 'http://127.0.0.1/'
OK
# 로컬 머신의 IP 주소를 차단 목록에 추한다.

$ curl -X POST -d '{"127.0.0.1":"1"}' \
'http://127.0.0.1/api/3/http/keyvals/blocklist'

이제 로컬 머신의 IP 주소가 값 1과 함께 blocklist라는 이름의 키-값 영역에 추가 되었다. 다음 요청에서 NGINX Plus는 키-값 영역에서 $remote_addr를 조회하고, 해당 항목을 찾은 후 값을 $blocked 변수에 매핑한다. 만약 변수에 값이 있으면, if 문은 True로 평가되어 NGINX Plus는 403 Forbidden 코드를 반환한다.


■ 응답 확인

# 차단 목록에 IP가 추가된 후 다시 요청하면 403 Forbidden 응답을 받음

$ curl 'http://127.0.0.1/'
Forbidden


■ 키 업데이트 및 삭제

# 키를 삭제하려면 PATCH 요청을 사용한다. 
# 값이 null인 경우 NGINX Plus는 키를 삭제하며, 요청은 다시 200 OK를 반환한다.

$ curl -X PATCH -d '{"127.0.0.1":null}' \
'http://127.0.0.1/api/3/http/keyvals/blocklist'



04. NJS 모듈로 엔진엑스 자바스크립트 기능 활용하기


NJS 모듈을 통해 NGINX는 요청과 응답을 처리하는 동안 JavaScript 로직을 삽입할 수 있다. 이를 통해 비즈니스 로직을 프록시 계층에 통합하고, 요청을 검증 및 조작할 수 있다. NJS는 업스트림 서비스의 응답도 조작할 수 있어, 유연한 웹 서비스 구축이 가능하다.


■ NJS 모듈 설치

# Debian/Ubuntu

apt-get install nginx-module-njs


# Debian/Ubuntu 및 NGINX Plus

apt-get install nginx-plus-module-njs
# RHEL/CentOS

yum install nginx-module-njs


# RHEL/CentOS 및 NGINX Plus

yum install nginx-plus-module-njs


■ JavaScript 파일 디렉토리 생성

mkdir -p /etc/nginx/njs


■ JavaScript 파일 작성

/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(JSON Web Token) 토큰을 파싱하고, 그 안에서 주체(subject)와 발급자(issuer) 값을 추출하는 함수들을 정의한 것이다. slice(7) 부분은 Authorization 헤더 값의 처음 7자를 제거하는 역할을 한다. JWT의 타입 값은 Bearer이며, Bearer는 6자이고, 공백 구분자도 제거해야 하므로 처음 7자를 잘라낸다. AWS Cognito와 같은 인증 서비스는 타입을 제공하지 않기 때문에, 이러한 서비스는 슬라이스 개수를 변경하거나 아예 제거하여 jwt 함수가 토큰 값만 받도록 한다.

1) JWT 함수는 data.split(‘.’)을 사용해 토큰을 .로 나누고, 첫 두 부분인 헤더와 페이로드만을 추출한다. 추출한 각 부분을 base64url로 디코딩하여 문자열로 변환하고 문자열을 JSON.parse로 파싱하여 객체로 변환한다.

2) r.headersIn.Authorization에서 “Bearer “ 이후의 토큰 부분을 추출한다. jwt 함수를 호출하여 헤더와 페이로드를 추출한 후, 페이로드에서 sub(subject)를 반환한다.

3) r.headersIn.Authorization에서 토큰을 추출하고, jwt 함수를 통해 헤더와 페이로드를 파싱한다. 페이로드에서 iss(issuer, 발행자)를 반환한다.


■ NGINX 설정 추가

# NGINX 설정 파일에서 NJS 모듈을 로드하고 JavaScript를 가져온다.
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;
    ...
}

위의 NGINX 설정은 NJS 모듈을 동적으로 로드하고, 이전에 정의한 JavaScript 파일을 가져온다. NGINX 지시어를 사용하여 JavaScript 함수의 반환 값을 NGINX 변수에 설정한다.


■ 서버 설정

server {
    listen 80 default_server;
    server_name _;
    location / {
        return 200 "$jwt_payload_subject $jwt_payload_issuer";
    }
}

위의 설정은 클라이언트가 Authorization 헤더를 통해 제공한 주체와 발급자 값을 반환하는 서버를 만든다. 이 값들은 정의된 JavaScript 코드에 의해 디코드된다.


■ 작동 확인

curl 'http://localhost/' -H "Authorization: Bearer <your_jwt_here>"\

JWT를 사용하여 서버에 요청을 보내고, JavaScript 코드가 제대로 작동하는지 확인한다.

NGINX 카테고리 내 다른 글 보러가기

댓글 남기기