Статьи

Развертывание приложения Docker Rails

В предыдущей статье Marko Locher мы узнали, как запустить среду разработки Rails в Docker . Марко также написал о том, как протестировать приложение Rails с помощью Docker . Итак, если у нас настроена среда разработки, наше приложение протестировано (и тесты проходят), мы готовы подумать о развертывании нашего приложения в производстве. Если вы еще этого не сделали, я рекомендую сначала прочитать эти две статьи, чтобы получить хороший обзор того, как работает Docker.

В этой статье мы поговорим о том, как быстро и просто развернуть наш контейнер Docker в Heroku. Как только мы закончим с этим, мы исследуем, как создать рабочий образ Docker, используя минимальную базу, которая не включает зависимости разработки. Затем мы отправим наш образ в Docker Hub и развернем его на сервере.

Простой Rails Docker на Heroku

Большинство разработчиков Rails знакомы с Heroku и, вероятно, в тот или иной момент развернули на нем приложение, даже если это был просто личный блог или что-то, с чем вы играли. Я использовал Heroku как для личных проектов, так и для крупных клиентских проектов с большим успехом. Heroku не только для приложений Rails … вы можете развертывать приложения PHP, приложения NodeJS, приложения Elixir, а с начала этого года вы даже можете развертывать контейнеры Docker .

Начало настройки

Первое, что вам нужно сделать, это установить плагин heroku-docker для пояса Heroku: heroku plugins:install heroku-docker

После того, как вы настроили обычное приложение Rails (новое или существующее), вам нужно добавить в корневой каталог дополнительный файл с именем app.js.

{
  "name": "My App Name",
  "description": "An example app.json for heroku-docker",
  "image": "heroku/ruby",
  "addons": [
    "heroku-postgresql"
  ]
}

В Heroku есть несколько разных изображений, которые вы можете использовать для каждого из поддерживаемых языков. Далее мы создадим файл, Procfileкоторый Heroku использует для запуска нашего приложения.

web: bundle exec puma -C config/puma.rb

Следующим шагом является создание нового Dockerfile . Для этого мы можем использовать инструментальный пояс Heroku, и, выполнив команду, heroku docker:initмы получим Dockerfileфайл и docker-compose.ymlфайл.

Dockerfile очень маленький, только вытягивая из heroku/rubyизображения:

FROM heroku/ruby

Этот docker-compose.ymlфайл немного сложнее и содержит два связанных изображения (одно для Ruby / Rails и одно для Postgres):

web:
  build: .
  command: 'bash -c ''bundle exec puma -C config/puma.rb'''
  working_dir: /app/user
  environment:
    PORT: 8080
    DATABASE_URL: 'postgres://postgres:@herokuPostgresql:5432/postgres'
  ports:
    - '8080:8080'
  links:
    - herokuPostgresql
shell:
  build: .
  command: bash
  working_dir: /app/user
  environment:
    PORT: 8080
    DATABASE_URL: 'postgres://postgres:@herokuPostgresql:5432/postgres'
  ports:
    - '8080:8080'
  links:
    - herokuPostgresql
  volumes:
    - '.:/app/user'
herokuPostgresql:
  image: postgres

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

Вы заметите, что DATABASE_URLв этом файле есть переменная окружения . Мы можем изменить наш database.ymlфайл, чтобы использовать эту переменную ENV для подключения к базе данных.

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000
  username: postgres
  host: <%= ENV['DATABASE_URL'] %>

Мы будем использовать веб-сервер Puma для этого приложения Rails, и нам нужно настроить файл конфигурации, расположенный в config/puma.rb. Пожалуйста, убедитесь, что у вас есть pumaв вашем Gemfile!

port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'

И работает локально

Чтобы запустить это приложение локально в контейнере Docker, вы можете использовать следующую команду, которая на данный момент является прямой Docker и Docker Compose (а не чем-то специфичным для Heroku):

docker-compose up web

Эта команда откроет приложение в браузере с правильным IP-адресом:

open "http://$(docker-machine ip default):8080"

Если все работает хорошо, вы сможете увидеть, как ваше приложение работает в контейнере Heroku / Ruby Docker.

Развертывание в Heroku

Для развертывания в Heroku сначала нужно убедиться, что мы создали приложение для Heroku. Запишите имя, которое они дали вашему приложению в выводе.

heroku create

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

heroku docker:release --app warm-fortress-1700

После завершения развертывания (которое займет несколько минут в первый раз, когда оно загружает в Heroku довольно большой фрагмент изображения), вы можете открыть его в браузере с помощью команды heroku open.

! Новый призыв к действию

Построение минимального производственного изображения

При сборке для производственной версии вам нужно максимально облегчить образы Docker, чтобы не использовать ресурсы сервера. Один из способов сделать это — использовать урезанный дистрибутив Linux, такой как Alpine . У CenturyLink есть отличное изображение Docker, которое мы можем использовать в качестве базового образа.

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

Dockerfile FROM centurylink/alpine-rails

# Configure the main working directory. This is the base
# directory used in any further RUN, COPY, and ENTRYPOINT
# commands.
RUN mkdir -p /app 
WORKDIR /app

# Copy the Gemfile as well as the Gemfile.lock and install
# the RubyGems. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY Gemfile Gemfile.lock ./ 
RUN gem install bundler && bundle install --jobs 20 --retry 5 --without development test

# Set Rails to run in production
ENV RAILS_ENV production 
ENV RACK_ENV production

# Copy the main application.
COPY . ./

# Precompile Rails assets
RUN bundle exec rake assets:precompile

# Start puma
CMD bundle exec puma -C config/puma.rb

В этом файле я запускаю bundle installс --without development testфлагом, чтобы избежать Gems, которые нам не нужны в нашей производственной среде. Я установил переменные ENV с именами RAILS_ENV и RACK_ENV для производства. И, наконец, я предварительно скомпилировал ресурсы, чтобы в образе уже были все предварительно скомпилированные ресурсы, и нам не нужно будет делать это снова после развертывания. Все дело в том, чтобы наш производственный имидж был максимально эффективным и небольшим.

Давайте создадим наш производственный имидж:

docker build -t leighhalliday/rails-alpine -f ./Dockerfile.prod .

Обратите внимание, что мы включили -fдирективу с именем файла, чтобы указать на наш Dockerfile.prodфайл вместо обычного Dockerfile.

Нажимаем наш образ в Docker Hub

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

docker push leighhalliday/rails-alpine

Теперь он доступен по адресу https://hub.docker.com/r/leighhalliday/rails-alpine/ .

Развертывание приложения Docker Rails

Сегодня мы развернем наше приложение на довольно типичном Linux-компьютере (Ubuntu, но это может быть что угодно), на котором уже установлены Docker и NGINX.

We’ll actually have to set up two other images (plus our Rails app, so three in total) on the server for the database. I’m going to be creating one image which is for storing DB data plus another for the DB server itself. Then we’ll run our main Alpine-Rails Docker image and link it to the DB server image. If you’re wondering why I’m doing it this way, Tim Butler has a great article on Linking and Volumes in Docker.

docker create -v /var/lib/postgresql/data --name db-data postgres:9.4 /bin/true

And for the DB server itself, which uses the --volumes-from directive to point to the db-data image from above:

docker run -d --name db-server --volumes-from db-data postgres:9.4

Now to run our Rails container:

docker run -d --name rails-server --link db-server -p 3000:3000 -e SECRET_KEY_BASE=`rake secret` leighhalliday/rails-alpine

Here is a breakdown of the directives:

  • -d: To run this Docker container in the background
  • --name: Giving this container a name
  • --link: Linking this container to the db-server image
  • -p: Exposing port 3000 and linking it to port 3000 on the host system
  • -e: Setting an environment variable which Rails will use. You may have to replace rake secret with an actual secret because the host server most likely won’t have rake installed

One important thing to note is that the database.yml file should be set up to point to our linked db-server image. To do this we can use the environment variables that the linked image exposes to us. To see a list of them you can run this command:

docker exec rails-server env

Two of the ENV variables listed will be something similar to the ones below. We can use these to point to our DB.

DB_SERVER_PORT_5432_TCP_ADDR=172.17.0.1
DB_SERVER_PORT_5432_TCP_PORT=5432

Lastly, you’ll probably need to enter the Rails console or to run rake db:create. To do so you can use the docker exec command on the server. docker exec rails-server bundle exec rake db:create

To run the Rails console you’ll have to pass the directives -it:

docker exec -it rails-server rails console

Configuring NGINX

If you visit the IP address of the server you won’t get any response yet. This is because our app is listening on port 3000. So instead of adding :3000 to the IP address, let’s configure NGINX to receive requests on port 80 and forward them to our app on 3000.

We’ll be using the upstream directive along with proxy_pass to hand the work off to another IP address and port, which happens to be the one for our Rails Docker container. This code would go inside of the http block in the NGINX config or in one of the sites-available conf files.

upstream docker { 
  server 127.0.0.1:3000; 
}

server { 
  listen 80; 
  location / { 
    proxy_pass http://docker; 
  } 
}

Conclusion

I’ll admit, getting comfortable with Docker can be a pretty steep learning curve. It does have some very powerful benefits though, not just for development but for deploying and running production code as well.

In this article we looked at doing the simplest deploy possible to Heroku. After that we went along more of a manual path, by hand crafting our own Dockerfile, building the image and deploying it to the Docker hub, and finally running this image on a production server.

This article appeared on the Codeship blog by Leigh Halliday.