GitlabでCI/CD(DockerでReactアプリをデプロイ)

プログラム
スポンサーリンク

現在参画しているプロジェクトで、CI/CD(Continuous Integration/Continuous Delivery:継続的インティグレーション/継続的デリバリー)の担当になることになりました。ビルドやテスト、デプロイの自動化を行うことになります。

そのプロジェクトではGitlabを利用しているため、私もGitlabでのCI/CDを勉強することにしました。

とりあえずの目標として、ビルド→ユニットテスト→デプロイという流れをGitlabのCI/CDでやってみることにしました。色々調べた結果、なんとかデプロイまで完了したのでその手順をこのページに纏めます。

CIはともかく、CDはデプロイ対象によってかなりの差があります。とりあえず無料で試せるところということで、この記事ではHerokuにDockerコンテナをデプロイすることにしました。

この記事を読むとわかること
  • GitlabでCI/CDを行うための基礎
  • npmとyarnでのCI(ビルド、テスト)方法
  • NginxのDockerコンテナを使ったReactアプリのHerokuへのデプロイ方法
前提条件
  • ReactとDocker、Gitlabの基本を知っている
  • Herokuのアカウントを持っている

この記事のソースコード

いつもならGithubにソースコードを公開するのですが、今回はGitlabの話なのでGitlabにコードを公開しています。

土谷俊介 / gitlab-react
GitLab.com

CI/CDを行う場合は、cloneしたものをGiltabにpushするだけだと動きません。Gitlabのリポジトリに設定を行う必要があるので、記事を読んでからpushすることをおすすめします。(masterブランチにpushするとCI/CDが動きます。先にpushしてもエラーになるだけで大きな問題は発生しません)

GitlabとHerokuの事前準備

CI/CDの設定はGitlabリポジトリのルートフォルダに.gitlab-ci.ymlというファイルを作成して、このファイルを編集することで行います。

ただ、その前にいくつか準備が必要なのでその説明から。

Herokuの準備

前提条件に書いたとおり、Herokuアカウントは持っている前提で話を進めます。

まずは今回デプロイする先のアプリケーションを作成しましょう。Herokuのログインページからログインして、[New]→[Create new app]を選択します。

Herokuのアプリケーション作成画面

この画面のApp nameに任意の名前を入力して、[Create app]ボタンを押せば完了です。App nameは他のユーザと重なってはいけません。(例えば「test」とかはすでに他のユーザに使われているので不可)regionはアジア圏は存在しないのでUnited Statesのままで良いでしょう。

作成したApp nameは後で使います。

Gitlabの準備

Gitlabの基本は知っている前提なので、リポジトリの新規作成などは飛ばします。実験用になにか任意のリポジトリを作成しておいてください。

CI/CDを行うリポジトリを開いたら、サイドバーの[Settings]→[CI/CD]と選択して、[Variables]という項目の横にある[Expand]を押します。すると以下のような画面になるはずです。

GitlabのCI/CDセッテイング、Variablesを開いたところ

[Add variable]ボタンを押して、2つの値を登録します。

  • HEROKU_APP:先程登録したApp name
  • HEROKU_TOKEN:HerokuのAPI Key(取得方法は下で説明)

API Keyは以下の方法で確認できます。

  1. 画面右上のアイコンをクリック→Account settingsを選択
  2. 画面を少し下にスクロールしてAPI Keyを表示し、[Reveal]ボタンを押す

HerokuにデプロイするDockerイメージの準備

ローカルでnginxのコンテナを動かすだけなら簡単だったのですが、Herokuでは少し工夫が必要でした。nginxのイメージにあるデフォルトの設定のままHerokuにデプロイすると、以下のようなエラーが発生してnginxがうまく起動しません。

bind() to 0.0.0.0:80 failed (13: Permission denied)

Herokuだと80番ポートを使うことができないようです。

そこで、少しおまじないをしてHerokuでもnginxのコンテナを使えるようにします。

nginxの設定ファイルを作成する

nginxはデフォルトだと80番ポートを使いますが、環境に応じたものに変更しなくてはなりません。以下のような設定ファイルをルートフォルダに作成し、コンテナデフォルトの設定と入れ替えます。

nginx.conf

server {
  listen 0.0.0.0:$PORT;

  location / {
    root /usr/share/nginx/html;
    index index.html;
  }   
}

ほとんどデフォルトと変わりませんが、listenするポートの部分を変数にしました。

Dockerファイルを作成する

続いてDockerfileです。

FROM nginx
COPY build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'

一番最後の行が鍵になります。Heroku環境だとコンテナがlistenするポートが変数PORTに格納されるため、設定ファイルの$PORTをlistenするポートに変更してからnginxを実行するようにしました。

なお、このDockerファイルでコンテナをローカル起動する場合は以下のようなコマンドで環境変数PORTを設定してあげる必要があります。

docker run -e PORT=80 -d -p 80:80  react/nginx

コンテナ名がreact/nginxで、「-e PORT=80」の部分でDockerに渡す環境変数PORTを指定しています。

Gitlabの.gitlab-ci.ymlを編集する

ここまで準備を整えたので、いよいよGitlabにCI/CDの設定をしていきましょう。

Gitlabではリポジトリ直下のディレクトリに.gitlab-ci.ymlというファイルを作成することでCI/CDの設定を行います。拡張子の通りYAML形式です。どのようにCIとCDを行うのかを記載します。

image: node

# CI/CDの順番を定義
stages:
  - build
  - test
  - deploy

# Reactのインストールとビルド
# 結果はキャッシュに1時間残す
build:
  stage: build
  artifacts:
    paths:
      - build
      - node_modules
    expire_in: 1 hour
  script:
  - npm install
  - npm run build

# テストを実行
test:
  stage: test
  script:
    - npm run test

# Herokuにデプロイ
push-heroku:
  stage: deploy
  only:
    - master
  image: docker:stable
  services:
    - docker:dind
  script:
    - echo $HEROKU_TOKEN | docker login --username=_ --password-stdin registry.heroku.com
    - docker build --iidfile imageid.txt -t registry.heroku.com/$HEROKU_APP/web .
    - docker push registry.heroku.com/$HEROKU_APP/web
    - apk add --no-cache curl
    - |-
      curl -X PATCH https://api.heroku.com/apps/$HEROKU_APP/formation --header "Content-Type: application/json" --header "Accept: application/vnd.heroku+json; version=3.docker-releases" --header "Authorization: Bearer ${HEROKU_TOKEN}" --data '{ "updates": [ { "type": "web", "docker_image": "'$(cat imageid.txt)'" } ] }'

上から順番に説明していきます。

利用するイメージとCI/CDの順番を定義

image: node

# CI/CDの順番を定義
stages:
  - build
  - test
  - deploy

最初にimageで各処理で利用するデフォルトのDockerイメージを指定します。Reactのビルドとテストを行うので、ここではnodeです。後で出てきますが、各処理個別での指定もできます。

さらにどの様な順番で処理を行うかをstagesで指定します。例えばtestに失敗した場合はその後の処理は実行されません。

指定する処理の名前は、各処理を定義する場所でstageとして指定します。

Reactのインストールとビルド

# Reactのインストールとビルド
# 結果はキャッシュに1時間残す
build:
  stage: build
  artifacts:
    paths:
      - build
      - node_modules
    expire_in: 1 hour
  script:
  - npm install
  - npm run build

この部分ではReactに必要なパッケージのインストールとビルドを行い、それぞれの結果を1時間保存するという処理を定義しています。

出だしのbuildは任意の文字列で大丈夫です。後で読み直すときにわかりやすい名前がいいでしょう。

続いてstageでこの処理のラベル名を指定します。このファイルでは先程指定した一番先頭の文字列と一緒でbuildとしていますが、別に異なる名前でも構いません。stagesで指定するのはこのstageで指定した名前になります。

artifactsは指定したパスのファイルをexpire_inの時間だけ保存するという指示です。これを行わないと、次の処理にインストールしたモジュールやビルド結果を持ち越せません。

scriptで実際に行うコマンドを入力していきます。今回の場合はnpmを使ったパッケージのインストールとビルドです。見慣れた処理なので、違和感は感じないと思います。

例えばこのbuildステージでビルド、テスト、そしてデプロイと全部まとめて処理を行うことも可能です。その場合だと途中で処理に失敗すると何が原因なのかわかりにくくなります。もちろん、処理結果のログがGitlabに残るので、それを確認することは可能です。しかし、どこで問題があったのかの視認性に劣るので余りおすすめはしません。

Reactのテスト

# テストを実行
test:
  stage: test
  script:
    - npm run test

続いてテストを行うステージの定義です。こちらはビルドより簡単なので説明は不要でしょう。

今回は作りませんでしたが、例えばESLintなどでコードの静的解析をする処理を追加するなら似たような形になるかと思います。

Herokuへのデプロイ

# Herokuにデプロイ
push-heroku:
  stage: deploy
  only:
    - master
  image: docker:stable
  services:
    - docker:dind
  script:
    - echo $HEROKU_TOKEN | docker login --username=_ --password-stdin registry.heroku.com
    - docker build --iidfile imageid.txt -t registry.heroku.com/$HEROKU_APP/web .
    - docker push registry.heroku.com/$HEROKU_APP/web
    - apk add --no-cache curl
    - |-
      curl -X PATCH https://api.heroku.com/apps/$HEROKU_APP/formation --header "Content-Type: application/json" --header "Accept: application/vnd.heroku+json; version=3.docker-releases" --header "Authorization: Bearer ${HEROKU_TOKEN}" --data '{ "updates": [ { "type": "web", "docker_image": "'$(cat imageid.txt)'" } ] }'

最後にHerokuへのデプロイです。

onlyでは何を行った場合に処理を行うのかを指定できます。今回の場合は、masterブランチへのマージやコミットがあった場合だけdeployステージを行うように指定しています。

servicesではDocker in Dockerを使うことを宣言しています。これがないと、docker loginに失敗するようです。なぜそうなるのか、細かい理由がわかりませんでした。

scriptはやや複雑になっています。

  1. Dockerイメージの作成
  2. Herokuへイメージをプッシュ
  3. curlのインストール
  4. プッシュしたイメージをリリース

行っているのは上記の作業です。Gitlabの準備で行ったVariablesの設定をここで利用しています。

curlを実行する前になにもない行にパイプを使っているのは、1行あたりの文字数制限を回避するためです。このcurlでの処理を行わないと、デプロイはされるものの本番環境に反映がされません。

デプロイ結果を確認する

今回の設定だと、masterブランチにコミットやマージをするたびにIDが行われます。

デプロイがうまくいくと、https://{HEROKU_APP}.herokuapp.com/でアプリが確認できるはずです。もし私のサンプルコードをそのまま使ったのであれば「Edit src/App.tsx and save to reload.」というメッセージとReactのアイコンが表示されるでしょう。

まとめ:最低限の処理はできるようになったはず

自分で調べながら一通りの処理を書いてみましたが、GitlabのCI/CD自体はそれほど難しいという印象ではありませんでした。むしろ、時間がかかったのはHeroku独自の癖の部分で、Dockerfileとかリリースのためにcurlでのアクセスが必要とかそちらの調査に時間がかかっています。

基本的なことはこの記事を作りながら学べました。あとはやりたいことができるたびに公式ドキュメントを調べることになりそうです。

コメント

タイトルとURLをコピーしました