ようへいの日々精進XP

よかろうもん

2019 年 12 月 26 日 (木)

ジョギング

  • お休み
  • 右足の太もも痛みが引き続き...

ギョーム

  • Lambda の Node.js ランタイムバージョンアップを検証しながら
  • LocalStack を使ってローカル環境で動かしてみてとか思ったけど, 意外に LocalStack がで Lambda を動かしたり, そもそも Serverless Framework を介したデプロイがうまく動かなかったり... なかなか骨が折れた

夕飯

とにかく太ももを早く治したいので, 牛もも肉の塊を食べた. ウェップ.

2019 年 12 月 25 日 (水)

ジョギング

  • 山王公園 35 min + 懸垂 x 8 回
  • 右足太ももの裏が辛くて途中で走るのを止めた...やばし

ギョーム

昨日の続きで Puppeteer 使ってサイトの変更検知を仕組み化した.

inokara.hateblo.jp

あとは ECS 用のインスタンスを入れ替えたり... なんか色々.

夕飯

奥さん特製のハンバーグ. ぶれない美味しさ.

今年も

残すところ出社は 2 日かーーー. なんとか一年間乗り切れたなあ.

最近ギョームでやったこと (6) 〜 一歩踏み込んだ Web サイトの監視について考察して仮実装してみた 〜

tl;dr

お疲れ様です. かっぱです. YAMAP に入社してもうすぐ一年が経とうとしています. あっと言う間の一年でした. この一年の振り返りを... と思いましたが, 最近の悩み事を技術で解決してみようと思って試験的に実装した内容を紹介させていただきます.

そして, この記事は, YAMAP エンジニア Advent Calendar 2019 の 25 日目の記事になる予定です.

qiita.com

悩み事

YAMAP では, yamap.com 以外にもいくつかの外部サイトを運営しています.

これらのサイトのデプロイ作業を含む各種運用に携わっていますが, これらのサイトがデプロイの度にレイアウトがデグレしてたり, 意図しないような状態で表示されたりすることが多く発生していました. 原因はいくつかありますが, コンテンツが CVS の管理外であったり, コンテンツをオンラインで更新していて CVS にマージされていなかったり人為的な手違いによるものが多く, さらに, その異常に気付くことが出来なかったりして胃が痛くなる日々が続いていました. ということで, ざっくりと悩み事をまとめると...

  • 意図しないサイトのデグレが度々発生する
  • ソースコードとコンテンツが別々に管理されている (WordPress で記事はデータベース, テーマのソースコードCVS で管理) ので変更検知は 2 箇所になるので, 最後に信じられるのは実際に動いている環境になりがち
  • コンテンツ管理者とテーマの管理者が異なる
  • HTTP ステータスコードレベルでの監視では満たされてない

こんな感じです.

どんな風に解決しようと試みたか

下図のような実装を考えて実装しました. (雑に手書きですいません.)

f:id:inokara:20191225211402j:plain

流れとしては...

こんな感じ. 上図では Puppeteer ってなっているところは, 実際には Jasmine から Puppeteer を操作してスクリーンショットを取得して, 従来のスクリーンショットとの差分を比較する処理が動きます.

作ったもの

リポジトリ

github.com

こだわり

出来るだけ手を動かすことなく CircleCI 上で動かすことを考えました.

また, Puppeteer でサイトにアクセスしてスクリーンショットを取得して比較するコアな部分は最小限の実装に スクリーンショットを S3 にアップロード, ダウンロードする部分はコアとは別に AWS CLI (実際には CircleCI の orbs) で実装しました. 以下は .circleci/config.yml の抜粋です.

version: 2.1

orbs:
  aws-s3: circleci/aws-s3@1.0.11
  slack: circleci/slack@3.4.1

executors:
  default:
    docker:
      - image: circleci/node:12.4
      - image: circleci/python:2.7

commands:
  check_prepare:
    steps:
      - aws-s3/sync:
          from: s3://${ROOT_PATH}/
          to: /home/circleci/project/${ROOT_PATH}/
          overwrite: true
          arguments: >
            --delete
  npm_install:
    steps:
      - run:
          name: Update npm
          command: sudo npm install -g npm@latest
      - restore_cache:
          name: Restore Dependencies
          keys:
            - dependencies-{{ checksum "package-lock.json" }}
            - dependencies
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          name: Save Dependencies
          key: dependencies-{{ checksum "package-lock.json" }}
          paths:
            - node_modules

jobs:
  check:
    executor:
      name: default
    steps:
      - checkout
      - run:
          name: Install Headless Chrome dependencies
          command: |
            sudo apt-get install -yq \
... 略 ...
      - npm_install
      - check_prepare
      - run:
          name: Run test
          command: ./node_modules/.bin/jasmine
... 略 ...
      - run:
          name: Store Images
          command: |
            aws s3 sync /home/circleci/project/${ROOT_PATH}/ s3://${ROOT_PATH}/
          when: always
      - slack/status:
          fail_only: true
          mentions: ${SLACK_MEMBER_IDS}

workflows:
  version: 2
  byhand-webpage-check:
    jobs:
      - check
  triggerd-webpage-check:
    triggers:
      - schedule:
          cron: "3 * * * *"
          filters:
            branches:
              only:
                - master
    jobs:
      - check

また, CircleCI の triggers を利用して一時間に一回定期的に実行させるようにしています.

circleci.com

さらに, S3 へのアップロードと Slack への通知は orbs を利用しています.

github.com

github.com

ちょっと痒いところに手が届かないところ (run で利用出来る when 的な定義が書けないとか) が残念でしたが, 基本的な使い方であれば全然 orbs でいけるので最高ですな.

苦労したところ

Puppeteer (1)

何よりも Puppeteer です. 全く触ったことなかったので, 見様見真似で写経 (コピペ) していました. しかし, 以下のように簡単にサイトのスクリーンショットを取得出来るのには感動しました.

//
// https://github.com/puppeteer/puppeteer#usage より引用
//
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://yamap.com');
  await page.screenshot({path: 'test.png'});

  await browser.close();
})();

これを test.js あたりで保存して node test.js とすると, 以下のようなスクリーンショットがシュッと撮れます.

f:id:inokara:20191225214856p:plain

しかし, 相手は JavaScript, 一筋縄ではいきません. asyncawait がよくわからないまま書き進めてしまい, 一応, 思ったような動作になりました... これでは, 成長が無いのでもう少し asyncawait をはじめ JavaScript について理解を深めていきたいと思います.

Puppeteer (2)

ページ全体のスクリーンショットを取得したい場合, 以下のように screenshot メソッドの引数に fullPage: true をつけてあげると取得出来ることは確認しました.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://xxxx.xxxx.com/');
  await page.screenshot({path: 'test.png', fullPage: true});

  await browser.close();
})();

ところが, サイトによっては, 下図のように画像が一部読み込まれず悩みました.

f:id:inokara:20191225235519p:plain

現在も解決出来ていません...

差分抽出

差分抽出には Resemble.js を利用しています.

github.com

この Resemble.js も Puppeteer 同様に以下のように簡単に画像の差分を抽出することが出来ます.

        resemble('after.png').compareTo('before.png')
          .ignoreColors()
          .onComplete(function(data) {
              fse.writeFileSync(DirName + 'diff.png', data.getBuffer());
          });

動いている様子

CircleCi でのテスト結果.

f:id:inokara:20200106104657p:plain

上記のように, 対象ページに差分があり, 差分許容率がしきい値を超えるとテストが Fail となります.

f:id:inokara:20191225221726p:plain

Fail したら Slack に上図のように通知されます. 通知が届いたら, S3 バケットに保存されている画像を確認して, 意図した差分なので, そうでないかをデザイナーさん等に確認する流れになることを想定しています.

f:id:inokara:20191225221116p:plain

また, テストと合わせて差分抽出画像が生成されます. 差分抽出画像は上図のような画像になります. 前回のスクリーンショットとの差分がピンク色に着色された状態になります.

参考

以下の記事を大幅に参考にさせていただきました. Puppeteer のことを 1bit もわからない自分でもなんとなく利用することが出来ました. ありがとうございました.

qiita.com

qiita.com

ということで

まとめ

Web サイトの監視について, 対象のコンテンツ差分をチェックするというアプローチを検討して実装してみました. 意図したコンテンツの変更についても検知してアラートが飛んでくる状況ですが, これはこれで外形監視上の変更履歴として利用出来るのではと考えています.

実装にあたり, 使った技術要素を見てみると, チェックのフレームワークとして Jasmine というテストフレームワーク (と言っていいのかわかりませんが), 実際の基盤としては CircleCI と出来合いのフレームワークを組み合わせることでシュッと仮実装まで持ってくることが出来ました. 技術の進化というものは本当に素晴らしいものですね.

最後に

YAMAP に入社して一年が経とうとしていますが, エンジニアとして, 自分なりの「新しい山を作ろう」で頑張っていきたいと思います. これからも宜しくお願い致します.

2019 年 12 月 24 日 (火)

ジョギング

  • すいません, 完全に怠けました
  • 足, 痛いです
  • 明日は少し走りたい...

ギョーム

  • デプロイとか
  • デプロイした後の確認を自動化したいなーと思って, Puppeteer 等を触り始める
  • 年末なのでタスクの整理をしたり...
  • またもや WordPress 案件で冷や汗をかく

クリスマスイヴ

ちらし寿司やらお刺身やら. 奥さん自家製のローストビーフを楽しみにしていたのに... 焼き肉になっててまたのチャレンジを楽しみにしております.

#鹿児島30K 振り返り

tl;dr

フルマラソンの練習会的な位置づけで全国各所で開催されているらしい「◯◯ 30K」シリーズの鹿児島開催版である「鹿児島 30K」で 30 キロ走ってきたので振り返りたいと思います.

runners.30k-series.com

どんな大会なのか

runners.30k-series.com

30km走はマラソンの1カ月前の必須トレーニング。マラソンシーズンで「自己ベスト更新」「初完走」を狙う人にとって本番へ向けた「脚づくり」とシミュレーションにぴったりです!

上記のページに記載されている通り, レースではなく練習会的な位置づけとなるイベントのようです. とは言え, 今年はフルマラソンを一回も走っていない自分にとっては少し距離が短いマラソン大会という位置づけで挑みました. また, 後付となりますが, 雨風のレースに対して激しい苦手意識があったので完走することでその苦手意識を少しでも払拭出来ればと臨みました.

鹿児島 30K で走ってきました。

とは言え...

前日からの雨, さらにスタート直前になって強くなる風と雨...棄権したい気持ち一杯でしたが, とりあえずいけるところまで走ろうと思いスタートしました. 目標は 1 キロ 4 分ペース. 30 キロで 2 時間を切れれば最高だと思っていましたが, ひとまずフルマラソン 3 時間切りを目指す人達に用意されたペースランナーの後ろについて一周 3 キロのコースを 4 分 10 秒 /Km 程度でペースを刻みます.

f:id:inokara:20191223234710p:plain

寒い

冷たい雨と強い風が体温を奪っていきます. 普通は走れば体が温まりますが, 今回は走っても走っても体は温まらず足先は冷えてしまい接地の感覚すら無い状態で走り続けます. 動かない体を無理やり動かそうとするので呼吸は上がってきて普段のジョッグと比較すると当然ですがキツさを感じました. また, 想定していたよりもアップダウンがあり, 特に 1.5 キロ過ぎの 200m くらいの上り坂は体力を奪い, 乳酸の高まりを感じました.

途中で摂取したのは塩飴 1 個とエナジージェルを 2 つ, 給水地点でのスポーツドリンクと水. 結局, 足が攣ってしまったので 2Run を摂っておくべきだったと反省していますが, 2Run 無しで 20 キロを超える距離を走れたことは小さな自信となりました.

また, 今回のレースでは HOKA ONE ONE の RINCON で走りました.

f:id:inokara:20191224002808p:plain

履いて走った感じをもう少しレポート出来れば良いのですが, 先述の通り足先が冷たく感覚が無い状態でしたので何となくクッションが効いているなーという感覚しかありませんでした. NIKE の ZOOM FLY3 あたりで感じる反発力はあまり感じませんでした. きっとカーボンプレートの有無なのかな.

ラスト一周

本来であれば, 後半の 5 周はペースアップしていきたかったところですが, 結局ラスト一周までは体力を温存, ラスト一周をやっと目標としていた 4 分 00 秒/Km で走りきることが出来ました. 今回は 2Run を一回も摂取せずに走ったので案の定, 足はピキピキ攣りまくりながら走りました.

ということで

苦手意識のあった雨風の中で完走も出来て 30 キロを 2 時間ちょっとで走ることが出来たことは良かったと思います. また, 最後の一周は 4 分 00 秒/Km くらいまでビルドアップ出来たのは良かったと思います. しかし, フルマラソンを 2 時間 50 分切りを目指している状況からすると, 終盤の体の重さと足攣りは相変わらずで, フルマラソンの場合の残り 12 キロをどう走るのか 2 月の別大までに自分なりの答えを見つけたいと思います.

冷たい風雨の中でスタッフのように働く父。

最後に練習会という位置づけながら一生懸命応援してくれた奥さんや父, 食事面のサポートをしてくれた母, あの過酷な状況でレースをサポートしてくださったスタッフの皆さんには本当に感謝しています. ありがとうございました.

2019 年 12 月 23 日 (月)

ジョギング

  • 足がパンパンでお休み
  • いつも痛い箇所が... 痛くなる... 辛い

福岡に帰る

  • 午前中は自宅で業務, Kibana 統合をやったり, AWS の新サービス (プラン) を駆使して EC2 の利用料をさらに下げる算段をしたりしていた
  • 午後からお休みを頂いて福岡に車で帰る... きつかった〜

2019 年 12 月 22 日 (日)

ジョギング

runners.30k-series.com

鹿児島 30K で 30 キロ走ってきた. 腹が立つくらいの風雨と寒さで DNS (Did not start 棄権) も考えたけど, 雨が降っている時の苦手意識を払拭したいという思いもあったので出走. 案の定, 雨は冷たいし風は強いしで体は冷えていく一方で接地の感覚もなく完走出来るか不安になりつつもなんとか完走することが出来た.

結果は以下の通り. 目標としていた 4 分ペースで走り切ることは出来なかった (早々に諦めた) けど, 無難にサブスリー圏内で走り切ることが出来た.

f:id:inokara:20191223233217p:plain

父の PC が壊れる (2)

結局, 鹿児島のアプライドで中古デスクトップ PC を購入. Core i5 にメモリ 8GB, SSD で予算内におさめることが出来た. 早速, セットアップを行い, 難なく移行が出来たので本当に良かった.

夕飯

  • 母の鍋, お出汁が美味しかった
  • お腹一杯

2019 年 12 月 21 日 (土)

ジョギング

  • 山王公園を軽く 45 分
  • 懸垂 x 8
  • 右足の大腿裏は鍼や奥さんのマッサージでなんとか落ち着いてきたけど, 走っていて最初からなんとも言えない疲労

移動

  • 鹿児島へ移動
  • 今回はなんとなくあっという間についた感じだった
  • 途中から雨が降り出して, 明日のレースが憂鬱になった

父の PC が壊れる

  • 今回の帰省の目的の一つだった, 父 PC の確認
  • [ キーが何もしていないのに PC 起動時から勝手に入力されるので, ログオンするのにも一苦労
  • 年金暮らしの身に大変申し訳ないのであるが, 新しい PC を購入することを薦める

夕飯

明日は...

  • せめて雨がやんでくれると嬉しい

2019 年 12 月 20 日 (金)

ジョギング

  • お休み *さらに引き続き, 右太ももの裏が... 強い張りで辛い

ギョーム

  • ウィークリーレポートを書いたり
  • 来季の目標を考えたり
  • Kibana 環境を ECS + Spot Instance 化したりする為に Terraform を書いていた

Carbon X

HOKA ONE ONE の Carbon X をキャナルシティのゼビオで発見!試し履きしていい感じで一瞬グラついたけど我慢した.

2019 年 12 月 19 日 (木)

ジョギング

  • お休み
  • 引き続き, 右太ももの裏が... 強い張りで辛い

ギョーム

  • ECS の Optimized AMI の更新作業, やっぱり手作業は辛かったので自動化を検討したい
  • Terraform の aws_ssm_parameter という Data Source がすごく自分のニーズに合ってて良かった
  • Elasticsearch のシャードがノードに割り当てられていない問題...大変ごめんなさい事案だった
  • Kibana サーバーを統合する作業

YAMAP Night

スタッフとして参加した.