NGINX Unit – 파일 시스템 격리 및 기타 향상된 기능

우선, NGINX Unit 팀은 “고립”이라는 용어가 최근 전 세계적으로 얻게 된 나쁜 평가를 반드시 받을 필요는 없다고 생각하며, 릴리스인 NGINX Unit 1.18.0에 그에 맞는 테마의 주요 업데이트가 적용되었습니다.

일반적으로 우리는 가벼운 마음으로 이런 블로그 포스팅을 시작하지만, 농담 삼아 이야기하기에는 시대적 분위기가 너무 침울한 것이 사실입니다. 하지만 여러분 모두 잘 지내시길 바라며, 다시 한 번 공유할 소식이 있습니다.

목차

1. NGINX Unit 파일 시스템 격리
2. PHP 타겟
3. URL 인코딩
4. NGINX Unit 결론

1. NGINX Unit 파일 시스템 격리

NGINX Unit 1.11.0에 도입된 격리 애플리케이션 설정 제품군에는 이제 새로운 rootfs 객체가 포함됩니다. 기본 OS에서 허용하는 경우 rootfs를 사용하여 임의의 디렉터리를 파일 시스템 루트로 지정할 수 있습니다.

{
    "type": "python 2.7",
    "path": "/",
    "home": "/venv/",
    "module": "wsgi",
    "isolation": {
        "rootfs": "/var/app/sandbox/"
    }
}

이렇게 구성된 애플리케이션은 /var/app/sandbox/ 디렉터리 내에 잠기며, 그 밖의 파일이나 디렉터리에 액세스할 수 없습니다. 위 예의 경로 및 홈 객체에서 볼 수 있듯이 앱이 제공하는 모든 경로 설정은 이를 고려해야 합니다.

이 기능은 특히 다른 격리 기능과 함께 사용하면 그 활용도가 매우 높습니다. 이를 통해 앱을 경량 온디맨드 컨테이너로 구성하고 실행하여 보안을 강화하고, 서로 및 기본 OS로부터 격리하며, 인프라의 세분성을 향상시킬 수 있습니다. 이것이 바로 컨테이너화 이니셔티브의 목표이며, 파일 시스템 루트의 격리는 이러한 노력의 중요한 이정표입니다.

앱을 격리하는 동안 NGINX Unit 은 지원하는 모든 언어에 대해 적절한 언어 런타임을 계속 사용할 수 있도록 편리하게 보장합니다. 또한 rootfs를 사용하여 앱의 다양한 런타임 버전(모듈, 라이브러리 등)을 유지 관리하고 원활하게 전환할 수 있습니다. 한편, 이 기능을 직접 사용해 보고 이것이 귀하의 인프라에 어떤 이점이 있는지 확인할 수 있습니다.

자세한 내용은 프로세스 격리 구성 가이드를 참조하세요.

2. PHP 타겟

NGINX Unit 에 두 번째 혁신은 PHP에 특화된 것입니다. 이 혁신은 이전에는 다소 번거로운 설정을 유지해야 했던 PHP-on-Unit 사용자들의 일상적인 작업을 간소화하는 것을 목표로 합니다. NGINX Unit 팀은 처음에 PHP 웹 앱이 동일한 애플리케이션 내의 개별 스크립트를 실행할 수 있다는 다양한 방식에 대해 여러 가지 작동 모드를 필요로 하는 경우가 많다는 사실을 발견했으며, 정적 콘텐츠를 처리하는 기존의 무수한 방식은 말할 것도 없습니다. 일반적으로 접근 방식은 다음 중 하나로 요약됩니다:

  • 단일 스크립트가 모든 URI에 대한 수신 요청을 처리하여 내부적으로 라우팅합니다.
  • 각 요청은 URI에 명시적으로 이름이 지정된 스크립트에 의해 처리됩니다.

NGINX Unit 은 PHP 지원 도입 초기부터 두 가지 접근 방식에 대한 옵션을 포함했습니다. 그럼에도 불구하고 실제 배포에서 가능한 조합의 수가 너무 많아서 변화가 필요했습니다. 예를 들어, 이 예제에서처럼 NGINX Unit 에서 PHP 애플리케이션을 설정할 때마다 여러 애플리케이션 객체를 정의하고 이들 간에 요청을 전달하는 복잡한 라우팅 규칙을 만들어야 했습니다:

{
    "routes": [
        {
            "match": {
                "uri": [
                     "*.php",
                     "*.php/*"
                ]
            },
            "action": {
                "pass": "applications/direct"
            }
        },
       
        {
            "action": {
                "pass": "applications/script_index"
            }
        },
    ],
    "applications": {
        "direct": {
            "type": "php",
            "processes": {
                "max": 5,
                "spare": 0
            },
            "user": "www-data",
            "group": "www-data",
            "root": "/var/www/app/",
            "index": "index.php"
        },
            "script_index": {
            "type": "php",
            "processes": {
                "max": 20,
                "spare": 5
            },
            "user": "www-data",
            "group": "www-data",
            "root": "/var/www/app/",
            "script": "index.php"
        }
    }
}

여기서 PHP 스크립트의 이름을 명시적으로 지정하는 요청은 직접 앱으로 전달되고, 그 외의 모든 요청은 앱 root에 있는 다목적 index.php 스크립트를 통해 처리하는 script_index 앱으로 전달됩니다(예쁜 URI는 후자의 일반적인 사용 사례입니다). 두 앱은 별개의 앱이므로 애플리케이션 객체에서 별도로 구성해야 하지만 중복된 옵션이 상당히 많다는 것을 쉽게 알 수 있습니다.

이러한 중복은 웹 애플리케이션의 각 섹션(섹션은 단일 엔티티로 구성해야 하는 임의의 PHP 스크립트 및 URI 집합으로 느슨하게 정의됨)에 대한 설정을 미세 조정하거나 앱의 각 개별 섹션에 전용 프로세스 풀을 할당하려는 경우 의미가 있습니다. 그러나 NGINX Unit 은 이러한 섹션을 최종 목표가 아닌 경우에도 별도의 앱으로 실행하므로 원치 않는 오버헤드가 발생할 수 있습니다. 앱이 실행되려면 여러 개의 프로세스가 필요하지만(각 세션당 최소 하나씩) 그 중 일부는 대부분의 시간 동안 유휴 상태로 유지되고(CPU 사이클과 메모리 낭비), 다른 일부는 요청 폭주로 인해 지속적으로 과부하가 발생할 수 있습니다. 앱의 각 섹션 간에 프로세스를 공유할 수 있는 방법이 필요합니다.

또한, 섹션당 하나의 앱 접근 방식은 동일한 설정 값을 반복적으로 반복해야 하므로 중복성, 불필요한 구성 오류 위험, 독립형 엔티티로 표시되는 앱의 개별 섹션 간의 논리적 일관성 결여를 초래합니다. 중복 설정을 업데이트하려면 여러 번의 비원자적 구성 업데이트(API 호출)가 필요하므로 잠재적인 불일치에 대한 우려가 제기됩니다. 이러한 모든 문제를 해결하기 위해 1.18.0 버전부터는 새로운 접근 방식을 도입했습니다.

이제 앱의 모든 섹션에 대해 스크립트를 사용자 정의 방식으로 처리하는 단일 애플리케이션 객체를 설정할 필요 없이 새로운 대상 옵션을 사용하여 단일 앱의 지붕 아래에서 모두 원활하게 함께 작동하도록 할 수 있습니다:

{
    "routes": [
        {
            "match": {
                "uri": [
                     "*.php",
                     "*.php/*"
                ]
            },
            "action": {
                "pass": "applications/phpapp/direct"
            }
        },
       
        {
            "action": {
                "pass": "applications/phpapp/script_index"
            }

        },

    ],

    "applications": {
        "phpapp": {
            "type": "php",
            "user": "www-data",
            "group": "www-data",
            "targets": {
                "direct": {
                    "root": "/var/www/app/"
                },
                "script_index": {
                    "root": "/var/www/app/",
                    "script": "index.php"
                }
            }
        }
    }
}

NGINX Unit 앱 내의 각 대상은 root, index 및 script 옵션의 고유한 조합을 가질 수 있으며, 자체 스크립트를 독립적으로 실행할 수 있습니다. 그러나 모든 타겟은 격리, 제한, 옵션 및 프로세스와 같은 애플리케이션 전체 설정을 공유합니다. 하지만 현재 앱 내 개별 대상 간의 리소스 배포를 관리하는 것은 불가능하다는 점에 유의하세요.

새로운 접근 방식은 애플리케이션 관리를 중앙 집중화하여 오류를 방지하는 동시에 개별 타겟이 있는 모든 애플리케이션의 수많은 엔드포인트를 유연하게 제어할 수 있습니다. 또한 필요에 따라 이전 방법과 결합하여 앱 구성을 그룹화하거나 분리할 수 있습니다.

자세한 내용과 예제는 타겟 구성 가이드 및 PHP 기반 앱의 사용 방법을 참조하세요.

3. URL 인코딩

NGINX Plus는 URL 인코딩(퍼센트 인코딩이라고도 함)을 구현하는데, 이는 영어 알파벳 이외의 문자(예: umlaut, tilde와 같은 발음 부호 표시가 있는 문자)와 특별한 의미를 가질 수 있는 문자(예: URI의 요소 사이를 구분하는 슬래시)를 해당 의미가 없을 때 표현하는 데 가장 자주 사용됩니다. URL 인코딩을 사용하여 pass, arguments 그리고 uri 옵션의 문자를 이스케이프 처리할 수 있습니다. 두 가지 주요 사용 사례가 있습니다.

첫째, 전달 옵션의 인수에서 슬래시 문자를 %2F로 이스케이프 처리할 수 있습니다:

{
    "listeners": {
         "*:80": {
             "pass": "routes/slashes%2Fin%2Froute%2Fname"
         }
    },
    "routes": {
         "slashes/in/route/name": [
         ]
    }
}

둘째, NGINX 단위 라우팅에서 특별한 의미를 갖는 문자를 포함하는 uri 및 arguments 옵션을 사용하여 필터를 만들거나 개별 바이트를 대상으로 할 수도 있습니다:

{
    "routes": {
        "slashes/in/route/name": [
            {
                "match": {
                    "uri": "/%2A",
                    "arguments": {
                        "%25": "%21%C3*"
                    }
                },
                "action": {
                    "return": 301,
                    "location": "http://fancyurls.example.com"
                }
            }
        ]
    }
}

여기서 uri 필터는 하나의 literal asterisk(*, %2A로 인코딩된 URL)를 예상하고 인수 필터는 느낌표로 구성된 값이 잇는 키로 백분율 기호(%, %25로 인코딩된 URL)를 예상합니다. (!, URL은 %21로 인코딩된 URL) 뒤에 Ö 또는 Å와 같은 발음 구별 UTF-8 문자가 옵니다. 바이트 값 %C3은 일반적으로 발음 구별 문자 집합을 나타내며 여기서는 임의 바이트 시퀀스(asterisk로 표시)가 옵니다.

이 두 가지 샘플 쿼리는 전체 구성의 효과를 보여줍니다:

$ curl -v 'http://localhost:80/*?%=!Ü'

...
< HTTP/1.1 301 Moved Permanently
< Location: http://fancyurls.example.com
< Server: Unit/1.18.0

$ curl -v 'http://localhost:80/*?%=!ò'
...
< HTTP/1.1 301 Moved Permanently
< Location: http://fancyurls.example.com
< Server: Unit/1.18.0

모든 요청이 모두 성공적으로 리다이렉션된 것을 볼 수 있는데, 이는 NGINX Unit 이 * 및 ! 와 발음 부호 문자를 의도한 대로 일치시켰다는 의미입니다. 이는 사실상 다른 인코딩에서 임의의 바이트 시퀀스와 문자를 골라낼 수 있음을 의미합니다.

4. NGINX Unit 결론

NGINX Unit 팀은 현재 로드맵에 포함된 구성 변수 지원, 일부 HTTP 기능 추가, 요청 매칭 패턴에서 와일드카드 지원 확장 등의 계획을 통해 이미 존재하는 기능의 완성도를 높이는 동시에 더욱 멋진 개선 작업을 계속하고 있습니다.

변경 사항 및 버그 수정 목록은 NGINX Unit 변경 로그를 참조하세요.

NGINX Plus 구독자는 추가 비용 없이 NGINX Unit 에 대한 지원을 받을 수 있습니다. 지금 바로 NGINX Plus 30일 무료 체험을 시작하세요.

NGINX Unit 도입 컨설팅 및 상담이 필요하신 경우 아래 양식을 통해 신청해주세요.

NGINX Unit config

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required