【发布时间】:2019-01-26 22:04:43
【问题描述】:
我已经准备好部署我的 Rails 应用程序,我想包装所有依赖项,包括 nginx 作为前端 Web 服务器、mysql 作为数据库和 cron 以执行循环任务。
【问题讨论】:
标签: ruby-on-rails docker docker-compose
我已经准备好部署我的 Rails 应用程序,我想包装所有依赖项,包括 nginx 作为前端 Web 服务器、mysql 作为数据库和 cron 以执行循环任务。
【问题讨论】:
标签: ruby-on-rails docker docker-compose
我们想要对 Rails 应用程序进行 docker 化的原因有很多。在 我的情况的主要原因是我想成为托管服务不可知论者。一世 想要轻松地将我的应用程序从一个托管服务移动到另一个托管服务 可能的摩擦。
多年来,我一直在为一台非常昂贵的专用服务器买单
因为要在更便宜的地方重新安装所有东西非常困难。它可能
当您有一个要迁移的应用程序时没问题,但我有大约 20 个应用程序正在运行
在这个服务器中,每一个都有不同的特点。其中一些需要
cronjobs,其他需要特殊的外部工具,例如特殊版本的 FFmpeg,
数据库,...
如果这些应用程序中的每一个都带有自己的 Dockercompose 设置 将它们移动到另一台服务器会少很多痛苦。
所以我开始 Dockerize(和 Dockercompose)我所有的应用程序,这就是我学到的。
这是对我的一个旧应用程序进行 docker 化的示例提交:
这就是我们要构建的:
本教程分为两部分,一是docker化本身 这是这一部分,第二部分将关注如何 设置服务器并启动应用程序。
假设您的应用需要所有这些服务:
所以这是 4 个服务,我们将为每个服务创建一个 Docker 容器 其中,我们将使用 Dockercompose 来包装配置并构建 所有这些
我们在 App 的根文件夹中创建 Dockercompose 配置:
# ./docker-compose.yml
version: '3'
services:
db:
image: mysql:8.0
volumes:
- ./_data:/var/lib/mysql
restart: always
ports:
- 127.0.0.1:13306:3306
environment:
MYSQL_ROOT_PASSWORD: root
command: [--default-authentication-plugin=mysql_native_password]
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
让我们逐个查看服务。
db:
image: mysql:8.0
volumes:
- ./_data:/var/lib/mysql
restart: always
ports:
- 127.0.0.1:13306:3306
environment:
MYSQL_ROOT_PASSWORD: root
command: [--default-authentication-plugin=mysql_native_password]
一些cmets:
image: mysql:8.0
这是最容易配置的服务,因为它是一个标准
一,我们正在使用公共图像mysql:8.0。记得添加
版本,所以它不会在你不被注意到的情况下改变。
volumes:
- ./_data:/var/lib/mysql
我们在这里配置的一件重要的事情是进入volumes 部分。
我们将一个容器内部文件夹链接到一个外部文件夹。文件夹
有问题的是/var/lib/mysql,毫无疑问,这就是所有
数据库数据将被存储。我们不希望这些数据存储在
一个容器内部文件夹,因为如果是这样,它就不会在
容器重新启动。所以我们将它与一个外部文件夹链接起来,在这种情况下:
APP_ROOT/_data.
一个重要的结果是来自 MySQL 的二进制数据将被存储到 Application 文件夹中,因此我们应该确保我们不会将其发送到 repo:
echo "_data/" >> .gitignore
港口:
ports:
- 127.0.0.1:3306:3306
另一个内部/外部链接配置是ports。这是一个基本的。
默认情况下,内部 mysql 服务将侦听端口 3306,因此
我们使用此配置使其可以从外部访问
environment:
MYSQL_ROOT_PASSWORD: root
此图像需要设置此 ENVVAR 以设置root 用户
主 MySQL 数据库。我们将在此处包含它的事实可能是
安全问题,但我不会为这个问题提供解决方案
在本教程中。
现在我们必须配置 database.yml 以使用 MySQL docker 映像作为 host
# /config/database.yml
production:
<<: *default
host: db
database: myapp
password: root
看到host 配置指向db,这是由Dockercompose 创建的主机。
这是将成为家的容器的定义 用于我们的 Rails 应用程序。
app:
build:
context: .
dockerfile: ./docker/app/Dockerfile
volumes:
- .:/var/www/app
restart: always
depends_on:
- db
一些cmets:
dockerfile: ./docker/app/Dockerfile
这是我们对 Dockercompose 说的哪里可以找到构建配置 对于这个 Docker 容器。
volumes:
- .:/var/www/app
这里有一些链接。我们正在链接容器内部文件夹/var/www/app
使用我们应用的根目录。
我认为不是将我们的 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 任务
是 Rails 应用程序的依赖项。他们将是rake 电话或
curl 向我们的一些 Rails 应用程序端点发出请求。
这是因为我还将 APP_ROOT 域链接到内部文件夹。
cron:
build:
context: .
dockerfile: ./docker/cron/Dockerfile
volumes:
- .:/var/www/app
restart: always
depends_on:
- db
将为单个 Docker 容器提供每个服务。
对于每个容器,我们都需要一个配置文件。组织自己 我正在分配文件夹中的所有 Docker 容器配置:
./docker
它不需要任何 dockerfile,因为我们使用的是公共镜像。
# ./docker/app/Dockerfile
FROM ruby:3.0.2
# Install dependencies
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
# Install ffmpeg for video processing
RUN apt-get install -y ffmpeg
# 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"
# Basic folders (required by puma)
RUN mkdir -p tmp/pids
RUN mkdir -p tmp/sockets
# 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 rails assets:precompile
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
这是一个基本的 Rails 应用程序 Dockerfile。通过快速的 google 会话,您可能会发现 解释这里正在发生的所有事情。
在此处添加SECRET_KEY_BASE 可能存在另一个安全问题,但正如我所说
我不会因为安全问题而使本教程复杂化。我把它放在那里
作为如何为 Docker 容器设置自定义 ENVVAR 的示例。
在这个容器中,我们包含 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
# Get certificates
COPY etc/secret/certificate.crt /etc/ssl/certificate.crt
COPY etc/secret/certificate.key /etc/ssl/certificate.key
# 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 配置文件复制到容器中 作为默认配置。我们也做了一些动态替换 以避免在我们的配置模板文件中写入特定的路径。
这里我们处理的是 SSL 证书:
# Get certificates
COPY etc/secret/certificate.crt /etc/ssl/certificate.crt
COPY etc/secret/certificate.key /etc/ssl/certificate.key
请务必记住将我们的证书上传到文件夹./etc/secret/。我建议不要在您的存储库中添加这些文件并添加它们
部署应用程序后,手动在您的服务器中。将此文件夹添加到.gitignore:etc/secret。
这里是我们的 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;
}
# timeout config
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
# port 80
server {
listen 80;
return 301 https://$host$request_uri; # automatically redirected to https
}
# Default server
server {
# define your domain
# server_name localhost;
listen 443 ssl;
ssl_certificate /etc/ssl/certificate.crt;
ssl_certificate_key /etc/ssl/certificate.key;
client_max_body_size 2050M; # for big uploads
# 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|storage)/ {
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-Forwarded-Proto $scheme;
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;
}
}
配置非常简单。
正如我之前所说,我假设我们的 cron 任务是 将我们的 Rails 应用程序作为依赖项,因此该容器具有相同的配置 作为我们的 Rail App 容器和一些额外的东西:
# ./docker/cron/Dockerfile
FROM ruby:3.0.2
# Install dependencies
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs cron
# Install ffmpeg for video processing
RUN apt-get install -y ffmpeg
# 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"
# 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'
我在第一次部署中错过的一个重要部分是添加此文件。没有它,docker build 会很大,因为它们会包含你所有的数据库数据、日志和其他你不需要的东西。
# .dockerignore
log/
storage/
tmp/backup/
_data/
现在我们的 Rails 应用程序已经完全 dockerize,我们想要部署它。
我不会使用花哨的部署工具,就我而言,我非常高兴 git 在我的服务器中手动拉取我的应用程序的 repo。这当然不会 在专业环境中扩展,每天进行多次部署。但是为了简洁 我不会在本教程中介绍自动部署。
即使设置了最漂亮的 docker,我们仍然需要做很多手动操作 在我们的服务器上工作以准备支持我们的东西。
这包括:
一旦我们安装了所有依赖项,我们需要:
git clone https://github.com/fguillen/MyApp.git
docker-compose build
docker-compose up -d
检查一切是否顺利:
docker-compose logs
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.29.1/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/PlaycocolaBackend
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
Docker 化 Rails 应用程序绝非易事。这可能需要很多时间。 多试错。很多事情都失败了,你不知道为什么。
我希望本指南至少可以减轻所有这些痛苦。
一旦成功,下一次成功的机会就很大;)
【讨论】: