Tutorial

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

Ruby on RailsDockerPostgreSQLRedis

Введение

Если вы активно занимаетесь разработкой приложений, использование 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:

  • cd rails-docker

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

  • nano Gemfile

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

~/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:

  • nano 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:

  • nano .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 для последующего монтирования в контейнер базы данных при его запуске.

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

  • nano init.sql

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

~/rails-docker/init.sql
CREATE USER sammy;
ALTER USER sammy WITH SUPERUSER;

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

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

  • chmod +x init.sql

Далее мы настроим 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, какие файлы нашего проекта нужно игнорировать. Откройте файл для редактирования:

  • nano .gitignore

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

~/rails-docker/.gitignore
yarn-debug.log*
.yarn-integrity
.env

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

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

  • .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:

  • nano db/seeds.rb

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

~/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 в текущей директории:

  • nano 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 для скриптов точек входа:

  • mkdir 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:

  • nano 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, который будет запускать контейнеры в фоновом режиме:

  • 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

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

  • docker-compose logs

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

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:

  • 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? еще раз. Вы снова увидите список выгруженных акул.

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

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

  • docker-compose down

Обратите внимание, что мы не используем параметр --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

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

  • docker-compose up -d

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

  • docker-compose exec app bundle exec rails console

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

  • Shark.last.inspect

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

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 с помощью следующей команды:

  • Endangered.all.count
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 для комплексной разработки.

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

0 Comments

Creative Commons License