2021年1月15日金曜日
GitHub dependabot でDockerタグをバージョン指定する
寒い日が続きますね。
ところで皆さんはGitHubのdependabotは使っていますでしょうか?
リポジトリをスキャンして、使っているライブラリやパッケージの脆弱性をお知らせしてくれるサービスですね。
その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タグの特定バージョンを無視する設定」を調べて入れてみました。
こちらのドキュメントによると、あるバージョンへの更新を無視するためには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系の範囲でバージョンアップするものになっていますね。
2020年11月12日木曜日
AWS ECRのライフサイクルポリシーを設定して自動クリーンアップ
今回は軽く、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に出してみる
AWSやGCPの料金、たまに気になりますよね。
そういえば今月どれくらい料金食ってるんだ?と思ったとき、もちろん各サービスのコンソールで課金管理のページを開けば確認できますね。
が、日常的にはお知らせが届くほうが簡単ですむよね、ということでslackに通知をさせてみました。
AWSは簡単だったのですがGCPはちょっと手間だったのでまとめておきたいと思います。
まあエンジニアとしては課金の具合を気にせず開発に集中できれば一番いいのですが、現実には会社の予算とかいろいろありますし私は課金周りの把握をする役回りもあるので、これはやっておきたいところです。
2020年7月10日金曜日
GCPでGitHub Actionsのセルフホストランナーを作った
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/actions
で Add 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のページ上にも出てきます。

Actionsをセルフホストランナーで実行するには、ワークフローのyamlで以下のように書きます。
runs-on: [self-hosted, linux, x64]
ワークフローでのセルフホストランナーの利用 - 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
})
}
--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')
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のメッセージが来るのがちょっとした安心感がありますね。
ここで紹介したコードはだいぶ基本的な部分で現在はもう少しいろいろ入れて運用していますが、さらに高度なレベルに持っていきたいですね💪🏻!