sitateru tech blog

sitateru tech blog

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

2021年2月26日金曜日

Node.jsのバージョン管理をVoltaに統一したわけ

2月 26, 2021

Node.jsを使っていれば永遠のテーマである(?)バージョン管理ですが、皆さんはどのようにしているでしょうか。

今更ながら、ローカル環境で使用するバージョン管理を原則としてVoltaに統一する運びになったので、その背景などを書いてみたいと思います。



今までは特にバージョン管理ツールを標準で決めておらず、個人やチームでそれぞれ好きなものを使っているような状態でした。
それはそれで大きく困るようなことはなかったのですが、先日多くのリポジトリにざっといくつかの設定・アップデートをして回るようなタスクがあって話が変わりました。

それぞれのリポジトリで使っているNodeのバージョンをどこからか見つけてきて切り替えする作業を1日に何度もやるのはやはり効率が悪いしストレスですね。
バージョン管理ツールを決めて全社標準で設定してもらわないことには、私が 😇 してしまう・・・

ということで何のツールをツールを使うか検討を始めました。
今回の悩みを解消するためには、要件はこんなところです。

  • 設定しておけばカレントディレクトリに応じて自動でNodeのバージョンが切り替わる
  • 設定したバージョンをVCSに記録できる
  • npmのバージョンも記録して自動切換えできる

ちなみに最後の条件はnpmのバージョン7がもう一般リリースされているので出てきたものです。
npm 7 is now generally available! - The GitHub Blog
それまでのバージョン6とは機能面の破壊的変更とlockファイルの変更などがあるので、これも併せてバージョン管理しておきたくなったわけですね。



さて結論ですが、Voltaを使うことにしました。
Volta - The Hassle-Free JavaScript Tool Manager
ある程度メジャーなツールで上の要件を満たすものとなるとVoltaしかなかったためです。

使うのも簡単でいい感じですね。
Voltaの使い方については詳しい記事が各地にあるのでここではあまり書きませんが、例えばNode 14, npm 6を使うなら

$ volta install node@14 npm@6
$ volta pin node@14 npm@6

とすると package.json にバージョンが書き込まれます。

{
  ...
  "volta": {
    "node": "14.15.5",
    "npm": "6.14.11"
  }
}

これをgitにコミットしておけば、自動でこのバージョンのnodeとnpmが実行されるようになります。
全リポジトリで設定されればもうバージョンを気にしなくてよくなるわけですね🤗

Nodeはもちろん、npmやその他nodeツール類のバージョン管理をしっかりやりたいという方はVoltaを一度試してみてはいかがでしょうか💁‍♂️

2021年1月15日金曜日

GitHub dependabot でDockerタグをバージョン指定する

1月 15, 2021

寒い日が続きますね。

ところで皆さんはGitHubのdependabotは使っていますでしょうか?
リポジトリをスキャンして、使っているライブラリやパッケージの脆弱性をお知らせしてくれるサービスですね。

依存関係を自動的に更新する - GitHub Docs

そのdependabotでちょっとした問題を調べて解決したので記録しておこうと思います。
題して「特定のDockerタグを無視する設定の書き方」です。
dependabotの概要や初期設定などはこの記事ではちょっと飛ばしますのでご了承ください🙇‍


dependabotはDockerタグにも対応していて、脆弱性対応された新しいバージョンタグがある場合はそれを出すようになっています。
例えばリポジトリの docker/Dockerfile をスキャン対象にする場合、コミットしておく設定ファイル .github/dependabot.yml はこのようになります。

version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/docker"

さて、基本的にはdependabotはメジャーバージョンアップも含めて最新バージョンにアップデートさせるように動作するようです。
そのため、Dokerfileで node:14.x.x イメージを使っている場合

このように node:15 系にアップデートしなよ!というPRが作成されるのです。

でもNode.jsはバージョン14はLTSで15はLTSじゃない・・・更新するなら14系の最新にしてほしい!
というわけで、「Dockerタグの特定バージョンを無視する設定」を調べて入れてみました。


依存関係の更新の設定オプション - GitHub Docs

こちらのドキュメントによると、あるバージョンへの更新を無視するためにはdependabot.ymlにそのバージョンを明記すればいいのですが、

範囲を定義する場合は、パッケージマネージャーの標準パターンを使用します

Dockerタグの標準パターンって何だ・・・?🤔

結局わからなかったのでソースを見てみたところ、判定しているのはおそらくこのあたり。

dependabot-core/update_checker.rb at main · dependabot/dependabot-core · GitHub

バージョンの取り扱いに使っているのは Gem::Versionクラス のようです。

class Gem::Version (Ruby 3.0.0 リファレンスマニュアル)

ということはGemfileでバージョンを指定するのと同じ記法で良さそうですね。
dependabot.ymlはこうなりました。

version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/docker"
    ignore:
      - dependency-name: "node"
        versions: ["~>15.0"]
        # nodeイメージの 15.x 系は無視

これでnodeイメージは 15.x を対象外にして更新するようになりました。
新しく作られるPRは14系の範囲でバージョンアップするものになっていますね。


dependabotでDocker更新、なかなか便利なのでお試しください🙏

2020年11月12日木曜日

AWS ECRのライフサイクルポリシーを設定して自動クリーンアップ

11月 12, 2020

今回は軽く、AWS ECR (Elastic Container Registry) の小ネタです。

AWSでコンテナを使って何かする場合ECRにコンテナイメージを保存しておくことが多いと思いますが、日ごろからイメージをよくビルド&プッシュしているとどんどんイメージが増えていきますね。


イメージサイズがものすごく大きいとかでなければそれほど料金を食うわけでもないのですが、まず使わない古いイメージをずっと保存しておくこともないよなーと思ったのでそのあたりを設定してみました。

ECRではライフサイクルポリシーという機能があり、いろいろな条件を定めてイメージを自動クリーンアップすることができるんですね。
Amazon ECRのライフサイクルポリシーでコンテナイメージのクリーンアップ | Amazon Web Services ブログ

設定できる条件は

  • タグ
  • イメージ数
  • プッシュされてからの経過日数
を組み合わせることができます。

今回は、「タグが latest ではない」かつ「プッシュされて90日以上」の条件に合うイメージを削除するポリシーを設定してみました。

AWSコンソールで設定する場合は、ECRのコンソールでリポジトリを選択して左のメニューの Lifecycle Policy をクリックすると設定画面があります。


「テストルールの編集」ボタンを押すとポリシーを適用した結果が確認できる(実際に保存されているイメージには何もしない)テスト用ページに移動するので、まずはそこで試してみるのがいいですね。

さて、ライフサイクルポリシーは「ルール」を優先順位つきで好きな数設定することで構成します。ルール作成画面はこうなっていて、一致条件は「イメージをプッシュしてから」「次の数値を超えるイメージ数」が選べます。


今回作りたい条件は「タグが latest ではない」かつ「プッシュされて90日以上」ということで、

  • タグ付け済("latest")、次の数値を超えるイメージ数(1)
  • すべて、イメージをプッシュしてから(90日)

という2つのルールを作ってこのようになります。


ルールを作ればあとは自動でそれに従ってイメージが毎日掃除されるので、とても楽ですね😊


ここまではコンソールで操作してきましたが、リポジトリが多い場合やコンソール面倒だと言う場合はもちろんCLIが使えます。

put-lifecycle-policy — AWS CLI 2.1.0 Command Reference

まずは先ほどのルールをJSON化して policy.json とでもファイルを作ります。

{
    "rules": [
        {
            "rulePriority": 1,
            "description": "keep latest image",
            "selection": {
                "tagStatus": "tagged",
                "tagPrefixList": ["latest"],
                "countType": "imageCountMoreThan",
                "countNumber": 1
            },
            "action": {
                "type": "expire"
            }
        },
        {
            "rulePriority": 2,
            "description": "expire images older than 90 days",
            "selection": {
                "tagStatus": "any",
                "countType": "sinceImagePushed",
                "countUnit": "days",
                "countNumber": 90
            },
            "action": {
                "type": "expire"
            }
        }
    ]
}

今回は全リポジトリに適用したかったのでシェルスクリプトを作りました。

#!/bin/sh
# ECRリポジトリにライフサイクルポリシーを設定する

for REPO in $(aws ecr describe-repositories --query "repositories[*].{name:repositoryName}" --output text)
do
  echo "repository: $REPO"
  aws ecr put-lifecycle-policy --lifecycle-policy-text "file://policy.json" --repository-name $REPO
done

これで一括適用ができますね💪

比較的簡単に設定ができるので、「うわっ…ECRのイメージ、多すぎ…?」と気づいた際には設定してみてはいかがでしょうか。

ちなみにterraformだとaws_ecr_lifecycle_policyリソースで設定できるようです。

aws_ecr_lifecycle_policy | Resources | hashicorp/aws | Terraform Registry

2020年9月4日金曜日

GCPの料金アラートをSlackに出してみる

9月 04, 2020

 AWSやGCPの料金、たまに気になりますよね。

そういえば今月どれくらい料金食ってるんだ?と思ったとき、もちろん各サービスのコンソールで課金管理のページを開けば確認できますね。
が、日常的にはお知らせが届くほうが簡単ですむよね、ということでslackに通知をさせてみました。

AWSは簡単だったのですがGCPはちょっと手間だったのでまとめておきたいと思います。

まあエンジニアとしては課金の具合を気にせず開発に集中できれば一番いいのですが、現実には会社の予算とかいろいろありますし私は課金周りの把握をする役回りもあるので、これはやっておきたいところです。


2020年7月10日金曜日

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

7月 10, 2020
朝野です。少し前の話ですが、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 でブラウザベースの負荷テスト

4月 30, 2020
どうも朝野です。
少し前に負荷テストツールの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 のいろいろなテクニック

3月 19, 2020
朝野です。
先日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を書いてみました。
何かの参考になれば幸いです。