2020|sitateru tech blog

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

2020年7月10日金曜日

GCPでGitHub Actionsのセルフホストランナーを作った

朝野です。少し前の話ですが、GitHubの料金が全体的に値下げされましたね。

https://github.co.jp/pricing.html

Teamプランは1ユーザーあたり4ドルになったのですが、さりげなく同時にGitHub Actionsの利用時間(organizationのプライベートリポジトリ全体で)が月3000分まで、と減ってしまいました 😥 (前は10000分)
以前の記事(GitHub ActionsとCypressでサイト監視してみる|sitateru tech blog)で紹介したようなCypressを使った監視をActionsで動かしていたので、月3000分は少ないなーと思って料金を見てみたところ・・・


https://github.co.jp/features/actions#pricing

セルフホストはタダなのか!しかしセルフホストって何だ?🤔
ということでやってみました。

セルフホストランナーについて - GitHub Docs

セルフホストランナーというのは、GitHub Actionsを実行するマシンのことです。自分の管理下にあるサーバーやPCでActionsが実行できちゃいます。前述のように、セルフホストランナーで実行する分にはGitHubに対して課金は発生しません。

・・・おっ、ということはGCPの永久無料枠のCompute Engineインスタンスを使えばタダで回し放題やんけ!( ^ω^)
Google Cloud の無料枠 | Google Cloud Platform の無料枠

と思ったのですが、先に結論を書きます、コンテナ上でCypressテストを実行するには無料枠の f1-micro では処理能力的に無理でした。もっと軽い処理なら足りるのかもしれませんが・・・
e2-micro ならなんとか動いたのでこれで良しとしましょう。

さて、セルフホストランナーをクラウド上のLinuxインスタンスで構築する手順に行きたいと思います。
セルフホストランナーの追加 - GitHub Docs

organizationで使えるランナーを追加するには、 まずhttps://github.com/organizations/{organization名}/settings/actionsAdd Runner をクリック。するとインストールするコマンドが出てくるので、それを実行すればOKです。簡単ですね。


インストールコマンドを実行するとこんな感じです。AAがいいですね。
途中でこのランナーにつける名前やラベルを聞かれますが、とりあえずは初期値でも大丈夫そうです。ランナーを複数登録する想定ならわかりやすいものをつけるとよさそうですね。

ちなみに、DockerイメージをAction上で使う場合はランナーマシンにDockerをインストールしておく必要があります。

インストール後、ランナーを起動するにはコマンドで起動する方法とサービスとして動かす方法があります。
コマンドの場合はランナーのパッケージ内の run.sh を実行するだけ。
サービスの場合は下ドキュメントにありますが

  • svc.sh でサービスを実行する
  • actions-runner/bin/actions.runner.service.template ファイルを編集して /etc/systemd/system/ あたりにうまいこと設置して systemctl でコントロールする
といった方法があります。
今回は後者の方法で、/etc/systemd/system/actions.service を設置しました。
[Unit]
Description=GitHub Actions Runner
After=network.target

[Service]
ExecStart=/usr/local/actions-runner/runsvc.sh
User=dev
WorkingDirectory=/usr/local/actions-runner
KillMode=process
KillSignal=SIGTERM
TimeoutStopSec=5min

[Install]
WantedBy=multi-user.target
セルフホストランナーアプリケーションをサービスとして設定する - GitHub Docs

インストールと起動がうまくいけば、このようにGitHubのページ上にも出てきます。


Actionsをセルフホストランナーで実行するには、ワークフローのyamlで以下のように書きます。
runs-on: [self-hosted, linux, x64]
同じOS&アーキテクチャのランナーが複数ある時はラベルをつければよさそうです。
ワークフローでのセルフホストランナーの利用 - GitHub Docs

このセルフホストランナー、今回やってみたようにサーバ上で常時稼働してもいいですが、ちょっとactionsを動かしていろいろデバッグとかしたいけど使用量の枠を食うのがやだなあ・・・というときにも便利そうですね。開発用マシンでその時だけランナーを動かすということもできるので。



なおセルフホストランナーですが、セキュリティ上の理由でパブリックリポジトリでは利用しないようにと公式ドキュメントに書かれています。
パブリックリポジトリではActionsが無料で使えるようなので、そちらでやればよさそうですね。
,

2020年4月30日木曜日

Flood Element でブラウザベースの負荷テスト

どうも朝野です。
少し前に負荷テストツールのFlood Elementというものを使ってみたので、紹介しようと思います。

まずはCLIをインストールします。npmかHomeBrewでインストールできます。
$ npm install -g @flood/element-cli
$ brew install flood-io/taps/element

CLIを使ってプロジェクトを作成、テストファイルを追加します。
$ element init
$ element generate some-test.ts

テストファイルにはブラウザ上での操作を書いていきます。elementはpuppeteerを利用しているので、書き方はかなりpuppeteerに近いですね。
googleのトップページにアクセスして"flood element"と検索するならこのようなコードになります。
import { step, TestSettings, By, Until } from '@flood/element'

export const settings: TestSettings = {
  // userAgent: 'flood-chrome-test',
  loopCount: 10,
  screenshotOnFailure: true,
  clearCache: true,
  clearCookies: true,
  actionDelay: 1,
  stepDelay: 1,
  waitTimeout: 180
}

export default () => {
  step('Sample', async browser => {
    await browser.visit('https://google.com')
    await browser.click(By.css('form input[type="text"]'))
    await browser.sendKeys('flood element', Key.ENTER)
    await browser.wait(Until.elementIsVisible(By.css('div#search')))
    await browser.takeScreenshot()
  })
}
ローカルで実行するときは
$ element run some-test.ts
でOKです。
スクリーンショット等の実行結果は、 tmp/element-results/<テストファイル名>/<実行タイムスタンプ> 以下に記録されます。

ファイル前半で定義しているのは動作時の設定なのですが、そのあたり他詳しいことは公式ドキュメントを参照してください。


さて、テストファイルができたら負荷テストを実行してみます。負荷テストSaaSのFloodの出番です。
Scalable software starts here - Flood

elementはFloodがテストを書くために作ったライブラリなんですね。
ログインしたら"Stream"タブの"CREATE STREAM"をクリック、先ほどのテストファイルをアップロードします。


"CONFIGURE LAUNCH"をクリックすると実行設定に移ります。

テストファイルを実行するリージョンをまず選びましょう。
"Users per Region"がリージョンごとの並列実行数で、"Duration"は実行時間の上限のようです。(試した限りでは、設定したDurationより早くテストファイルの実行が終わったらその時点でテスト完了になりました)

あとは"LAUNCH TES"を押せば負荷テストが始まります。
結果はこのようにグラフで見られます。


ちなみにFloodの料金ですが、VUHという単位の使用量によって決まります。
Flood Load Testing Pricing
VUH = Virtual User Hourであり、 (実行時に設定したUser数) x (テストを実行した時間(15分単位)) というものです。例えば200ユーザーで30分間(=0.5時間)動かしたら 200x0.5=100VUH 、という要領ですね。

ひと月あたり500VUHまでは無料で、それ以降は500VUHごとに$22程度かかる従量課金制になっています。うっかり使いすぎないよう気をつけないといけないですね。


実際のブラウザアクセスを想定した負荷テストって難しそうだなと思っていたのですが、これで無料でもちょっとした実験はすることができました。

好きなサイトに大量のアクセスをかけることができてしまうので悪用厳禁なツールですが、簡単な負荷テストやってみたいという際には試してみてはいかがでしょうか。

, ,

2020年3月19日木曜日

Cypress on GitHub Actions のいろいろなテクニック

朝野です。
先日CypressとGitHubを使う記事を書いたのですが、今回はもう少し突っ込んだ話として、ActionsでCypressを使う上で引っかかったところなどをまとめてみたいと思います。

Cypressのテストが止まったままになる??

Dockerコンテナ上でCypress(ブラウザはChrome)を実行していると途中で止まってしまうことがあります。
調べてみたところChromeの共有メモリが足りなくなっているようです。
そんなときは cypress/plugin/index.js にこの記述で直りました。
module.exports = (on, config) => {
  on('before:browser:launch', (browser, launchOptions) => {
    if (browser.name === 'chrome') {
      launchOptions.args.push('--disable-dev-shm-usage')
    }
    return launchOptions
  })
}
Chromeの起動時に --disable-dev-shm-usage オプションをつけているのですが、これによって共有メモリファイルを /tmp 以下に配置するので充分な容量が確保できるということだそうです。

:hover

CypressにはCSSの:hover状態を発生させる機能が無いようです🙄
hover | Cypress Documentation
If cy.hover() is used, an error will display and redirect you to this page.
Cypressでは非表示状態の要素はクリックなどの操作ができないのですが、じゃあ:hover時に表示される要素をクリックしたいときはどうするの?という話になりますね。
上記のドキュメントでは
cy.get('.hidden').invoke('show').click()
cy.get('.hidden').click({ force: true })
という方法が紹介されています。
ちょっと試したところ invoke('show') はうまくいかなかったのですが click({ force: true }) はできたので、これでいいかなということにしました。

XPath

ページに表示されているテキストを検索して要素を選択したいときなどにはXPathを使いたくなりますが、その場合はプラグインを追加するという方法になります。
package.jsonに cypress-xpath を追加し、cypress/support/index.js に
require('cypress-xpath')
と1行加えればOKです。
cy.xpath('//div[contains(text(), "ここをクリック")]') のように要素を取得できます。

ESLint

ESLintを使っている場合、Cypressのコードはそのままだと引っかかってエラーが出てしまいます。
そんなときはlintのcypressプラグインを使いましょう。
eslint-plugin-cypress を追加し、cypress/.eslintrc.jsonを作成します。
最低限の設定をするならこんなもんです。
{
  "plugins": [
    "cypress"
  ],
  "env": {
    "cypress/globals": true
  },
  "extends": [
    "plugin:cypress/recommended"
  ]
}

Cypressだけインストールする

これはCypressではなくnpmのテクニックなのですが、Actions内でCypressを実行するだけなら、Cypress以外のnpmパッケージをインストールする必要はないですよね。
package.jsonやpackage-lock.jsonの中身を無視してCypressだけインストールするならこうする手もあります。

$ npm install cypress@4.1.0 --no-save --no-package-lock
npm-install | npm Documentation

本当はnode_modulesをキャッシュしたいんですが、
依存関係をキャッシュしてワークフローのスピードを上げる - GitHub ヘルプ
によると
pull_requestのclosedイベントの場合を除く、push及びpull_requestイベントで起動されたワークフロー内のキャッシュにのみアクセスできます。
ということなので、スケジュール実行だとできないのです・・・



というわけで、CypressとActions関係のさまざまなTipsを書いてみました。
何かの参考になれば幸いです。
, ,

2020年2月5日水曜日

GitHub ActionsとCypressでサイト監視してみる

こんにちは、朝野です。

皆さんはGithub Actions使ってますか?
シタテルではCI/CDはCircleCIを使っているのですが、せっかくなのでActionsも何かに使いたいなあと思って、簡単なサイト監視のようなものを作ってみたのでそのことを書いてみます。

監視に使うのは、E2EテストフレームワークのCypressです。
ブラウザの自動化やテスト用に使っている方も多いのではないかと思います。
サクッと動かすなら
  • テストコードを書いて cypress/integration/ ディレクトリに入れておく
  • npmでcypressをインストール
  • $ npx cypress run で実行
と、なかなかお手軽です。

たとえば https://sitateru.com/ のページが表示されることをチェックするコードをCypressで書くとこのようになります。
describe('Check page', function() {
  it('top', function() {
    // URLを開く
    cy.visit('https://sitateru.com/')
    // ページタイトルをチェック
    cy.title().should('eq', 'sitateru - シタテル - | その服は、つくれる。')
    // ページ内に特定の文字列があることを確認する
    cy.contains('かんたん無料登録')
    // CSSセレクタを使って、要素が存在することを確認する
    cy.get('.top-main-visual').should('exist')
  })
})

また、ログイン操作であればこんなふうになります。
describe('Login', function() {
  it('login', function() {
    cy.visit('https://atelier.sitateru.com/login')
    // フォームに入力
    cy.get('#email').type(Cypress.env('LOGIN_EMAIL'))
    cy.get('#password').type(Cypress.env('LOGIN_PASSWORD'))
    // ログインボタンをクリック
    cy.get('input[type="submit"]').click()

    // 遷移先のURLパスをチェック
    cy.location('pathname').should('eq', '/topics')
  })
})
パスワードなどの情報は環境変数に入れておくのがいいですね。
CYPRESS_SOMEENV のように CYPRESS_ プレフィックスをつけた環境変数を定義しておくと、コード内で Cypress.env('SOMEENV') と書いて呼び出すことができます。


GitHub Actionsは .github/workflows/ にやりたいことを書いたyamlファイルを置けば実行されるので簡単ですね。
ざっと作ってみたのがこちら。
name: e2e test
on:
  # 毎時0分に実行
  schedule:
    - cron: '0 * * * *'

jobs:
  build:
    name: monitoring
    runs-on: ubuntu-latest
    # cypress公式のDockerイメージを使う
    container:
      image: cypress/browsers:node12.14.0-chrome79-ff71
    steps:
      - name: checkout
        uses: actions/checkout@master
        with:
          ref: 'master'
      - name: install
        run: npm install
      - name: run test
        env:
          LANG: "ja_JP.UTF-8"
          CYPRESS_LOGIN_EMAIL: ${{ secrets.CYPRESS_LOGIN_EMAIL }}
          CYPRESS_LOGIN_PASSWORD: ${{ secrets.CYPRESS_LOGIN_PASSWORD }}
        run: npx cypress run --browser chrome
      # 失敗したときはスクリーンショットをダウンロードできるように
      - name: gather artifact on failure
        uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: cypress-screenshots
          path: cypress/screenshots
      # 結果をslackに通知 (失敗したときはメンション)
      - name: post to slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: 'e2e test'
          mention: 'here'
          mention_if: 'failure'
          channel: '#notification-actions'
          icon_emoji: ':github:'
          url: ${{ secrets.SLACK_WEBHOOK_URL }}

実行時の環境変数は env: で定義できます。
yaml内に直接書きたくないパスワードなどは、https://github.com/USERNAME/REPOSITORY/settings/secrets で登録しておけば、
${{ secrets.SECRET_NAME }} で参照することができます。

このyamlでは、ソースコードをチェックアウトしてcypressをインストール&実行した後、失敗した場合のスクリーンショットを見られるようにupload-artifact、結果をslackに送るslatifyを使ってみました。

また、Actionsでテストを実行すると、何もしなければブラウザのデフォルト言語が英語になるようです。今回は日本語表示を監視したかったので、環境変数LANGで日本語を設定しています。


というわけで、1時間に一度サイトをチェックすることができるようになりました。
slackに監視OKのメッセージが来るのがちょっとした安心感がありますね。

ここで紹介したコードはだいぶ基本的な部分で現在はもう少しいろいろ入れて運用していますが、さらに高度なレベルに持っていきたいですね💪🏻!