Nic Lin's Blog

喜歡在地上打滾的 Rails Developer

如何在 Jenkins 上用 Docker 跑 Rails + Rspec 做 CI

基本上要跑 Rails CI 通常有很多第三方服務搭 integrate 可以做到,但如果你遇到和我的環境類似,也許你可以考慮自己把 Rails 專案用 Docker 包起來後丟到 Jenkins 上去跑。

至於為什麼會這樣做呢?這是我遇到的場景

  • Rails 啟動有 data dependency (Redis 上沒「特別的」資料格式啟動不了)
  • Rails 啟動有特規的套件要裝(apt-get install 沒有的東西、或是要使用低版本的套件之類的…)
  • 沒有要付費使用第三方的 CI
  • 只有已經架好的 Jenkins

如果要能夠讓 Jenkins 把 code 拉下來跑 Rspec 測試,那麼就要去主機上裝很多套件(Ruby / MySQL / Redis / etc),而且還有指定低版本問題,再加上主機也不是只有單獨跑這個專案而已,為了不想要有其他影響的可能,所以最後決定就是把這些問題包到 Docker 裡面,這樣一來就可以把環境的部分處理掉,也可以有效隔離 dependency。

初步想法

這邊希望做到的是,Jenkins 從 Git Server 拉下專案,透過專案 Build image,然後用這個 image run test,所以會有一個場景是 Jenkins 上的 workspace 有了 Rails 專案,透過 Docker build 把 Rails 專案 sync 進容器的部分。

(Jenkins Pull Project) --> (Docker build image) --> (Run docker image)

如果 Docker Image 直接從 Ruby / MySQL / Redis 開始裝起,那每跑一次 Docker image 應該會久到爆炸,這不是一個有效率的作法,所以我們會把固定的環境先包成 Docker base,做一個基礎容器,然後再將 Rails 要用的容器堆疊在上面。

這樣一來,Jenkins 就可以在每次執行任務時,去拉下 Base Image 然後開始 Build Rails 專案,會節省掉要一直重新 Build Base Image 的部分。

所以我們計畫如下

  1. 先製作 Docker Base Image
  2. 包 test 專用 image
  3. 丟到 Jenkins 去跑

Docker Base

在這裡我做了一支 Image Base,主要安裝

  • GPG 指定版本號為 1 系列
  • Ruby 2.6.2
  • MySQL
  • Redis
  • SSDB

Dockfile 看要放哪都可以,這個可以不用放在 Rails 目錄裡。

# Docker Base
FROM ruby:2.6.2
MAINTAINER NicLin

# We don't like apt-get warnings.
ENV DEBIAN_FRONTEND noninteractive

# Basic package
RUN apt-get update && apt-get install -y exiftool build-essential libpq-dev nodejs imagemagick gnupg1
RUN apt-get remove --auto-remove gpg
RUN cp /usr/bin/gpg1 /usr/bin/gpg

# === MySQL ===
RUN apt-get -y install mysql-server mysql-client --no-install-recommends

# === Redis ===
RUN apt-get -y -q install redis-server

# === SSDB ===
RUN wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip
RUN unzip master
RUN cd ssdb-master && make

docker build -t niclin:base .

這包在本地 Build 完後,就推到 Docker Hub 上了,如果有自己的 Server 也可以推自己的。

Test Image

現在我打算包一個「跑 Rspec」專用的 Image,他的生命週期就是

(Build Image) --> (Run Test Case) --> (Remove!)

這裡的 Dockfile 檔案就是直接放在 Rails 根目錄,因為這是每次 Jenkins 拉新的 code 下來要直接 build 的 docker image。

FROM 如果沒有指定自己的 server url,默認就是依照用戶名和 image 名稱直接去 docker hub 上抓

# Using Base Image
FROM niclin/base

MAINTAINER NicLin

# 設定一個程式起始的目錄
ENV APP_HOME /usr/src/app
RUN mkdir -p $APP_HOME

# 在這邊先加入 Gemfile 並 Bundle
COPY Gemfile $APP_HOME/Gemfile
COPY Gemfile.lock $APP_HOME/Gemfile.lock
RUN cd $APP_HOME && bundle install --jobs 4 --without development production

# 將現在(本地)所在專案目錄加入到 Docker Image 內
ADD . $APP_HOME

# 連結需要的檔案
RUN ln -s /usr/bin/convert /usr/local/bin/convert
RUN ln -s /usr/bin/exiftool /usr/local/bin/exiftool

# 加入 Jenkins workspace folder gpg key
# 這是我專案要用到的部分,一般人可能不需要就是了,可以跳過這裡
ADD ./gpg_home $APP_HOME/lib/gpg_home

# 連結需要的設定檔
RUN cp $APP_HOME/config/settings.yml.ci $APP_HOME/config/settings.yml
RUN cp $APP_HOME/config/storage.yml.ci $APP_HOME/config/storage.yml
RUN cp $APP_HOME/config/database.yml.ci $APP_HOME/config/database.yml

RUN cd $APP_HOME && RAILS_ENV=test bundle exec rake assets:precompile

# 設定 Docker 的工作目錄
WORKDIR $APP_HOME

ENTRYPOINT ["/bin/bash", "ci_script.sh"]

上面這隻 Docker image 要用到的東西有

  • config/settings.yml.ci
  • config/storage.yml.ci
  • config/database.yml.ci
  • ci_script.sh

上面 .ci 的部分就是我希望這些設定是只有給 CI server 用的,所以獨立出來。

ci_script.sh 的部分如下

service mysql start &&\
service redis-server start &&\
/ssdb-master/ssdb-server -d start /ssdb-master/ssdb.conf

RAILS_ENV=test bundle exec rake ci:initialize &&\
RAILS_ENV=test bundle exec rake db:create &&\
RAILS_ENV=test bundle exec rake db:migrate &&\
RAILS_ENV=test bundle exec rspec

至於這個啟動腳本就是在 Docker 啟動時直接做,所以放在 ENTRYPOINT 跑

  1. 啟動 MySQL
  2. 啟動 Redis
  3. 啟動 SSDB
  4. 跑 Redis data initialize
  5. 跑 database 建立
  6. 最後一步,跑 Rspec

如果要在自己電腦上先測試,可以在本地可以自己包一遍

docker build -t project:test .

然後直接執行看看會不會跑完整個任務

docker run -it project:test

Jenkins 設定

套件使用:

  • docker build and publish plugin
  1. 拉 Project
  2. shell 前置作業,看有沒有什麼固定檔案要先放的,如果沒有可以跳過此步
  3. 設定 Docker build, Skip publish
  4. shell 跑 docker run

小技巧:docker plugin 中的 build tag 打 $BUILD_NUMBER, 這樣他就會依照數字下去跑類似 project/test:142 ,然後最後一個 shell script 執行

docker run --rm project/test:$BUILD_NUMBER

這樣就可以確保跑完刪掉,一次性的測試,不會塞太多 Image 在 Jenkins Server

參考資源

comments powered by Disqus