基本上要跑 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 的部分。
所以我們計畫如下
- 先製作 Docker Base Image
- 包 test 專用 image
- 丟到 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 跑
- 啟動 MySQL
- 啟動 Redis
- 啟動 SSDB
- 跑 Redis data initialize
- 跑 database 建立
- 最後一步,跑 Rspec
如果要在自己電腦上先測試,可以在本地可以自己包一遍
docker build -t project:test .
然後直接執行看看會不會跑完整個任務
docker run -it project:test
Jenkins 設定
套件使用:
- docker build and publish plugin
- 拉 Project
- shell 前置作業,看有沒有什麼固定檔案要先放的,如果沒有可以跳過此步
- 設定 Docker build, Skip publish
- shell 跑 docker run
小技巧:docker plugin 中的 build tag 打 $BUILD_NUMBER
, 這樣他就會依照數字下去跑類似 project/test:142
,然後最後一個 shell script 執行
docker run --rm project/test:$BUILD_NUMBER
這樣就可以確保跑完刪掉,一次性的測試,不會塞太多 Image 在 Jenkins Server