Как Dockerize приложение Rails с задачами mysql, nginx и cron - PullRequest
0 голосов
/ 27 января 2019

У меня готово к развертыванию приложение Rails, и я хотел бы обернуть все зависимости, включая nginx в качестве внешнего веб-сервера, mysql в качестве базы данных и cron для выполнения повторяющихся задач.

1 Ответ

0 голосов
/ 27 января 2019

Есть много причин, по которым мы хотели бы докеризировать наше приложение Rails.В моем случае главная причина в том, что я хочу быть независимым от хостинга.Я хочу легко перенести свое приложение с одного хостинга на другой с наименьшим возможным трением.

Я годами мучаюсь, платя очень дорогой выделенный сервер только из-за того, что было слишком сложно снова все настроить вдешевле.Это может быть нормально, если у вас есть одно приложение для миграции, но у меня было ~ 20 приложений, запущенных на этом сервере, каждое со своими особенностями.Некоторые из них требуют cronjobs, другие требуют специальных внешних инструментов, таких как специальная версия FFmpeg, базы данных, ...

Если бы каждое из этих приложений имело свою собственную настройку Dockercompose, перемещение их на другой сервер было бы намного меньшеболезненный.

Итак, я начал Dockerize (и Dockercompose) все мои приложения, и вот чему я научился.

Это пример фиксации одного из Dockerize одного из моих старых приложений:

Вот что мы собираемся построить:

enter image description here

Настройка всехDocker

В этом учебном пособии есть две части, одна из которых посвящена самой докеризации, а вторая будет посвящена настройке сервера и запуску приложения.

Службы

Давайте предположим, что вашему приложению понадобятся все эти службы:

  • MySql
  • Cronjobs
  • Nginx (каквнешний интерфейс прокси), с SSL
  • Приложение (само приложение Rails)

Итак, это 4 Сервиса, и мы собираемся создать контейнер Docker для каждого из них, и мы собираемся, чтобы пользователь Dockercompose обернул конфигурацию и собрал всеиз них

Мы создаем нашу конфигурацию Dockercompose в корневой папке нашего приложения:

# ./docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - ./db/data:/var/lib/mysql
    restart: always
    ports:
      - 127.0.0.1:3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root
  app:
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
    volumes:
      - .:/var/www/app
    restart: always
    depends_on:
      - db
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    depends_on:
      - app
    ports:
      - 80:80
      - 443:443
    volumes:
      - .:/var/www/app
    restart: always
  cron:
    build:
      context: .
      dockerfile: ./docker/cron/Dockerfile
    volumes:
      - .:/var/www/app
    restart: always
    depends_on:
      - db

Давайте рассмотрим сервис по сервисам.

Сервис MySQL

  db:
    image: mysql:5.7
    volumes:
      - ./db/data:/var/lib/mysql
    restart: always
    ports:
      - 127.0.0.1:3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root

Некоторые комментарии:

image: mysql:5.7

Это наиболее простой в настройке сервис, поскольку он является стандартным и мы используем общедоступный образ mysql:5.7.Не забудьте добавить версию, чтобы она не изменилась в будущем, пока вас не заметят.

volumes:
  - ./db/data:/var/lib/mysql

Одна важная вещь, которую мы настраиваем здесь, находится в разделе volumes.Мы связываем одну внутреннюю папку контейнера с внешней папкой.Папка, о которой идет речь, - /var/lib/mysql, что, что неудивительно, - здесь хранятся все данные БД.Мы не хотим, чтобы эти данные сохранялись во внутренней папке контейнера, потому что в этом случае они не будут постоянными при перезапуске контейнера.Поэтому мы связали его с внешней папкой, в данном случае: APP_ROOT/db/data.

Одним из важных следствий является то, что двоичные данные из MySQL будут храниться в папке приложения, поэтому мы должны быть уверены, что мы неотправка его в репо:

echo "/db/data" >> .gitignore

Порты:

ports:
  - 127.0.0.1:3306:3306

Другая внутренняя / внешняя конфигурация соединения - ports.Это основной.Внутренняя служба mysql будет прослушивать порт 3306 по умолчанию, поэтому мы делаем его доступным извне, используя эту конфигурацию

environment:
  MYSQL_ROOT_PASSWORD: root

Это изображение требует настройки ENVVAR для настройки пользователя rootв основной базе данных MySQL.Тот факт, что мы включим его здесь, может быть проблемой безопасности, но я не собираюсь включать решение для этой проблемы в это Tuto.

Теперь нам нужно настроить наш database.yml для использования образа док-станции MySQL в качествеa host

# /config/database.yml
production:
  <<: *default
  host: db
  database: myapp
  password: root

См., что конфигурация host указывает на db, который является хостом, созданным Dockercompose.

Служба приложений Rails

Thisэто определение контейнера, который станет домом для нашего приложения Rails.

app:
  build:
    context: .
    dockerfile: ./docker/app/Dockerfile
  volumes:
    - .:/var/www/app
  restart: always
  depends_on:
    - db

Некоторые комментарии:

dockerfile: ./docker/app/Dockerfile

Именно здесь мы сказали Dockercompose, где найти сборку.конфигурация для этого контейнера Docker.

volumes:
  - .:/var/www/app

Некоторые ссылки здесь.Мы связываем внутреннюю папку контейнера /var/www/app с корнем нашего приложения.

Служба прокси Nginx

Вместо того, чтобы подвергать наше Rails-приложение напрямую HTTP-запросам, я думаю, что хорошей идеей будет поставить мощный прокси-сервер.Это добавит некоторые функции обработки пула запросов, поддержки https и доставки статических файлов.

web:
  build:
    context: .
    dockerfile: ./docker/web/Dockerfile
  depends_on:
    - app
  ports:
    - 80:80
    - 443:443
  volumes:
    - .:/var/www/app
  restart: always

Нам были переданы важные части этого файла конфигурации в предыдущих разделах.

ports:
  - 80:80
  - 443:443

Вк этой услуге мы подключаем 2 разных порта.Один для подключений http, а другой для подключений https.

Служба задач Cron

Я предполагаю, что задачи cron, которые мы собираемся настроить, являются зависимостью приложения Rails.Это будут rake вызовы или curl запросы к некоторым из наших конечных точек приложения Rails.

Это потому, что я также связываю домен APP_ROOT с внутренней папкой.

cron:
  build:
    context: .
    dockerfile: ./docker/cron/Dockerfile
  volumes:
    - .:/var/www/app
  restart: always
  depends_on:
    - db

Контейнеры

Каждый сервис будет предоставляться для отдельного контейнера Docker.

Для контейнера earch нам нужен файл конфигурации.Для самоорганизации я размещаю всю конфигурацию контейнера Docker в папке:

./docker

Контейнер MySQL

Для него не требуется файл Docker, поскольку мы используем общедоступный образ.

Контейнер приложения Rails

# ./docker/app/Dockerfile
FROM ruby:2.5.1

# Install dependencies
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# Set an environment variable where the Rails app is installed to inside of Docker image:
ENV RAILS_ROOT /var/www/app
RUN mkdir -p $RAILS_ROOT

# Set working directory, where the commands will be ran:
WORKDIR $RAILS_ROOT

# Setting env up
ENV RAILS_ENV="production"
ENV RACK_ENV="production"
ENV SECRET_KEY_BASE="8704xxhhb0b5889cb81d8452a218251f7940d285ffgg79a3c9b4108dd1e9875227a868bb122bc23c833432ca37b3fe7b7c514xxccc9285661e5b2ce8a5a53453"

# Adding gems
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install --jobs 20 --retry 5 --without development test

# Adding project files
COPY . .
RUN bundle exec rake assets:precompile

EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Это базовый Dockerfile приложения Rails.С помощью короткого сеанса Google вы можете найти объяснение всех вещей, которые здесь происходят.

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

Контейнер Nginx Proxy

В этом контейнере мы включаем 2 разных файла конфигурации, один из которыхDockerfile сам по себе, а другой - настраиваемая конфигурация для ngnix.

# ./docker/web/Dockerfile
# Base image:
FROM nginx
# Install dependencies
RUN apt-get update -qq && apt-get -y install apache2-utils

# establish where Nginx should look for files
ENV RAILS_ROOT /var/www/app

# Set our working directory inside the image
WORKDIR $RAILS_ROOT

# create log directory
RUN mkdir log

# copy over static assets
COPY public public/

# Copy Nginx config template
COPY docker/web/nginx.conf /tmp/docker.nginx

# substitute variable references in the Nginx config template for real values from the environment
# put the final config in its place
RUN envsubst '$RAILS_ROOT' < /tmp/docker.nginx > /etc/nginx/conf.d/default.conf

EXPOSE 80

# Use the "exec" form of CMD so Nginx shuts down gracefully on SIGTERM (i.e. `docker stop`)
CMD [ "nginx", "-g", "daemon off;" ]

Это настроит сервер nginx, наиболее интересная часть здесь может быть такая:

# Copy Nginx config template
COPY docker/web/nginx.conf /tmp/docker.nginx

# substitute variable references in the Nginx config template for real values from the environment
# put the final config in its place
RUN envsubst '$RAILS_ROOT' < /tmp/docker.nginx > /etc/nginx/conf.d/default.conf

Где мы копируемнаш пользовательский файл конфигурации nginx в контейнер в качестве конфигурации по умолчанию.Мы также делаем некоторую динамическую замену, чтобы избежать записи определенных PATH в наш файл шаблона конфигурации.

И вот наш файл шаблона конфигурации nginx:

# ./docker/web/nginx.conf
# This is a template. Referenced variables (e.g. $RAILS_ROOT) need
# to be rewritten with real values in order for this file to work.

upstream rails_app {
  server app:3000;
}

# Default server
server {
  # define your domain
  # server_name localhost;

  listen 80;
  listen 443 ssl;
  ssl_certificate $RAILS_ROOT/secret/ssl_certificates/myapp.com.crt;
  ssl_certificate_key $RAILS_ROOT/secret/ssl_certificates/myapp.com.key;

  # define the public application root
  root   $RAILS_ROOT/public;
  index  index.html;

  # define where Nginx should write its logs
  access_log $RAILS_ROOT/log/nginx.access.log;
  error_log $RAILS_ROOT/log/nginx.error.log;

  # deny requests for files that should never be accessed
  location ~ /\. {
    deny all;
  }

  location ~* ^.+\.(rb|log)$ {
    deny all;
  }

  # serve static (compiled) assets directly if they exist (for rails production)
  location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ {
    try_files $uri @rails;

    access_log off;
    gzip_static on; # to serve pre-gzipped version

    expires max;
    add_header Cache-Control public;

    # Some browsers still send conditional-GET requests if there's a
    # Last-Modified header or an ETag header even if they haven't
    # reached the expiry date sent in the Expires header.
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }

  # send non-static file requests to the app server
  location / {
    try_files $uri @rails;
  }

  location @rails {
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://rails_app;
  }
}

Конфигурация очень проста.Я думаю, что наиболее интересным моментом является то, где мы настраиваем наши сертификаты SSL:

ssl_certificate $RAILS_ROOT/secret/ssl_certificates/myapp.com.crt;
ssl_certificate_key $RAILS_ROOT/secret/ssl_certificates/myapp.com.key;

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

Контейнер задач Cron

Как я уже говорил ранее, я предполагаю, чтонаши задачи cron будут иметь наше приложение Rails как зависимость, поэтому этот контейнер имеет ту же конфигурацию, что и наш контейнер Rail App, и некоторые дополнительные вещи:

# ./docker/cron/Dockerfile
FROM ruby:2.5.1

# Install dependencies
RUN apt-get update && apt-get -y install cron

# Set an environment variable where the Rails app is installed to inside of Docker image:
ENV RAILS_ROOT /var/www/app
RUN mkdir -p $RAILS_ROOT

# Set our working directory inside the image
WORKDIR $RAILS_ROOT

# Setting env up
ENV RAILS_ENV="production"
ENV RACK_ENV="production"
ENV SECRET_KEY_BASE="87042df8b0b5889cb81d8452a218251f7940d2854fde79a3c9b4108dd1e9875227a868bb122bc23c833432ca37b3fe7b7c51453a0c9285661e5b2ce8a5a53453"

# Adding gems
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install --jobs 20 --retry 5 --without development test

# Adding project files
COPY . .
RUN bundle exec rake assets:precompile

EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

## Cron config

# Add crontab file to the cron.d directory
COPY crontab /etc/cron.d/app

# Give execution rights on the cron job
# Files in /etc/cron.d can not have names with "-" or ".". It can be problematic
RUN chmod 0644 /etc/cron.d/app

# To load the env variables in cron sessions
# without this the user in the cron session won't be able to find commands and Gems
RUN printenv | grep -v "no_proxy" >> /etc/environment

# Run the command on container startup
CMD ["cron", "-f"]

Итак, единственными новинками являются:

## Cron config

# Add crontab file to the cron.d directory
COPY crontab /etc/cron.d/app

# Give execution rights on the cron job
# Files in /etc/cron.d can not have names with "-" or ".". It can be problematic
RUN chmod 0644 /etc/cron.d/app

# To load the env variables in cron sessions
# without this the user in the cron session won't be able to find commands and Gems
RUN printenv | grep -v "no_proxy" >> /etc/environment

# Run the command on container startup
CMD ["cron", "-f"]

И самая важная часть из вышеперечисленного - это то, где мы устанавливаем внутреннюю конфигурацию crontab:

COPY crontab /etc/cron.d/app

Ожидается найти файл в вашем APP_ROOT с совместимой конфигурацией crontab, например:

# ./crontab
0 * * * * root /bin/bash -l -c 'cd $RAILS_ROOT && bundle exec rake myapp:mytask'

Сервер, настройка и развертывание

Теперь, когда наше Rails-приложение полностью загружено, мы хотим его развернуть.

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

Установка зависимостей

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

Это включает в себя:

  • git (базовый, если мы хотим использовать в качестве механизма передачи файлов)
  • Docker (сюрприз!)
  • Dockercompose

Настройка кластера Dockercompose

После того, как мы установили все зависимости, нам нужно:

Загрузите наш код приложения

git clone https://github.com/fguillen/MyApp.git

Создание наших изображений

docker-compose build

Настройка всех контейнеров / услуг

docker-compose up -d

Проверьте, все ли прошло хорошо:

docker-compose logs

Выполнение базовых предварительных задач Rails

docker-compose exec app bundle exec rake db:create db:schema:load
docker-compose exec app bundle exec rake db:seed # Optional

Сценарий настройки

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

У меня это работает в дистрибутивах Ubuntu.

# ./server_setup.sh
#!/bin/bash
set -e
set -x

apt-get update
apt-get install git-core

# Install Docker
# From here: https://docs.docker.com/install/linux/docker-ce/ubuntu/#set-up-the-repository
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

apt-get install docker-ce

# Docker compose
# From here: https://docs.docker.com/compose/install/
sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

# Download the App
mkdir -p /var/apps
cd /var/apps
git clone https://user@github.com/user/myrepo.git

# Start the App
cd /var/apps/MyApp
docker-compose build
docker-compose up -d
docker-compose exec app bundle exec rake db:create db:schema:load
# docker-compose exec app bundle exec rake db:seed # Optional

Я использую для включения этого файла в репозиторий приложений по пути:

./docker/server_setup.sh

Выводы

Докеризация приложения Rails совсем не тривиальна. Это может занять много времени. Много пробных ошибок. Многие вещи терпят неудачу, и вы не знаете почему.

Я надеюсь, что это руководство хотя бы немного уменьшит всю эту боль.

Как только это сработает, шансы, которые сработают в следующий раз, велики;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...