部署 生产环境使用 Docker 部署 Rails 应用 Puma 和 Sidekiq

embbnux · 2016年05月23日 · 最后由 lidashuang 回复于 2019年07月06日 · 9935 次阅读

有幸拿到 docker beta 的测试资格,在 Mac OSX 下使用 docker 更加方便好玩了。这篇博文介绍如何在生产环境也就是线上利用 docker 实现快速部署以及横向扩展,为大规模负载均衡做准备。这里使用一个 docker 容器来跑 rails 应用,另一个容器来跑异步队列 sidekiq 等服务,数据库和 redis 使用 RDS 和云 redis,直接使用 docker 镜像的数据库也可以。

本文原文出处:生产环境使用 docker 部署 rails 应用 puma 和 sidekiq

一、生产环境使用 docker 前准备

首先你的 web 应用要足够干净,rails 也好,nodejs 也一样,不依赖于本地的任何东西,应该是一个 docker 镜像 pull 下来,加上一些环境变量等配置就能直接跑起来。

  1. 数据库 url 可配置,可以连接远程数据库,或者连接其他数据库容器
  2. redis 也应该是远程连接可配置,redis 独立出去,异步队列像 sidekiq 的也可以在单独容器里跑了,因为一个 docker 容器只支持一直进程跑,所以 server 和队列是分开的,通过 redis 通讯。
  3. 只处理动态流量,静态资源请走 CDN, 图片的上传也不是储存在本地磁盘的,图片的上传可以上传到容器再由容器传到云存储服务器,或者直接由客户端上传到云存储服务器,数据库里只存图片的地址就可以。

二、配置生产环境 Dockerfile

首先讲一些我的工程目录结构,主要就是 rails 的结构,这里只列出关键的文件目录结构:

|__ app
|__ config
|__ |__ puma_docker.rb
|__ |__ database.yml
|__ |__ redis.yml
|__ |__ sidekiq.yml
|__ public
|
|__ Dockerfile
|__ Gemfile
|__ Gemfile.lock

Dockerfile 原则应该是只添加有需要的:

##########################################
# Dockerfile for rails app with puma and sidekiq postgres
# Author: Embbnux Ji
# HomePage: www.embbnux.com
##########################################

FROM ruby:2.3.1

MAINTAINER Embbnux [email protected]
RUN apt-get update && apt-get install -y build-essential libssl-dev libpq-dev libxml2-dev libxslt1-dev nodejs git imagemagick libbz2-dev libjpeg-dev libevent-dev libmagickcore-dev libffi-dev libglib2.0-dev zlib1g-dev libyaml-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*

ENV APP_HOME /app

RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile $APP_HOME/
COPY Gemfile.lock $APP_HOME/

RUN bundle install

COPY . $APP_HOME

RUN bundle exec rake assets:precompile RAILS_ENV=production

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

三、配置 rails 工程

我的 rails 是使用 puma 来作为 web 服务器的,docker 自然也一样,所以 app 容器默认是执行 puma 启动 server 的命令,对外输出接口为 8080, 使用 nginx 代理流量到这个服务端口即可。 puma 这里需要配置为暴露 8080 端口:

# puma_docker.rb:

threads 4, 16
workers 1
environment 'production'
bind 'tcp://0.0.0.0:8080'
preload_app!

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end

数据库配置:

# database.yml:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000

production:
  <<: *default
  pool: 10
  database: <%= ENV["DATABASE"] %>
  host: <%= ENV['DATABASE_HOST'] %>
  username: <%= ENV["DATAUSER"] %>
  password: <%= ENV["DATAPASSWD"] %>

redis 的配置也一样,redis 的地址用环境变量代替:ENV["REDIS_URL"]

四、使用远程仓库自动构建

我这边采取的远程仓库方案是 Github 加 Docker Hub,实现代码更新自动构建镜像,方法很简单,就是使用 docker hub 的自动构建功能,关联 github 仓库即可。需要在工程根目录下有一个 Dockerfile. 这样 git push 代码后过几分钟镜像就会被自动构建完成。 也可以使用 docker hub 的 webhook 功能实现构建完成自动部署,这个我暂时没测试。

五、部署 docker 镜像到生产环境

docker 镜像的部署很简单,直接 pull 下来跑就可以了。这里为了演示,数据库和 redis 也用一个单独的 docker 容器来跑,模拟远程连接,云储存用 docker 的 volume 功能实现,具体如下:

# 下载redis镜像
docker pull redis
# 下载postgres镜像
docker pull postgres
# 下载已经自动构建完成的app镜像
docker pull embbnux/app
# 后台运行redis容器
docker run --name app_redis -d redis
# 后台运行postgres容器, 指定用户名密码
docker run --name app_postgres -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -e POSTGRES_DB=app_db -d postgres
# 后台运行app容器, 环境变量使用.env.docker文件传入, 映射容器的8080端口到本地的8080端口
docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres -v /var/www/public/uploads:/app/public/public -v /var/log/app:/app/log --name app_web -p 127.0.0.1:8080:8080 -d embbnux/app
# 上传assets文件到cdn
# docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres --name app_assets --rm embbnux/app rake cdn:upload_assets
# 运行sidekiq容器
docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres -v /var/www/public/uploads:/app/public/public -v /var/log/app:/app/log --name app_sidekiq -d embbnux/app bundle exec sidekiq -C config/sidekiq.yml

环境变量文件如下:

# .env.docker
RAILS_ENV=production
SECRET_KEY_BASE=1237293729347238719422b4e25fe42a311bc4e5ffb242397934cbad3adabfbcfae4b431a5029ad6486bce777382470327493287402
DATABASE_HOST=app_postgres
DATABASE=app_db
DATAUSER=user
DATAPASSWD=password
REDIS_URL=redis://app_redis:6379

以后升级代码,只需要把 app pull 下来跑就可以了,多机器部署建议用 capistrano 等工具。 在开发环境使用docker 快速构建 rails 开发环境可以看之前的博客。

本文原文出处,Embbnux 博客,生产环境使用 docker 部署 rails 应用 puma 和 sidekiq.欢迎转载,转载请注明原文出处,并保留原文链接

正好用到了。。。学习了

赞,按 LZ 教程操作了一遍确实不错。如果是国内下载 docker 镜像慢的话可以跑下面的命令设置:

pinata get daemon | jq -cm '."registry-mirrors" = ["https://XXXXX.mirror.aliyuncs.com"]' | pinata set daemon -

镜像加速可以用阿里云的(https://dev.aliyun.com/search.html),也可以用网易。

uploads 和 logs 目录挂载,我觉得可以写进 Dockerfile 里

学习了。支持。我之前研究了自发现服务。然后感觉用起来就省很多事情了。

有配合 jenkins 使用的教程吗?

谢谢楼主分享,学到了新知识

# 后台运行redis容器
docker run --name app_redis -d redis
# 后台运行postgres容器, 指定用户名密码
docker run --name app_postgres -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -e POSTGRES_DB=app_db -d postgres
# 后台运行app容器, 环境变量使用.env.docker文件传入, 映射容器的8080端口到本地的8080端口
docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres -v /var/www/public/uploads:/app/public/public -v /var/log/app:/app/log --name app_web -p 127.0.0.1:8080:8080 -d embbnux/app
# 上传assets文件到cdn
# docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres --name app_assets --rm embbnux/app rake cdn:upload_assets
# 运行sidekiq容器
docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres -v /var/www/public/uploads:/app/public/public -v /var/log/app:/app/log --name app_sidekiq -d embbnux/app bundle exec sidekiq -C config/sidekiq.yml

这些docker run ...的操作可以组织到 docker-compose.yml 配置文件里,通过 Docker Compose 来管理各个容器的衔接和配置,会不会显得更方便一点?

另外,因为 Rails 容器正常运行,需要连接 DB 容器并执行 Migrate 操作。

这里是不是应该在所有容器启动之后,再配合 GitLab-CI 这类持续部署工具做接下来的步骤?

因为运用了 Docker 完全封装了环境和源码,很多观念跟 Cap/Mina 工具不一致,是不是应该更换成 GitLab-CI 或 Jenkins 了?

#7 楼 @pinewong 我记得现在生产环境还不推荐用 docker-compose。是要配合 CI 的,从自动构建 到 自动测试 到 自动部署,这里说 cap 只是为了实现多机同时部署,不用也可以,jenkins 上调用 cap 去部署,或者直接用一些第三方提供的 docker 云管理也可以

#9 楼 @embbnux 最近在弄部署,Docker Compose 是有挺多不如意的地方,不过暂时没有找到更好组织这些镜像容器的方案(似乎可以用 makefile,但手动管理命令成本也高,而且以后不好跟着官方升级),所以先用着吧。

用 Cap 多机部署是挺方便的,适用场景的工具用着也行吧。

pinewong 运维环境简单化 - 容器封装 提及了此话题。 11月19日 18:57

按照这种部署 rails 的方法,每次都要把 rails 的代码拷贝到 docker 镜像里面,感觉效率很低。 是不是可以把 rails 的代码作为卷加载到 docker 里面,每次只去启动一个 app server 就可以了? 而且这种启动 rails 的方法,导致 dockerfile 每次都要在 copy 命令之后重新构建,不能利用镜像缓存,相当于每次想部署一次应用都要去构建一次 docker 镜像,这明显不合理,一个 rails 应用在上线后,一般其依赖的环境是比较稳定的。 说了这么多,我想说的是:用 docker 来部署 rails 应用实质上就只需要部署 rails 的 app server 即可

#13 楼 @uestc_bird 并不同意你的观点,首先 rails 一般是敏捷迭代开发,依赖的环境比如 gem 包,以及前端文件,在每次迭代后都会有更新,每次更新后把这些 build 在镜像里可以保证环境的统一,而且构建也是有缓存的,比如除非 Gemfile 变化,不然不会重新构建这一步的。这里的镜像都是无状态的,每次启动一个 container 也就是一个 app server

#14 楼 @embbnux 1. 前端资源和后端 server 的分离我个人觉得是非常有利于加快开发的速度的(当然这个目前争议也比较大),rails5 的 api 化也说明了这一点;

  1. 如果把 rails 的代码和静态资源放入 docker 中也会导致生成的 image 过大;
  2. 每次代码变更,哪怕是改了一行代码都要 rebuild image,这个很明显不爽;
  3. docker build 时,在首次遇到 COPY 指令之后就不再使用之前构建生成的中间镜像的缓存了;

我想请问为什么我运行# 运行 sidekiq 容器/这一步,但是我的 sidekiq 容器并没有跑起来,但是我的 redis,已经是安装成功了的 (链接不上 redis,127.0.0.1:6379),

yu7272yu 回复

host 为 redis:6379

需要 登录 后方可回复, 如果你还没有账号请 注册新账号