entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

GitLab CIとPuppeteerを使ってはてなブログのデザインを継続的にデプロイする - pixiv inside

こんにちは!ピクシブ福岡オフィスでエンジニアをしている@tasshiです。

今回はpixiv insideリニューアルのデプロイ環境についてお話ししたいと思います。

pixiv insideについて

「pixiv inside(ピクシブ インサイド)」は、ピクシブ株式会社の日常を伝えるためのオウンドメディアです。
2014年に「pixiv engineering blog」としてスタートし、2017年に現在の「pixiv inside」になりました。

WordPressからはてなブログへ

pixiv insideでは2020年1月にセルフホスティングのWordPressからはてなブログへと移行し、新デザインへのリニューアルを行っています。

デザインリニューアルではデザイナーさんの作ったデザインを元にして、エンジニアがJavaScript, CSSなどのデザインリソースを実装します。
その後、リソースをデプロイするのですが、リニューアル初期にはエンジニアがデザイン管理画面からリソースを1つ1つ貼り付けていたため、以下のような問題がありました。

  • デザインリソースの貼り忘れがある
  • 手作業なので時間がかかる
  • 現在適用されているのがいつのコミットなのか記録されない
  • デプロイ前に他の人がデプロイしていないか尋ねる必要がある

そのため、pixiv insideではデザインのデプロイ環境を整備し、開発効率の改善を試みました。

pixiv insideのデザインデプロイ環境

現在、pixiv Insideのデザインデプロイ環境はこのようになっています。 inside-deploy-environment.svg (223.7 kB)

GitLab CI

デザインリソースがGitLabにpushされると、GitLab CI上のコンテナでTypeScript, CSSのビルドとテストが自動実行されます。
TypeScriptはrollup.js, CSSはPostCSS(clean-css + preset-env)でビルドします。

テストに成功すると、CIの画面からstaging, productionそれぞれへのデプロイが実行できるようになります。
デプロイワークフローは以下のようになっています。

  • test成功時
    • masterブランチ
      • stagingには自動デプロイ実行
      • productionには手動デプロイ可
        • デプロイ成功時に自動でrelease tagを付与
    • masterブランチ以外
      • staging, productionに手動デプロイ可
  • test失敗時
    • デプロイ不可

デプロイワークフローはプロジェクトルートの gitlab-ci.ymlに記述します。
以下にpixiv insideで実際に使用している.gitlab-ci.ymlを一部お見せします。

# テスト用のデフォルト設定
.default-test:
  image: node:latest
  before_script:
    - yarn install --silent
# デプロイ用のデフォルト設定
# Puppeteerの依存関係, aws-cliをインストール
.default-deploy:
  image: circleci/python:latest-node
  before_script:
    - |-
      sudo apt-get update -yqq \
      && sudo apt-get install -yqq wget gnupg \
      && sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - \
      && sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
      && sudo apt-get update -yqq \
      && sudo apt-get install -yqq google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
        --no-install-recommends \
      && sudo rm -rf /var/lib/apt/lists/*
    - pip install awscli --upgrade --user --quiet && \
    - yarn install --silent
stages:
  - test
  - deploy
# ジョブ: ビルド & テスト
test:
  extends: .default-test
  stage: test
  script:
    - yarn build
# ジョブ: stagingへの自動デプロイ(masterのみ)
deploy-staging-auto:
  extends: .default-deploy
  stage: deploy
  only:
    - master
  environment:
    name: staging
  script:
    - ./gitlab-ci/deploy.sh staging
# ジョブ: stagingへの手動デプロイ(master以外)
deploy-staging-manual:
  extends: .default-deploy
  stage: deploy
  except:
    - master
  when: manual
  environment:
    name: staging
  script:
    - ./gitlab-ci/deploy.sh staging
# ジョブ: productionへの手動デプロイ
deploy-production:
  extends: .default-deploy
  stage: deploy
  when: manual
  environment:
    name: production
  script:
    - ./gitlab-ci/setup_ssh_key.sh
    - ./gitlab-ci/deploy.sh production

デプロイで使用するGitやユーザの情報は予約環境変数を使うと簡単に取得できます。
Predefined environment variables reference | GitLab

.gitlab-ci.ymlをコミットしたらGitLabのCI/CDの画面にジョブ一覧が表示され、実行結果の確認やジョブを実行ができるようになります。

inside-gitlab-ci-with-comment.png (177.6 kB)

Amazon S3 & CloudFront

GitLab CIでビルドされたJavaScript, CSSはAmazon S3にアップロードされ、CloudFront経由で配信されます。

このとき、キャッシュによりデザインが更新されないことを防ぐため、Cache-Controlフィールドにmust-revalidate を付与します。
HTTP キャッシュ - HTTP | MDN

$ aws s3 sync ./output s3://${AWS_BUCKET_NAME} \
    --exact-timestamps --delete --cache-control "must-revalidate"

Puppeteer

デザイン管理画面はブラウザ上からしか操作できないため、Puppeteerを用いてHeadless Chromium上で操作します。

Puppeteerを使う上での問題点として、はてなブログのHTML構造が変わるとPuppeteerが上手く動作しなくなります。
しかし、はてなブログの変更に合わせてエンジニアが保守するほうが毎回手作業でデプロイするよりも効率が上がると考え、開発を進めました。

操作手順はざっくりと以下のようになっています。

  • ログイン(本記事では省略)
  • Puppeteerで操作しやすくする前処理
    • アコーディオンメニューを全て開く
    • テキストエディタを無効にする
    • ダイアログが出たら自動的にOKを押す
  • モジュールの全削除
  • HTMLファイルの挿入
  • モジュールの追加

まず、Puppeteerで操作しやすくするためにいくつかの前処理をします。

アコーディオンメニューを全て開く

inside-accordion.png (120.6 kB)

初期状態ではアコーディオンメニューが全て閉じており(display: none;)、Puppeteerで設定項目を操作することができません。
そのため、CSSを追加することで全てのアコーディオンメニューを強制的に開いた状態にします。

// アコーディオンメニューを全て開く
await page.evaluate(() => {
  const styleSheet = document.querySelector("style")?.sheet as
    | CSSStyleSheet
    | null
    | undefined;

  styleSheet?.insertRule(`
    .アコーディオンメニューのクラス {
      display: block !important;
    }
  `);
});
テキストエディタを無効にする

inside-ace-editor.gif (273.9 kB)

管理画面ではTextAreaをクリックするとテキストエディタが起動します。
これは自動補完機能もあり手作業では重宝するのですが、今回は不要となるためDOMを取り除くことで無効とします。

// テキストエディタを無効にする
await page.evaluate(() => {
  document.querySelectorAll(".テキストエディタに関するクラス").forEach(e => {
    e.remove();
  });
});
ダイアログが出たら自動的にOKを押す

モジュールの削除などの際に確認ダイアログが出るのですが、毎回OKを押すのは面倒です。
Puppeteerのdialogイベントに対してOKを押す処理を割り当てます。

page.dialog dialog.accept

// ダイアログが表示されたら全てOKにする
page.on("dialog", async dialog => {
  dialog.accept();
});
モジュールの全削除

Puppeteerでモジュールの並び順などを確認しながら変更を加えるのは非常に手間なので、一度全てのモジュールを削除します。

  // 全てのモジュールを削除する
  while (await page.$("モジュールの削除ボタンのクエリ")) {
    await page.click("モジュールの削除ボタンのクエリ");
  }

これでHTMLの挿入とモジュールの追加の準備ができました。
あとは目的のDOMを見つけてvalueの変更やClickをしていくだけです。

Demo

headless: false で実行中の画面はこんな感じです。

inside-puppeteer-demo.gif (1.7 MB)

デプロイの開始と完了の際にはSlackに通知が飛ぶようにしています。

inside-slack.png (95.7 kB)

デプロイ環境を整備したことによるメリット

  • デプロイ環境を整備したことで、エンジニアが開発の作業に集中できるようになった
  • デプロイのコストが低くなったので、デザイナーさんとのレビューサイクルも速くすることができた
  • 自動でRelseaseタグが付与されるようになったので、デプロイされているコミットが分かるようになった
  • デザインリソース全体をGitLab上で管理できるようになった

まとめ

いかがでしたでしょうか?
pixiv insideでは今回のデプロイ環境の整備によって、全体としての開発体験の向上を果たすことができました。 この記事がデザインデプロイでお悩みの方の助けになれば幸いです。

ピクシブ福岡オフィスでは、現在カスタマーサポートメンバーを支えるサポートエンジニアを募集しています。興味のある方はぜひご応募ください。
【福岡オフィス採用】サポートエンジニア(アルバイト)(福岡オフィス)の採用情報 | ピクシブ株式会社

tasshi
2019年1月から福岡オフィスでアルバイトしている。主にGoやTypeScriptを使って開発に携わっていて、たまにDockerコンテナ作りもする。趣味はロードバイクとゲーム。