📝

GitLab CIでMR毎にプレビュー環境を作ろう!

概要

フォルシアではアプリのリポジトリ管理のために、社内に GitLab のインスタンスを立てて運用しており、開発は GitLab の MR(Merge Request) の作成 → チームメンバーからのレビューというサイクルで行われています。
レビューの際、特に見た目の変更を伴う修正が入る場合には自分のローカル開発環境に開発ブランチを持ってきてアプリを立ち上げる必要が出てきます。
レビュー対象が増えてくると、ブランチを切り替えていくのもなかなか大変なので、もう少し簡単にレビューができないだろうかということで試してみました。

方針

タイトルの通りですが、以下のような方針とします。

  • MR が作成されたら対応するプレビュー環境が作成される。
  • MR のマージ/クローズに伴ってプレビュー環境を破棄する。

プレビュー環境をどこに立ち上げるかという点ですが、フォルシアでは社内で運用する Bot などを動かすためにコンテナを自由に立ち上げることのできるサーバーがあったためこれを利用します。

ここまでで決めたフローを図に起こすと以下の通りです。

アプリの用意

今回レビューを行う対象ですが、Zenn に上げるブログ記事とします。
フォルシアでは Zenn で公開する記事を GitLab にて管理しており、公開前に社内のエンジニアがレビューを行っています。
このレビュー時に zenn-cli を利用して、記事毎のプレビュー環境を立ち上げることを目標とします。
今回の記事では詳細を省略しますが、zenn previewを実行する Dockerfile も別途用意しました。

.gitlab-ci.yml の作成

今回作成する.gitlab-ci.yml は以下のようなジョブに分けて作成します。

  • build
    • zenn preview を実行するイメージの作成およびレジストリへのプッシュ
  • deploy
    • コンテナ実行サーバーにてコンテナを起動する
  • destroy
    • 起動中のコンテナを停止する

それぞれの設定を見ていきます。

build ジョブ

variables:
  REGISTRY: your-registry
  IMAGE_NAME: your-image
  TAG_NAME: ${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}

build:
  stage: build
  script:
    # コンテナ実行サーバーへの接続部分は省略
    - docker build -t ${IMAGE_NAME}:${TAG_NAME} -f docker/Dockerfile .
    - docker tag ${IMAGE_NAME}:${TAG_NAME} ${REGISTRY}/${IMAGE_NAME}:${TAG_NAME}
    - docker push ${REGISTRY}/${IMAGE_NAME}:${TAG_NAME}
  rules:
    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME

先ほど触れた Dockerfile を元にイメージの作成を行います。
今回は MR(≒ ブランチ)毎にプレビュー環境を作成するため、ブランチ名をイメージのタグに利用しました。
また if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAMEというルールを指定することで、MR が存在する場合にのみ CI を起動しています。

deploy ジョブ

続いて、実際にプレビュー環境を立ち上げる処理を記載します。

variables:
  CONTAINER_NAME: review-${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
  PREVIEW_HOST: http://your-preview-host

deploy:
  stage: deploy
  script:
    # コンテナ実行サーバーへの接続部分は省略
    - docker stop ${CONTAINER_NAME} || echo ${CONTAINER_NAME} is not running...
    - docker pull ${REGISTRY}/${IMAGE_NAME}:${TAG_NAME}
    - |-
      for port in $(seq 8000 8100);
      do
        echo "Check Port: ${port}"
        if [ `sudo lsof -i:${port} | grep LISTEN | wc -l` -eq 0 ]; then
          echo "Port ${port} is not used."
          docker run --name ${CONTAINER_NAME} --rm -d -p ${port}:8000 ${REGISTRY}/${IMAGE_NAME}:${TAG_NAME}
          echo "DYNAMIC_ENVIRONMENT_URL=${PREVIEW_HOST}:${port}" >> deploy.env
          exit 0
        fi
      done
  artifacts:
    reports:
      dotenv: deploy.env
  environment:
    name: review/${CI_COMMIT_REF_SLUG}
    url: $DYNAMIC_ENVIRONMENT_URL
    on_stop: destroy
  rules:
    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME

ここでのポイントは起動したコンテナにアクセス可能な URL (DYNAMIC_ENVIRONMENT_URL) を artifacts を経由して GitLab に渡しているところです。
これにより MR 内に「View app」というボタンが設置され、クリックすることで MR に対応したプレビュー環境にアクセスすることができます。

上記のボタンをクリックすると、無事に Zenn のプレビューが参照できました!

destroy ジョブ

destroy:
  stage: deploy
  script:
    - docker stop ${CONTAINER_NAME} || echo ${CONTAINER_NAME} is not running...
    - docker rmi ${REGISTRY}/${IMAGE_NAME}:${TAG_NAME} || echo Image is not exist...
  dependencies: []
  environment:
    name: review/${CI_COMMIT_REF_SLUG}
    action: stop
  rules:
    - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
      when: manual

先の deploy ジョブに記載の on_stop: destroy と destroy ジョブの action: stopを記載することで、MR のマージ/クローズ時のみに実行するジョブになっています。
これにより不要になったプレビュー環境を削除するということを実現しています。

まとめ

ということで、今回は GitLab CI/CD を使ったプレビュー環境生成方法をまとめました。
実際のアプリ開発で今回の仕組みを利用しようとすると、アプリがアクセスする API はどうする?データは?などの問題も出てきそうな気がするので、モックの方法なども勉強したいと思います。

プレビュー環境作成に限らず、MR に紐づいたパイプライン実行は何かと便利な気がするので、今後も GitLab CI/CD を活かした開発サイクルを回していきたいと思います。

この記事を書いた人

籏野 拓
2018年新卒入社

FORCIA Tech Blog

Discussion