🌏

GitLab Pagesと本番サイトの双方にJekyllで生成されたページをデプロイする

2023/02/02に公開

GitLab CI/CD を使って、GitLab.com 上で管理しているリポジトリから Jekyll を使って静的なサイトを構築し、ドラフト版(master以外)は GitLab Pages で、本番(master)は外部のサイト (FTPS接続を受け付けている) で公開するという運用をしています。

この場合に注意することをメモします。

使っている .gitlab-ci.yml の内容

image: ruby:3.2

variables:
  JEKYLL_ENV: production
  LC_ALL: C.UTF-8
  # DST_DIR is used to set root directory for HTTP(S) service.
  DST_ROOT: www
  # DST_DIR is used as base-url, it might be '/'.
  DST_DIR: /

cache:
  paths:
    - vendor/
    - dist/
    - .jekyll-metadata

before_script:
  - bundle install --path vendor
  - apt-get update -qy
  - apt-get install -y git-restore-mtime  

pages:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH != "master"'
  script:
    - /usr/lib/git-core/git-restore-mtime
    - bundle exec jekyll build -I -d public
  artifacts:
    paths:
    - public

deploy_pages:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "master"'
  script:
    - /usr/lib/git-core/git-restore-mtime
    - bundle exec jekyll build -V -I -d dist --baseurl "$DST_DIR"
    - lftp -e "
      set ftp:ssl-allow yes;
      open $FTP_SERVER;
      user $FTP_USERNAME $FTP_PASSWORD;
      mirror -X .* -X .*/ --reverse --ignore-time --verbose
      dist/ $DST_ROOT$DST_DIR;
      bye"

内容の解説

本番サイトと GitLab Pages とのパスの違いの吸収

GitLab Pages で公開される URL はプロジェクト名から自動生成されるため、本番サイトとルートの場所が違う構成になる場合があります。その場合はジョブの script のコマンドライン引数で baseurl を設定する(例: --baseurl "/") ことで対応しています。この際、コマンドライン引数による設定は、_config.yml による設定よりも優先されるのがポイントです。

利用する Docker Image

Docker Image としては、Ruby 公式の内、Jekyll がサポートしているバージョン[1]に対応する Ruby 公式イメージを使えば良いでしょう。
Ruby 公式の Docker Image では apt が使えるので、必要なライブラリなどは apt-get でインストールしていきます。

必要なプログラムは基本的に before_script の部分で指定しています。

Bundler キャッシュの活用

毎回、Bundler を再構成していると無駄が多いので、Bundler を --path 引数付きで実施し、インストール先のパスを cache として指定しています。

https://gitlab-docs.creationline.com/ee/user/project/pages/getting_started/pages_from_scratch.html#build-faster-with-cached-dependencies

テスト用ビルドと本番ビルドの切り分け

ジョブの設定として rules 以下の if 文でジョブの実行条件を指定し、コミットされたブランチにより挙動を変えています。

この際、ブランチは GitLab により自動的に設定される変数 $CI_COMMIT_BRANCH を利用して判定します。
https://gitlab-docs.creationline.com/ee/ci/variables/predefined_variables.html

本番サイトのビルド

ジョブ deploy_pages では本番サイト向けのビルド、およびアップロードを行っています。

以下の処理を行います:

  1. jekyll で静的サイトのデータを生成します。
  2. 生成されたデータを lftp で本番環境へアップロードします。

lftp による本番サイトへのアップロード

本番サイトへのアップロード手段としてFTP, FTPS, SFTPなどが使えるのであれば、lftp を使うのが比較的簡単です。このパッケージはデフォルトでは入っていないので、CIのレシピ中でインストールします。

lftp 用パスワードの秘匿

この際、.gitlab-ci.yml に直接FTPパスワードを書くのはセキュリティ上推奨できないので、GitLab のUIから設定できる(一般ユーザーには見えない)環境変数を使うと良いでしょう。
https://gitlab-docs.creationline.com/ee/ci/variables/#cicd-変数を-ui-で定義します
https://savjee.be/blog/gitlab-ci-deploy-to-ftp-with-lftp/

例の場合、$FTP_PASSWORD はUI設定の環境変数として定義しています。

ミラーリング効率化のためのオプション

lftp でミラーする際、通常はファイルのタイムスタンプ[2]とサイズを比較して差があった場合にファイルが差し替えられます。しかし、タイムスタンプの比較は正常に動作しないことが多いため[3]--ignore-time というオプションをつけておくと良さそうです[4]。ただし、--ignore-time が指定された場合、ファイルサイズの比較のみで変更の有無が判別され、サイズが変わらない変更の場合にアップロードが行われなくなるので[5]、パフォーマンスと確実性のバランスを考えて設定するかどうか判断してください。

FTPS をサポートしていない場合

対象のサーバが FTPS に対応していない場合は、 set ftp:ssl-allow no に変更してご利用下さい。

テスト用ビルドは GitLab Pages へ

ジョブ pages は GitLab Pages を使った公開です。

master 以外のブランチでは Job pages で GitLab Pages 向けのデータをビルドします。結果を public というフォルダに格納し、それを artifacts として指定することで、ビルド結果が gitlab.io で公開されます。

https://gitlab-docs.creationline.com/ee/user/project/pages/getting_started/pages_from_scratch.html

TODO: インクリメンタルビルドを使ってJekyllのサイト構築の時間を減らせる可能性があるが、キャッシュを使っても今の所想定通りの動作になっていないので、引き続き検討が必要です。

脚注
  1. Jekyll 4.1.1 の場合、Ruby は 3.2 以下である必要があるので、ruby:3.2 を使います。 ↩︎

  2. リモートのタイムスタンプはFTPのMDTMコマンドを用いて取得するようだが、このMDTMコマンドが期待したとおりに動かないことが多いようです。 ↩︎

  3. 変更がないファイルまでファイルアップロードが行われます。 ↩︎

  4. 参考: https://github.com/lavv17/lftp/issues/14 ↩︎

  5. Webサイトを Jekyll で構築するような場合、ファイルサイズが変化しない修正を実施する可能性は無視できるくらい低い。 ↩︎

Discussion