Картофельные лонгриды

Назад, к списку

Стены статики: NGINX

Просыпайтесь, работяги, время доставлять штимельки пользователям!

Не вижу смысла долго мариновать тебя статьями про обслуживание приложений, пока мы не умеем их разворачивать, поэтому сегодня поговорю про один из самых популярных серверов для отдачи статики — NGINX (произносится как Engine X, в простонародье НЖЫНКС).

Перед прочтением

Что вообще такое сервер статики и почему NGINX?

Если просто, сервер статики — это приложение, к которому подключается браузер пользователя, чтобы скачать index.html, а затем и сопутствующие ему зависимости. Сервер статики знает, как обработать эти запросы, где найти файлики, за которыми мы пришли, и как их отдать.

NGINX отлично подходит на роль сервера статики (хотя изначально позиционировал себя как реверс-прокси) по нескольким причинам:

Как установить?

Мы не будем устанавливать NGINX на ПК, а будем пользоваться им внутри докера. Докер поможет нам тем, что он хорошо умеет: создавать повторяемые окружения. Мы при желании сможем залезть внутрь контейнера, поломать там конфиги, а потом просто поднять новое, полностью рабочее приложение снова.

У NGINX есть официальный образ в Docker Hub, его и запустим с помощью docker run -p 80:80 nginx. Если мы теперь зайдем на localhost в браузере, то увидим, что NGINX работает, ура!

Где настроить?

Главный конфигурационный файл nginx лежит по пути /etc/nginx/nginx.conf, но для того, чтобы начать разбираться в синтаксисе, сначала посмотрим на другой конфиг, который он импортирует — /etc/nginx/conf.d/default.conf:

Конфигурация NGINX состоит из двух видов конструкций (директив) — простых и блочных.

Простые директивы — это ключевые слова вместе с опциональными аргументами после них. Заканчиваются они точками с запятой. В нашем файлике это, например, listen 80;, root /usr/share/nginx/html и тому подобное.

Блочные директивы (или контексты) — это ключевые слова, после которых идет блок из инструкций внутри фигурных скобок. У нас это server и два location.

Давай прочитаем наш конфиг. Стартуем мы в контексте server. Директивы listen в начале говорят о том, что мы слушаем подключения по 80 порту (вторая нужна для того, чтобы слушать и ipv6). Директива server_name указывает домен, к которому привязан контекст server. Можно его не указывать, и тогда он будет слушать запросы вне зависимости от хоста.

Ещё внутри server у нас есть два блока location: первый раздает файлы из директории /usr/share/nginx/html:

А второй блок location за счет префикса = работает только если мы пойдем по пути localhost/50x.html и отдаст нам /usr/share/nginx/html/50x.html. Это условие попадает и под первый блок, так что я, честно говоря, не знаю, зачем в стандартном конфиге их вынесли в два разных.

Между ними затесалась директива error_page. Можно догадаться, что она делает: для ошибок 500, 502 – 504 переадресовывает нас как раз на 50x.html.

Теперь на файл /etc/nginx/nginx.conf можете посмотреть сами, а неизвестные директивы подглядеть в документации. Ещё важный момент: в главном конфиге есть простые директивы, которые не относятся ни к какой блочной, но на самом деле относятся к главному (main) контексту.

Как настроить?

Если мы имеем SPA (например react/anguar/vue/svelte + любой роутер), то нам нужно отдавать js/css чанки при прямых запросах за ними, а на остальные запросы отдавать index — дальше разрулит роутер.

В nginx это поведение можно реализовать при помощи try_files:

Она сделает ровно то, что мы хотим: сначала попробует достать из root файл по uri, с которого мы пришли (например, для localhost/main.css — /usr/share/nginx/html/main.css), а если такого нет — отдаст index.html.

Как развернуть?

Алгоритм сборки нашего фронтового приложения следующий:

Получается несложный докерфайл:

Из того, что ты мог ещё не видеть, в нём используется две конструкции FROM: для ноды и для nginx. Рассказываю: в этом случае наш итоговый контейнер будет использовать образ, собранный из последнего FROM — nginx. Просто до перехода в него нам нужно было собрать приложение в окружении с установленной нодой.

После сборки и запуска нашего контейнера мы должны увидеть почти готовое к жизни в продакшене приложение! Улучшения вроде работы с HTTPS, победы над CORS и кеширования рассмотрим в следующих статьях 💛