2
2

More than 3 years have passed since last update.

マイクロフロントエンド② GitHub ActionsでCI/CDの構築

Last updated at Posted at 2021-03-18

前回の記事でマイクロフロントエンドの基礎を学んだので、今回はReactでマイクロフロントエンドを構築し、GitHub ActionsでCI/CDを組んでみることにしました。

3つのプロジェクト(Marketing, Authentification, Dashboard)で計6つの画面を実装し、Containerでまとめて表示するような構成にします(記事内で作成するのは、Marketingのみになります)。
スクリーンショット 2021-03-17 21.15.48.png

マイクロフロントエンド構築における鉄則

1. 子プロジェクト同士は疎結合させる

プロジェクト間での関数/オブジェクト/クラスのimportやstateの共有をさせないようにする必要があります。
もし2つのプロジェクト(MarketingとAuthentificationなど)でstateの共有などを行ってしまうと、数年後に片方のプロジェクトを使わなくなってしまったときに、もう片方も大きく修正を加えなければならない状態になってしまうからですね。

2. Containerと子プロジェクトはできるだけ疎結合させる

親プロジェクトであるContainerと子プロジェクト同士も疎結合が基本です。Containerは子プロジェクトのフレームワークの種類に依存しないような設計になっていなければなりません。ただ、ルーティングを実装するときにコールバックやイベントを渡すこともあり、完全な疎結合化は難しいです。

3. CSSが他のプロジェクトの表示に影響を与えない

クラス名が被ってしまうことによる表示崩れに気をつける必要があります。

4. Containerは子プロジェクトのバージョンを選択できる

常に最新のバージョンを選択する場合はContainerの再デプロイが必要なく、特定のバージョンを選択する場合は再デプロイが必要になります。

プロジェクト構築の注意点

Webpackの設定ファイルは3つ必要

開発環境(webpack.config.dev.js)、本番環境(webpack.config.prod.js)、共通設定(webpack.common.js)の3つのファイルを作成します。

疎結合な設計

鉄則2で書いたように、Containerと子プロジェクトは疎結合である必要があります。そのため、Containerで子プロジェクトを読み込む際には、間にもう一つコンポーネントをはさんであげる必要があります。
スクリーンショット 2021-03-18 7.35.18.png

Marketing側ではReactDOMmountという関数に置き換えて、 Reactが使われていることを外側のプロジェクト(Container)からわからないようにします。

bootstrap.js
const mount = (el) => {
  ReactDOM.render(<App />, el);
};

export { mount };

Container側では新しくMarketingApp.jsというファイルをつくり、Marketing側で作成したmount関数を読み込みます。そして、Container側のDOMにアクセスするためにuseRef Hooksを利用します。

MarketingApp.js
import { mount } from 'marketing/MarketingApp';
import React, { useRef, useEffect } from 'react';

export default () => {
  const ref = useRef(null);

  useEffect(() => {
    mount(ref.current);
  });

  return <div ref={ref} />;
};

最後にContainer側のApp.jsMarketingAppを読み込みます。

App.js
import React from 'react';
import MarketingApp from './components/MarketingApp';

export default () => {
  return (
    <div>
      <MarketingApp />
    </div>
  );
};

このような設計で実装すれば、Marketing側をReactからVueに変えなければいけなくなったとしても、同様にmount関数としてエクスポートしてあげればContainer側でそのまま読み込むことができます。

本番環境の構築

webpack.prod.jsの作成

webpack.dev.jsと共有する設定については、webpack.common.jsにまとめます。

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react', '@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime'],
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ]
};

webpack.prod.jsは以下のように記述し、ファイルの最後でwebpack.common.jsとマージしてエクスポートします。PRODUCTION_DOMAINという環境変数を使っていますが、これはMarketingのremoteEntry.jsが配置されるS3のドメインを指しており、後ほどGitHubで設定します。

webpack.prod.js
const { merge } = require('webpack-merge'); // webpack.common.jsとマージするためのモジュール
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); //外部プロジェクトを読み込むためのモジュール
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');

const domain = process.env.PRODUCTION_DOMAIN; //S3ドメイン(GitHubで環境変数を設定する)

const prodConfig = {
  mode: 'production',
  output: {
    filename: '[name].[contenthash].js', //アセットの中身が変更されるごとにハッシュを付与する
    publicPath: '/container/latest/', // ビルドファイルのアウトプット先
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        marketing: `marketing@${domain}/marketing/latest/remoteEntry.js`, // remoteEntry.jsがあるS3バケットのディレクトリ
      },
      shared: packageJson.dependencies,
    }),
  ],
};

module.exports = merge(commonConfig, prodConfig); // webpack.common.jsとwebpack.prod.jsをマージしてエクスポート

構成

ビルドしたファイルをS3にデプロイして、ブラウザからはCloudFront経由でアクセスできるようにします。
スクリーンショット 2021-03-18 21.10.37.png

GitHub Actions

今回はGitHub ActionsでCI/CDを構築します。

  • CI: ソフトウェアのビルドやテストを自動化することでソフトウェアの品質向上や開発効率化を目指す手法
  • CD: リリースやデプロイを自動化する手法

GitHubリポジトリのmasterブランチにプッシュしてContainerフォルダに変更があった場合のみ、以下のワークフローが実行されるような設定を行います。

  1. dependencyのインストール
  2. 本番用ファイルのビルド
  3. S3へのファイルのデプロイ

AWSの設定

S3の設定

新しくバケットを作成します。バケット名はグローバルで一意なので、他ユーザがつくったバケットの名前は使えません。
スクリーンショット 2021-03-15 21.13.45.png

ホスティングの設定を行います。
スクリーンショット 2021-03-15 21.16.40.png

パブリックアクセスをブロックしないようにチェックを外します。
スクリーンショット 2021-03-15 21.19.12.png

ブラウザからCloudFront経由でS3にアクセスするために、バケットポリシーの設定が必要となります。JSONをベタ書きして作成することもできるのですが、今回はAWS Policy Generatorを使います。
スクリーンショット 2021-03-15 21.24.25.png

ARNはバケットのプロパティに書いてあるものを使い、/*を最後につけます。
スクリーンショット 2021-03-18 21.55.56.png

作成したバケットポリシーをアクセス許可タブのバケットポリシーにコピペします。
これでS3の設定は完了です。
スクリーンショット 2021-03-18 22.01.42.png

CloudFrontの設定

Distributionを新しく作成します。
スクリーンショット 2021-03-15 21.30.46.png

Edit DistributionのDefault Root Objectで読み込むhtmlファイルのS3のディレクトリを指定します。
スクリーンショット 2021-03-15 21.33.25.png

エラーレスポンスの設定を行います。
スクリーンショット 2021-03-15 21.34.52.png

IAMの設定

今回、GitHub ActionsのワークフローからAWS CLI経由でS3にビルドしたファイルをデプロイします。
そのため、S3オブジェクトにアクセス可能なIAMユーザーを準備し、アクセスキーIDとシークレットアクセスキーを作成する必要があります。
スクリーンショット 2021-03-15 21.40.01.png

S3とCloudFrontのフルアクセスポリシーをIAMユーザにアタッチします。
スクリーンショット 2021-03-15 21.43.16.png

作成されたアクセスキーIDとシークレットアクセスキーをGitHubのsecretsに保存し、以後は環境変数として使用します。
スクリーンショット 2021-03-15 21.45.25.png

GitHub Actionsの設定

GitHubリポジトリで管理したいディレクトリの直下に.github/workflowsというフォルダを作成し、ワークフローを~.ymlファイルに記述します。

container.ymlの作成

Containerのワークフローを以下のように記述します。

container.yml
name: deploy-container #ワークフローの名前

on: #イベント
  push: #プッシュ
    branches: #プッシュ先ブランチ
      - master
    paths: #変更箇所
      - 'packages/container/**'

defaults: #すべてのジョブに適用されるデフォルト設定
  run:
    working-directory: packages/container #runを実行するディレクトリ

jobs: #ジョブ
  build: #ビルド
    runs-on: ubuntu-latest #ジョブを実行するための仮想マシン

    steps:
      - uses: actions/checkout@v2 #仮想マシンにロードするためにチェックアウトするブランチ
      - run: npm install #dependencyのインストール
      - run: npm run build #Webpackでビルド
        env:
          PRODUCTION_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }} #CloudFrontのドメイン名(GitHubのsecretsに保存)

      - name: ACTIONS_ALLOW_UNSECURE_COMMANDS
        run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV

      - uses: chrislennon/action-aws-cli@v1.1
      - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/container/latest #S3の/container/latestにビルドしたファイルを配置
        env: #AWS-CLIの実行に必要なアクセスキーIDとシークレットアクセスキーの指定
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - run: aws cloudfront create-invalidation --distribution-id ${{secrets.AWS_DISTRIBUTION_ID}} --paths "/container/latest/index.html" #CloudFrontのキャッシュの無効化
        env: #AWS-CLIの実行に必要なアクセスキーIDとシークレットアクセスキーの指定
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

再デプロイしてもCloudFrontのキャッシュによって表示が変わらないことがあるので、以下の部分でinvalidation(無効化)を設定しています。

      - run: aws cloudfront create-invalidation --distribution-id ${{secrets.AWS_DISTRIBUTION_ID}} --paths "/container/latest/index.html"
          env:
            AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
            AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

marketing.ymlの作成

Marketingのワークフローは以下のようになります。

marketing.yml
name: deploy-marketing

on: #イベント
  push:
    branches:
      - master
    paths:
      - 'packages/marketing/**'

defaults:
  run:
    working-directory: packages/marketing

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - run: npm install
      - run: npm run build
      - name: ACTIONS_ALLOW_UNSECURE_COMMANDS
        run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV

      - uses: chrislennon/action-aws-cli@v1.1
      - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/marketing/latest
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - run: aws cloudfront create-invalidation --distribution-id ${{secrets.AWS_DISTRIBUTION_ID}} --paths "/marketing/latest/remoteEntry.js"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

GitHub Actionsワークフローの実行

Containerフォルダのファイルを変更した上で、git push origin masterでリモートリポジトリにプッシュするとワークフローが実行されます。
リポジトリのActionsタブを開くとジョブの進捗がわかり、無事に実行を終えると以下の画面になります。
スクリーンショット 2021-03-18 23.13.02.png

CloudFrontのドメインにアクセスしたらちゃんと開けました!
スクリーンショット 2021-03-18 23.20.51.png

おわりに

次の記事ではマイクロフロントエンドにおけるCSSとReact Routerの設定についてまとめます。

参考資料

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2