One place for hosting & domains

      Контейнеризация

      Контейнеризация приложения Laravel 6 для разработки с помощью Docker Compose в Ubuntu 18.04


      Введение

      Контейнеризация приложения — это процесс адаптации приложения и его компонентов для работы в облегченных средах, называемых контейнерами. Это изолируемые одноразовые среды, которые используются для разработки, тестирования и развертывания приложений в производственной среде.

      В этом обучающем руководстве мы научимся использовать Docker Compose для контейнеризации приложения Laravel 6 для целей разработки. После завершения обучения мы получим демонстрационное приложение Laravel, работающее в трех отдельных служебных контейнерах:

      • Служба app с PHP7.4-FPM;
      • Служба db с MySQL 5.7;
      • Служба nginx, использующая службу app для синтаксического анализа кода PHP перед предоставлением приложения Laravel конечному пользователю.

      Чтобы ускорить разработку и упростить отладку приложения, мы обеспечим синхронизацию файлов приложения с использованием общих томов. Также мы узнаем, как использовать команды docker-compose exec для запуска Composer и Artisan в контейнере app.

      Предварительные требования

      Шаг 1 — Получение демонстрационного приложения

      Для начала работы мы получим демонстрационное приложение Laravel из его репозитория на Github. Нас интересует раздел tutorial-01, содержащий базовое приложение Laravel, которое мы создали в первом обучающем руководстве этой серии.

      Чтобы получить код приложения, совместимый с настоящим обучающим руководством, загрузите версию tutorial-1.0.1 в свою домашнюю директорию с помощью следующей команды:

      • cd ~
      • curl -L https://github.com/do-community/travellist-laravel-demo/archive/tutorial-1.0.1.zip -o travellist.zip

      Нам потребуется команда unzip для распаковки кода приложения. Если вы еще не установили этот пакет, сделайте это с помощью следующей команды:

      • sudo apt update
      • sudo apt install unzip

      Теперь распакуйте содержимое приложения и переименуйте распакованную директорию для удобства доступа:

      • unzip travellist.zip
      • mv travellist-laravel-demo-tutorial-1.0.1 travellist-demo

      Перейдите в директорию travellist-demo:

      На следующем шаге мы создадим файл конфигурации .env для настройки приложения.

      Шаг 2 — Настройка файла .env приложения

      Файлы конфигурации Laravel располагаются в директории config внутри корневой директории приложения. Также файл .env используется для настройки конфигурации, зависимой от среды, в том числе учетных данных и любой информации, которая может изменяться между операциями развертывания. Этот файл не включен в систему контроля версий.

      Предупреждение. Файл конфигурации среды содержит важную информацию о вашем сервере, включая учетные данные базы данных и ключи безопасности. В связи с этим не следует предоставлять этот файл в открытый доступ.

      Значения в файле .env имеют приоритет по отношению к значениям в обычных файлах конфигурации, расположенных в директории config. Для каждого случая установки в новую среду требуется персонализированный файл среды, где будут определены такие настройки, как параметры подключения к базе данных, параметры отладки, URL приложения и другие параметры, в зависимости от используемой приложением среды.

      Теперь мы создадим новый файл .env для настройки индивидуальных параметров конфигурации для создаваемой нами среды разработки. В комплектацию Laravel входит образец файла .env, который мы скопируем для создания собственного файла:

      Откройте этот файл с помощью nano или другого текстового редактора на ваш выбор:

      Текущий файл .env из демонстрационного приложения travellist содержит настройки использования локальной базы данных MySQL с адресом хоста базы данных 127.0.0.1. Нам нужно обновить переменную DB_HOST, чтобы она указывала на службу базы данных, которую мы создадим в нашей среде Docker. В этом обучающем руководстве мы присвоим службе базы данных имя db. Замените указанное значение DB_HOST именем службы базы данных:

      .env

      APP_NAME=Travellist
      APP_ENV=dev
      APP_KEY=
      APP_DEBUG=true
      APP_URL=http://localhost:8000
      
      LOG_CHANNEL=stack
      
      DB_CONNECTION=mysql
      DB_HOST=db
      DB_PORT=3306
      DB_DATABASE=travellist
      DB_USERNAME=travellist_user
      DB_PASSWORD=password
      ...
      

      При желании вы также можете изменить название базы данных, имя пользователя и пароль. Эти переменные будут использоваться на последующем шаге, когда мы настроим файл docker-compose.yml для настройки наших служб.

      Сохраните файл после завершения редактирования. Если вы использовали nano, нажмите Ctrl+x, а затем Y и Enter для подтверждения.

      Шаг 3 — Настройка файла Dockerfile для приложения

      Хотя наши службы MySQL и Nginx будут основаны на образах по умолчанию, полученных из Docker Hub, нам все равно потребуется создать персонализированный образ контейнера приложения. Для этого мы создадим новый файл Dockerfile.

      Наш образ travellist будет основан на php:7.4-fpm, официальном образе PHP из Docker Hub. Поверх базовой среды PHP-мы установим несколько дополнительных модулей PHP и инструмент Composer для управления зависимостями.

      Также мы создадим нового пользователя системы, что необходимо для выполнения команд artisan и composer при разработке приложения. Параметр uid обеспечивает соответствие uid пользователя внутри контейнера и системного пользователя на хосте, где работает Docker. Любые файлы, созданные этими командами, воспроизводятся на хосте с соответствующими разрешениями. Также это означает, что вы можете использовать предпочитаемый редактор кода на хосте для разработки приложения, работающего внутри контейнеров.

      Создайте новый файл Dockerfile:

      Скопируйте следующие строки в файл Dockerfile:

      Dockerfile

      FROM php:7.4-fpm
      
      # Arguments defined in docker-compose.yml
      ARG user
      ARG uid
      
      # Install system dependencies
      RUN apt-get update && apt-get install -y 
          git 
          curl 
          libpng-dev 
          libonig-dev 
          libxml2-dev 
          zip 
          unzip
      
      # Clear cache
      RUN apt-get clean && rm -rf /var/lib/apt/lists/*
      
      # Install PHP extensions
      RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
      
      # Get latest Composer
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      
      # Create system user to run Composer and Artisan Commands
      RUN useradd -G www-data,root -u $uid -d /home/$user $user
      RUN mkdir -p /home/$user/.composer && 
          chown -R $user:$user /home/$user
      
      # Set working directory
      WORKDIR /var/www
      
      USER $user
      
      

      Не забудьте сохранить файл после завершения работы.

      Вначале наш файл Dockerfile определяет базовый образ, который мы используем: php:7.4-fpm.

      После установки системных пакетов и расширений PHP мы установим Composer, скопировав исполняемый файл composer из последнего официального образа в образ нашего приложения.

      При этом создается новый пользователь системы и выполняется настройка его параметров с помощью аргументов user и uid в начале файла Dockerfile. Эти значения вставляются Docker Compose во время сборки.

      В заключение мы зададим рабочую директорию по умолчанию /var/www и переключимся на созданного пользователя. Это гарантирует, что вы будете подключаться как обычный пользователь и находиться в правильной директории при выполнении команд composer и artisan в контейнере приложения.

      Шаг 4 — Настройка конфигурации Nginx и файлов дампа базы данных

      При создании сред разработки с помощью Docker Compose часто бывает необходимо использовать общие конфигурации или файлы инициализации со служебными контейнерами для настройки этих служб или их включения в процессе загрузки. Такая практика упрощает изменение файлов конфигурации для отладки среды во время разработки приложения.

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

      Для настройки Nginx мы будем использовать файл travellist.conf, настраивающий обслуживание приложения. Создайте папку docker-compose/nginx с помощью следующей команды:

      • mkdir -p docker-compose/nginx

      Откройте в этой директории новый файл с именем travellist.conf:

      • nano docker-compose/nginx/travellist.conf

      Скопируйте следующую конфигурацию Nginx в этот файл:

      docker-compose/nginx/travellist.conf

      
      server {
          listen 80;
          index index.php index.html;
          error_log  /var/log/nginx/error.log;
          access_log /var/log/nginx/access.log;
          root /var/www/public;
          location ~ .php$ {
              try_files $uri =404;
              fastcgi_split_path_info ^(.+.php)(/.+)$;
              fastcgi_pass app:9000;
              fastcgi_index index.php;
              include fastcgi_params;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              fastcgi_param PATH_INFO $fastcgi_path_info;
          }
          location / {
              try_files $uri $uri/ /index.php?$query_string;
              gzip_static on;
          }
      }
      

      Этот файл настраивает Nginx для прослушивания порта 80 и использования index.php в качестве страницы индекса по умолчанию. Это задаст корневую директорию документа /var/www/public, а затем Nginx будет настроен для использования службы app на порту 9000 для обработки файлов *.php.

      Сохраните и закройте файл после завершения редактирования.

      Чтобы настроить базу данных MySQL, мы откроем для общего доступа дамп базы данных, который будет импортирован при инициализации контейнера. Эта возможность обеспечивается образом MySQL 5.7, который мы будем использовать в этом контейнере.

      Создайте новую папку для файлов инициализации MySQL в папке docker-compose:

      • mkdir docker-compose/mysql

      Откройте новый файл .sql:

      • nano docker-compose/mysql/init_db.sql

      Следующий дамп MySQL основан на базе данных, которую мы настроили в обучающем руководстве по Laravel на LEMP. Она создаст новую таблицу с именем places. Затем таблица будет заполнена местами на основе образца.

      Добавьте в файл следующий код:

      docker-compose/mysql/db_init.sql

      DROP TABLE IF EXISTS `places`;
      
      CREATE TABLE `places` (
        `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
        `visited` tinyint(1) NOT NULL DEFAULT '0',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
      
      INSERT INTO `places` (name, visited) VALUES ('Berlin',0),('Budapest',0),('Cincinnati',1),('Denver',0),('Helsinki',0),('Lisbon',0),('Moscow',1),('Nairobi',0),('Oslo',1),('Rio',0),('Tokyo',0);
      

      Таблица places содержит три поля: id, name и visited. Поле visited — это флаг, используемый для отметки мест со статусом to go. Вы можете свободно изменять или добавлять места в образец. Сохраните и закройте файл после завершения.

      Мы завершили настройку файла Dockerfile приложения и файлов конфигурации служб. Далее мы выполним настройку Docker Compose для использования этих файлов при создании наших служб.

      Шаг 5 — Создание мультиконтейнерной среды с Docker Compose

      Docker Compose позволяет создавать мультиконтейнерные среды для приложений, работающих на Docker. Он использует определения служб для построения полностью настраиваемых сред с несколькими контейнерами, которые могут использовать общие сети и тома хранения данных. Это обеспечивает полную интеграцию компонентов приложения.

      Для настройки наших определений служб мы создадим новый файл с именем docker-compose.yml. Обычно этот файл располагается в корневой папке приложения и определяет контейнеризованную среду, в том числе базовые образы, используемые для построения контейнеров, и взаимодействие между службами.

      Мы определим в файле docker-compose.yml три разные службы: app, db и nginx.

      Служба app построит образ с именем travellist на базе ранее созданного файла Dockerfile. Определяемый этой службой контейнер запустит сервер php-fpm для синтаксической проверки кода PHP и возврата результатов в службу nginx, которая будет работать в отдельном контейнере. Служба mysql определяет контейнер, где запущен сервер MySQL 5.7. Наши службы будут использовать общую соединительную сеть с именем travellist.

      Файлы приложения будут синхронизироваться в службах app и nginx посредством монтирования привязок. Монтирование привязок — это полезный инструмент в средах разработки, позволяющий организовать эффективную двустороннюю синхронизацию между хостом и контейнерами.

      Создайте новый файл docker-compose.yml в корневой папке приложения:

      Обычно файл docker-compose.yml начинается с определения версии, после которого идет узел services, где определяются все службы. Общие сети обычно определяются в конце этого файла.

      Для начала скопируйте этот шаблонный код в файл docker-compose.yml:

      docker-compose.yml

      version: "3.7"
      services:
      
      
      networks:
        travellist:
          driver: bridge
      

      Теперь мы отредактируем узел services и добавим в него службы app, db и nginx.

      Служба app

      Служба app настраивает контейнер с именем travellist-app. Она строит новый образ Docker на базе файла Dockerfile в той же директории, что и файл docker-compose.yml. Новый образ сохраняется на локальном компьютере с именем travellist.

      Хотя корневая директория документов выдается приложением в контейнере nginx, нам нужно, чтобы файлы приложения находились внутри контейнера app, поскольку мы хотим выполнять задачи командной строки с помощью инструмента Laravel Artisan.

      Скопируйте следующее определение службы в узел services в файле docker-compose.yml:

      docker-compose.yml

        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      

      Эти параметры имеют следующее назначение:

      • build: данная конфигурация предписывает Docker Compose построить локальный образ службы app с использованием заданного пути (контекста) и указаний из файла Dockerfile. Аргументы user и uid вставляются в файл Dockerfile для настройки команд создания пользователя во время сборки.
      • image: имя, которое будет использоваться для создаваемого образа.
      • container_name: задает имя контейнера для этой службы.
      • restart: всегда выполнять перезапуск, если служба не остановлена.
      • working_dir: задает для этой службы директорию по умолчанию /var/www.
      • volumes: создает общий том, который будет синхронизировать содержимое текущей директории с директорией /var/www внутри контейнера. Следует отметить, что это не корневая директория документов, поскольку она находится в контейнере nginx.
      • networks: настраивает службу для использования сети с именем travellist.

      Служба db

      Служба db использует готовый образ MySQL 5.7 из Docker Hub. Поскольку Docker Compose автоматически загружает файлы переменных .env, находящиеся в той же директории, что и файл docker-compose.yml, мы можем получить параметры базы данных из файла Laravel .env, созданного на предыдущем шаге.

      Добавьте следующее определение служб в узел services сразу же после определения службы app:

      docker-compose.yml

        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      

      Эти параметры имеют следующее назначение:

      • image: определяет образ Docker, который следует использовать для этого контейнера. В данном случае мы используем образ MySQL 5.7 из Docker Hub.
      • container_name: задает имя контейнера для этой службы: travellist-db.
      • restart: всегда перезапускать службу, если она явно не остановлена.
      • environment: определяет переменные среды в новом контейнере. Мы используем полученные из файла Laravel .env значения для настройки нашей службы MySQL, которая автоматически создаст новую базу данных и пользователя на базе заданных переменных среды.
      • volumes: создает том для общего доступа к дампу базы данных .sql, который будет использоваться для инициализации базы данных приложения. Образ MySQL будет автоматически импортировать файлы .sql, расположенные в директории /docker-entrypoint-initdb.d внутри контейнера.
      • networks: предписывает службе использовать сеть с именем travellist.

      Служба nginx

      Служба nginx использует готовый образ Nginx на базе облегченного дистрибутива Linux под названием Alpine. Она создает контейнер с именем travellist-nginx, и он использует определение ports для создания переадресации с порта 8000 системы хоста на порт 80 внутри контейнера.

      Добавьте следующее определение службы в узел services сразу после службы db:

      docker-compose.yml

        nginx:
          image: nginx:1.17-alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d
          networks:
            - travellist
      

      Эти параметры имеют следующее назначение:

      • image: определяет образ Docker, который следует использовать для этого контейнера. В данном случае мы используем образ Alpine Nginx 1.17.
      • container_name: задает имя контейнера для этой службы: travellist-nginx.
      • restart: всегда перезапускать эту службу, если она явно не остановлена.
      • ports: задает переадресацию портов, разрешающую внешний доступ через порт 8000 к веб-серверу на порту 80 внутри контейнера.
      • volumes: создает два общих тома. Первый из них синхронизирует содержимое текущей директории с директорией /var/www внутри контейнера. При внесении локальных изменений в файлы приложения эти изменения быстро отражаются в приложении, обслуживаемом Nginx внутри контейнера. Второй том обеспечивает копирование нашего файла конфигурации Nginx docker-compose/nginx/travellist.conf в папку конфигурации Nginx контейнера.
      • networks: предписывает этой службе использовать сеть с именем travellist.

      Готовый файл docker-compose.yml

      Так выглядит готовый файл docker-compose.yml:

      docker-compose.yml

      version: "3.7"
      services:
        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      
        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      
        nginx:
          image: nginx:alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d/
          networks:
            - travellist
      
      networks:
        travellist:
          driver: bridge
      

      Обязательно сохраните файл после завершения.

      Шаг 6 — Запуск приложения с помощью Docker Compose

      Теперь мы используем команды docker-compose для сборки образа приложения и запуска заданных нами служб.

      Выполните сборку образа app с помощью следующей команды:

      Выполнение этой команды может занять несколько минут. Вывод будет выглядеть следующим образом:

      Output

      Building app Step 1/11 : FROM php:7.4-fpm ---> fa37bd6db22a Step 2/11 : ARG user ---> Running in f71eb33b7459 Removing intermediate container f71eb33b7459 ---> 533c30216f34 Step 3/11 : ARG uid ---> Running in 60d2d2a84cda Removing intermediate container 60d2d2a84cda ---> 497fbf904605 Step 4/11 : RUN apt-get update && apt-get install -y git curl libpng-dev libonig-dev ... Step 7/11 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer ---> e499f74896e3 Step 8/11 : RUN useradd -G www-data,root -u $uid -d /home/$user $user ---> Running in 232ef9c7dbd1 Removing intermediate container 232ef9c7dbd1 ---> 870fa3220ffa Step 9/11 : RUN mkdir -p /home/$user/.composer && chown -R $user:$user /home/$user ---> Running in 7ca8c0cb7f09 Removing intermediate container 7ca8c0cb7f09 ---> 3d2ef9519a8e Step 10/11 : WORKDIR /var/www ---> Running in 4a964f91edfa Removing intermediate container 4a964f91edfa ---> 00ada639da21 Step 11/11 : USER $user ---> Running in 9f8e874fede9 Removing intermediate container 9f8e874fede9 ---> fe176ff4702b Successfully built fe176ff4702b Successfully tagged travellist:latest

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

      Output

      Creating travellist-db ... done Creating travellist-app ... done Creating travellist-nginx ... done

      Так ваши контейнеры будут работать в фоновом режиме. Чтобы вывести информацию о состоянии активных служб, запустите команду:

      Вывод будет выглядеть следующим образом:

      Output

      Name Command State Ports ------------------------------------------------------------------------------- travellist-app docker-php-entrypoint php-fpm Up 9000/tcp travellist-db docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp travellist-nginx nginx -g daemon off; Up 0.0.0.0:8000->80/tcp

      Теперь ваша среда работает, но для завершения настройки приложения нам нужно выполнить еще несколько команд. Вы можете использовать команду docker-compose exec для выполнения команд в контейнерах служб, например ls -l для отображения подробной информации о файлах в директории приложения:

      • docker-compose exec app ls -l

      Output

      total 256 -rw-rw-r-- 1 sammy 1001 738 Jan 15 16:46 Dockerfile -rw-rw-r-- 1 sammy 1001 101 Jan 7 08:05 README.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 app -rwxr-xr-x 1 sammy 1001 1686 Jan 7 08:05 artisan drwxrwxr-x 3 sammy 1001 4096 Jan 7 08:05 bootstrap -rw-rw-r-- 1 sammy 1001 1501 Jan 7 08:05 composer.json -rw-rw-r-- 1 sammy 1001 179071 Jan 7 08:05 composer.lock drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 config drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 database drwxrwxr-x 4 sammy 1001 4096 Jan 15 16:46 docker-compose -rw-rw-r-- 1 sammy 1001 1015 Jan 15 16:45 docker-compose.yml -rw-rw-r-- 1 sammy 1001 1013 Jan 7 08:05 package.json -rw-rw-r-- 1 sammy 1001 1405 Jan 7 08:05 phpunit.xml drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 public -rw-rw-r-- 1 sammy 1001 273 Jan 7 08:05 readme.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 resources drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 routes -rw-rw-r-- 1 sammy 1001 563 Jan 7 08:05 server.php drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 storage drwxrwxr-x 4 sammy 1001 4096 Jan 7 08:05 tests -rw-rw-r-- 1 sammy 1001 538 Jan 7 08:05 webpack.mix.js

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

      • docker-compose exec app composer install

      Вывод будет выглядеть следующим образом:

      Output

      Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 85 installs, 0 updates, 0 removals - Installing doctrine/inflector (1.3.1): Downloading (100%) - Installing doctrine/lexer (1.2.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.4): Downloading (100%) - Installing symfony/polyfill-ctype (v1.13.1): Downloading (100%) - Installing phpoption/phpoption (1.7.2): Downloading (100%) - Installing vlucas/phpdotenv (v3.6.0): Downloading (100%) - Installing symfony/css-selector (v5.0.2): Downloading (100%) … Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: facade/ignition Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.

      В последнюю очередь перед тестированием приложения нам нужно сгенерировать уникальный ключ приложения с помощью artisan, инструмента командной строки Laravel. Этот ключ используется для шифрования пользовательских сеансов и других важных данных:

      • docker-compose exec app php artisan key:generate

      Output

      Application key set successfully.

      Откройте браузер и введите в адресную строку доменное имя вашего сервера или IP-адрес с портом 8000:

      http://server_domain_or_IP:8000
      

      Страница будет выглядеть следующим образом:

      Демонстрационное приложение Laravel

      Вы можете использовать команду logs для проверки журналов, сгенерированных вашими службами:

      • docker-compose logs nginx
      Attaching to travellist-nginx
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:25 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:26 +0000] "GET /favicon.ico HTTP/1.1" 200 0 "http://localhost:8000/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:42 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      …
      

      Если вы хотите приостановить среду Docker Compose, сохраняя состояние всех служб, используйте команду:

      Output

      Pausing travellist-db ... done Pausing travellist-nginx ... done Pausing travellist-app ... done

      Вы можете возобновить работу служб с помощью команды:

      Output

      Unpausing travellist-app ... done Unpausing travellist-nginx ... done Unpausing travellist-db ... done

      Чтобы закрыть среду Docker Compose и удалить все ее контейнеры, сети и тома, используйте команду:

      Output

      Stopping travellist-nginx ... done Stopping travellist-db ... done Stopping travellist-app ... done Removing travellist-nginx ... done Removing travellist-db ... done Removing travellist-app ... done Removing network travellist-laravel-demo_travellist

      Обзор всех команд Docker Compose можно найти в справочном материале по командной строке Docker Compose.

      Заключение

      В этом обучающем руководстве мы настроили среду Docker с тремя контейнерами, используя Docker Compose для определения инфраструктуры в файле YAML.

      Теперь вы можете работать над своим приложением Laravel без необходимости устанавливать и настраивать локальный веб-сервер для целей разработки и тестирования. Вы будете работать в одноразовой среде, которую можно легко дублировать и распространять. Это полезно как на этапе разработки приложения, так и на этапе его использования в производственной среде.



      Source link

      Контейнеризация приложения Ruby on Rails для разработки с помощью Docker Compose


      Введение

      Если вы активно занимаетесь разработкой приложений, использование Docker может упростить ваш рабочий процесс и процесс развертывания вашего приложения в продакшен. Работа с контейнерами в процессе разработки предоставляет следующие преимущества:

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

      Это обучающее руководство поможет вам научиться настраивать среду разработки для приложений Ruby on Rails с использованием Docker. Мы создадим контейнеры для самого приложения, базы данных PostgreSQL, Redis и службы Sidekiq с помощью Docker Compose. Настройка обеспечит следующее:

      • Синхронизацию кода приложения на хосте с кодом в контейнере, чтобы облегчить внесение изменений во время разработки.
      • Сохранение данных приложения между перезапусками контейнера.
      • Настройку рабочих элементов Sidekiq для обработки заданий ожидаемым образом.

      После изучения данного руководства у вас будет рабочее приложение с информацией об акулах, работающее на контейнерах Docker:

      Домашняя директория приложения Sidekiq

      Предварительные требования

      Для данного обучающего руководства вам потребуется следующее:

      Шаг 1 — Клонирование проекта и добавление зависимостей

      Прежде всего мы клонируем репозиторий rails-sidekiq из учетной записи сообщества DigitalOcean на GitHub. Этот репозиторий содержит код установки, описанный в обучающем руководстве Добавление Sidekiq и Redis в приложение Ruby on Rails, где объясняется процедура добавления Sidekiq в существующий проект Rails 5.

      Клонируйте репозиторий в директорию rails-docker:

      • git clone https://github.com/do-community/rails-sidekiq.git rails-docker

      Перейдите в директорию rails-docker:

      В этом обучающем руководстве мы будем использовать базу данных PostgreSQL. Для работы с PostgreSQL вместо SQLite 3 нам потребуется добавить зависимость pg в список зависимостей проекта в файле Gemfile. Откройте этот файл в nano или другом текстовом редакторе по вашему выбору:

      Добавьте зависимость в любое место в списке основных зависимостей проекта (над зависимостями разработки):

      ~/rails-docker/Gemfile

      . . .
      # Reduces boot times through caching; required in config/boot.rb
      gem 'bootsnap', '>= 1.1.0', require: false
      gem 'sidekiq', '~>6.0.0'
      gem 'pg', '~>1.1.3'
      
      group :development, :test do
      . . .
      

      Также можно выделить зависимость sqlite как комментарий, поскольку мы больше не будем ее использовать:

      ~/rails-docker/Gemfile

      . . .
      # Use sqlite3 as the database for Active Record
      # gem 'sqlite3'
      . . .
      

      Наконец, мы превратим в комментарий зависимость spring-watcher-listen в разделе development:

      ~/rails-docker/Gemfile

      . . .
      gem 'spring'
      # gem 'spring-watcher-listen', '~> 2.0.0'
      . . .
      

      Если мы не отключим эту зависимость, мы будем постоянно получать сообщения об ошибке при доступе к консоли Rails. Эти сообщения об ошибке связаны с тем, что с этой зависимостью Rails использует listen для отслеживания изменений разработки, а не запрашивает изменения в файловой системе. Поскольку эта зависимость следит за корневой директорией проекта, включая директорию node_modules, она выводит сообщения об ошибке, связанные с отслеживаемыми директориями, засоряя ими консоль. Если вам требуется экономить ресурсы процессора, отключение этой зависимости может вам не подойти. В этом случае имеет смысл обновить приложение Rails до версии Rails 6.

      Сохраните и закройте файл после завершения редактирования.

      Мы подготовили репозиторий проекта, добавили зависимость pg в файл Gemfile и выделили зависимость spring-watcher-listen как комментарий. Теперь мы можем перейти к настройке приложения для работы с PostgreSQL.

      Шаг 2 — Настройка приложения для работы с PostgreSQL и Redis

      Для работы с PostgreSQL и Redis во время разработки нам потребуется следующее:

      • Настройте приложение для работы с PostgreSQL как с адаптером по умолчанию.
      • Добавьте в проект файл .env с именем пользователя и паролем базы данных и хостом Redis.
      • Создайте скрипт init.sql для создания пользователя sammy для базы данных.
      • Добавьте инициализатор Sidekiq для обеспечения работы со службой redis в контейнере.
      • Добавьте файл .env и другие требуемые файлы в файлы gitignore и dockerignore нашего проекта.
      • Создайте исходные записи базы данных, чтобы у нашего приложения были записи, с которыми мы можем работать при запуске.

      Откройте файл конфигурации базы данных в директории config/database.yml:

      В этом файле содержатся следующие параметры по умолчанию, которые применяются при отсутствии других настроек:

      ~/rails-docker/config/database.yml

      default: &default
        adapter: sqlite3
        pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
        timeout: 5000
      

      Нам нужно изменить их и указать, что мы используем адаптер postgresql, поскольку мы будем создавать службу PostgreSQL с помощью Docker Compose для постоянного хранения данных нашего приложения.

      Удалите код, задающий SQLite в качестве адаптера, и замените его следующими настройками, которые будут задавать адаптер и другие переменные, необходимые для подключения:

      ~/rails-docker/config/database.yml

      default: &default
        adapter: postgresql
        encoding: unicode
        database: <%= ENV['DATABASE_NAME'] %>
        username: <%= ENV['DATABASE_USER'] %>
        password: <%= ENV['DATABASE_PASSWORD'] %>
        port: <%= ENV['DATABASE_PORT'] || '5432' %>
        host: <%= ENV['DATABASE_HOST'] %>
        pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
        timeout: 5000
      . . .
      

      Затем мы изменим настройки среды development, которую будем использовать в данном примере.

      Удалите существующую конфигурацию базы данных SQLite, чтобы раздел выглядел следующим образом:

      ~/rails-docker/config/database.yml

      . . .
      development:
        <<: *default
      . . .
      

      Удалите параметры database для сред production и test:

      ~/rails-docker/config/database.yml

      . . .
      test:
        <<: *default
      
      production:
        <<: *default
      . . .
      

      Эти изменения параметров базы данных по умолчанию помогут нам динамически настраивать информацию базы данных с помощью переменных среды из файлов .env, для которых не будет применяться контроль версий.

      Сохраните и закройте файл после завершения редактирования.

      Следует отметить, что при создании проекта Rails с нуля можно задать адаптер с помощью команды rails new, как описано в шаге 3 обучающего руководства Использование PostgreSQL с приложением Ruby on Rails в Ubuntu 18.04. Эта команда задаст адаптер в файле config/database.yml и автоматически добавит зависимость pg в наш проект.

      Мы уже разместили ссылку на переменные среды, и теперь нам нужно создать для них файл с предпочитаемыми настройками. Такое извлечение параметров конфигурации является частью 12-факторного подхода к разработке приложений, который определяет лучшие практики обеспечения надежности приложений в распределенных средах. При будущей настройке рабочей среды и среды тестирования для изменения настроек базы данных потребуется создание дополнительных файлов .env и размещение ссылок на соответствующие файлы в наших файлах Docker Compose.

      Откройте файл .env:

      Добавьте в файл следующие значения:

      ~/rails-docker/.env

      DATABASE_NAME=rails_development
      DATABASE_USER=sammy
      DATABASE_PASSWORD=shark
      DATABASE_HOST=database
      REDIS_HOST=redis
      

      В дополнение к настройке имени базы данных, имени пользователя и пароля мы также зададим значение DATABASE_HOST. Значение database относится к службе database PostgreSQL, которую мы создадим с помощью Docker Compose. Также мы задаем REDIS_HOST для определения службы redis.

      Сохраните и закройте файл после завершения редактирования.

      Чтобы создать пользователя базы данных sammy, мы можем написать скрипт init.sql для последующего монтирования в контейнер базы данных при его запуске.

      Откройте файл скрипта:

      Добавьте следующий код для создания пользователя sammy с привилегиями администратора:

      ~/rails-docker/init.sql

      CREATE USER sammy;
      ALTER USER sammy WITH SUPERUSER;
      

      Этот скрипт создаст соответствующего пользователя базы данных и предоставит ему привилегии администратора.

      Задайте в скрипте подходящие разрешения:

      Далее мы настроим Sidekiq для работы с нашей службой redis в контейнере. Мы можем добавить инициализатор в директорию config/initializers, где Rails ищет параметры конфигурации после загрузки структур и надстроек. Этот инициализатор будет задавать значение для хоста Redis.

      Откройте файл sidekiq.rb для указания этих настроек:

      • nano config/initializers/sidekiq.rb

      Добавьте в файл следующий код, чтобы задать значения REDIS_HOST и REDIS_PORT:

      ~/rails-docker/config/initializers/sidekiq.rb

      Sidekiq.configure_server do |config|
        config.redis = {
          host: ENV['REDIS_HOST'],
          port: ENV['REDIS_PORT'] || '6379'
        }
      end
      
      Sidekiq.configure_client do |config|
        config.redis = {
          host: ENV['REDIS_HOST'],
          port: ENV['REDIS_PORT'] || '6379'
        }
      end
      

      Как и наши параметры конфигурации базы данных, они позволяют выполнять динамическую настройку параметров хоста и портов, заменяя соответствующие значения во время исполнения без изменения кода приложения. В дополнение к REDIS_HOST мы задаем значение по умолчанию для REDIS_PORT, если оно больше нигде не задано.

      Сохраните и закройте файл после завершения редактирования.

      Чтобы важные данные приложения не копировались в систему контроля версий, мы можем добавить .env в файл .gitignore нашего проекта, указывая Git, какие файлы нашего проекта нужно игнорировать. Откройте файл для редактирования:

      Добавьте в конце файла запись для .env:

      ~/rails-docker/.gitignore

      yarn-debug.log*
      .yarn-integrity
      .env
      

      Сохраните и закройте файл после завершения редактирования.

      Далее мы создадим файл .dockerignore и зададим, что не следует копировать в наши контейнеры. Откройте файл для редактирования:

      Добавьте в файл следующий код, предписывающий Docker игнорировать элементы, которые мы не хотим копировать в наши контейнеры:

      ~/rails-docker/.dockerignore

      .DS_Store
      .bin
      .git
      .gitignore
      .bundleignore
      .bundle
      .byebug_history
      .rspec
      tmp
      log
      test
      config/deploy
      public/packs
      public/packs-test
      node_modules
      yarn-error.log
      coverage/
      

      Добавьте .env в конец файла:

      ~/rails-docker/.dockerignore

      . . .
      yarn-error.log
      coverage/
      .env
      

      Сохраните и закройте файл после завершения редактирования.

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

      Откройте файл для исходных данных в директории db:

      Добавьте в файл следующий код для создания четырех демонстрационных акул и одного образца сообщения:

      ~/rails-docker/db/seeds.rb

      # Adding demo sharks
      sharks = Shark.create([{ name: 'Great White', facts: 'Scary' }, { name: 'Megalodon', facts: 'Ancient' }, { name: 'Hammerhead', facts: 'Hammer-like' }, { name: 'Speartooth', facts: 'Endangered' }])
      Post.create(body: 'These sharks are misunderstood', shark: sharks.first)
      

      Исходные данные создадут четыре акулы и одно сообщение, связанное с первой акулой.

      Сохраните и закройте файл после завершения редактирования.

      Мы настроили приложение для работы с PostgreSQL и создали переменные среды. Теперь мы готовы к написанию файла Dockerfile для нашего приложения.

      Шаг 3 — Написание файла Dockerfile и скриптов точек входа

      Файл Dockerfile указывает, что будет включено в контейнер приложения при его создании. Использование Dockerfile позволяет определить среду контейнера и избежать расхождений с зависимостями и версиями модуля исполнения.

      Следуя этим указаниям по построению оптимизированных контейнеров, мы сделаем наш образ максимально эффективным, используя базовый образ Alpine и постаравшись минимизировать количество уровней образа.

      Откройте файл Dockerfile в текущей директории:

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

      Добавьте в файл следующий код, чтобы добавить образ Ruby alpine как базовый:

      ~/rails-docker/Dockerfile

      FROM ruby:2.5.1-alpine
      

      Образ alpine является производным проекта Alpine Linux, и это помогает уменьшить размер образа. Дополнительную информацию о том, подходит ли образ alpine для вашего проекта, можно найти в обсуждении в разделе Image Variants на странице образа Ruby на Docker Hub.

      При использовании alpine для разработки нужно учитывать ряд факторов:

      • Уменьшение размера образа ускорит загрузку страниц и ресурсов, особенно если вы хотите сохранить объемы минимальными. Это поможет ускорить пользовательский интерфейс при разработке и сделает его ближе к тому, как он бы выглядел при локальной работе в среде без контейнеров.
      • Паритет между образами для разработки и рабочими образами упрощает успешное развертывание. Поскольку команды разработчиков часто используют образы Alpine для ускорения работы, использование базового образа Alpine поможет компенсировать проблемы при переносе в рабочую среду.

      Далем мы зададим переменную среды, которая будет указывать версию Bundler:

      ~/rails-docker/Dockerfile

      . . .
      ENV BUNDLER_VERSION=2.0.2
      

      Это один из шагов, которые мы предпримем для предотвращения конфликтов версий между версией bundler по умолчанию в нашей среде и кодом нашего приложения, для которого требуется версия Bundler 2.0.2.

      Затем следует добавить в Dockerfile пакеты, необходимые для работы с приложением:

      ~/rails-docker/Dockerfile

      . . .
      RUN apk add --update --no-cache 
            binutils-gold 
            build-base 
            curl 
            file 
            g++ 
            gcc 
            git 
            less 
            libstdc++ 
            libffi-dev 
            libc-dev 
            linux-headers 
            libxml2-dev 
            libxslt-dev 
            libgcrypt-dev 
            make 
            netcat-openbsd 
            nodejs 
            openssl 
            pkgconfig 
            postgresql-dev 
            python 
            tzdata 
            yarn
      

      Это пакеты nodejs, yarn и другие. Поскольку наше приложение обслуживает ресурсы с помощью webpack, нам нужно добавить пакеты Node.js и Yarn для надлежащей работы приложения.

      Следует помнить, что образ alpine является минимальным: мы перечислили не все пакеты, которые вам можно или нужно будет установить в среде разработки при контейнеризации вашего приложения.

      Далее следует установить подходящую версию bundler:

      ~/rails-docker/Dockerfile

      . . .
      RUN gem install bundler -v 2.0.2
      

      Этот шаг гарантирует паритет контейнеризованной среды и спецификаций в файле Gemfile.lock нашего проекта.

      Теперь настройте рабочую директорию для приложения на контейнере:

      ~/rails-docker/Dockerfile

      . . .
      WORKDIR /app
      

      Скопируйте файлы Gemfile и Gemfile.lock:

      ~/rails-docker/Dockerfile

      . . .
      COPY Gemfile Gemfile.lock ./
      

      Копирование этих файлов — отдельный шаг, после которого выполняется команда bundle install. Благодаря этому зависимости проекта не нужно будет воссоздавать каждый раз при внесении изменений в код приложения. Это работает в сочетании с объемом зависимостей, который мы включаем в наш файл Compose, монтирующий зависимости в контейнер приложений, когда производится воссоздание службы, но зависимости остаются без изменений.

      Далее мы установим параметры конфигурации для сборки зависимостей nokogiri:

      ~/rails-docker/Dockerfile

      . . .
      RUN bundle config build.nokogiri --use-system-libraries
      . . .
      

      На этом шаге выполняется сборка nokogiri с версиями библиотек libxml2 и libxslt, которые мы добавили в контейнер приложений на шаге RUN apk add… выше.

      Далее мы установим зависимости проекта:

      ~/rails-docker/Dockerfile

      . . .
      RUN bundle check || bundle install
      

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

      Далее мы повторяем эту же процедуру для пакетов и зависимостей JavaScript. Сначала мы копируем метаданные пакета, затем устанавливаем зависимости, а в заключение копируем код приложения в образ контейнера.

      Чтобы начать работать с разделом Javascript в нашем файле Dockerfile, нужно скопировать файлы package.json и yarn.lock из текущей директории проекта на хосте в контейнер:

      ~/rails-docker/Dockerfile

      . . .
      COPY package.json yarn.lock ./
      

      Затем мы установим требуемые пакеты с помощью команды yarn install:

      ~/rails-docker/Dockerfile

      . . .
      RUN yarn install --check-files
      

      Эта команда включает флаг --check-files с командой yarn так, чтобы ранее установленные файлы не удалялись. Как и в случае с зависимостями, мы будем управлять состоянием постоянного хранения пакетов в директории node_modules при написании файла Compose.

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

      ~/rails-docker/Dockerfile

      . . .
      COPY . ./
      
      ENTRYPOINT ["./entrypoints/docker-entrypoint.sh"]
      

      Использование скрипта точки входа позволяет запускать контейнер как исполняемый файл.

      Итоговый файл Dockerfile будет выглядеть следующим образом:

      ~/rails-docker/Dockerfile

      FROM ruby:2.5.1-alpine
      
      ENV BUNDLER_VERSION=2.0.2
      
      RUN apk add --update --no-cache 
            binutils-gold 
            build-base 
            curl 
            file 
            g++ 
            gcc 
            git 
            less 
            libstdc++ 
            libffi-dev 
            libc-dev 
            linux-headers 
            libxml2-dev 
            libxslt-dev 
            libgcrypt-dev 
            make 
            netcat-openbsd 
            nodejs 
            openssl 
            pkgconfig 
            postgresql-dev 
            python 
            tzdata 
            yarn
      
      RUN gem install bundler -v 2.0.2
      
      WORKDIR /app
      
      COPY Gemfile Gemfile.lock ./
      
      RUN bundle config build.nokogiri --use-system-libraries
      
      RUN bundle check || bundle install
      
      COPY package.json yarn.lock ./
      
      RUN yarn install --check-files
      
      COPY . ./
      
      ENTRYPOINT ["./entrypoints/docker-entrypoint.sh"]
      

      Сохраните и закройте файл после завершения редактирования.

      Далее мы создадим директорию entrypoints для скриптов точек входа:

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

      Откройте файл скрипта точки входа приложения:

      • nano entrypoints/docker-entrypoint.sh

      Добавьте в файл следующий код:

      rails-docker/entrypoints/docker-entrypoint.sh

      #!/bin/sh
      
      set -e
      
      if [ -f tmp/pids/server.pid ]; then
        rm tmp/pids/server.pid
      fi
      
      bundle exec rails s -b 0.0.0.0
      

      Первая важная строка — это строка set -e, которая предписывает оболочке выполнения скрипта /bin/sh быстро прекратить работу при обнаружении любых проблем со скриптом. Далее скрипт проверяет отсутствие tmp/pids/server.pid для предотвращения конфликтов с сервером при запуске приложения. В заключение скрипт запускает сервер Rails с помощью команды bundle exec rails s. Мы используем с этой командой опцию -b для привязки сервера ко всем IP-адресам, а не только к адресу localhost по умолчанию. При таком вызове сервер Rails перенаправляет входящие запросы на IP-адрес контейнера, а не на адрес localhost по умолчанию.

      Сохраните и закройте файл после завершения редактирования.

      Создайте исполняемый скрипт:

      • chmod +x entrypoints/docker-entrypoint.sh

      Далее мы создадим скрипт для запуска службы sidekiq, который будет обрабатывать наши задания Sidekiq. Дополнительную информацию об использовании Sidekiq в этом приложении можно найти в обучающем руководстве Добавление Sidekiq и Redis в приложение Ruby on Rails.

      Откройте файл скрипта точки входа Sidekiq:

      • nano entrypoints/sidekiq-entrypoint.sh

      Добавьте в файл следующий код для запуска Sidekiq:

      ~/rails-docker/entrypoints/sidekiq-entrypoint.sh

      #!/bin/sh
      
      set -e
      
      if [ -f tmp/pids/server.pid ]; then
        rm tmp/pids/server.pid
      fi
      
      bundle exec sidekiq
      

      Этот скрипт запускает Sidekiq в контексте комплекта нашего приложения.

      Сохраните и закройте файл после завершения редактирования. Сделайте его исполняемым:

      • chmod +x entrypoints/sidekiq-entrypoint.sh

      Со скриптами точки входа и файлом Dockerfile мы готовы определять службы в файле Compose.

      Шаг 4 — Настройка служб с помощью Docker Compose

      С помощью Docker Compose мы сможем запустить несколько контейнеров, необходимых для нашего приложения. Мы определим службы Compose в основном файле docker-compose.yml. Служба в Compose — это запущенный контейнер, а определения служб, которые вы будете добавлять в ваш файл docker-compose.yml, содержат информацию о том, как будет запускаться образ каждого контейнера. Compose позволяет вам определять различные службы для создания приложений с несколькими контейнерами.

      Установка приложения предусматривает следующие службы:

      • Само приложение
      • База данных PostgreSQL
      • Redis
      • Sidekiq

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

      Обратите внимание, что мы не определяем службу test, поскольку в настоящем обучающем руководстве и серии материалов не описывается тестирование, но вы можете использовать для ее определения процедуру, аналогичную приведенной здесь для службы sidekiq.

      Откройте файл docker-compose.yml:

      Добавьте определение службы приложения:

      ~/rails-docker/docker-compose.yml

      version: '3.4'
      
      services:
        app:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - database
            - redis
          ports:
            - "3000:3000"
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
      

      Определение службы приложения включает следующие параметры:

      • build: это определение параметров конфигурации, включая context и dockerfile, которые будут применяться при создании образа приложения Compose. Если вы хотите использовать существующий образ из реестра, например, из Docker Hub, вы можете использовать инструкцию image с информацией об имени пользователя, репозитория и теге образа.
      • context: это определение контекста сборки для сборки образа, в этом случае текущая директория проекта.
      • dockerfile: данный параметр определяет Dockerfile в текущей директории проекта в качестве файла, который Compose будет использовать для сборки образа приложения.
      • depends_on: настраивает контейнеры database и redis первыми, чтобы они запускались до приложения.
      • ports: сопоставляет порт 3000 хоста с портом 3000 контейнера.
      • volumes: мы используем два типа монтирования:
        • Первый тип — это связанное монтирование, которое подразумевает монтирование кода приложения на хост в директорию /app в контейнере. Это упрощает быструю разработку, поскольку любые изменения, которые вы вносите в код хоста, будут немедленно добавлены в контейнер.
        • Второй тип — это том с именем gem_cache. При запуске команды bundle install в контейнере она устанавливает зависимости проекта. Добавление этого тома означает, что при воссоздании контейнера зависимости монтируются в новый контейнер. Такое монтирование предполагает отсутствие изменений в проекте, поэтому если вы вносите изменения в зависимости проекта во время разработки, этот том нужно удалить до воссоздания службы приложения.
        • Третий том — это том с именем для директории node_modules. Поскольку монтирование node_modules на хост может привести к расхождениям с пакетом и конфликтам при разработке, этот том обеспечивает постоянство пакетов в данной директории и их соответствие текущему состоянию проекта. Если вы измените зависимости Node проекта, этот том нужно удалить и воссоздать.
      • env_file: указывает Compose, что мы хотим добавить переменные среды из файла .env в контексте сборки.
      • environment: данная опция позволяет установить некритичную переменную среды, передавая информацию о среде Rails в контейнер.

      Добавьте под определением службы app следующий код для определения службы database:

      ~/rails-docker/docker-compose.yml

      . . .
        database:
          image: postgres:12.1
          volumes:
            - db_data:/var/lib/postgresql/data
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      

      В отличие от службы app, служба database извлекает образ postgres непосредственно из Docker Hub. Здесь мы также закрепляем версию, а не устанавливаем последнюю версию и не указываем конкретную версию (по умолчанию — последнюю). Так мы обеспечим работу этой системы с указанными здесь версиями и сможем избежать непредвиденных сюрпризов при нарушениях изменений кода образа.

      Также мы добавляем здесь том db_data, который сохраняет в постоянном виде данные приложения в промежутках между запуском контейнеров. Также мы смонтировали скрипт пуска init.sql в соответствующую директорию контейнера docker-entrypoint-initdb.d/ для создания нашего пользователя базы данных sammy. Когда точка входа образа создает пользователя и базу данных postgres по умолчанию, она выполняет все скрипты из директории docker-entrypoint-initdb.d/, которые можно использовать для выполнения необходимых задач по инициализации. Более подробную информацию можно найти в разделе Скрипты инициализации в документации по образам PostgreSQL.

      Затем следует добавить определение службы redis:

      ~/rails-docker/docker-compose.yml

      . . .
        redis:
          image: redis:5.0.7
      

      Как и служба database, служба redis использует образ из Docker Hub. В этом случае мы не сохраняем кэш заданий Sidekiq.

      В заключение следует добавить определение службы sidekiq:

      ~/rails-docker/docker-compose.yml

      . . .
        sidekiq:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - app      
            - database
            - redis
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
          entrypoint: ./entrypoints/sidekiq-entrypoint.sh
      

      Наша служба sidekiq напоминает службу app в некоторых отношениях. В частности, она использует тот же контекст сборки и образ, те же переменные среды и тома. Однако она зависит от служб app, redis и database и поэтому запускается в последнюю очередь. Кроме того, она использует точку входа, заменяющую заданную в Dockerfile. Этот параметр точки входа указывает на файл entrypoints/sidekiq-entrypoint.sh, который включает команду для запуска службы sidekiq.

      На заключительном шаге мы добавим определения томов под определением службы sidekiq:

      ~/rails-docker/docker-compose.yml

      . . .
      volumes:
        gem_cache:
        db_data:
        node_modules:
      

      Наш ключ томов верхнего уровня определяет тома gem_cache, db_data и node_modules. Когда Docker создает тома, содержимое тома сохраняется в части файловой системы хоста, /var/lib/docker/volumes/, а данным процессом управляет Docker. Содержимое каждого тома сохраняется в директории /var/lib/docker/volumes/ и монтируется в любой контейнер, который использует том. Таким образом, данные информации об акулах, которые будут добавлять наши пользователи, будут сохраняться в томе db_data даже при удалении и последующем восстановлении службы database.

      Итоговый файл будет выглядеть примерно так:

      ~/rails-docker/docker-compose.yml

      version: '3.4'
      
      services:
        app:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:     
            - database
            - redis
          ports:
            - "3000:3000"
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
      
        database:
          image: postgres:12.1
          volumes:
            - db_data:/var/lib/postgresql/data
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      
        redis:
          image: redis:5.0.7
      
        sidekiq:
          build:
            context: .
            dockerfile: Dockerfile
          depends_on:
            - app      
            - database
            - redis
          volumes:
            - .:/app
            - gem_cache:/usr/local/bundle/gems
            - node_modules:/app/node_modules
          env_file: .env
          environment:
            RAILS_ENV: development
          entrypoint: ./entrypoints/sidekiq-entrypoint.sh
      
      volumes:
        gem_cache:
        db_data:
        node_modules:     
      

      Сохраните и закройте файл после завершения редактирования.

      Когда ваши определения службы будут записаны, вы сможете запустить приложение.

      Шаг 5 — Тестирование приложения

      Имея в распоряжении файл docker-compose.yml, вы можете создать ваши службы с помощью команды docker-compose up и создать исходные записи базы данных. Также вы можете проверить сохранение данных, останавливая работу контейнеров и удаляя их с помощью docker-compose down, а затем воссоздавая их.

      Во-первых, необходимо выполнить сборку образов и создать службы, запустив docker-compose up с флагом -d, который будет запускать контейнеры в фоновом режиме:

      Вы увидите сообщение, подтверждающее успешное создание служб:

      Output

      Creating rails-docker_database_1 ... done Creating rails-docker_redis_1 ... done Creating rails-docker_app_1 ... done Creating rails-docker_sidekiq_1 ... done

      Также вы можете получить более подробную информацию о процессах запуска, отобразив вывод журнала из служб:

      Если запуск был выполнен корректно, вы должны увидеть примерно следующее:

      Output

      sidekiq_1 | 2019-12-19T15:05:26.365Z pid=6 tid=grk7r6xly INFO: Booting Sidekiq 6.0.3 with redis options {:host=>"redis", :port=>"6379", :id=>"Sidekiq-server-PID-6", :url=>nil} sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-musl] sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: See LICENSE and the LGPL-3.0 for licensing details. sidekiq_1 | 2019-12-19T15:05:31.097Z pid=6 tid=grk7r6xly INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org app_1 | => Booting Puma app_1 | => Rails 5.2.3 application starting in development app_1 | => Run `rails server -h` for more startup options app_1 | Puma starting in single mode... app_1 | * Version 3.12.1 (ruby 2.5.1-p57), codename: Llamas in Pajamas app_1 | * Min threads: 5, max threads: 5 app_1 | * Environment: development app_1 | * Listening on tcp://0.0.0.0:3000 app_1 | Use Ctrl-C to stop . . . database_1 | PostgreSQL init process complete; ready for start up. database_1 | database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: starting PostgreSQL 12.1 (Debian 12.1-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 database_1 | 2019-12-19 15:05:20.160 UTC [1] LOG: listening on IPv6 address "::", port 5432 database_1 | 2019-12-19 15:05:20.163 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" database_1 | 2019-12-19 15:05:20.182 UTC [63] LOG: database system was shut down at 2019-12-19 15:05:20 UTC database_1 | 2019-12-19 15:05:20.187 UTC [1] LOG: database system is ready to accept connections . . . redis_1 | 1:M 19 Dec 2019 15:05:18.822 * Ready to accept connections

      Также вы можете проверить состояние ваших контейнеров с помощью docker-compose ps:

      Вы получите вывод, указывающий, что ваши контейнеры запущены:

      Output

      Name Command State Ports ----------------------------------------------------------------------------------------- rails-docker_app_1 ./entrypoints/docker-resta ... Up 0.0.0.0:3000->3000/tcp rails-docker_database_1 docker-entrypoint.sh postgres Up 5432/tcp rails-docker_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp rails-docker_sidekiq_1 ./entrypoints/sidekiq-entr ... Up

      Далее создайте базу данных, создайте для нее исходные записи и выполните миграцию с помощью следующей команды docker-compose exec:

      • docker-compose exec app bundle exec rake db:setup db:migrate

      Команда docker-compose exec позволяет запускать команды в службах. Здесь мы используем ее для запуска команд rake db:setup и db:migrate в контексте нашего приложения для создания базы данных, создания исходных записей и проведения миграции. Команда docker-compose exec полезна для проведения миграции с базой данных, используемой для разработки.

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

      Output

      Created database 'rails_development' Database 'rails_development' already exists -- enable_extension("plpgsql") -> 0.0140s -- create_table("endangereds", {:force=>:cascade}) -> 0.0097s -- create_table("posts", {:force=>:cascade}) -> 0.0108s -- create_table("sharks", {:force=>:cascade}) -> 0.0050s -- enable_extension("plpgsql") -> 0.0173s -- create_table("endangereds", {:force=>:cascade}) -> 0.0088s -- create_table("posts", {:force=>:cascade}) -> 0.0128s -- create_table("sharks", {:force=>:cascade}) -> 0.0072s

      Оставив службы работать, откройте в браузере адрес localhost:3000 или http://your_server_ip:3000. Вы увидите стартовую страницу, которая будет выглядеть примерно так:

      Домашняя директория приложения Sidekiq

      Теперь мы можем протестировать сохранение данных. Создайте новую акулу, нажав кнопку Get Shark Info, после чего откроется путь sharks/index:

      Страница указателя акул с исходными данными

      Чтобы убедиться в работе приложения, добавим в него примеры данных. Нажмите New Shark. Вам будет предложено ввести имя пользователя (sammy) и пароль (shark) в связи с параметрами аутентификации проекта.

      На странице New Shark введите Mako в поле Name и Fast в поле Facts.

      Нажмите кнопку Create Shark для создания акулы. После создания акулы нажмите Home на панели навигации сайта, чтобы вернуться на главную страницу приложения. Теперь мы можем протестировать работу Sidekiq.

      Нажмите кнопку Which Sharks Are in Danger? Поскольку вы еще не выгрузили акул, находящихся под угрозой вымирания, сейчас откроется экран endangered index:

      Экран Endangered Index

      Нажмите Import Endangered Sharks для импорта акул. Вы увидите сообщение о состоянии, где будет указано, что акулы были импортированы:

      Начало импорта

      Также вы увидите начало импорта. Обновите страницу, чтобы увидеть таблицу целиком:

      Обновление таблицы

      Благодаря Sidekiq пакетная выгрузка акул, находящихся под угрозой вымирания, была проведена успешно без блокировки браузера и без помех для работы других приложений.

      Нажмите кнопку Home внизу страницы, чтобы вернуться на главную страницу приложения:

      Домашняя директория приложения Sidekiq

      Далее нажмите Which Sharks Are in Danger? еще раз. Вы снова увидите список выгруженных акул.

      Теперь мы знаем, что наше приложение работает хорошо и можем протестировать сохранение данных.

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

      Обратите внимание, что мы не используем параметр --volumes и поэтому наш том db_data не удаляется.

      Следующая информация подтверждает, что ваши контейнеры и сеть были удалены:

      Output

      Stopping rails-docker_sidekiq_1 ... done Stopping rails-docker_app_1 ... done Stopping rails-docker_database_1 ... done Stopping rails-docker_redis_1 ... done Removing rails-docker_sidekiq_1 ... done Removing rails-docker_app_1 ... done Removing rails-docker_database_1 ... done Removing rails-docker_redis_1 ... done Removing network rails-docker_default

      Повторно создайте контейнеры:

      Откройте консоль Rails в контейнере app с помощью команд docker-compose exec и bundle exec rails console:

      • docker-compose exec app bundle exec rails console

      Проверьте в командной строке последнюю запись Shark в базе данных:

      Вы увидите только что созданную запись:

      IRB session

      Shark Load (1.0ms) SELECT "sharks".* FROM "sharks" ORDER BY "sharks"."id" DESC LIMIT $1 [["LIMIT", 1]] => "#<Shark id: 5, name: "Mako", facts: "Fast", created_at: "2019-12-20 14:03:28", updated_at: "2019-12-20 14:03:28">"

      Вы можете проверить сохранение акул из списка Endangered с помощью следующей команды:

      IRB session

      (0.8ms) SELECT COUNT(*) FROM "endangereds" => 73

      Ваш том db_data был успешно смонтирован в воссозданную службу database, благодаря чему служба app смогла получить доступ к сохраненным данным. Если вы перейдете напрямую на страницу index shark по адресу localhost:3000/sharks или http://your_server_ip:3000/sharks, вы также увидите эту запись:

      Страница Sharks Index с Mako

      Находящиеся под угрозой исчезновения виды акул также можно будет посмотреть на экране localhost:3000/endangered/data или http://your_server_ip:3000/endangered/data:

      Обновление таблицы

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

      Заключение

      В этом обучающем руководстве вы создали систему разработки приложений Rails с использованием контейнеров Docker. Вы сделали проект более модульным и портативным посредством извлечения важной информации и отсоединения состояния приложения от кода. Вы также настроили шаблонный файл docker-compose.yml, который вы можете изменять в зависимости от потребностей разработки и изменений требований.

      В процессе разработки вы можете захотеть узнать больше о проектировании приложений для контейнеризованных рабочих процессов и процессов Cloud Native. Дополнительную информацию по этим темам вы можете посмотреть в статьях Разработка архитектуры приложений для Kubernetes и Модернизация приложений для Kubernetes. Если вы предпочитаете пройти обучение по Kubernetes, ознакомьтесь с нашим учебным планом по Kubernetes для комплексной разработки.

      Чтобы узнать больше о коде приложения, ознакомьтесь с другими обучающими руководствами этой серии:



      Source link

      Контейнеризация приложения Node.js для разработки с использованием Docker Compose


      Введение

      Если вы активно занимаетесь разработкой приложений, использование Docker может упростить ваш рабочий процесс и процесс развертывания вашего приложения в продакшен. Работа с контейнерами в процессе разработки предоставляет следующие преимущества:

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

      Из этого руководства вы узнаете, как выполнять настройку среды разработки для приложения Node.js с помощью Docker. Вы создадите два контейнера, один для приложения Node, а другой для базы данных MongoDB с помощью Docker Compose. Поскольку это приложение работает с Node и MongoDB, необходимо выполнить следующие настройки:

      • Синхронизируйте код приложения на хосте с кодом в контейнере, чтобы облегчить внесение изменений во время разработки.
      • Убедитесь, что изменения, вносимые в код приложения, используются без необходимости перезапуска.
      • Создайте пользователя и базу данных с защитой паролем для хранения данных приложения.
      • Сохраните данные.

      После изучения данного руководства у вас будет рабочее приложение с информацией об акулах, работающее на контейнерах Docker:

      Полное собрание акул

      Предварительные требования

      Для данного обучающего модуля вам потребуется следующее:

      Шаг 1 — Клонирование проекта и изменение зависимостей

      Первым, что необходимо сделать при настройке системы, является клонирование кода проекта и изменение файла package.json, который включает зависимости проекта. Мы добавим nodemon в devDependencies проекта, чтобы указать, что мы будем использовать его при разработке. Запуск приложения с nodemon обеспечивает автоматический перезапуск при внесении изменений в ваш код.

      Во-первых, клонируйте репозиторий nodejs-mongo-mongoose из учетной записи DigitalOcean Community на GitHub. Этот репозиторий включает код настройки, описанной в руководстве Интеграция MongoDB с вашим приложением Node, которое содержит описание процесса интеграции базы данных MongoDB с существующим приложением Node с помощью Mongoose.

      Клонируйте репозиторий в директорию с именем node_project:

      • git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project

      Перейдите в директорию node_project:

      Откройте файл проекта package.json с помощью nano или вашего любимого редактора:

      После зависимостей проекта и над закрывающей фигурной скобкой создайте новый объект devDependencies, который включает nodemon:

      ~/node_project/package.json

      ...
      "dependencies": {
          "ejs": "^2.6.1",
          "express": "^4.16.4",
          "mongoose": "^5.4.10"
        },
        "devDependencies": {
          "nodemon": "^1.18.10"
        }    
      }
      

      Сохраните и закройте файл после завершения редактирования.

      После добавления кода проекта и изменения зависимостей вы можете перейти к рефакторингу кода для контейнеризованного рабочего процесса.

      Шаг 2 — Настройка вашего приложения для работы с контейнерами

      Изменение нашего приложения для контейнеризованного рабочего процесса подразумевает, что код должен быть более модульным. Контейнеры обеспечивают возможность перемещения между рабочими средами, и наш код должен отражать это, оставаясь как можно менее привязанным к лежащей в основе операционной системе. Для этого мы выполним рефакторинг нашего кода, чтобы добиться более эффективного использования свойства Node process.env, которое возвращает объект с информацией о среде пользователя при запуске. Мы можем использовать этот объект в нашем коде для динамического сохранения информации о конфигурации при запуске в переменных среды.

      Давайте начнем с app.js, главной точки входа нашего приложения. Откройте файл:

      Внутри файла вы увидите определение для константы port, а также функцию listen, которая использует эту константу для указания порта, который будет прослушивать приложение:

      ~/home/node_project/app.js

      ...
      const port = 8080;
      ...
      app.listen(port, function () {
        console.log('Example app listening on port 8080!');
      });
      

      Давайте изменим константу port, чтобы разрешить динамическое присвоение при запуске, с помощью объекта process.env. Внесите следующие изменения в определение константы и функцию listen:

      ~/home/node_project/app.js

      ...
      const port = process.env.PORT || 8080;
      ...
      app.listen(port, function () {
        console.log(`Example app listening on ${port}!`);
      });
      

      Наше новое определение константы присваивает port динамически с помощью значения, передаваемого при запуске, или 8080. Аналогично мы переписали функцию listen для использования шаблонного литерала, который будет интерполировать значение порта при прослушивании соединений. Поскольку мы будем выполнять маппинг портов где-либо еще, эти изменения позволят нам избежать необходимости постоянно пересматривать этот файл при внесении изменений в нашу среду.

      После завершения редактирования сохраните и закройте файл.

      Затем нам нужно изменить информацию о подключении базы данных, чтобы удалить любые учетные данные конфигурации. Откройте файл db.js, содержащий следующую информацию:

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

      • Импортирует Mongoose, Object Document Mapper (ODM), который мы используем для создания схем и моделей данных нашего приложения.
      • Устанавливает учетные данные базы данных в качестве констант, включая имя пользователя и пароль.
      • Подключается к базе данных с помощью метода mongoose.connect.

      Дополнительную информацию о файле см. в шаге 3 руководства Интеграция MongoDB с вашим приложением Node.

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

      ~/node_project/db.js

      ...
      const MONGO_USERNAME = 'sammy';
      const MONGO_PASSWORD = 'your_password';
      const MONGO_HOSTNAME = '127.0.0.1';
      const MONGO_PORT = '27017';
      const MONGO_DB = 'sharkinfo';
      ...
      

      Вместо того, чтобы жестко задавать в коде эту информацию, вы можете использовать объект process.env для захвата значений этих констант в момент запуска. Измените блок следующим образом:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      ...
      

      Сохраните и закройте файл после завершения редактирования.

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

      Откройте файл:

      Этот файл будет содержать информацию, которую вы удалили из db.js: имя пользователя и пароль для базы данных вашего приложения, а также настройки порта и имя базы данных. Обязательно измените имя пользователя, пароль и имя базы данных, перечисленные здесь, на собственные:

      ~/node_project/.env

      MONGO_USERNAME=sammy
      MONGO_PASSWORD=your_password
      MONGO_PORT=27017
      MONGO_DB=sharkinfo
      

      Обратите внимание, что мы удалили настройки хоста, которые первоначально присутствовали в db.js. Теперь мы определим наш хост на уровне файла Docker Compose вместе с другой информацией о наших сервисах и контейнерах.

      Сохраните и закройте файл после завершения редактирования.

      Поскольку ваш файл .env содержит важную информацию, вы должны быть уверены, что она содержится в файлах .dockerignore и .gitignore вашего проекта и не копируется в систему контроля версий или контейнеры.

      Откройте ваш файл .dockerignore:

      Добавьте следующую строку внизу файла:

      ~/node_project/.dockerignore

      ...
      .gitignore
      .env
      

      Сохраните и закройте файл после завершения редактирования.

      Файл .gitignore в этом репозитории уже включает .env, но вы можете убедиться в его наличии:

      ~~/node_project/.gitignore

      ...
      .env
      ...
      

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

      Шаг 3 — Изменение настроек подключения к базе данных

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

      Откройте db.js для редактирования:

      Вы увидите добавленный нами ранее код, а также константу url для URI подключения к Mongo и метод подключения к Mongoose:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, {useNewUrlParser: true});
      

      В настоящий момент метод connect поддерживает параметр, который указывает Mongoose использовать новый парсер URL в Mongo. Давайте добавим дополнительные параметры в этот метод, чтобы определить параметры для попыток восстановления соединения. Мы можем сделать это, создав константу options, которая содержит соответствующую информацию, в дополнение к новой опции парсера URL. Под константами Mongo добавьте следующее определение константы options:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      ...
      

      Параметр reconnectTries указывает Mongoose продолжать выполнять попытки подключения неопределенный срок, а reconnectInterval определяет период между попытками переподключения в миллисекундах. connectTimeoutMS указывает 10 секунд в качестве периода, которое драйвер Mongo будет ждать, прежде чем попытаться снова установить соединение.

      Теперь мы можем использовать новую константу options в методе connect Mongoose для тонкой настройки подключения Mongoose. Также мы добавим promise для обработки потенциальных ошибок подключения.

      В настоящее время метод connect Mongoose выглядит следующим образом:

      ~/node_project/db.js

      ...
      mongoose.connect(url, {useNewUrlParser: true});
      

      Удалите существующий метод connect и замените его следующим кодом, который включает константу options и promise:

      ~/node_project/db.js

      ...
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      В случае успешного подключения наша функция сохраняет соответствующее сообщение, в ином случае она перехватывает и сохраняет ошибку, позволяя нам устранять ошибки.

      Итоговый файл будет выглядеть примерно так:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      Сохраните и закройте файл после завершения редактирования.

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

      Шаг 4 — Настройка служб с помощью Docker Compose

      После выполнения рефакторинга кода вы можете начинать добавлять в файл docker-compose.yml определения служб. Служба в Compose — это запущенный контейнер, а определения служб, которые вы будете добавлять в ваш файл docker-compose.yml, содержат информацию о том, как будет запускаться образ каждого контейнера. Compose позволяет вам определять различные службы для создания приложений с несколькими контейнерами.

      Прежде чем определять службы, мы добавим в наш проект инструмент с именем wait-for, чтобы гарантировать, что наше приложение будет пытаться подключиться к нашей базе данных только после завершения всех задач, связанных с запуском базы данных. Этот скрипт обертки использует netcat для опроса, поддерживает или нет конкретный хост и порт подключение по протоколу TCP. Благодаря этому вы можете контролировать попытки подключения вашего приложения к базе данных, проверяя, готова ли база данных к подключению.

      Хотя Compose позволяет вам указывать зависимости между службами с помощью параметра depends_on, данный порядок определяется тем, запущен контейнер или нет, а не его готовностью. Использование depends_on при настройке не является оптимальным решением, поскольку мы хотим, чтобы наше приложение подключилось только после того, как все задачи запуска базы данных, включая добавление пользователя и пароля в базу данных аутентификации admin, будут выполнены. Дополнительную информацию об использовании wait-for и других инструментов для контроля порядка запуска можно найти в соответствующих разделах с рекомендациями в документации Compose.

      Откройте файл с именем wait-for.sh:

      Вставьте в файл следующий код, чтобы создать функцию опроса готовности:

      ~/node_project/app/wait-for.sh

      #!/bin/sh
      
      # original script: https://github.com/eficode/wait-for/blob/master/wait-for
      
      TIMEOUT=15
      QUIET=0
      
      echoerr() {
        if [ "$QUIET" -ne 1 ]; then printf "%sn" "$*" 1>&2; fi
      }
      
      usage() {
        exitcode="$1"
        cat << USAGE >&2
      Usage:
        $cmdname host:port [-t timeout] [-- command args]
        -q | --quiet                        Do not output any status messages
        -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
        -- COMMAND ARGS                     Execute command with args after the test finishes
      USAGE
        exit "$exitcode"
      }
      
      wait_for() {
        for i in `seq $TIMEOUT` ; do
          nc -z "$HOST" "$PORT" > /dev/null 2>&1
      
          result=$?
          if [ $result -eq 0 ] ; then
            if [ $# -gt 0 ] ; then
              exec "$@"
            fi
            exit 0
          fi
          sleep 1
        done
        echo "Operation timed out" >&2
        exit 1
      }
      
      while [ $# -gt 0 ]
      do
        case "$1" in
          *:* )
          HOST=$(printf "%sn" "$1"| cut -d : -f 1)
          PORT=$(printf "%sn" "$1"| cut -d : -f 2)
          shift 1
          ;;
          -q | --quiet)
          QUIET=1
          shift 1
          ;;
          -t)
          TIMEOUT="$2"
          if [ "$TIMEOUT" = "" ]; then break; fi
          shift 2
          ;;
          --timeout=*)
          TIMEOUT="${1#*=}"
          shift 1
          ;;
          --)
          shift
          break
          ;;
          --help)
          usage 0
          ;;
          *)
          echoerr "Unknown argument: $1"
          usage 1
          ;;
        esac
      done
      
      if [ "$HOST" = "" -o "$PORT" = "" ]; then
        echoerr "Error: you need to provide a host and port to test."
        usage 2
      fi
      
      wait_for "$@"
      

      Сохраните и закройте файл после добавления кода.

      Создайте исполняемый скрипт:

      Затем откройте файл docker-compose.yml:

      Во-первых, определите службу приложения nodejs, добавив в файл следующий код:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      

      Определение службы nodejs включает следующие параметры:

      • build: это определение параметров конфигурации, включая context и dockerfile, которые будут применяться при создании образа приложения Compose. Если вы хотите использовать существующий образ из реестра, например, из Docker Hub, вы можете использовать инструкцию image с информацией об имени пользователя, репозитория и теге образа.
      • context: это определение контекста сборки для сборки образа, в этом случае текущая директория проекта.
      • dockerfile: данный параметр определяет Dockerfile в текущей директории проекта в качестве файла, который Compose будет использоваться для сборки образа приложения. Дополнительную информацию об этом файле см. в руководстве Создание приложения Node.js с помощью Docker.
      • image, container_name: эти параметры присваивают имена для образа и контейнера.
      • restart: данный параметр определяет политику перезапуска. По умолчанию установлено значение no, но мы задали значение, согласно которому контейнер будет перезапускаться, пока не будет закрыт.
      • env_file: этот параметр указывает Compose, что мы хотим добавить переменные среды из файла с именем .env, расположенного в контексте сборки.
      • environment: с помощью этого параметра вы можете добавить настройки подключения к Mongo, которые вы определили в файле .env. Обратите внимание, что мы не задаем значение development для NODE_ENV, так как это поведение Express по умолчанию, если значение NODE_ENV не задано. При переходе в продакшен вы можете установить значение production, чтобы активировать кэширование вида и получать более короткие сообщения об ошибках. Также необходимо отметить, что мы указали в качестве хоста контейнер базы данных db, как описано в шаге 2.
      • ports: данный параметр назначает порт 80 хоста для порта 8080 в контейнере.
      • volumes: мы используем два типа монтирования:
        • Первый тип — это связанное монтирование, которое подразумевает монтирование кода приложения на хост в директорию /home/node/app в контейнере. Это упрощает быструю разработку, поскольку любые изменения, которые вы вносите в код хоста, будут немедленно добавлены в контейнер.
        • Второй тип — это том, node_modules. Когда Docker запускает инструкцию npm install, присутствующую в Dockerfile приложения, npm будет создавать новую директорию node_modules в контейнере, которая включает пакеты, необходимые для запуска приложения. Связанное монтирование, которое мы только что создали, будет скрывать эту созданную нами директорию node_modules. Поскольку директория node_modules в хосте пустая, связывание будет назначать пустой каталог для контейнера, переопределяя новую директорию node_modules и не позволяя запускать наше приложение. Том node_modules решает эту проблему, сохраняя содержимое директории /home/node/app/node_modules и монтируя его в контейнер, скрывая связку.

      При использовании этого подхода следует учитывать следующие моменты:

      • Ваша связка будет монтировать содержимое директории node_modules в контейнере на хост, а эта директория будет принадлежать к root, поскольку том с данным названием был создан Docker.
      • Если на хосте уже существует директория node_modules, она будет перезаписана директорией node_modules, созданной в контейнере. Настройка, которую мы создаем в рамках данного руководства, полагает, что у вас нет существующей директории node_modules и вы не будете работать с npm на хосте. Это соответствует подходу к разработке приложений с учетом 12 факторов, который минимизирует зависимости между средами исполнения.

        • networks: данный параметр указывает, что служба приложения будет подключаться к сети app-network, которую мы определим внизу файла.
        • command: данный параметр позволяет вам задавать команду, которая должна быть выполнена при запуске Compose образа. Обратите внимание, что этот параметр переопределяет инструкцию CMD, заданную нами в Dockerfile нашего приложения. Итак, мы запускаем приложение с помощью скрипта wait-for, который будет опрашивать службу db на порте 27017, чтобы проверить, готова ли служба базы данных. После успешной проверки готовности скрипт будет выполнять заданную нами команду /home/node/app/node_modules/.bin/nodemon app.js, чтобы запустить приложение с nodemon. Это будет гарантировать, что любые будущие изменения, которые мы будем вносить в наш код, будут подгружаться без необходимости перезапуска приложения.

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

      ~/node_project/docker-compose.yml

      ...
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:  
            - dbdata:/data/db   
          networks:
            - app-network  
      

      Некоторые настройки, которые мы определили для службы nodejs, остаются прежними, но мы также должны внести следующие изменения в определения image, environment и volumes:

      • image: для создания этой службы Compose будет запрашивать образ 4.1.8-xenial​​​ Mongo из Docker Hub. Мы закрепляем конкретную версию, чтобы избежать возможных будущих конфликтов при изменении образа Mongo. Дополнительную информацию о закреплении версии см. в документации Docker Рекомендации по работе с Dockerfile.
      • MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD: образ mongo делает эти переменные среды доступными, чтобы вы могли изменять порядок инициализации экземпляра вашей базы данных. MONGO_INITDB_ROOT_USERNAME и MONGO_INITDB_ROOT_PASSWORD вместе создают root-пользователя в базе данных аутентификации admin и гарантируют, что аутентификацию будет активирована при запуске контейнера. Мы задали MONGO_INITDB_ROOT_USERNAME и MONGO_INITDB_ROOT_PASSWORD, используя значения из нашего файла .env, который мы передаем службе db, используя параметр env_file. Подобные действия означают, что пользователь нашего приложения sammy будет root-пользователем в экземпляре базы данных с доступом ко всем административным и оперативным правам этой роли. При работе в продакшене вы можете захотеть создать специального пользователя приложения с соответствующим набором привилегий.
        ​​​​​​[note]
        Примечание: необходимо помнить, что эти переменные не будут применяться, если вы запускаете контейнер, используя существующую директорию данных.
      • dbdata:/data/db: том с именем dbdata будет сохранять данные, которые хранятся в директории данных Mongo по умолчанию, /data/db. Это будет гарантировать, что вы не потеряете данные в случаях остановки или удаления контейнеров.

      Также мы добавили службу db в сеть app-network с параметром networks.

      В качестве завершающего шага добавьте определения тома и сети в конец файла:

      ~/node_project/docker-compose.yml

      ...
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      Создаваемая пользователем мостовая система app-network позволяет организовать коммуникацию между нашими контейнерами, поскольку они находятся на одном хосте демона Docker. Это позволяет организовать трафик и коммуникации внутри приложения, поскольку она открывает все порты между контейнерами в одной мостовой сети, скрывая все порты от внешнего мира. Таким образом, наши контейнеры db и nodejs могут взаимодействовать друг с другом, и нам нужно будет только открыть порт 80 для внешнего доступа к приложению.

      Наш ключ volumes верхнего уровня определяет тома dbdata и node_modules. Когда Docker создает тома, содержимое тома сохраняется в части файловой системы хоста, /var/lib/docker/volumes/, а данным процессом управляет Docker. Содержимое каждого тома сохраняется в директории /var/lib/docker/volumes/ и монтируются в любой контейнер, который использует том. Таким образом, данные информации об акулах, которые будут добавлять наши пользователи, будут сохраняться в томе dbdata даже при удалении и последующем восстановлении контейнера db.

      Итоговый файл docker-compose.yml будет выглядеть примерно так:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:     
            - dbdata:/data/db
          networks:
            - app-network  
      
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      Сохраните и закройте файл после завершения редактирования.

      Когда ваши определения службы будут готовы, вы сможете запустить приложение.

      Шаг 5 — Тестирование приложения

      Имея в распоряжении файл docker-compose.yml, вы можете создать ваши службы с помощью команды docker-compose up. Также вы можете проверить, что ваши данные будут сохраняться, останавливая работу контейнеров и удаляя их с помощью docker-compose down.

      Во-первых, необходимо выполнить сборку образов и создать службы, запустив docker-compose с флагом -d, который будет запускать контейнеры nodejs и db в фоновом режиме:

      Вы увидите вывод, подтверждающий, что ваши службы были успешно созданы:

      Output

      ... Creating db ... done Creating nodejs ... done

      Также вы можете получить более подробную информацию о процессах запуска, отобразив вывод журнала из служб:

      Если запуск был выполнен корректно, вы должны увидеть примерно следующее:

      Output

      ... nodejs | [nodemon] starting `node app.js` nodejs | Example app listening on 8080! nodejs | MongoDB is connected ... db | 2019-02-22T17:26:27.329+0000 I ACCESS [conn2] Successfully authenticated as principal sammy on admin

      Также вы можете проверить состояние ваших контейнеров с помощью docker-compose ps:

      Вы получите вывод, указывающий, что ваши контейнеры запущены:

      Output

      Name Command State Ports ---------------------------------------------------------------------- db docker-entrypoint.sh mongod Up 27017/tcp nodejs ./wait-for.sh db:27017 -- ... Up 0.0.0.0:80->8080/tcp

      После запуска служб вы можете посетить страницу http://your_server_ip​​​ в браузере. Вы увидите стартовую страницу, которая будет выглядеть примерно так:

      Начальная страница приложения

      Нажмите кнопку Get Shark Info (Получить информацию об акулах). Вы увидите страницу с формой входа, где вы можете ввести имя акулы и описание общего типа этой акулы:

      Форма информации об акуле

      В этой форме добавьте акулу по вашему выбору. В демонстрационных целях мы добавим Megalodon Shark в поле Shark Name (Имя акулы) и Ancient в поле Shark Character (Тип акулы):

      Заполненная форма акулы

      Нажмите кнопку Submit (Отправить). Вы увидите страницу с информацией об акуле, отображаемую для вас:

      Вывод акулы

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

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

      Обратите внимание, что мы не включаем параметр --volumes, и поэтому наш том dbdata не удаляется.

      Следующая информация подтверждает, что ваши контейнеры и сеть были удалены:

      Output

      Stopping nodejs ... done Stopping db ... done Removing nodejs ... done Removing db ... done Removing network node_project_app-network

      Повторно создайте контейнеры:

      Затем вернитесь к форме информации об акуле:

      Форма информации об акуле

      Введите новую акулу по вашему выбору. Мы будем использовать значение Whale Shark и Large:

      Ввод новой акулы

      После нажатия кнопки *Submit *(Отправить) вы увидите, что новая акула добавлена в коллекцию акул в вашей базе данных без потери ранее введенных данных:

      Полное собрание акул

      Ваше приложение сейчас запущено в контейнерах Docker с сохранением данных и активацией синхронизации кода.

      Заключение

      Выполняя указания данного руководства, вы создали настройку разработки для вашего приложения Node с помощью контейнеров Docker. Вы сделали ваш проект более модульным и портативным с помощью извлечения чувствительной информации и отделив состояние вашего приложения от кода приложения. Вы также настроили шаблонный файл docker-compose.yml, который вы можете изменять в зависимости от потребностей разработки и изменений требований.

      В процессе разработки вы можете захотеть узнать больше о проектировании приложений для контейнеризованных рабочих процессов и процессов Cloud Native. Дополнительную информацию по этим темам вы можете посмотреть в статьях Разработка архитектуры приложений для Kubernetes и Модернизация приложений для Kubernetes.

      Дополнительную информацию о коде, используемом в этом руководстве, см. в статьях Сборка приложения Node.js с помощью Docker и Интеграция MongoDB с вашим приложением Node. Дополнительную информацию о развертывании приложения Node с обратным прокси-сервером Nginx с помощью контейнеров см. в статье Обеспечение безопасности контейнеризованного приложения Node.js с Nginx, Let’s Encrypt и Docker Compose.



      Source link