Docker замечательно справляется с изолированием приложений и их окружений, облегчая распространение и репликацию состояний между различными средами (dev, test, beta, prod и т. д.). Его использование позволяет избавиться от проблемы «на моей машине все работает» и помогает с легкостью масштабировать приложение по мере его роста.


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


В этой статье мы возьмем простое приложение на Rails и подготовим его для использования в Docker-контейнере («докеризуем»).


Необходимые компоненты


Наше приложение будет написано под Rails 5; базу данных возьмем PostgreSQL. Если вы хотите подключить другую СУБД, то потребуется поправить несколько файлов.


Вы можете воспользоваться заранее подготовленным шаблоном для создания приложения, которое сконфигурировано с помощью Dockerfile и config/database.yml:


$ rails new --database=postgresql --skip-bundle --template=https://gist.githubusercontent.com/cblunt/1d3b0c1829875e3889d50c27eb233ebe/raw/01456b8ad4e0da20389b0b91dfec8b272a14a635/rails-docker-pg-template.rb my-app
$ cd my-app

Конфигурация базы данных


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


Отредактируйте файл конфигурации config/database.yml


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


Добавьте в config/database.yml переменные окружения:


# config/database.yml
default: &default                                                                    
  adapter: postgresql                                                                
  encoding: unicode                                                                  
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db
  username: <%= ENV.fetch('POSTGRES_USER') %>
  password: <%= ENV.fetch('POSTGRES_PASSWORD') %>

development:
  <<: *default                                                                       
  database: my-app_development

test:
  <<: *default                                                                       
  database: my-app_test

production:
  <<: *default                                                                       
  database: my-app_production

Создание Dockerfile


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


Для экономии дискового пространства я предпочитаю использовать базовый образ alpine-linux Ruby. Alpine linux — крошечный linux-дистрибутив, идеально подходящий для использования в контейнерах. В Docker доступен базовый образ ruby:alpine, которым мы и воспользуемся.


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


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


# /path/to/app/Dockerfile
FROM ruby:2.3-alpine

# Установка часового пояса
RUN apk add --update tzdata &&     cp /usr/share/zoneinfo/Europe/London /etc/localtime &&     echo "Europe/London" > /etc/timezone

# Установка в контейнер runtime-зависимостей приложения
RUN apk add --update --virtual runtime-deps postgresql-client nodejs libffi-dev readline sqlite

# Соберем все во временной директории
WORKDIR /tmp
ADD Gemfile* ./

RUN apk add --virtual build-deps build-base openssl-dev postgresql-dev libc-dev linux-headers libxml2-dev libxslt-dev readline-dev &&     bundle install --jobs=2 &&     apk del build-deps

# Копирование кода приложения в контейнер
ENV APP_HOME /app
COPY . $APP_HOME
WORKDIR $APP_HOME

# Настройка переменных окружения для production
ENV RAILS_ENV=production     RACK_ENV=production

# Проброс порта 3000 
EXPOSE 3000

# Запуск по умолчанию сервера puma
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]                              

А что если я не хочу использовать PostgreSQL?


Если вы используете другую СУБД (например, MySQL), то для установки соответствующих пакетов потребуется внести изменения в Dockerfile.


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


$ docker run --rm -it ruby:2.3-alpine apk search --update mysql | sort
...
mariadb-client-libs-10.1.22-r0
mariadb-dev-10.1.22-r0
mariadb-libs-10.1.22-r0
mysql-10.1.22-r0
mysql-bench-10.1.22-r0
...

Поскольку Dockerfile уже готов, пора запустить сборку Docker-образа для нашего приложения:


Собираем образ


$ docker build . -t my-app

Образ готов, можно начинать! Запустите контейнер следующей командой:


$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app my-app

Мы передали команде docker run несколько аргументов:


  • -it — на самом деле это 2 аргумента, которые позволяют взаимодействовать с контейнером с помощью командной оболочки (например, чтобы передать комбинацию клавиш Ctrl+C);
  • --env — позволяет передать контейнеру переменные окружения. Здесь они используются для установки параметров подключения к базе данных;
  • --rm — говорит докеру удалить контейнер после завершения его работы (например, после нажатия Ctrl+C);
  • --publish — пробрасывает порт 3000 контейнера на порт 3000 хоста. Таким образом у нас появляется возможность подключиться к сервису так, как будто он запущен напрямую на хосте (например, http://localhost:3000);
  • --volume — говорит докеру подмонтировать в контейнер текущую директорию хоста. Таким образом вы получаете возможность редактировать код на хосте, но при этом он будет доступен в контейнере. Без этого вам пришлось бы после каждого изменения кода заново создавать контейнер.

Запуск контейнера базы данных


Хотя контейнер с приложением и запустился, попытка открыть ссылку localhost:3000, к сожалению, приведет к ошибке:


could not translate host name “db” to address: Name does not resolve

У нас пока нет доступного приложению PostgreSQL-сервера. Сейчас мы это починим, запустив Docker-контейнер с PostgreSQL:




Совет. Не забывайте, что в Docker один контейнер должен выполнять одну и только одну функцию.


В нашем случае будет 2 контейнера: один для приложения и один для базы данных (PostgreSQL).




Запуск нового контейнера с PostgreSQL


Для остановки (и удаления) контейнера с приложением нажмите Ctrl+C, затем запустите новый контейнер с PostgreSQL:


$ docker run -d -it --env POSTGRES_PASSWORD=superSecret123 --env DB_NAME=my-app_development --name mydbcontainer postgres:9.6

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


Использование однозадачных (Single–Task) контейнеров


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


Они идеальны для разовых задач, таких как команды rails (например, bin/rails db:setup).


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


Выполнение задачи rails db:migrate с использованием контейнера


Для запуска копии контейнера с приложением выполните следующую команду. Затем запустите в контейнере bin/rails db:setup и выключите его.


Обратите внимание: вам потребуется настроить переменные окружения для соединения с базой данных (они вставляются в config/database.yml, который вы ранее редактировали).


Опция --link позволит подключиться к контейнеру с PostgreSQL (mydbcontainer), используя имя хоста db:


$ docker run --rm --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --link mydbcontainer:db --volume ${PWD}:/app my-app bin/rails db:create db:migrate

Флаг --rm удалит контейнер после завершения его работы.


После выполнения этой команды в контейнере mydbcontainer будет настроенная под нужны приложения база данных. Наконец-то мы сможем его запустить!


Запуск приложения


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


$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app --link mydbcontainer:db my-app

=> Puma starting in single mode...
=>  * Version 3.8.2 (ruby 2.4.1-p111), codename: Sassy Salamander
=>  * Min threads: 5, max threads: 5
=>  * Environment: development
=>  * Listening on tcp://0.0.0.0:3000
=>  Use Ctrl-C to stop

Откройте в браузере страницу localhost:3000, где вы должны увидеть наше приложение, работающее полностью из-под Docker!


Следующие шаги


Docker — это очень удобный инструмент разработчика. Со временем вы можете перенести в него все компоненты своего приложения (БД, redis, рабочие процессы sidekiq, cron и т. д.).


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


Ссылки:


  1. Оригинал: Rails on Docker: Getting Started with Docker and Ruby on Rails.
Поделиться с друзьями
-->

Комментарии (4)


  1. AndrewDr
    01.08.2017 16:21

    Спасибо за статью, но наверное стоит вынести данные из контейнера базы, чтобы после переборки контейнера или обновлении версии Postgres не потерять все данные.


  1. therhino
    01.08.2017 16:21

    Один вопрос: Зачем?!


    1. AndrewDr
      01.08.2017 16:27

      Для того чтобы окружение на сервере и при разработке было идентично.


    1. foxmuldercp
      02.08.2017 00:45

      Зачем что?
      Приложение в контейнерах? Для идентичности окружения, для быстроты запуска и прогона килотонн тестов. я знаю некоторые проекты (нет, не рельсы) где тесты могут гоняться часами на десятках агентов.
      Базы данных в контейнерах тоже удобно для тестирования и разработки — отличия в версиях, библиотеках, даже в разных дистрибутивах могут давать различия.


      Но рабочие сервера баз данных и docker — весьма плохое сочетание, кроме, разве что, кешей вроде редиса