Rails 部署實踐 - 多階段建置
經過兩週的努力,我們已經可以製作出能夠運行 Ruby on Rails 的環境,然而這個狀態的環境依舊同時混合了「編譯」跟「運行」兩種狀態的套件,在過去我們需要透過 RUN
合併命令來清理,然而在新版的 Docker 提供了「多階段建置(Multi-Stage Build)」的選項,因此我們可以直接切割開來處理。
什麼是多階段建置
多階段建置的機制很有趣,原本我們在 Dockerfile 所撰寫的建置腳本只能產生一個容器鏡像,然而多階段建置允許我們產生多個鏡像。
透過這樣的方式,我們可以將安裝 Ruby Gem、素材預先編譯等等步驟分離開來,最後再利用 COPY
命令,將所需的檔案複製到最終生成的鏡像,這樣就可以避免許多建置階段產生的額外檔案被加入到部署用的鏡像之中。
如何使用多階段建置
要使用多階段建置非常容易,基本上只需要多次使用 FROM
即可。
1FROM ruby:3-alpine
2
3RUN echo "HELLO WORLD" > /tmp/example.txt
4
5FROM ruby:3-alpine
6
7COPY --from=0 /tmp/example.txt /root
我們最後建置的鏡像會是最後一個 FROM
所對應的,一般來說用 --from
選項可以指定要複製的來源,然而這樣並不容易管理,因此還會搭配 AS
來進行命名。
1FROM ruby:3-alpine AS source
2
3RUN echo "HELLO WORLD" > /tmp/example.txt
4
5FROM ruby:3-alpine
6
7COPY --from=source /tmp/example.txt /root
像這樣子偷過 AS
的標記,就會更容易管理不同階段的建置資訊,除此之外也能將不同的建制處理區分出來,更容易釐清每個步驟的意圖。
改善 Rails 鏡像建置
接下來就可以用多階段部署的方式,將我們在前幾週的處理,調整成多階段的版本,將多餘的檔案再次排除,近一步減少容器鏡像的大小。
1FROM ruby:3-alpine AS gem
2
3RUN apk add --no-cache build-base
4
5RUN mkdir -p /src
6COPY Gemfile Gemfile.lock /src
7
8RUN gem install bundler:2.3.3 \
9 && bundle config --local deployment 'true' \
10 && bundle config --local frozen 'true' \
11 && bundle config --local no-cache 'true' \
12 && bundle config --local system 'true' \
13 && bundle config --local without 'development test' \
14 && bundle install -j "$(getconf _NPROCESSORS_ONLN)" \
15 && find /src/vendor/bundle -type f -name '*.c' -delete \
16 && find /src/vendor/bundle -type f -name '*.o' -delete \
17 && find /usr/local/bundle -type f -name '*.c' -delete \
18 && find /usr/local/bundle -type f -name '*.o' -delete \
19 && rm -rf /usr/local/bundle/cache/*.gem
20
21FROM ruby:3-alpine
22
23COPY --from=gem /usr/local/bundle/config /usr/local/bundle/config
24COPY --from=gem /usr/local/bundle /usr/local/bundle
25COPY --from=gem /src/vendor/bundle /src/vendor/bundle
透過多階段部署,我們在第一階段加入 build-base
套件,安裝了可以編譯 C Extension 的環境,讓我們可以順利的把運行 Ruby on Rails 所需的 Gem 安裝。
接下來我們再從這個階段產生的檔案中,單純複製 Ruby Gem 的部分,這樣就可以避開 build-base
套件產生的檔案,進而縮小鏡像大小。
建置快取
雖然多階段建置可以製作非常乾淨的容器鏡像,然而原本的快取機制會無法正常運作。這是因為每一個階段都是單獨的鏡像,因此我們在進行建置的時候需要使用 --cache-from
選項,告知每一個階段的鏡像為何。
除此之外,也需要用 --target
選項先將不同階段的鏡像建置好,並且用於快取,也因此我們的建置指令可能會變成這樣。
1$ docker build \
2 --target gem
3 --cache-from rails:gem
4 --tag rails:gem .
5
6$ docker build \
7 --cache-from rails:gem
8 --cache-from rails:latest
9 --tag rails:latest .
如果沒有先另外進行建置,每次要進行多階段建置的時候還是會需要執行前面幾個階段的建置,而不會使用快取,因此在使用上還是需要多加注意。
如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用Rails 部署實踐回饋表單告訴我。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- Rails 部署實踐 - 補上 Rails 教學缺少的一塊
- Rails 部署實踐 - 以容器部署 Rails 的方案
- Rails 部署實踐 - 部署前置準備
- Rails 部署實踐 - 容器化 Rails 專案概述
- Rails 部署實踐 - 上傳容器鏡像
- Rails 部署實踐 - 伺服器搭建
- Rails 部署實踐 - 撰寫 Docker Compose
- Rails 部署實踐 - 使用 HTTPS 協定加密連線
- Rails 部署實踐 - 健康檢查
- Rails 部署實踐 - 滾動更新
- Rails 實踐部署 - 使用 Alpine 製作容器鏡像
- Rails 部署實踐 - 容器化的 Bundler 最佳設定
- Rails 部署實踐 - 多階段建置
- Rails 部署實踐 - 素材預先編譯
- Rails 部署實踐 - 容器進入點
- Rails 部署實踐 - 容器相關工具
- Rails 部署實踐 - 持續部署
- Rails 部署實踐 - 使用 GitLab CI 自動化建置
- Rails 部署實踐 - 使用 GitHub Actions 自動化建置
- Rails 部署實踐 - 使用 Watchtower 自動更新
- Rails 部署實踐 - Docker Swarm 與 Docker Compose
- Rails 部署實踐 - Docker Swarm 安裝與設定
- Rails 部署實踐 - 部署到 Docker Swarm
- Rails 部署實踐 - 整合 GitLab CI 自動部署
- Rails 部署實踐 - 使用 GitLab 的 Review Apps 機制
- Rails 部署實踐 - 部署不是終點