【问题标题】:How to Dockerize a Rails application with mysql, nginx and cron tasks using docker-compose如何使用 docker-compose 对带有 mysql、nginx 和 cron 任务的 Rails 应用程序进行 Dockerize
【发布时间】:2019-01-26 22:04:43
【问题描述】:

我已经准备好部署我的 Rails 应用程序,我想包装所有依赖项,包括 nginx 作为前端 Web 服务器、mysql 作为数据库和 cron 以执行循环任务。

【问题讨论】:

    标签: ruby-on-rails docker docker-compose


    【解决方案1】:

    我们想要对 Rails 应用程序进行 docker 化的原因有很多。在 我的情况的主要原因是我想成为托管服务不可知论者。一世 想要轻松地将我的应用程序从一个托管服务移动到另一个托管服务 可能的摩擦。

    多年来,我一直在为一台非常昂贵的专用服务器买单 因为要在更便宜的地方重新安装所有东西非常困难。它可能 当您有一个要迁移的应用程序时没问题,但我有大约 20 个应用程序正在运行 在这个服务器中,每一个都有不同的特点。其中一些需要 cronjobs,其他需要特殊的外部工具,例如特殊版本的 FFmpeg, 数据库,...

    如果这些应用程序中的每一个都带有自己的 Dockercompose 设置 将它们移动到另一台服务器会少很多痛苦。

    所以我开始 Dockerize(和 Dockercompose)我所有的应用程序,这就是我学到的。

    这是对我的一个旧应用程序进行 docker 化的示例提交:

    这就是我们要构建的:

    配置所有 Docker 东西

    本教程分为两部分,一是docker化本身 这是这一部分,第二部分将关注如何 设置服务器并启动应用程序。

    服务

    假设您的应用需要所有这些服务:

    • MySql
    • 定时任务
    • Nginx(作为前端代理),带有 SSL
    • 应用程序(Rails 应用程序本身)

    所以这是 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
    

    让我们逐个查看服务。

    MySQL 服务

    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 应用服务

    这是将成为家的容器的定义 用于我们的 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 使用我们应用的根目录。

    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 容器提供每个服务。

    对于每个容器,我们都需要一个配置文件。组织自己 我正在分配文件夹中的所有 Docker 容器配置:

    ./docker
    

    MySQL 容器

    它不需要任何 dockerfile,因为我们使用的是公共镜像。

    Rails 应用容器

    # ./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 的示例。

    Nginx 代理容器

    在这个容器中,我们包含 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/。我建议不要在您的存储库中添加这些文件并添加它们 部署应用程序后,手动在您的服务器中。将此文件夹添加到.gitignoreetc/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 任务容器

    正如我之前所说,我假设我们的 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'
    

    .dockerignore

    我在第一次部署中错过的一个重要部分是添加此文件。没有它,docker build 会很大,因为它们会包含你所有的数据库数据、日志和其他你不需要的东西。

    # .dockerignore
    log/
    storage/
    tmp/backup/
    _data/
    

    服务器,配置和部署

    现在我们的 Rails 应用程序已经完全 dockerize,我们想要部署它。

    我不会使用花哨的部署工具,就我而言,我非常高兴 git 在我的服务器中手动拉取我的应用程序的 repo。这当然不会 在专业环境中扩展,每天进行多次部署。但是为了简洁 我不会在本教程中介绍自动部署。

    安装依赖项

    即使设置了最漂亮的 docker,我们仍然需要做很多手动操作 在我们的服务器上工作以准备支持我们的东西。

    这包括:

    • 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.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 应用程序绝非易事。这可能需要很多时间。 多试错。很多事情都失败了,你不知道为什么。

    我希望本指南至少可以减轻所有这些痛苦。

    一旦成功,下一次成功的机会就很大;)

    【讨论】:

    • 所以我的问题是:你的 database.yml 文件是什么样的?
    • @fguillen 当我们添加另一个应用程序时会发生什么?您的图像显示了 2 个 rails 应用程序。这有点令人困惑。
    • @kirqe 第二个 Rails 应用是用来执行 Cron 任务的,如果你没有就不需要它
    猜你喜欢
    • 2019-02-16
    • 1970-01-01
    • 1970-01-01
    • 2020-05-23
    • 2018-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多