Pull to refresh

Деплой Rails-приложения с помощью Docker

Reading time 6 min
Views 21K
Original author: ichiel Sikkes

Введение


Эта запись о том, как я деплоил Ruby On Rails-приложение на сервер DigitalOcean, чтобы оно работало в отдельном Docker-контейнере. Для простоты, я собираюсь очень подробно объяснить процесс развертки Rails-приложения внутри Docker-контейнера
В этом посте:
  • Как я установил Docker на сервере
  • Dockerfile для моего Rails-приложения
  • Сборка с гемами из Gemfile
  • Сборка со скомпилированными ассетами
  • Запуск приложения в Docker
  • Переменные окружения Docker для database.yml

Давайте начнем с установки на сервере.

Установка Docker на сервере


Первым делом я загрузил новый Ubuntu 14.04 на DigitalOcean и установил Docker:
workstation $ ssh root@178.62.232.206
server $ apt-get install docker.io
server $ docker -v
Docker version 1.0.1, build 990021a

Dockerfile и nginx.conf


Теперь нам нужно собрать Docker-образ из Rails-приложения. Так получилось, что Jeroen (Jeroen van Baarsen, прим. перев.) написал об этом на прошлой неделе: Как я собрал Docker-образ для Rails-приложения. Я буду использовать его пост в качестве основы для дальнейших шагов.
Я буду собирать образ на том же сервере, на котором хочу впоследствии хостить само приложение. Я решил сделать так, потому что хочу, чтобы приложение не было в публичном доступе, поэтому публичный Docker-репозитарий — плохой вариант для этого. Я бы мог настроить для себя приватный репозитарий, но тогда я должен был бы поддерживать его, чего я не хочу делать в данный момент. В этом посте, я рассматриваю самый простой способ использования Docker для хостинга приложения.
В свой проект intercity-website я добавил следующие конфигурационные файлы Dockerfile и nginx.conf:

Dockerfile

FROM phusion/passenger-ruby21
MAINTAINER Firmhouse "hello@firmhouse.com"

ENV HOME /root
ENV RAILS_ENV production

CMD ["/sbin/my_init"]

RUN rm -f /etc/service/nginx/down
RUN rm /etc/nginx/sites-enabled/default
ADD nginx.conf /etc/nginx/sites-enabled/intercity_website.conf

ADD . /home/app/intercity_website
WORKDIR /home/app/intercity_website
RUN chown -R app:app /home/app/intercity_website
RUN sudo -u app bundle install --deployment
RUN sudo -u app RAILS_ENV=production rake assets:precompile

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Как можно заметить, Dockerfile использует базовый образ phusion/passenger-ruby21. Он добавляет конфиг Nginx, код приложения, запускает bundler для установки gem'ов а также прекомпилирует ассеты.

nginx.conf

# This is the server block that serves our application.
server {
  server_name intercityup.com;
  root /home/app/intercity_website/public;

  passenger_enabled on;
  passenger_user app;
  passenger_ruby /usr/bin/ruby2.1;
}

# This is the server block that redirects www to non-www.
server {
  server_name www.intercityup.com;
  return 301 $scheme://intercityup.com$request_uri;
}

Сборка образа для контейнера приложения


Я добавил эти файлы в мой репозитарий. Теперь я собираюсь загрузить его на сервер и собрать контейнер:
my_workstation $ git archive -o app.tar.gz --prefix=app/ master
my_workstation $ scp app.tar.gz root@178.62.232.206:
my_workstation $ ssh root@ 178.62.232.206
server $ tar zxvf app.tar.gz
server $ docker build --tag="intercity-website" app/

Эта команда выводит много результатов и делает много всего. Когда я в первый раз я запустил docker build, это заняло несколько минут. Это потому, что Docker должен скачать базовый образ phusion/passenger-ruby21. Это делается только один раз. После загрузки базового образа процесс продолжится в соответствии с моим Dockerfile.

Теперь команда docker images показывает мой образ:
server $ docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
intercity-website          latest              629f05f42915        3 minutes ago       1.011 GB

Запуск контейнера в первый раз


Настало время запустить приложение.
server $ docker run --rm -p 80:80 intercity-website

Эта команда запускает контейнер, выводит некоторые данные, и, наконец, выводит следующую строку:
[ 2014-09-23 11:23:11.9005 113/7fb22942b780 agents/Watchdog/Main.cpp:728 ]: All Phusion Passenger agents started!

Теперь давайте посмотрим, корректно ли работает приложение. Выполним запрос с помощью curl:
server $ curl -H "Host: intercityup.com" http://localhost/
<!DOCTYPE html>
<html>
<head>
  <title>We`re sorry, but something went wrong (500)</title>
...

Упс, что-то пошло не так. Судя по логу, находящийся внутри контейнера, (для доступа в который я использовал docker-bash от Phusion), я забыл создать базу данных. Так что теперь я собираюсь установить на сервере MySQL.

Установка базы данных


Я буду использовать стандартный сервер MySQL, доступный в Ubuntu 14.04:
server $ apt-get install mysql-server

После установки, и задания пароля администратора, я могу создать базу данных для приложения:
server $ mysql -u root -p
mysql> create database intercity_website_production;
Query OK, 1 row affected (0.00 sec)
mysql> grant all on intercity_website_production.* to 'intercity' identified by 'rwztBtRW6cFx9C';
Query OK, 0 rows affected (0.00 sec)

После этого я изменил /etc/mysql/my.cnf а также bind-address с 127.0.0.1 на мой внешний IP адрес, 178.62.232.206. Таким образом, Rails в моем контейнере теперь может использовать MySQL. В /etc/mysql/my.cnf я заменил строку с bind-address на следующую:
bind-address        = 178.62.232.206

И перезапустил MySQL:
server $ /etc/init.d/mysql restart


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


Я собираюсь использовать переменные окружения, чтобы мой контейнер мог использовать их для авторизации в MySQL. Чтобы сделать это, мне нужно сделать две вещи: 1) Подготовить database.yml файл в репозитории к использованию переменных окружения. и 2) настроить Nginx для передачи этих переменных в процесс passenger'a.

Вот мой новый database.yml, подготовленный для переменных окружения:
production:
  adapter: mysql2
  host: <%= ENV['APP_DB_HOST'] %>
  port: <%= ENV['APP_DB_PORT'] || "3306" %>
  database: <%= ENV['APP_DB_DATABASE'] %>
  username: <%= ENV['APP_DB_USERNAME'] %>
  password: <%= ENV['APP_DB_PASSWORD'] %>

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

Я добавил в Rails-приложение файл rails-env.conf:
env APP_DB_HOST;
env APP_DB_PORT;
env APP_DB_DATABASE;
env APP_DB_USERNAME;
env APP_DB_PASSWORD;

А также поправил Dockerfile, чтобы он добавлял файл rails_env при сборке контейнера:
FROM phusion/passenger-ruby21
MAINTAINER Firmhouse "hello@firmhouse.com"

ENV HOME /root
ENV RAILS_ENV production

CMD ["/sbin/my_init"]

RUN rm -f /etc/service/nginx/down
RUN rm /etc/nginx/sites-enabled/default
ADD nginx.conf /etc/nginx/sites-enabled/intercity_website.conf
# Add the rails-env configuration file
ADD rails-env.conf /etc/nginx/main.d/rails-env.conf

ADD . /home/app/intercity_website
WORKDIR /home/app/intercity_website
RUN chown -R app:app /home/app/intercity_website
RUN sudo -u app bundle install --deployment
RUN sudo -u app RAILS_ENV=production rake assets:precompile

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

EXPOSE 80


Построение образа с поддержкой переменных окружения


Я добавил в репозитарий новый конфиг Nginx. Теперь я намерен пересобрать новую версию контейнера:
workstation $ git archive -o app.tar.gz --prefix=app/ master
workstation $ scp app.tar.gz root@178.62.232.206:
workstation $ ssh root@178.62.232.206
server $ tar zxvf app.tar.gz
server $ docker build --tag="intercity-website" app/

Запуск rake с переменными окружения


После сборки контейнера я могу настроить базу данных. В следующей команде я использую переменные окружения для передачи информации о подключении к базе данных для запуска rake db:setup. Обратите внимание, что я добавил к команде аргумент -u app. Этот аргумент нужен, чтобы удостовериться, что rake db:setup запускается от имени пользователя app внутри контейнера.
server $ docker run --rm -e "RAILS_ENV=production" -e "APP_DB_HOST=178.62.232.206" -e "APP_DB_DATABASE=intercity_website_production" -e "APP_DB_USERNAME=intercity" -e "APP_DB_PASSWORD=rwztBtRW6cFx9C" -e "APP_DB_PORT=3306" -u app intercity-website rake db:setup

intercity_website_production already exists
-- create_table("invite_requests", {:force=>true})
   -> 0.0438s
-- initialize_schema_migrations_table()
   -> 0.1085s

Ух-ты! Сработало!


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


Теперь я могу запустить контейнер с теми же переменными окружения и попытаться получить доступ к нему из браузера, чтобы проверить, работает ли он:
server $ docker run --rm -p 80:80 -e "RAILS_ENV=production" -e "APP_DB_HOST=178.62.232.206" -e "APP_DB_DATABASE=intercity_website_production" -e "APP_DB_USERNAME=intercity" -e "APP_DB_PASSWORD=rwztBtRW6cFx9C" -e "APP_DB_PORT=3306" intercity-website

Когда я открываю 178.62.232.206, я вижу Rails-приложение, которое подключается к базе данных, и также вижу, ассеты был скомпилированы и все работает. Победа!

Заключение


На этом мы завершаем пост, где мы:
  1. Установили Docker на сервере
  2. Настроили Dockerfile и построили образ контейнера
  3. Настроили базу данных с помощью переменных окружения

Что дальше?


У меня до сих пор есть вопросы, требующие ответа. Я и другие разработчики в Intercity ещё будем писать о них. Вот некоторые из вопросов, которые нужно решить:
  • Как автоматизировать развертывание? Может быть использовать что-то вроде Capistrano?
  • Что мне нужно, чтобы получить нулевое время простоя? Когда я сперва остановлю, а потом запущу контейнер, для запуска новой версии приложения, подключения будут сброшены.
  • Где хранить переменные окружения для каждого из приложений, которые я собираюсь развернуть на сервере?
  • Как мне ускорить построение контейнера? Нужно ли мне запускать каждый раз bundler и rake assets:precompile для каждого деплоя?

Я надеюсь, что вам понравился этот пост. Буду рад советам и вопросам!

Большое спасибо за внимание.
Tags:
Hubs:
+13
Comments 20
Comments Comments 20

Articles