Введение
Эта запись о том, как я деплоил 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-приложение, которое подключается к базе данных, и также вижу, ассеты был скомпилированы и все работает. Победа!
Заключение
На этом мы завершаем пост, где мы:
- Установили Docker на сервере
- Настроили Dockerfile и построили образ контейнера
- Настроили базу данных с помощью переменных окружения
Что дальше?
У меня до сих пор есть вопросы, требующие ответа. Я и другие разработчики в Intercity ещё будем писать о них. Вот некоторые из вопросов, которые нужно решить:
- Как автоматизировать развертывание? Может быть использовать что-то вроде Capistrano?
- Что мне нужно, чтобы получить нулевое время простоя? Когда я сперва остановлю, а потом запущу контейнер, для запуска новой версии приложения, подключения будут сброшены.
- Где хранить переменные окружения для каждого из приложений, которые я собираюсь развернуть на сервере?
- Как мне ускорить построение контейнера? Нужно ли мне запускать каждый раз bundler и rake assets:precompile для каждого деплоя?
Я надеюсь, что вам понравился этот пост. Буду рад советам и вопросам!
Большое спасибо за внимание.