Logo

docker-compose部署实践

avatar jade 29 Jun 2018

将项目交付给一个没有 Rails开发经验的团队来部署上线成本非常高,Rails 常用的部署方式是使用 Capistrano 或者 Mina,如果对方团队的技术栈是基于 windows环境开发,那在本地跑项目的执行部署脚本的难度会非常的高 。这个时候 Docker就能很好的解决这个问题,Docker 将应用程序与该程序的依赖,打包在一个文件里面,对方只要执行这个文件就可以,不用担心环境问题。

Docker Compose 简介

Docker Compose是一个用来定义和运行复杂应用的Docker工具。使用Compose,你可以在一个文件中定义一个多容器应用,然后使用一条命令来启动你的应用,完成一切准备工作。

实战

安装环境

  • docker安装: 参考官网
  • Docker-Compose安装:参考官网

项目技术栈 Rails + postgres + nginx

项目核心使用 rails 开发, 使用 nginx作为HTTP服务器,数据库使用的是 postgres

rails:核心Web开发框架

postgres: 使用的数据库

nginx: 高性能的HTTP和反向代理服务器

所以docker-compose需要定义包含的镜像有

  • ruby
  • postgres
  • nginx

将docker-compose应用到现有的rails项目

1、在项目根目录创建docker文件夹, 用于存放docker 相关的配置

2、根据项目需要,创建如下目录结构

- docker
  - app
  	- Dockerfile
  - web
  	- Dockerfile
  	- nginx.conf

解释

  • app: 即对应我们开发的 web app,让rails跑在这个容器内
  • web: 即对应nginx服务,相关的ngxin服务会跑在web容器内

3、配置相关容器的Dockerfile

  • docker/app/Dockerfile
# Base image: ruby 基础镜像
FROM ruby:2.5.1

# 安装基础环境
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# 在镜像内创建项目文件夹,使用真实项目名替换__project_name__
RUN mkdir /__project_name__

# 设置工作目录
WORKDIR /__project_name__

# 将 Gemfile 和 Gemfile.lock 拷贝到容器对应的位置
COPY Gemfile /__project_name__/Gemfile
COPY Gemfile.lock /__project_name__/Gemfile.lock

# 设置容器内环境变量
ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true
ENV RAILS_LOG_TO_STDOUT true
ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD postgres

# 执行bundle install
RUN bundle install --without development test

# 将项目拷入容器内
COPY . /__project_name__
# 暴露 3000端口
EXPOSE 3000
# 前端编译静态文件
RUN RAILS_ENV=$RAILS_ENV bundle exec rake assets:precompile

  • 在项目目录下新建cert文件夹,将申请的ssl证书放入
  • docker/web/nginx.conf,以下是nginx.conf模版,需替换__project_name__为真实项目名
upstream __project_name___server {
    server app:3000;
}

server {
    listen       443 ssl;
    server_name  __domain_name__;
    root /visa-admin/public;
    try_files $uri/index.html $uri.html $uri @app;

    location @app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect off;
        proxy_pass http://__project_name___app_server;
    }

    location /.well-known/acme-challenge {
        root /__project_name__/public;
    }

    location ~ ^/(assets)/  {
        try_files $uri @app;
        gzip_static on;
        gzip_min_length 1k;
        gzip_buffers 4 16k;
        gzip_comp_level 7;
        gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        expires max;
        add_header Cache-Control public;
        if ($request_filename ~* ^.*?\.(eot)|(ttf)|(woff)|(svg)|(otf)$){
            add_header Access-Control-Allow-Origin *;
        }
    }

    access_log  /__project_name__/log/nginx_access.log;
    error_log   /__project_name__/log/nginx_error.log  error;
    error_page   500 502 503 504  /500.html;
    error_page   404 /404.html;
    error_page   422 /422.html;

    ignore_invalid_headers off;
    add_header X-Frame-Options SAMEORIGIN;
    client_max_body_size 30M;

    ssl_certificate   cert/__domain_name__.pem;
    ssl_certificate_key  cert/__domain_name__.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name __domain_name__;
    if ($host = __domain_name__) {
        return 301 https://$host$request_uri;
    }
    return 404;
}

  • docker/web/Dockerfile
# Base image nginx 基础镜像

FROM nginx

# 安装依赖
RUN apt-get update -qq && apt-get -y install apache2-utils

# 定义好Nginx应该在哪里查找文件
ENV RAILS_ROOT /var/www/__project_name__

# 设定镜像内工作目录
WORKDIR $RAILS_ROOT

# 创建存放日志文件夹
RUN mkdir log
# 复制静态文件到镜像内
COPY public public/

# 复制证书文件到镜像内
COPY cert /etc/nginx/cert

# 复制 Nginx 配置模版
COPY docker/web/nginx.conf /tmp/docker.nginx
# 用Nginx配置模板中的变量引用替换环境中的真实值
# 把最终配置放在指定位置
RUN envsubst '$RAILS_ROOT' < /tmp/docker.nginx > /etc/nginx/conf.d/default.conf

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

4、配置好对应的Dockerfile之后,用docker-compose将相应的多个镜像进行定义

在项目根目录创建 docker-compose.yml,内容如下

version: "3"

services:
  db:
    image: postgres:9.5
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432"
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
  app:
      build:
        context: .
        dockerfile: ./docker/app/Dockerfile
      command: bash -c "bundle exec rake db:migrate && bundle exec puma -C config/puma.rb"
      depends_on:
        - db
      links:
        - db
      volumes:
        - .:/__project_name__
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    depends_on:
      - app
    ports:
      - 80:80
      - 443:443

参数解释

services:服务,定义应用需要的一些服务,每个服务都有自己的名字、使用的镜像、挂载的数据卷、所属的网络、依赖哪些其他服务等等

volumes: 数据卷,定义的数据卷(名字等等),然后挂载到不同的服务下

image:指定镜像,如果本地不存在,Compose会尝试去docker hub pull下来

build:指定Dockerfile文件的路径,Compose将会以一个已存在的名称进行构建并标记,并随后使用这个image

command:重写默认的命令

links: 连接到其他服务中的容器,可以指定服务名称和这个链接的别名,或者只指定服务名称

ports:暴露端口,指定两者的端口(主机:容器),或者只是容器的端口(主机会被随机分配一个端口)

expose:暴露端口而不必向主机发布它们,而只是会向链接的服务(linked service)提供,只有内部端口可以被指定

environment:加入环境变量,可以使用数组或者字典,只有一个key的环境变量可以在运行Compose的机器上找到对应的值,这有助于加密的或者特殊主机的值

depends_on:依赖项,这个配置可以确保依赖服务被配置,但是并不能保证启动顺序

第一次部署在本地仓库根目录执行以下命令构建容器

// 构建
docker-compose build
// depends_on并不能保证启动顺序,需要先把数据库跑起来
docker-compose up -d db
// 创建数据库初始化数据库
docker-compose run app rake db:setup
// 构建运行,并在后台运行
docker-compose up -d

以后每次部署只需更新代码,然后执行下面两句命令

docker-compose build
docker-compose up -d

踩过的坑

  • depends_on并不能保证启动顺序,如果没先将db跑起来,执行 docker-compose run app rake db:create 会失败

总结

通过正确的 Docker 设置,软件部署过程可以比以往更快更便捷。无论应用程序在哪里运行,都可以确保一致的环境。能够实现便捷的交付。

Tags
docker-compose
rails
docker