첫 번째 OpenResty 프로그램: Hello World

API7.ai

September 9, 2022

OpenResty (NGINX + Lua)

새로운 개발 언어를 배우기 시작할 때, 튜토리얼은 간단한 hello world 예제를 제공합니다. 그러니 설치 과정은 잠시 건너뛰고 OpenResty에서 이 예제를 어떻게 작성하는지 먼저 살펴보겠습니다:

$ resty -e "ngx.say('hello world')"
hello world

이것은 아마도 여러분이 본 가장 직관적인 hello world 코드일 것입니다. Python과 유사합니다:

$ python -c 'print("hello world")'
hello world

이 뒤에는 OpenResty의 철학이 담겨 있습니다. 코드는 간결해야 하며, "입문에서 포기까지"라는 생각을 버릴 수 있도록 해야 합니다. 이 글은 hello world 예제에 초점을 맞출 것입니다.

OpenResty와 NGINX의 차이점 글에서 언급했듯이, OpenResty는 NGINX를 기반으로 합니다. 그래서 여러분은 여기서 NGINX의 흔적을 볼 수 없는 이유에 대해 궁금할 수 있습니다. 걱정하지 마세요, resty 뒤에서 실행되는 것을 보기 위해 코드 한 줄을 추가해 보겠습니다:

$ resty -e "ngx.say('hello world'); ngx.sleep(10)" &

ngx.sleep 메서드를 추가하여 문자열 hello world를 출력한 후 프로그램이 종료되지 않도록 했습니다. 이렇게 하면 다음과 같은 것을 발견할 수 있습니다:

$ ps -ef | grep nginx
root     15944  6380  0 13:59 pts/6    00:00:00 grep --color=auto nginx

마침내 NGINX 프로세스가 나타났습니다! resty 명령어는 기본적으로 NGINX 서비스를 시작하는 것 같습니다. 그렇다면 resty는 무엇일까요?

여러분의 머신에 OpenResty가 설치되어 있지 않을 수 있으므로, 처음에 건너뛰었던 설치 단계로 돌아가 OpenResty를 설치한 후 계속 진행하겠습니다.

설치

다른 오픈 소스 소프트웨어와 마찬가지로, OpenResty는 다양한 방법으로 설치할 수 있습니다. 예를 들어 운영 체제의 패키지 관리자를 사용하거나, 소스 코드를 컴파일하거나, 도커 이미지를 사용할 수 있습니다. 그러나 저는 먼저 yum, apt-get, brew와 같은 패키지 관리자를 사용하여 OpenResty를 설치할 것을 권장합니다. 이 글에서는 macOS를 예로 들어 설명하겠습니다:

$ brew tap openresty/brewbrew install openresty

다른 운영 체제도 비슷합니다. 먼저 OpenResty 저장소의 URL을 패키지 관리자에 추가한 후, 패키지 관리자를 사용하여 OpenResty를 설치합니다. 더 자세한 단계는 공식 문서를 참조할 수 있습니다.

그러나 이렇게 간단해 보이는 설치 과정 뒤에는 두 가지 문제가 있습니다:

  1. 왜 소스 코드를 사용하여 설치하는 것을 권장하지 않을까요?
  2. 왜 운영 체제의 공식 저장소에서 직접 설치할 수 없고, 다른 저장소를 먼저 설정해야 할까요?

이 두 질문에 대해 먼저 생각해 보세요.

여기서 한 가지 더 추가하고 싶습니다. 이 과정에서는 외형 뒤에 숨겨진 많은 "왜"를 다룰 것입니다. 새로운 것을 배우면서 생각하는 습관을 기르길 바랍니다. 결과가 맞든 틀리든 상관없습니다. 불행히도 기술 분야에서도 독립적인 사고는 흔치 않습니다. 각자의 기술 분야와 깊이에 따라 강사는 어떤 과정에서도 개인적인 의견과 지식의 오류를 피할 수 없습니다. 학습 과정에서 몇 가지 "왜"를 더 묻고 이해함으로써 우리만의 기술 체계를 점차 형성할 수 있습니다.

많은 엔지니어들이 소스 코드를 직접 빌드하는 것을 즐기며, 저도 몇 년 전에는 그랬습니다. 그러나 오픈 소스 프로젝트를 사용할 때, 저는 항상 수동으로 configuremake를 통해 소스 코드를 빌드하고 일부 컴파일 매개변수를 수정하는 것이 이 머신의 환경에 가장 적합하고 성능을 극대화하는 최선의 방법이라고 생각했습니다.

그러나 현실은 그렇지 않습니다. 소스 코드를 컴파일할 때마다 이상한 환경 문제에 직면하게 되고, 비틀거리며 설치를 완료할 수밖에 없었습니다. 이제는 오픈 소스 프로젝트를 사용하여 비즈니스 요구를 해결하는 것이 원래 목적이었음을 이해했습니다. 환경과 싸우며 시간을 낭비해서는 안 되며, 패키지 관리자와 컨테이너 기술이 바로 이러한 문제를 해결하기 위해 존재한다는 것을 깨달았습니다.

주제로 돌아가서, OpenResty 소스 코드를 사용하여 설치하는 것은 단계가 번거로울 뿐만 아니라 PCRE, OpenSSL과 같은 외부 의존성을 해결해야 하며, 해당 버전의 OpenSSL에 패치를 수동으로 적용해야 합니다. 그렇지 않으면 SSL 세션을 처리할 때 기능이 부족할 수 있습니다. 예를 들어, ngx.sleep과 같이 yield를 발생시키는 Lua API를 사용할 수 없게 됩니다. 이 부분에 대해 더 알고 싶다면 공식 문서를 참조하여 더 자세한 정보를 얻을 수 있습니다.

OpenResty는 이러한 패치를 OpenSSL 패키지 스크립트에서 직접 유지 관리하고 있습니다. OpenResty가 OpenSSL 버전을 업그레이드할 때마다 해당 패치를 재생성하고 완전한 회귀 테스트를 수행해야 합니다.

Source0: https://www.openssl.org/source/openssl-%{version}.tar.gz

Patch0: https://raw.githubusercontent.com/openresty/openresty/master/patches/openssl-1.1.0d-sess_set_get_cb_yield.patch
Patch1: https://raw.githubusercontent.com/openresty/openresty/master/patches/openssl-1.1.0j-parallel_build_fix.patch

동시에 CentOS에서 OpenResty의 패키지 스크립트를 살펴보면 다른 숨겨진 점이 있는지 확인할 수 있습니다:

BuildRequires: perl-File-Temp
BuildRequires: gcc, make, perl, systemtap-sdt-devel
BuildRequires: openresty-zlib-devel >= 1.2.11-3
BuildRequires: openresty-openssl-devel >= 1.1.0h-1
BuildRequires: openresty-pcre-devel >= 8.42-1
Requires: openresty-zlib >= 1.2.11-3
Requires: openresty-openssl >= 1.1.0h-1
Requires: openresty-pcre >= 8.42-1

여기서 볼 수 있듯이, OpenResty는 자체 버전의 OpenSSLzlib, PCRE를 유지 관리합니다. 그러나 후자 두 개는 컴파일 매개변수를 조정했을 뿐 패치를 보존하지는 않았습니다.

따라서 이러한 요소를 고려할 때, 여러분이 세부 사항을 이미 알고 있지 않는 한 소스 코드를 사용하여 OpenResty를 컴파일하는 것을 권장하지 않습니다.

첫 번째 질문에 답할 때, 두 번째 질문도 함께 답변했습니다: 왜 운영 체제의 공식 패키지 저장소에서 직접 설치할 수 없고, 다른 저장소를 먼저 설정해야 할까요?

이는 공식 저장소가 OpenSSL, PCRE, zlib 패키지를 제3자가 유지 관리하는 것을 받아들이기를 꺼리기 때문입니다. 이는 다른 사용자들이 어떤 것을 선택해야 할지 혼란스러워할 수 있기 때문입니다. 반면, OpenResty는 정상적으로 실행되기 위해 특정 버전의 OpenSSLPCRE 라이브러리가 필요하며, 시스템의 기본 버전은 상대적으로 오래된 것입니다.

OpenResty CLI

OpenResty를 설치한 후, OpenResty CLI resty는 기본적으로 이미 설치되어 있습니다. 이는 Perl 스크립트이며, 앞서 언급했듯이 OpenResty 생태계 도구는 모두 Perl로 작성되어 있습니다. 이는 OpenResty 저자의 기술적 선호에 의해 결정된 것입니다.

$ which resty
/usr/local/bin/resty

$ head -n 1 /usr/local/bin/resty
#!/usr/bin/env perl

resty CLI는 매우 강력하며, resty -h를 사용하거나 공식 문서를 읽어 전체 기능 목록을 확인할 수 있습니다. 다음으로 두 가지 흥미로운 기능을 소개하겠습니다.

$ resty --shdict='dogs 1m' -e 'local dict = ngx.shared.dogs dict:set("Tom", 56) print(dict:get("Tom"))'

56

위 예제는 Nginx 설정과 Lua 코드를 결합하여 공유 메모리 딕셔너리 설정 및 조회를 완료했습니다. dogs 1mdogs라는 이름의 공유 메모리 공간을 선언하고 크기를 1m으로 설정한 Nginx 설정입니다. 이 공유 메모리는 Lua 코드에서 딕셔너리로 사용됩니다.

또한 --http-include--main-include 매개변수는 NGINX 설정 파일을 설정하는 데 사용되므로, 위 예제를 다음과 같이 다시 작성할 수 있습니다:

$ resty --http-conf 'lua_shared_dict dogs 1m;' -e 'local dict = ngx.shared.dogs dict:set("Tom", 56) print(dict:get("Tom"))'

OpenResty 세계의 표준 디버깅 도구인 gdb, valgrind, sysetmtap, Mozilla rr도 resty와 함께 사용할 수 있어 일반적인 개발과 테스트를 용이하게 합니다. 이들은 resty의 다른 명령어에 해당하므로 내부 구현은 매우 간단하며, 단지 명령줄 호출에 한 층 더 추가된 것입니다. valgrind를 예로 들어 보겠습니다.

$ resty --valgrind -e "ngx.say('hello world'); "

ERROR: failed to run command "valgrind /usr/local/openresty/nginx/sbin/nginx -p /tmp/resty_rJeOWaYGIY/ -c conf/nginx.conf": No such file or directory

이들은 OpenResty 세계에만 적용되는 것이 아니라 서버 측의 일반적인 도구이므로, 단계별로 배워 나가겠습니다.

더 공식적인 "hello world"

처음에 작성한 첫 번째 OpenResty 프로그램은 resty 명령어를 사용했으며, master 프로세스 없이 특정 포트를 리스닝하지 않았습니다. 이제 다른 hello world 프로그램을 구현해 보겠습니다.

이를 완료하기 위해 최소한 세 단계가 필요합니다.

  1. 작업 디렉토리를 생성합니다.
  2. NGINX 설정 파일을 수정하여 Lua 코드를 포함시킵니다.
  3. OpenResty 서비스를 시작합니다.

먼저 작업 디렉토리를 생성해 보겠습니다.

$ mkdir openresty-sample
$ cd openresty-sample
$ mkdir logs/ conf/

다음은 루트 디렉토리에 OpenResty의 content_by_lua 지시어를 포함한 최소한의 nginx.conf입니다. 이는 ngx.say 메서드를 포함합니다.

events {
    worker_connections 1024;
}

http {
    server {
        listen 8080;
        location / {
            content_by_lua '
                ngx.say("hello, world")
            ';
        }
    }
}

먼저 openresty가 PATH 환경 변수에 추가되었는지 확인한 후, OpenResty 서비스를 시작합니다:

$ openresty -p `pwd` -c conf/nginx.conf

오류가 발생하지 않았다면, OpenResty 서비스가 성공적으로 시작된 것입니다. cURL 명령어를 사용하여 접근하여 반환된 결과를 확인할 수 있습니다.

$ curl -i 127.0.0.1:8080

HTTP/1.1 200 OK
Server: openresty/1.13.6.2
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
hello, world

축하합니다! OpenResty에서의 공식적인 Hello World가 완성되었습니다!

요약

오늘 배운 내용을 다시 살펴보겠습니다. 먼저 간단한 "hello, world" 코드로 OpenResty 설치와 CLI를 시작했고, 마지막으로 OpenResty 프로세스를 시작하고 실제 백엔드 프로그램을 실행했습니다.

그 중에서 resty는 앞으로 자주 사용하게 될 명령줄 도구입니다. 따라서 이 과정의 데모 코드는 백그라운드에서 OpenResty 서비스를 시작하는 대신 resty를 사용하여 실행됩니다.

더 중요한 것은, OpenResty 뒤에는 많은 문화적, 기술적 세부 사항이 숨겨져 있으며, 이는 바다 위에 떠 있는 빙산과 같습니다. 이 과정을 통해 여러분에게 더 포괄적인 OpenResty를 보여주고, 단순히 노출된 API뿐만 아니라 그 뒤에 숨겨진 것들도 이해할 수 있기를 바랍니다.