プロダクト支援チームでkintoneのStorybookをホスティングした話

こんにちは。生産性向上チーム&フロントエンドエキスパートチームです。

今回は私たちプロダクト支援チームが普段どのようにプロダクトチームを支援しているかの一例として、kintoneのStorybookを社内からいつでも確認できるようにホスティングするまでの流れを紹介します。

支援チームとプロダクトチームの関わり

まずは私たち支援チームとプロダクトチームが社内でどのように関わり、開発しているのかを紹介します。

サイボウズ開発組織図

プロダクト支援チームはプロダクトチームに属しておらず、さまざまなプロダクトの課題解決のために動いています。 例えば、プロダクトチームが手の回っていない最新技術への追従や開発体験の改善を行っています。

生産性向上チームとフロントエンドエキスパートチームの詳細については、それぞれ以下の紹介記事を参照してください。

今回はその支援のひとつとして、プロダクトチームでStorybookを社内からいつでも確認できるようにホスティングしたいというIssueがあったので、一緒に改善しました。

kintone開発におけるStorybookの活用

Storybookとは、UIコンポーネント一覧をカタログのように表示できるようにして、開発チーム内でUI開発のコミュニケーションをとりやすくするツールです。

Storybookの解説

画像引用元:https://storybook.js.org/docs/react/get-started/introduction

現在、kintoneチームではClosure Toolsで作られているのをReactに置き換えています。
この置き換えにデザイナー、開発者、PMなど多くの関係者が関わっており、実装されているUIの動作やスタイルを視覚的に確認するために、Storybookを活用しています。

しかし、改善前はStorybookを確認するときに以下のような課題がありました。

  • 確認のたびに開発環境を立ち上げないといけない
  • PMやデザイナーと一緒に確認するときに、開発者しか手元で見れない
  • 開発者以外に環境を作ってもらうのはハードルが高い

開発チームではこの課題を認識はしていたものの、機能開発をしている中で優先度が落ちており、手が付けられていない状態が続いていました。
そこでプロダクト支援チームである私たちがこの課題を解決するため、社内からいつでもStorybookを確認できるようにホスティングの仕組みを整えました。

Storybookをホスティングする方法

Storybookをホスティングする上で、以下のことを実現しようと考えました。

  • 社内環境からのみ閲覧できる
  • PRごとにStorybookを見られるURLが発行される

これを実現する方法はいくつもあり、私たちが検討した選択肢と、それらをなぜ採用したのか・しなかったのかを以下にまとめます。

GitHub Pages

kintoneチームはGitHub Enterprise Serverを使っているので、GitHub Pagesを使えば前提条件は満たせると考えました。

しかし、PRごとにホスティングしようとすると、同一リポジトリで複数ディレクトリにわける、もしくは別リポジトリに転送する必要があり、少し手間がかかりそうでした。
このため、今回は採用を見送りました。

社内インフラにホスティング

社内でVMなどを構築して、PRごとにそのVMに転送してホスティングするという方法も検討しました。

しかし、VMを構築するとなると、VM自体の運用や監視が必要になるため、運用コストが大きいと判断して見送りました。

Chromatic

ChromaticはStorybookのメンテナーによって作られたサービスです。
サービス内にStorybookを展開できるだけではなく、UI上でコメントのやりとりができたり、UIの差分テストを行うこともできます。

kintoneチームで使用しているのがGitHub Enterprise Serverだったため、Enterprise Planの契約が必要であり、手軽には使い始められなさそうということがわかりました。
今回やりたいことはStorybookをホスティングすることだけだったので、コストとやりたいことが見合ってないため見送りました。

いつか使ってみたいです。

AWS Amplify

AWS Amplifyを使うことで、静的なウェブサイトをホスティングできます。

AWS AmplifyでIPアドレス制限をかける方法を調査したところ、Amplify単体では実現できず、AWS WAFを組み合わせるのが一般的ということがわかりました。
やりたいことに対して構築が大掛かりすぎて、コストが見合ってないと判断し見送りました。

Netlify

Netlifyは静的ホスティングサービスです。
社内の一部ですでにNetlifyを使っており、GitHubと連携して手軽に静的ホスティングができます。

しかし、NetlifyはIPアドレスやSSOによるアクセス制限機能が見当たらなかったため、社内からのみ見られるようにしたいという今回の用途には合わず、採用を見送りました。

Amazon S3

S3は静的サイトをホスティングする機能があり、バケットポリシーでIPアドレス制限を設定できます。
すでに社内でAWSを使用しており、利用するための導入コストはほとんどかかりません。(参考:AWS + Azure ADによるSingle Sign-Onと複数AWSアカウント切り替えのしくみ作り - Cybozu Inside Out | サイボウズエンジニアのブログ

調査したところ、株式会社スタディストさんの記事で今回やりたいことがほぼ実現されており、仕組みの構築も簡単そうでした。

手軽に始められそうだったため、今回はS3で実現してみることにしました。

StorybookをS3に上げてIPアドレス制限をかける方法

S3バケットの作成

StorybookをホスティングするためのS3バケットを作成し、バケットポリシーでIPアドレス制限を設定します。

S3バケットの設定はTerraformで管理することにしました。ここではS3バケット名を仮に"hosting-storybook"として、以下のようなTerraform設定ファイルを作成しました。

resource "aws_s3_bucket" "hosting_storybook" {
  bucket = "hosting-storybook"
}

resource "aws_s3_bucket_policy" "hosting_storybook" {
  bucket = aws_s3_bucket.hosting_storybook.id

  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "s3:GetObject",
        Resource : "arn:aws:s3:::hosting-storybook/*",
        Condition : {
          IpAddress : {
            "aws:SourceIp" : [<社内のIPアドレス一覧>]
          },
          Bool : {
            "aws:SecureTransport" : "true" # https のみ許可する
          }
        }
      }
    ]
  })
}

これで作成したS3バケットにアップロードされた静的ファイルは、社内環境からのみアクセスできるようになります。

また、CIからこのS3バケットにStorybookの成果物をアップロードするためのIAMユーザーとポリシーを作成し、アクセスキーを発行しておきます。

CIの設定

今回はCircleCIでPRごとにStorybookの成果物をS3にアップロードするジョブを設定しました。
具体的なジョブの設定内容は以下のようになります。
CircleCIの環境変数に、AWSのアクセスキーIDとシークレットアクセスキー、GitHubのパーソナルアクセストークンがそれぞれ、AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYGITHUB_TOKENに設定されている想定です。

deploy-storybook:
    docker:
      - image: <Storybookのビルドができ、AWS CLIが入ったイメージ>
    environment:
      STORYBOOK_OUTDIR: /tmp/storybook-static # Storybookビルド時に成果物が保存されるディレクトリ
    steps:
      - run: npm ci
      - run:
          name: Build Storybook
          command: <Storybookビルドコマンド 参考:https://storybook.js.org/docs/react/workflows/publish-storybook>
      - run:
          name: Deploy Storybook to AWS S3
          command: |
            aws s3 sync $STORYBOOK_OUTDIR s3://hosting-storybook/$CIRCLE_BRANCH
      - run:
          name: Create commit status
          command: |
            STORYBOOK_URL=https://hosting-storybook.s3-ap-northeast-1.amazonaws.com/${CIRCLE_BRANCH}/index.html
            STORYBOOK_STATUS_BODY="{\"state\": \"success\", \"target_url\": \"$STORYBOOK_URL\", \"context\": \"storybook\", \"description\": \"このPRのStorybookを見るにはDetailsをクリック!\"}"
            GITHUB_API_END_POINT="https://<GitHubのパス>/api/v3/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/statuses/$CIRCLE_SHA1"
            wget --method=POST \
              --header="Authorization: token $GITHUB_TOKEN" \
              --header="Content-Type: application/json" \
              --body-data="$STORYBOOK_STATUS_BODY" \
              $GITHUB_API_END_POINT

このジョブをトピックブランチでコミットごとに実行するように設定しました。
この結果、コミットステータスにホスティングされたStorybookへのURLが保存されるようになり、以下の画像のようにPR上から簡単にStorybookを閲覧可能になりました。

GitHub上でのStorybookプレビュー

ハマったところ

今回はブランチごとにS3バケットのサブパスにStorybookをデプロイするため、Storybookで参照している画像などのメディアファイルを一緒にデプロイする場合、コードからファイルを参照するためには相対パスを設定する必要がありました。
最初は絶対パスで指定していたためアイコンが表示されない状態になり、トラブルシューティングが大変でした。(参考:Absolute versus relative paths

作っただけで終わりではない!

ここまでの内容で社内からいつでもStorybookを確認できるようにホスティングの仕組みを整えることができました。 ところが、実際に運用を進めていく上で次のような修正や改善を行いました。

利用者が求めているものとのギャップを修正

成果物ができたので実際に運用をするために、kintoneチームと一緒にデザインチームともMTGを行いました。
そこで上がった内容は、デザインチームがStorybookを確認するタイミングは、メインブランチにある最新状態のものだけで十分ということでした。
開発メンバーは手元でStorybookの確認ができるため、ブランチごとにStorybookの状態を確認する必要はないということに気がつきました。

この意見を踏まえて、各ブランチごとにStorybookのビルドとホスティングをしていたのを、メインブランチのみで行うようにジョブを修正しました。

実際に作ったものと利用者が求めてるものにギャップが発生してしまっていたため、こまめに関係者とコミュニケーションをとって調整することは重要だと実感しました。
ここは今回の反省ポイントです。

周知と全体への社内ドキュメント化

大抵、サイボウズではプロダクト支援チームとプロダクトチームはよくモブをして解決することが多いですが、今回はプロダクト支援チームで開発したため、プロダクトチームではなにが行われているか分からない状況でした。

そのため、一連の流れをZoomで説明して、録画も残しておきました。
録画を残すことで、今回の作業に入っていない人も情報を追うことができます。

その後、社内ドキュメントでも今回Storybookをホスティングした目的や仕組みを文書化しました。

プロダクト支援チームは開発するまでがゴールではなく、プロダクトチーム側で運用が回せるように支援を行っています。

おわりに

今回はプロダクト支援チームがプロダクトチームの課題を解決した例を紹介しました。
この記事で紹介したもの以外にも日々さまざまな課題に取り組んでいます。

私たちは一緒に課題を解決してくれる仲間を募集しています。

cybozu.co.jp cybozu.co.jp

興味がある方はTwitterでも受け付けています。
お気軽にご連絡ください。

  • 生産性向上チーム: @miyajan
  • フロントエンドエキスパートチーム: @sakito