docker+rails+postgresql+redis+puma+nginx

看了两天的docker相关文章,就尝试把一个现有的小blog换成docker部署尝试一番.
前期有个疑问,capistrano一类的工具也可以很方便的部署,为什么还要docker.
有人说保持环境一致,这个不多做评价,不过个人感觉,rails分为三个环境,各有优势,为何要保持一致.
在我看来,capistrano和docker的主要区别,还是一个适合多机部署,一个适合单机部署.也有一些人说docker更适合微架构,不过我觉得多机部署也没有什么区别把,单点故障之类的都可以.
有些扯远了,还是言归正传吧.

我们首先得安装docker及相应的工具.

  • 安装docker

    1
    curl -sSL https://get.daocloud.io/docker | sh

    这里可以把docker添加到组里,安装成功之后会有这么一句话.
    sudo usermod -aG docker name

  • 安装docker-compose

    curl -L https://github.com/docker/compose/releases/download/1.7.1/docker-compose-Linux-x86_64 > \ /tmp/docker-compose && \ chmod +x /tmp/docker-compose && sudo mv /tmp/docker-compose /usr/local/bin

    然后我们验证是否安装成功.

docker --version docker-compose --version

这里主要是针对现存的项目进行处理.假设项目名称是 psp.
我们在项目主目录下新建一个文件
Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FROM ruby:2.3.1 #基本镜像
#author info
MAINTAINER Xiaozhu didmehh@163.com
#基本的依赖
RUN apt-get update && apt-get install -y build-essential libpq-dev nodejs
#设置环境变量
ENV RAILS_ENV production
ENV RACK_ENV production
上面两个是指定应用环境
ENV RAILS_ROOT /home/issue/www/psp
#创建工作目录
RUN mkdir -p $RAILS_ROOT
#指定工作目录
WORKDIR $RAILS_ROOT
#处理gem
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN gem install bundler
RUN bundle install
# 复制puma的配置
COPY config/puma.rb config/puma.rb
# 复制主目录
COPY . .
# 暴露3000
EXPOSE 3000
#运行shell脚本
CMD bash start_up.sh

上面的是Dockerfile的基本配置.发现上面有出现 config/puma.rb 以及 start_up.sh.

config/puma.rb在rails5里会自动创建,可以自己找.

start_up.sh 内容如下:

1
2
3
4
5
6
#!/bin/bash
# 处理静态资源
bundle exec rake assets:precompile
#启动puma
bundle exec puma -C config/puma.rb

这个是基本的配置,针对rails服务.下面来看看nginx的处理.

在主目录下创建 Dockerfile-nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM nginx
RUN apt-get update -qq && apt-get -y install apache2-utils
ENV RAILS_ROOT /home/issue/www/psp
WORKDIR $RAILS_ROOT
RUN mkdir log
COPY public public/
COPY config/nginx.conf /tmp/docker_example.nginx
RUN envsubst '$RAILS_ROOT' < /tmp/docker_example.nginx > /etc/nginx/conf.d/default.conf
# RUN rm -rf /etc/nginx/sites-available/default
# ADD config/nginx.conf /etc/nginx/sites-enabled/nginx.conf
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]

到这里,我们把基本的Dockerfile给创建了.
接下来我们需要在项目里写个nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#config/nginx.conf
upstream puma {
server app:3000;
}
server {
listen 80;
client_max_body_size 4G;
keepalive_timeout 10;
error_page 500 502 504 /500.html;
error_page 503 @503;
server_name xiaohesong.com;
root /home/issue/www/psp/public;
try_files $uri/index.html $uri @puma;
access_log /home/issue/www/psp/log/nginx.access.log;
error_log /home/issue/www/psp/log/nginx.error.log;
location @puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://puma;
# limit_req zone=one;
}
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
location = /50x.html {
root html;
}
location = /404.html {
root html;
}
location @503 {
error_page 405 = /system/maintenance.html;
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html break;
}
rewrite ^(.*)$ /503.html break;
}
if ($request_method !~ ^(GET|HEAD|PUT|PATCH|POST|DELETE|OPTIONS)$ ){
return 405;
}
if (-f $document_root/system/maintenance.html) {
return 503;
}
location ~ \.(php|html)$ {
return 405;
}
}

####上面配置完了nginx以及主应用,那么我们得把他们关联起来.
我们需要docker-compose
在主目录下创建docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: '2'
services:
app:
build: .
command: bundle exec puma -C config/puma.rb
volumes:
- .:/home/issue/www/psp
expose:
- "3000"
links:
- postgres
- redis
env_file:
- .secret.env
web:
build:
context: .
dockerfile: Dockerfile-nginx
links:
- app
ports:
- "80:80"
postgres:
image: postgres:9.4
ports:
- "5432"
volumes:
- ~/main-database:/var/lib/postgresql/data
redis:
image: redis:3.0.5
ports:
- '6379:6379'
volumes:
- ~/cache-database:/var/lib/redis/data
  • 可以发现在redis以及postgres加上了volumes.这个是可以设置共享数据,挂在数据卷,便于持久化存储数据.
  • redis以及postgres都是直接从远程仓库拷贝下来的镜像.hub docker
  • volumes前面的宿主机可以通过 docker volume create --name cache-database 来创建,如果不创建,会默认生成.可以通过 docker volume ls 查看.
    设置的别名如果过多,一个个的创建很麻烦,你也可以这样处理.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    version: '2'
    services:
    app:
    ...
    ...
    ...
    volumes:
    cache-database:
    main-database:
  • links是把需要的服务拿过来使用.例如app作为主服务,需要持久化存储的数据库.

  • 配置数据库的用户密码.

    1
    2
    3
    4
    5
    6
    postgres:
    image: postgres:9.4
    ...
    environment:
    POSTGRES_USER: pg_user
    POSTGRES_PASSWORD: secret password
  • app的配置里,我们有个env_file,这个可以在主目录下创建一个.env的文件.
    可以用来存放一些环境变量,或者可以直接shell脚本直接写文件到电脑系统配置里
    我们这里存放的主要是secret:

    1
    SECRET_KEY_BASE=ecd694a53572357f98c4644991ef5d6e27d46a1dc18d605b7e58c7143ff0122c868b63f771788c82fe825fd6945dfa0f322ec73dddcf19d1e6c85234a66eae44

SECRET_KEY_BASE可以通过 RAILS_ENV=production bundle exec rake secret 生成.

接下来配置数据库.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: 5
timeout: 5000
username: postgres
# please see the update below about using hostnames to
# access linked services via docker-compose
host: postgres #是 docker-compose.yml中的key,与app,web同列,redis同样
port: 5432
password: #<%= ENV['POSTGRES_PASSWORD'] %>
development:
<<: *default
database: psp_dev
test:
<<: *default
database: psp_test
production:
<<: *default
database: psp_pro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# config/redis.yml
defaults: &defaults
host: redis
port: 6379
development:
<<: *defaults
test:
<<: *defaults
staging:
<<: *defaults
production:
<<: *defaults

接下来,我们构建这些个服务.

1
docker-compose build

这里可能需要一些时间,构建好之后,我们先启动测试一下,可以不要以守护状态运行,直接:

1
docker-compose up

不出意外.可以发现启动成功.我们看下启动了哪些容器.打开一个标签页,运行以下命令查看:

1
docker ps

会有以下的输出:

1
2
3
4
5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f716c3ee2414 psp_web "nginx -g 'daemon ..." About an hour ago Up About an hour 0.0.0.0:80->80/tcp, 443/tcp psp_web_1
57702197c38c psp_app "bundle exec puma ..." About an hour ago Up About an hour 3000/tcp psp_app_1
415edf596c26 redis:3.0.5 "/entrypoint.sh re..." About an hour ago Up About an hour 0.0.0.0:6379->6379/tcp psp_redis_1
cecf281102b3 postgres:9.4 "docker-entrypoint..." About an hour ago Up About an hour 0.0.0.0:32770->5432/tcp psp_postgres_1

启动了也存在问题呀,数据库没有,那我们就创建一个数据库.

1
2
docker-compose run app rake db:create
docker-compose run app rake db:migrate

如果想删除所有的容器,可以运行下面这个命令:

1
docker rm `docker ps --no-trunc -aq`

Ps: 源码

###参考

###外传,遇到的问题:

  • 问题一:无法build容器.
    执行docker-compose build 无法正常构建.
    如下图所示:
    折腾了很久,换源也不行,在google找到了一个类似的 .把代理关了就好....

  • 问题二:nginx只有 welcome to nginx
    没有正常启动项目,就是没有和项目绑定在一起.
    找了很久,最后经过各种排查以及尝试.把nginx的Dockerfile-nginx 修改一下:

    1
    2
    3
    4
    COPY config/nginx.conf /tmp/docker_example.nginx
    RUN envsubst '$RAILS_ROOT' < /tmp/docker_example.nginx > /etc/nginx/conf.d/default.conf
    # RUN rm -rf /etc/nginx/sites-available/default
    # ADD config/nginx.conf /etc/nginx/sites-enabled/nginx.conf

可以发现下面的两行被注释了,以前习惯的把配置文件放在/etc/nginx/sites-enabled/nginx.conf 下面.这次就不行了.
这是为什么?进入nginx容器看下.
先找到nginx的容器id, 通过docker ps 查看当前运行的容器.假设输出如下:

1
2
CONTAINER ID | IMAGE | COMMAND | ...
f716c3ee2414 | ... | ... | ...

然后我们进入这个容器: docker exec -it f716c3ee2414 bash
进入容器之后 cat /etc/nginx/nginx.conf | grep include 会发现输出一下内容:
include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf;
这个 include /etc/nginx/conf.d/*.conf; 是包含配置文件的路劲.都在这个下面,然而我们改的是在/etc/nginx/sites-enabled/* 下面.所以说,配置文件没有生效,使用的默认的default配置.

  • 问题三: 启动时出错,docker-compose up 出错.
    出错是:
    1
    2
    3
    4
    5
    6
    ERROR: for redis driver failed programming external connectivity on endpoint psp_redis_1 (13693b22524bf940932c25f9b7f5dac45671f68fbc04fad1a76ed08274db5699): Error starting userland proxy: listen tcp 0.0.0.0:6379: bind: address already in use
    Traceback (most recent call last):
    File "<string>", line 3, in <module>
    File "compose/cli/main.py", line 63, in main
    AttributeError: 'ProjectError' object has no attribute 'msg'
    docker-compose returned -1

显示已经存在,很怪异吧.经过排查,是因为本地的redis已经启动.关了本地的redis服务就好.
sudo service redis-server stop
然后重启,docker-compose up 发现不会redis出错地址绑定关系.
又出现一个错误.

1
ERROR: Named volume "db:/var/lib/postgresql/data:rw" is used in service "db" but no declaration was found in the volumes section.

这个是因为语法错误,如下:

1
2
3
4
redis:
...
volumes:
- cache-redis:/var/lib/redis/data

这样就会出错,换一种写法,如下:

1
2
volumes:
- ~/cache-redis:/var/lib/redis/data

Note: 目前对docker知之甚少,后期会补充,有不是的地方,恳请留言,谢谢! 后期会继续补充!

blog原文