preloader
軟體工程

Docker + Docker-compose 建立 Rails app 的開發與正式環境經驗分享

緣起

我喜歡做 Rails app prototype 放在 Digital Ocean,Rails app A 5.2.0 已經部署在上面,最近想再部署其他小型 Rails app 到同一臺機器上,但發現部署其他小型 Rails app B 到相同正式機器時,無法正常啟動 Rails 5.2.0 環境,看 Log 訊息表示是 Passenger + Apache 環境無法讀到正確的 rails credential master key 給 B 使用,不論我調整設定 config.require_master_key 是否去使用 master.key 都是無法正常啟動。尋找過 stackoverflow 和相關 github issue tracker,也有人遇到多個 Rails app 共站的相同環境變數問題,推測原因可能是B Rails app 使用到 A Rails app 的環境變數,但目前無一勞永逸和適用全體的解法。

我考慮過是否要再開一臺機器給 B Rails app 使用,但認為目前不值得開的原因有二:

  1. 一個 Rails app 就開一台機器太花錢
  2. 現有單一機器的計算資源足夠,足夠讓 A、B Rails app 共站

  推測多個Rails app 共站失敗的原因後,我想到使用 Docker 來隔絕每個 Rails app 的環境,並且使用 host 環境的 Apache virtualhost 功能來達成共站。這樣做在未來升級相關元件時會比較輕鬆,例如:

  • 升級 host OS 版本,從 Ubuntu 14.04 升級成 18 以上版本;
  • 升級 Ruby 版本;
  • 更換 host 環境的 web server , 從 Apache 換成 Nginx ;
  • 升級單一 Rails app 爆紅須 scale out 用的 load balancer ,以及配上更多的 app server farm。

 

成果

B Rails app 成功用 Docker 和 Docker-compose 隔絕開 A Rails app ,B 和 A 能同時正常運作,達成我想要的多個 Rails App 共站。 B Rails app 目前部署到正式環境是包好 Docker image 執行,A Rails app 仍然維持使用 Capistrano 方式部署到正式環境。

架構

B Rails app server 包在 docker image 裡,由 host environment 的 apache 提供 web server 功能和 Reverse proxy 功能,使自己能被外部使用者使用。如下圖。

B app server + puma + postgres + Ruby

       |

       |                                                 http                                                               https

Docker-compose network(s)   <————>  apache reverse proxy at host env. <——> Browser

 

A Rails app server 仍然直接存在 host environment 中,並搭配 apache + passenger 來對外提供服務。如下圖。

                                                                                                           https

A app server <——> apache + passenger + ruby at host env. <————> Browser 

       |

       |

 postgres at host env. 

 

 

B app server 共站架構的參考說明,我會另外翻譯一篇文章介紹,架構主要來自這篇文章 https://codepany.com/blog/rails-5-and-docker-puma-nginx/ 的第六種做法 (Dockerize Rails app into container and run Nginx on host)。我的經驗是小公司用過這個架構去安排內部開發和測試環境,若是撰寫很多 Rails app for prototyping 而需要為了部署開很多 VPS 的人,很適合用這個架構的做法節省開支。

 

以環境區分 Dockerfile 和 docker-compose.yml 的考量因素

我用 Docker-compose + Docker 做好 B Rails app 的運作環境。因為想要以後串 CI/CD 工具和單元測試功能流程,所以我用三種環境區分:本機開發環境、測試環境和正式環境,去做Docker-compose.yml 和相對應的 Dockerfile 檔案。每個環境有自己的 docker-compose.ymlDockerfile 原因是,我偏好這樣的方式,每個環境的 docker-compose.yml 就是一個可運作的 Rails app 環境,而且不想要刻意為了減少維護的 docker-compose.ymlDockerfile 檔案數量,造成這兩個檔案內容變得複雜且難維護,這是考量後的取捨,當然你能用自己的做法。我希望 Dockerfile 寫的環境變數和變數值都是固定的或者是不常變動的,這樣能保持 docker image 不用因為值的更動而要重建 docker image。如果預期會頻繁變動的參數值,或者是變動參數值會很麻煩的類型,我就不會寫在 Dockerfile,而是用 docker-compose.yml 去載入即可。

這裡先提醒一下,因為我將 Rails app 和 Docker 運作環境分開用不同 Repo 管理,且做法:每個環境有自己的 docker-compose.ymlDockerfile,會需要注意 Docker repo 跟 Rails app repo 的目錄結構的彼此階層關係,後面提到時會再詳述。

 

開發、測試和正式環境

開發環境是要讓開發者在開始實做功能之前,不用煩惱建置環境的大大小小問題,且進入開發流程後,開發者容易開發和自行測試,所以會在 docker-compose.yml 用 volume predicate 再掛入 Rails app 程式碼,覆蓋 docker image Rails app 程式碼裡的所有檔案,以及掛入Rails app 使用的 .env 設定檔。

目前沒有實際的測試環境,但我仍製作適用測試環境的 docker-compose.ymlDockerfile 檔案,我定位測試環境是屬於 staging 意義,其不同於開發環境且更接近正式環境,開發者在自己本機測過程式碼後,再用測試環境去測一次,staging 測過沒問題就上到正式環境。測試環境的 docker-compose.yml 不會用 volume predicate 複寫 docker image Rails app 程式碼,也掛入適當的 .env 檔案,這個 .env 檔案內容不保證完全相同於 production 。

正式環境也是有自己的 docker-compose.ymlDockerfile ,跟測試環境一樣,不會再用 volume predicate 複寫 docker image Rails app 程式碼,也會掛入適當的 .env 檔案,這份 .env 就是符合正式環境所需的設定值。值得一提的是,我部署正式環境的做法,不同於社群上和 Docker 官方建議的做法:發布編好的 docker image 到 docker hub 或私有 hub ,再從正式環境拉下 docker image 佈署;而是在正式環境再建立一次 docker image。我這樣做的原因是,現階段不會頻繁更新 Rails app 且規模小,短暫中斷而影響使用者觀感對我來說不是損失,而且我就是要達成多 Rails app 共站的目標,所以能接受正式環境重建 docker image 和耗費資源的痛。

開發、測試和正式環境的 docker-compose.yml 當搭配專屬的 Dockerfile 時,會依照自己環境再載入額外的環境變數值或建置參數。

 

三個環境的 docker-compose.yml 和 Dockerfile 內容

本文前面提到我分開管理 Rails app repo 和 Docker repo ,這裏需要注意兩者在各種環境的階層目錄關係。以下是 docker-compose.yml 內容假設兩者 repo 的目錄路徑關係:

myHomeDir

      |

      | ——— rails_projects

      |                    |

      |                    |_____ B rails app——— Dockerfile.dev

      |                                                    |____Dockerfile.prod.mac

      |                                                    |____Dockerfile.prod.ubuntu

      |                                                    |____.env 

      |                                                    |____ .dockerignore

      |

      |———- docker_projects

                           |

                           |______B-rails-app-docker

                                                 |  

                                                 |____dev———docker-compose.yml

                                                 |                   |__Dockerfile.dev.template

                                                 |                   |_ .env

                                                 |                   |_ .dockerignore

                                                 |____prod

                                                             |

                                                             |___mac———docker-compose.yml

                                                             |                  |___Dockerfile.prod.mac.template

                                                             |                  |__ .env

                                                             |                  |__ .dockerignore

                                                             |___ubuntu ——docker-compose.yml

                                                                                   |__Dockerfile.prod.ubuntu.template

                                                                                   |__ .env

                                                                                   |__ .dockerignore 

 

因為分開管理兩個 Repo,當由指定環境(例如 prod.mac )的 docker-compose.yml 要啟動 Rails app in docker服務環境時,需要寫清 Dockerfile build image 會使用呼叫的情境(context),所以需要改動 docker-compose.yml 的 build image 預設位置,從點(.) 改變成 那個服務使用的 Dockerfile 相對於 docker-compose.yml 所在的目錄路徑 ( 例如

context: ../../../../rails_projects/blogApp

),和指名那個 Dockerfile 名稱(例如 Dockerfile.prod.mac)。

 

為什麼 docker-compose.yml context predicate 要寫這麼複雜呢?因為我曾踩過一個 docker build image bug,請參考本文下面的 Dockerfile.dev 內容:

Dockerfile 有個語句是

WORKDIR /app
COPY . /app

 

如果你寫成:

WORKDIR /app
COPY ../../myfile1 /app

你的用意是要將 Dockerfile.dev 上兩層目錄的 myfile1 目錄裡的所有檔案複製進 docker 的 /app 目錄中。不論你在 B rails app 目錄下指令:

docker build -f Dockerfile.dev

或者是在 B-rails-app-docker/dev 目錄下指令:

docker-compose up --build

,都會無法建立 blogapp docker image ,因為 DockerfileCOPY 指令只能使用在 Dockerfile.dev 所在目錄以及所在目錄的底下任意層數的目錄與檔案路徑,不能用在上面一層或上面好幾層的目錄和檔案路徑上。

Dockerfile 的內容有一部份參考 5xRuby 五倍紅寶石公司的公開 Dockerfile 簡介( https://5xruby.tw/posts/rails-docker-image/ ),例如 Ruby 語系、時區、環境變數 RAILS_ENV。bundle install 目前未照他們文章建議,在COPY複製整個專案目錄之前且在複製 Gemfile 和 Gemfile.lock 先做,也許等我有空嘗試成功後再來修改此文章。 已經測試過, rebuild image 真的有變快。

 

開發環境

Docker-compose.yml

version: "2.1"

services:
  blogapp: # docker-compose up 啟動後,docker 網路內,服務元件的名稱。
  image: blogapp&nbsp; # 建立服務元件所需要使用的 image 來源名稱
  build:
    context: ../../../../rails_projects/blogApp # 當需要 build 服務元件的 docker image時,要呼叫哪個位置的 Dockerfile , context 值很重要,因為跟 dockerfile 檔案內容的 copy 指令有關,Docker 軟體對此有些限制
    dockerfile: Dockerfile.dev # 被呼叫的 Dockerfile 名稱
  command: bundle exec rails s -p 3001 -b 0.0.0.0 # 當建好 image後,要下達什麼指令,此處是 在 docker 網路中的 port 3001,啟動 rails server
  ports:
    - 3000:3001 # host environment 的 port 3000 對應到 docker network 3001 , host environment 的服務可以在 port 3000 找到 docker 網路中的 blogapp

以上 docker-compose.yml 內容沒有 db服務,因為 Rails app 的 RAILS_ENV = development 開發環境本身自帶 sqlite3 服務,直接使用 Rails app 的 sqlite3。

 

Dockerfile.dev for rails app
FROM smikino/ruby-node:2-10 AS passenger_ruby # 使用 ruby and nodejs 版本,因為我發現如果當需要編譯 assets:precompiles ,使用純粹的 ruby 版本會無法編譯 assets,而且我之後打算使用 webpack 和 vuejs ,所以挑選 ruby with nodejs 的版本當做基底 image。2019.07.16 更新: 升級到 ruby 2.6.3 . 2019.12.29 更新: 升級到 2.6.5
LABEL maintainer=“[email protected]" # 設定LABEL 維護者的資料
LABEL Name=blogapp Version=0.0.1 # 設定 LABEL image 名稱和版本
EXPOSE 3000  # 這個 image 預設在 docker 網路中,可供信任的 docker 網路服務連線的 port ,我認為我的環境不須寫 expose ,因為此環境 docker-compose.yml 裡面 command 有指定 rails serer 要執行在哪個 docker network port


# Default Environment
ENV APACHE_VERSION 2.4.7 # 設定環境變數 這個 image 將要搭配的 apache 版本,本 image 中並未使用到 apache ,所以沒用到。

# Requirement
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev libpcap-dev libssl-dev # 為 Rails image 升級和安裝必要 Ubuntu 系統元件 , libpg-dev 是給 postgres 使用。 libpcap-dev 和 libssl-dev 不清楚詳細用途,很多 rails dockerfile 都有引入,所以我就引入。

# === Rails Application 開始建 Rails app 的 image 範圍
# FROM AS
FROM passenger_ruby

# locale for unicode
ENV LC_ALL C.UTF-8 # 設定 Ruby 使用的語系

# timezone
ENV TZ Asia/Taipei # 設定 Ruby 使用的時區

# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1

WORKDIR /app # 設定 Rails app image 工作目錄,此行之後的所有指令工作目錄都在 /app

COPY Gemfile Gemfile.lock ./ # 複製 Gemfile, Gemfile.lock 到現有工作目錄,所以是 /app

#2019.07.16 更新:dirty hack for workarounding use bunder version 2 to install: [https://stephencodes.com/upgrading-ruby-dockerfiles-to-use-bundler-2-0-1/](https://stephencodes.com/upgrading-ruby-dockerfiles-to-use-bundler-2-0-1/)

ENV BUNDLER\_VERSION 2.0.2
RUN gem install bundler && bundle install # 安裝 Gemfile 內容提到的 gem 套件

COPY . /app # 複製現有 Rails app 目錄的所有檔案到 /app 目錄之下

 

測試環境(staging, mac)

docker-compose.yml

version: “2.1" # docker-compose.yml 適用的 docker engine 版本, [https://docs.docker.com/compose/compose-file/compose-versioning/#compatibility-matrix](https://docs.docker.com/compose/compose-file/compose-versioning/#compatibility-matrix)

services:
  db: # Rails app config/database.yml可設定要連到哪一台機器, 本文後面會提到。db 是連線機器名稱,不用煩惱 ip 要寫什麼, docker network 自動幫你解析好名稱和 docker network ip 的對應關係
    image: howard/postgres_10.3:1.0 # 我自己包的 postgres docker image
    expose:
      - 5432 # 服務要開給 docker network 中其他服務連線的 port , 如果這個 image 的 Dockerfile 已經設定過約定俗成的連線 port ,就不須再設定。除非你因為特殊需求,而另開 port
    volumes:
      - /usr/local/var/postgres:/var/lib/postgresql/data # config, db and table data . 冒號左邊是我自訂 host environment 既有的 postgres 服務, 要讓 docker postgres 來用已經存在的資料庫資料。冒號右邊是 docker postgres 運作時存放資料庫資料的地方。這行的意義是用 host environment 的 postgres 檔案蓋過 docker postgres 存放的資料,使 docker postgres 能使用 host environment 的資料
      - /Users/xxxx/test_pg:/var/run/postgresql # connect.pid file specified only for mac # 特別創一個資料夾存放 docker postgres db 連線的 connect.pid 檔案。因為上一行 host environment 的 /usr/local/var/postgres 有存放 host postgres 的 connect.pid ,如果沒分開放 host environment 和 docker postgres 各自的 connect.pid ,會造成兩者之一的 postgres 會無法啟動。
    tty: true # 開個 tty 讓服務持續運作,不會當 docker-compose 建立好 image 並啟動後,自動關閉服務
  blogapp: # blogapp 服務名稱
    image: blogapp # 我先前已經建好 docker image 。如果沒建好,用 docker-compose up 時也會自動寫 tag 名稱值是這個名稱
    build:
      context: ../../../../rails_projects/blogApp # 當需要用 docker-compose build blogapp docker image 時,需要給 context 情境目錄值。請參考本文上面我提過 docker repo 和 Rails app 的目錄階層關係圖。
      dockerfile: Dockerfile.prod.mac # 在 context 目錄下,要用哪個 `dockerfile`  build blogapp docker image
    command: bundle exec rails s -p 3001 # blogapp image build 好後,要執行的指令,此例是啟動 puma server 並且開在 docker network 的 port 3001. 註: Rails 5之後預設 puma 是 app server
    environment: # 要設定的環境變數集合
      - RAILS_SERVE_STATIC_FILES=123 # just for display assets with browser. Value of 123 doesn't have any meaning # 因為想要用 Rails app 處理靜態檔案 assets ,根據 Rails guild 官方文件,需要設定這個參數有任意值
    ports:
      - “3000:3001" # host environment port 3000 對應到 docker network 的 3001,當有服務連線到 host environment port 3000 ,會連線到 docker network 的 port 3001
    depends_on: # 啟動 blogapp 服務之前,哪些在 docker network 的服務要先啟動。以此例是 db 要先啟動好,才啟動 blogapp
      - db

 

Dockerfile.mac (大部分內容跟 dev 環境的 Dockerfile.dev 就不再贅述)
FROM smikino/ruby-node:2-10 AS passenger_ruby # 2019.07.16 更新: 升級到 ruby 2.6.3, 2019.12.29 更新: 升級到 ruby 2.6.5

LABEL maintainer="[email protected]"
LABEL Name=blogapp Version=0.0.1

EXPOSE 3000

# Default Environment
ENV APACHE_VERSION 2.4.7

# Requirement
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev libpcap-dev libssl-dev

# === Rails Application
# FROM AS
FROM passenger_ruby

# locale for unicode
ENV LC_ALL C.UTF-8

# timezone
ENV TZ Asia/Taipei

# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1

# Set Rails to run in production
ENV RAILS_ENV production # 設定 RAILS_ENV 環境變數值是 production ,用意是讓所有 gem 套件明確知道,現在處於 production 環境
ENV RACK_ENV production # 設定 RACK_ENV 環境變數值是 production ,用意是讓所有 rack-base gem 套件明確知道,現在處於 production 環境

WORKDIR /app
COPY Gemfile Gemfile.lock ./

# 2019.07.16 更新:dirty hack for workarounding use bunder version 2 to install: https://stephencodes.com/upgrading-ruby-dockerfiles-to-use-bundler-2-0-1/
ENV BUNDLER_VERSION 2.0.2
RUN gem install bundler && bundle install # 安裝 Gemfile 內容提到的 gem 套件

COPY . /app

# Precompile Rails assets
RUN bundle exec rails assets:precompile # Rails app for production 需要編譯靜態檔案 assets,所以在 docker image 裡面先編譯

 

正式環境(ubuntu)
docker-compose.yml (大部分跟 staging mac 相近,所以不再贅述一些內容)
version: "2.1" # docker-compose.yml 適用的 docker engine 版本, https://docs.docker.com/compose/compose-file/compose-versioning/#compatibility-matrix

services:
  db:
    image: howard/postgres_10.3:1.0
    expose:
      - "5432" # 服務要開給 docker network 中其他服務連線的 port , 如果這個 image 的 Dockerfile 已經設定過約定俗成的連線 port ,就不須再設定。除非你因為特殊需求,而另開 port。這個我可以刪掉。
    ports: 
      - "5435:5432" # 在 production 目前因為我沒特別開特殊 port 用不到 port 5435,也是可以刪掉,但沒刪掉的原因是,之後若開多台 docker 機器在 app server farm ,只需連線到同一台 docker postgres 服務就好
    volumes:
      - /home/myUser/blogapp_pg_data:/var/lib/postgresql/data # 跟 docker-compose.yml for staging 用途類似,docker postgres 存放的資料庫資料另外指定在 host environment 的 /home/myUser/blogapp_pg_data
      - /home/myUser/blogapp_pg_connectpid:/var/run/postgresql # 存放 docker pg connect.pid 的檔案,將不會和 host environment 的 postgres 同時運作時衝突
    tty: true
  blogapp:
    image: blogapp
    build:
      context: ../../../blogapp
      dockerfile: Dockerfile.prod.ubuntu
      args:
        ARG_SECRET_KEY_BASE: ${SECRET_KEY_BASE} # 正式環境需要攜帶 Rails 5 app 編譯 assets:precompile 用到的 secret_key_base
    command: bundle exec rails s -p 3001
    environment:
      - RAILS_SERVE_STATIC_FILES="123" # just for display assets with browser. Value of 123 doesn't have any meaning ## 因為想要用 Rails app 處理靜態檔案 assets ,根據 Rails guild 官方文件,需要設定這個參數有任意值
    volumes:
      - /home/myUser/blogappconfig/.env:/app/.env # 額外掛入 Rails app 正式環境需要的執行參數,例如 app server 需要的資料庫帳密和 app 金鑰
      - /home/myUser/blogAppLog:/app/log # Rails app 層級的 log 導出在 host environment 的目錄下,方便管理和追蹤
    ports:
      - "3000:3001"
    depends_on:
      - db

 

Dockerfile.ubuntu(大部分跟 staging mac 相近,所以不再贅述一些內容)
FROM smikino/ruby-node:2-10 AS passenger_ruby #2019.07.16 更新: 升級到 ruby 2.6.3

LABEL maintainer="[email protected]"
LABEL Name=blogapp Version=0.0.1

EXPOSE 3000

# Default Environment
ENV APACHE_VERSION 2.4.7

# Requirement
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev libpcap-dev libssl-dev

# === Rails Application
# FROM AS
FROM passenger_ruby

# locale for unicode
ENV LC_ALL C.UTF-8

# timezone
ENV TZ Asia/Taipei

# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1

# Set Rails to run in production
ENV RAILS_ENV production
ENV RACK_ENV production
ARG ARG_SECRET_KEY_BASE # 取得 docker-compose.yml build blogapp docker image時,傳入的 SECRET_KEY_BASE 參數
ENV SECRET_KEY_BASE = $ARG_SECRET_KEY_BASE # 將傳入的 ARG_SECRET_KEY_BASE 值,設定給環境變數 SECRET_KEY_BASE, assets precompile 會用到。

WORKDIR /app
COPY Gemfile Gemfile.lock ./

#2019.07.16 更新: dirty hack for workarounding use bunder version 2 to install:https://stephencodes.com/upgrading-ruby-dockerfiles-to-use-bundler-2-0-1/

ENV BUNDLER_VERSION 2.0.2
RUN gem install bundler && bundle install # 安裝Gemfile內容提到的 gem套件

COPY . /app

# Precompile Rails assets
RUN bundle exec rails assets:precompile

 

這裡再次提醒一次,因為我的正式環境用 host environment 的 Apache 轉傳多個 Rails app 共站的 request ,所以 docker-compose.yml 沒有 web server 服務。.dockerignore 用途是設定哪些檔案和目錄,當 Dockerfile 執行 COPYADD 指令時,不列入動作的清單,如同 .gitignore 用途和寫法。

 

建立 postgres docker image

本文前面三個環境的 docker-compose.yml 都提到 db image 是我改原先 postgres 官方 docker image :

db:
 image: howard/postgres_10.3:1.0

 

為什麼我不直接使用 postgres 官方自己出的 Dockerfile 要自己改?因為我用到的資料庫編碼是 zh_TW.UTF-8,不同於官方預設的 en_US.UTF-8,而且我想要更改預設的資料庫名稱 POSTGRES_DB、資料庫使用者登入帳號 POSTGRES_USER和登入密碼 POSTGRES_PASSWORD。

自行調整的 postgres 的 Dockerfile

FROM postgres:10.3

LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="postgres 10.3 with zh_TW.UTF-8"

ENV POSTGRES_DB xxxxxx
ENV POSTGRES_USER xxxxxx
ENV POSTGRES_PASSWORD xxxxxxx
ENV LANG zh_TW.utf8

RUN localedef -i zh_TW -c -f UTF-8 -A /usr/share/locale/locale.alias zh_TW.UTF-8

 

改動 postgres 設定檔 postgresql.conf

請改動 listen_address 的值,變成:

listen_address = '*'

docker-compose.yml 檔案內其他的服務,也就是 docker network 中其他的服務,能連線到 docker network 的 postgres 服務。

 

改動 postgres 設定檔 pg_hba.conf

請新增一行資料,如上面 postgres 的 Dockerfile 提到的變數值,以下是範例:

#TYPE      DATABASE         USER            ADDRESS         METHOD

host        xxxxxx         xxxxxx         172.18.0.0/16     password

          # POSTGRES_DB   POSTGRES_USER    blogappIP       認證方法

 

設定 postgres docker 中的 postgresql.confpg_hbf.conf 能讓 docker network 的其他服務正常連到 postgres at docker 服務。

 

建立和測試 postgres image 問題集錦

當 host environment 正在執行一個 postgres 服務,docker-compose.yml 也正在執行 postgres 服務,兩個服務開的 port 都相同是 5432,且 docker-compose.yml 掛入 volume 目錄給 postgres docker 不同於 host environment 執行中的 postgres 目錄,兩者 postgres 都能正常運作且不會互相干擾。但如果 postgres at docker 掛入的 volume 目錄相同於 postgres at host environment,則兩者中至少有一個會無法啟動運作,原因是兩者的 connect pid 寫在同一個目錄,連名字都相同。

另外,docker-compose.yml db的部分不用特意寫出 expose 和 ports (host environment port mapping docker network port) 語句,就能使 docker-compose.yml 裡面的各個需要連到 docker postgres 正常連線,而不會誤連到 host environment 的 postgres。

 

改動 Rails app 的 config/database.yml 設定
production:
  host: db # 在 docker-compose 裡面寫的服務名稱
  port: 5432 # 你自行定義 Rails app 要如何連到 postgres 的 port

———

恭喜你,若設定到這裡,就已經建立好 B Rails app in docker,剩下 apache virtualhost 和 reverse proxy 功能,讓 B Rails app in docker 可以對外運作。

———

使用 apache 設定 reverse proxy 功能導向 request 到 Rails app in docker,將用其他文章 https://howardlee.cloud/blog/27 說明如何設定。

———

心得

每個開發框架用 Docker 和 Docker-compose 建立經驗稍有不同,要考慮搭配的慣例元件也不同,在這次用 Docker + Docker-compose 搭建 Rails app 服務經驗之前,已在公司用 Docker 和 Docker-compose 建立過:

  1. 各專案搭配 Apache + PHP 5.6 + MySQL 5.6,
  2. Laravel 5.5 + PHP 7 + Nginx + MySQL 5.6 的測試、開發和正式環境。

這些經驗不完全能用在這次經驗上,幸好網路分享的中文與英文教學文章很多,終於成功建立 blogapp built by Rails 5.2.0 範例程式的開發與正式環境。

 

未來改進方向

Dockerfiledocker-compose.yml 內容過程中,發現包好的 image 檔案常常太大,很少運用到 Docker 公司提倡的 multi-stage 包 container ,以及 blogapp Dockerfile 中,每次 build image 都需要安裝一次 gem, 兩者導致 image 檔案過大且 build image 耗時很長,常常需要 7 到 10 分鐘。若要改善 image 檔案太大和每次建立 image 時間過長,需要未來再花時間改善,因為會影響後面串 CI 工具的選擇和設定。這是有心從事 DevOps 工作的人,能夠展現價值的地方。我希望未來也能做到更進一步的限制 docker 使用資源和發出 Docker 耗盡資源或無法重啟的通知。

 

待做事項

  1. 將農產品價格網站的 Rails 5.2.0 app 也改用 Docker 和 Docker-compose 包裝,預計用 Apache 2.4.7 的 virtualhost 共站和 Reverse proxy 功能,跟其他 App 一起共站與規模化。
  2. 陸續將 Apache 換成使用 Nginx 。
  3. 升級 docker-compose.yml 的版本,從 2.1 升級到 3.x 以上的版本。因為 docker-compose.yml version 2.x 開始支援設定 service 用量限制, 3.x 開始妥善支援 Docker Swarm 的功能,也就是 Service 和 server 的群集溝通功能。

 

參考文件

  1. Postgres docker 無法連線 https://forums.docker.com/t/postgres-solved-fail-to-connect-postgresql-container/46924/4
  2. Dockerfile 撰寫問題和踩到的慣例 bug 1 https://blog.codeship.com/running-rails-development-environment-docker/  
  3. Dockerfile 撰寫問題和踩到的慣例 bug 2 https://blog.codeship.com/testing-rails-application-docker/ 
  4. Dockerfile 撰寫問題和踩到的慣例 bug 3 https://blog.codeship.com/deploying-docker-rails-app/ 
  5. Dockerfile 撰寫問題和踩到的慣例 bug 4 https://nickjanetakis.com/blog/dockerize-a-rails-5-postgres-redis-sidekiq-action-cable-app-with-docker-compose   
  6. Dockerfile example from 5xruby https://5xruby.tw/posts/rails-docker-image/
  7. Docker 的六種架構 https://codepany.com/blog/rails-5-and-docker-puma-nginx/   
  8. Postgres docker https://hub.docker.com/_/postgres/ 
  9. Workaround for installing bundler 2.0.2 with ruby 2.6.3 https://stephencodes.com/upgrading-ruby-dockerfiles-to-use-bundler-2-0-1/