Как стать автором
Обновить

Деплоим изоморфное веб-приложение на примере Nuxt.js

Разработка веб-сайтов *JavaScript *Node.JS *VueJS *
Из песочницы
Tutorial

В средних и больших проектах сайт не ограничивается одним сервисом  —  к примеру только сайтом, как правило существует база данных, API, сервер который маршрутизирует запросы ко всем этим сервисам. Выкатывать и обновлять все это без какой-либо стандартизации непросто, а масштабировать на множество серверов еще сложнее.

Решить эту проблему нам поможет docker —   ставший стандартом де-факто в мире упаковки, доставки и публикации приложений.

Docker позволяет нам обернуть приложение или сервис со всеми зависимостями и настройками в изолированный контейнер, гарантируя консистентность содержимого на любой платформе.

В качестве изоморфного приложения мы будем использовать фреймворк Nuxt.js, который состоит из Vue.js и Node.js, позволяя писать универсальные веб-приложения с отрисовкой на стороне сервера (SSR).

Данный выбор обусловлен личным предпочтением, однако аналогичным образом можно взять любой другой фреймворк, например Next.js.

Собираем и публикуем первый образ.


Прежде всего необходимо настроить порт и хост внутри приложения. Существует несколько способов это сделать, мы воспользуемся настройками в package.json, добавив новую секцию:

"config": {
 "nuxt": {
   "host": "0.0.0.0",
   "port": "3000"
 }
}

Для дальнейших действий нам потребуется docker, docker-compose установленные в системе и редактор с открытым проектом.

Создадим Dockerfile который поместим в корень и опишем инструкции для сборки образа.

Нам необходимо собрать образ базируясь на образе Node.js версии 10, в данном случае используется облегченная версия alpine:

FROM node:10-alpine

Затем установим переменную окружения с названием директории:

ENV APP_ROOT /web

Установим в качестве рабочей директории и добавим исходники:

WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}

Устанавливаем зависимости и собираем приложение:

RUN npm ci
RUN npm run build

И пишем команду запуска приложения внутри образа:

CMD ["npm", "run", "start"]

Dockerfile
FROM node:10-alpine
ENV APP_ROOT /web
ENV NODE_ENV production

WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}

RUN npm ci
RUN npm run build

CMD ["npm", "run", "start"]


После чего открываем в терминале текущую папку и собираем образ:

docker build -t registry.gitlab.com/vik_kod/nuxtjs_docker_example .

Запускаем образ локально для проверки что все работает корректно:

docker run -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example

Перейдя по адресу localhost:3000 мы должны увидеть следующее:



Отлично! Мы успешно запустили production сборку приложения на локальной машине.

Теперь нам необходимо опубликовать образ в docker репозиторий, для того чтобы на целевом сервере использовать готовый собранный образ. Можно использовать как self-hosted репозиторий так и любой другой, например официальный hub.docker.com.

Я воспользуюсь репозиторием в gitlab, вкладка с docker репозиториями там называется registry. Предварительно я уже создал репозиторий для проекта поэтому сейчас выполняю команду:

docker push registry.gitlab.com/vik_kod/nuxtjs_docker_example

После того как образ успешно загрузился можно приступить к конфигурации VPS сервера,
у моего она следующая:

  • 1 ГБ оперативной памяти
  • 4 ядра
  • 30 ГБ диск

Также я воспользовался возможностью поставить docker сразу при создании сервера, поэтому если на вашем VPS он не установлен, инструкцию можно почитать на официальном сайте.

После создания сервера заходим на него и логинимся в docker репозитории, в моем случае это gitlab:

docker login registry.gitlab.com

После авторизации мы можем запустить приложения ранее виденной командой:

docker run -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example



Образ скачался и запустился, давайте проверим:



Видим знакомую картину, мы запустили контейнер с приложением, но уже на удаленном сервере.

Остался последний штрих, сейчас при закрытии терминала образ будет остановлен, поэтому добавим атрибут -d для того, чтобы запустить контейнер в фоне.
Останавливаем и перезапускаем:

docker run -d -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example

Теперь можем закрыть терминал и убедиться, что наше приложение успешно функционирует.

Мы добились необходимого — запустили приложение в docker и теперь оно пригодно для развертывания, как самостоятельный образ, так и в рамках более масштабной инфраструктуры.

Добавляем reverse proxy


На текущем этапе мы можем публиковать простые проекты, но что если нам нужно поместить приложение и API на одном домене и в дополнение к этому отдавать статику не через Node.js?

Таким образом появляется необходимость так называемого reverse proxy сервера, на который будут поступать все запросы и перенаправляться в зависимости от запроса к связанным сервисам.

В качестве такого сервера мы будем использовать nginx.

Управлять контейнерами если их больше чем один по отдельности не очень удобно. Поэтому мы воспользуемся docker-compose как способом организации и управления контейнерами.

Создадим новый пустой проект, в корень которого добавим файл docker-compose.yml и папку nginx.

В docker-compose.yml пишем следующее:

version: "3.3"
# Указываем раздел со связанными сервисами
services:
 # Первый сервис, nginx
 nginx:
   image: nginx:latest
   # Пробрасываем порты 80 для http и 443 для https
   ports:
     - "80:80"
     - "443:443"
   # Опциональный параметр с именем контейнера
   container_name: proxy_nginx
   volumes:
     # Используем свой nginx конфиг, он заменит дефолтный в контейнере
     - ./nginx:/etc/nginx/conf.d
     # Монтируем папку с логами на хост машину для более удобного доступа
     - ./logs:/var/log/nginx/
 # Второй сервис Nuxt.js приложение
 nuxt:
   # Используем ранее собранный образ
   image: registry.gitlab.com/vik_kod/nuxtjs_docker_example
   container_name: nuxt_app
   # Также пробрасываем порт на котором висит приложение
   ports:
     - "3000:3000"

В папку nginx добавляем конфиг, который рекомендует официальный сайт Nuxt.js, c небольшими изменениями.

nginx.conf
map $sent_http_content_type $expires {
    "text/html" epoch;
    "text/html; charset=utf-8"  epoch;
    default off;
}

server {
    root /var/www;
    listen 80; # Порт который слушает nginx
    server_name localhost; # домен или ip сервера
    gzip on;
    gzip_types  text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location / {
        expires $expires;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        # Адрес нашего приложения, так как контейнеры связаны при помощи
        # docker-compose мы можем обращаться к ним по имени контейнера, в данном случае nuxt_app
        proxy_pass http://nuxt_app:3000;
    }
}


Выполняем команду для запуска:

docker-compose up



Все корректно запустилось, теперь если мы перейдем по адресу который слушает nginx, localhost — то увидим наше приложение, визуально отличий не будет, однако теперь все запросы сначала идут на nginx где и перенаправляются в зависимости от указанных правил.

Сейчас у нас нет дополнительных сервисов или статики, давайте добавим папку static в которую поместим какое-нибудь изображение.

Смонтируем её в контейнер nginx добавив строчку в docker-compose:

...
container_name: proxy_nginx
volumes:
 #  Монтируем папку со статикой
 - ./static:/var/www/static
...

Обновленный docker-compose.yml
version: "3.3"
# Указываем раздел со связанными сервисами
services:
  # Первый сервис, nginx
  nginx:
    image: nginx:latest
    # Пробрасываем порты 80 для http и 443 для https
    ports:
      - "80:80"
      - "443:443"
    # Опциональный параметр с именем контейнера
    container_name: proxy_nginx
    volumes:
      # Используем свой nginx конфиг, он заменит дефолтный в контейнере
      - ./nginx:/etc/nginx/conf.d
      # Монтируем папку с логами на хост машину для более удобного доступа
      - ./logs:/var/log/nginx/
      #  Монтируем папку со статикой
      - ./static:/var/www/static
  # Второй сервис Nuxt.js приложение
  nuxt:
    # Используем ранее собранный образ
    image: registry.gitlab.com/vik_kod/nuxtjs_docker_example
    container_name: nuxt_app
    # Так же пробрасываем порт на котором висит приложение
    ports:
      - "3000:3000"


Затем добавим новый location в nginx.conf:

location /static/ {
   try_files $uri /var/www/static;
}

Обновленный nginx.conf
map $sent_http_content_type $expires {
    "text/html" epoch;
    "text/html; charset=utf-8"  epoch;
    default off;
}

server {
    root /var/www;
    listen 80; # Порт который слушает nginx
    server_name localhost; # домен или ip сервера
    gzip on;
    gzip_types  text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location / {
        expires $expires;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        # Адрес нашего приложения, так как контейнеры связаны при помощи
        # docker-compose мы можем обращаться к ним по имени контейнера, в данном случае  nuxt_app
        proxy_pass http://nuxt_app:3000;
    }

    location /static/ {
      try_files $uri /var/www/static;
    }
}


Перезапускаем docker-compose:

docker-compose up --build

Переходим по адресу localhost/static/demo.jpg




Теперь статика отдается через Nginx, снимая нагрузку с Node.js в основном приложении.

Убедившись что все работает, можно публиковать нашу сборку на сервере. Для этого я создам репозиторий в текущей директории. Предварительно добавив папку logs и static в .gitignore.

После чего заходим на сервер, останавливаем ранее запущенный docker образ и клонируем репозиторий.



Прежде чем приступать к запуску сборки, необходимо переместить папку со статикой на сервер, переходим в терминал на локальной машине и через командную утилиту scp перемещаем папку на сервер:

scp -r /Users/vik_kod/PhpstormProjects/nuxtjs_docker_proxy_example/static root@5.101.48.172:/root/example_app/

Если объем статики большой, лучше сначала сжать папку и отправлять архивом, после чего распаковать на сервере. Иначе загрузка может затянуться надолго.

Возвращаемся в терминал на сервере и перейдя в склонированную папку запускаем команду:

docker-compose up -d

Закрываем терминал и переходим на сайт:




Отлично! С помощью reverse proxy мы отделили статику от приложения.

Дальнейшие шаги


Все что мы с вами сделали выше это достаточно простой вариант, в больших проектах необходимо учитывать больше вещей, ниже краткий список того что можно делать дальше.

  • Data only контейнеры для статичных админок, SPA приложений и базы данных
  • Дополнительные сервисы для обработки и оптимизации изображений, пример
  • Интеграция CI/CD, сборка образа при пуше в выбранную ветку а также автоматическое обновление и перезапуск сервисов
  • Создание кластера Kubernetes или Swarm если серверов больше чем 1, для балансировки нагрузки и легкого горизонтального масштабирования

Итого


  • Мы успешно опубликовали приложение на сервер и подготовили его к дальнейшему масштабированию.
  • Познакомились с docker и получили представление о том как оборачивать свое приложение в контейнер.
  • Узнали какие шаги можно совершить далее для улучшения инфраструктуры.

Исходники


Приложение
Конфиги

Благодарю за внимание и надеюсь данный материал вам помог!
Теги:
Хабы:
Всего голосов 22: ↑20 и ↓2 +18
Просмотры 32K
Комментарии 6
Комментарии Комментарии 6