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のメッセージが来るのがちょっとした安心感がありますね。
ここで紹介したコードはだいぶ基本的な部分で現在はもう少しいろいろ入れて運用していますが、さらに高度なレベルに持っていきたいですね💪🏻!
2019年12月19日木曜日
安全にkubectl applyするコマンドを作った
最近 本番環境でやらかしちゃった人 Advent Calendar が話題ですね。 私も楽しく読ませてもらっています。
かくいう私も先日、kubernetes環境で作成中のアプリケーションを間違って別アプリケーション用のnamespaceにデプロイしてしまうという事故をやってしまいました😇
何をどうミスしたかというと、
- 新しく作るnamespace以下にkubectl applyでアプリケーションをデプロイしようとした
- 対象のyamlファイルは別プロジェクトのソースファイルをコピーして作ったものだった
- yaml内に書かれているapply先namespaceが別プロジェクトのnamespaceのままだった
普段kubernetes環境へのapplyは原則CircleCI内で行っているのですが、今回は環境構築中なアプリケーションだったので手動で実行しようとしていたのでした。
実行前に確認しなかった私が悪いのですが、これは何とかして防げるようにしたいなあということで、applyする前にyaml内のnamespaceをチェックしておかしかったら止める、というシェルスクリプトを作りました。
作るにあたっては以下のような前提があります。
- 環境の切り替え管理にkustomizeを使っている
- 各環境のyamlは k8s/overlays/{環境名} ディレクトリに置いて、
kubectl apply -k k8s/overlays/{環境名}
でデプロイ - デプロイ先namespaceは {リポジトリ名}-{環境名} に統一する
#!/bin/sh
# check whether namespace is {repository}-{env} correctly or not
kubectl kustomize $1 > /dev/null 2> /dev/null
if [ $? != 0 ]; then
/bin/echo 'usage: kubeapply [kustomize_dir]'
exit 1
fi
PROCEED=0
REPOSITORY_NAME=$(git rev-parse --show-toplevel | rev | cut -f1 -d'/' | rev)
NAMESPACES=$(kubectl kustomize $1 | grep namespace | sed -E 's/namespace:(.*)/\1/' | tr -d ' ' | sort | uniq)
OVERLAY_DIR=$(echo $1/ | tr -s '/' | rev | cut -f2 -d'/' | rev)
FOUND=()
for NAMESPACE in $NAMESPACES
do
if [ ${NAMESPACE} != ${REPOSITORY_NAME}-${OVERLAY_DIR} ]; then
FOUND+=("${NAMESPACE}")
PROCEED=0
else
PROCEED=1
fi
done
if [ ${PROCEED} = 0 ]; then
/bin/echo "expected namespace is '${REPOSITORY_NAME}-${OVERLAY_DIR}'"
/bin/echo "but, namaspace '${FOUND[@]}' found in kustomize"
/bin/echo -n "proceed apply? (yes/No): "
read ANSWER
if [ ${ANSWER} = 'yes' ]; then
PROCEED=1
fi
fi
if [ ${PROCEED} = 1 ]; then
kubectl apply -k $@
else
echo "stop applying"
fi
これを私はkubeapplyという名前にしてPATHが通っているところに置いてます。
$ kubeapply k8s/overlays/dev
のように実行すればOKです。やっていることは、
- 引数がkustomizeディレクトリとして正しいかチェック
- kustomize結果内のnamespaceを抽出
- 抽出されたnamespaceが{リポジトリ名}-{環境名(=引数のディレクトリ名)}と一致しているか確認
- すべて一致していれば
kubectl apply -k {引数}
を実行 - 一致していないものがあればプロンプトを表示、yesと回答したらapplyを実行
2019年10月11日金曜日
GASでG Suiteグループ追加をやってみた
以前の記事で、作業を楽にするためにRPAを触ってみたという話を書いたのですが
その関係の取り組みの一つとして現在Google Apps Script(GAS)でアカウント管理操作を集約・省力化するという作業を進めています。
Google Apps Scriptはご存知の方も多いと思いますが、Googleの様々なサービスと連携させて動かすことができるスクリプト言語です。
シタテルではG Suiteをバリバリ使っているので、アカウント管理システムもGASを中心として作っていこうと考えています。
そこで、まずはGASで何か作ってみる第1号として、ユーザーをグループに追加するフォームを作ってみました。
グループ追加はG Suiteの管理画面からできるのですが若干作業として面倒だったりするんですね。
というわけでまずはGoogleフォームを作成。
「追加するユーザー」「追加先グループ」の入力欄を作ります。
それからオプション設定の「メールアドレスを収集する」をチェック。
つづいて右上メニューから「スクリプトエディタ」を選択するとGASのオンラインエディタが開きます。
ここでソースコードを編集します。
今回のコードはこのようなものです。
var privilegedRespondent = ['taro@sitateru.sample', 'jiro@sitateru.sample'];
// グループ追加,フォーム送信時に実行
function addUserToGroup(event) {
console.log('addUserToGroup()');
var message = "グループ追加フォームからの通知です\n\n";
const respondent = event.response.getRespondentEmail();
if(privilegedRespondent.indexOf(respondent) === -1) {
console.log('Respondent ' + respondent + ' not allowed');
message += 'あなた( ' + respondent + " )にはアカウントを作成する権限がありません\n";
sendResult(respondent, message);
return;
}
const response = parseResponse(event.response.getItemResponses());
const userName = response['追加するユーザー'];
const groups = response['追加先グループ'].split(',');
for (var i = 0; i < groups.length; i++) {
var groupName = groups[i].trim();
try {
var member = addGroupMember(userName, groupName);
console.log(' added member', member);
if(member == undefined) {
message += "ユーザーをグループに追加できませんでした。入力内容を確認してください\n";
message += ' ユーザー: ' + userName + "@sitateru.com\n";
message += ' グループ: ' + groupName + "@sitateru.com\n\n";
} else {
message += "ユーザーをグループに追加しました\n";
message += ' ユーザー: ' + userName + "@sitateru.com\n";
message += ' グループ: ' + groupName + "@sitateru.com\n\n";
}
} catch(error) {
console.log(' error', error);
message += "ユーザーをグループに追加できませんでした。入力内容を確認してください\n";
message += ' ユーザー: ' + userName + "@sitateru.com\n";
message += ' グループ: ' + groupName + "@sitateru.com\n";
message += ' エラー: ' + error.toString() + "\n\n"
}
}
sendResult(respondent, message);
}
// Googleユーザーをグループに追加
function addGroupMember(userName, groupName) {
if(userName && groupName) {
const userEmail = userName + '@sitateru.com';
const groupEmail = groupName + '@sitateru.com';
console.log(' add ' + userEmail + ' to ' + groupEmail);
const member = {
email: userEmail,
role: 'MEMBER'
};
return AdminDirectory.Members.insert(member, groupEmail);
}
}
// メール送信
function sendResult(recipient, message) {
MailApp.sendEmail({
name: 'アカウント管理システム',
to: recipient,
subject: 'グループ追加フォーム実行結果',
body: message
});
console.log('send result');
}
// フォームの入力内容をオブジェクトにして返す
function parseResponse(itemResponses) {
var obj = {};
for (var i = 0; i < itemResponses.length; i++) {
var itemResponse = itemResponses[i];
var question = itemResponse.getItem().getTitle();
// var type = itemResponse.getItem().getType();
var answer = itemResponse.getResponse();
obj[question] = answer;
}
return obj;
}
あとはスクリプトにグループを操作する権限を与えるために、メニューの「Googleの拡張サービス」を選択して「Admin Directory API」をONにします。
今回のコードではグループ追加フォームとして実用上あったほうがいい機能もいろいろつけてみました。
・
event.response.getRespondentEmail()
でフォーム送信者のメールアドレスを取得し、あらかじめ定義しておいた privilegedRespondent
にいる人でなければ実行しない・「追加先グループ」はカンマ区切りで複数指定できるように
・最後に実行結果をメール送信
MailApp.sendEmail()
と、こんなもので実際に利用を始めています。
こちらにリファレンスがありますが、GASはかなりの種類のGoogleのサービスを操れるのが強みです。↓のページで左メニューの「G Suite Services」や「Advanced Google Services」を開いてみよう!
https://developers.google.com/apps-script/reference
これからもいろいろなものを作って楽をしていこうと思います!
2019年9月9日月曜日
SQLをつかってJSONで格納されたデータを検索してみた
こんにちは!
シタテルでエンジニアをしている建山です。
今回は、データベース(MySQL)にJSON形式で格納されているデータをSQLで検索する方法を紹介します。
シタテルでは、AWS上のデータベースに蓄積されたデータをredashというツールを使い、SQLでデータ抽出したあと、データ分析できるようになっています。
やりかた
今回はMySQLの
たとえば、格納データが以下の2レコードあったとします。
{"maxCount":1000,"targets":{"ladies":false,"mens":true,"kids":false,"baby":false}}
{"maxCount":50,"targets":{"ladies":true,"mens":false,"kids":false,"baby":false}}
ここで、maxCountが50のデータを検索したいときは、
JSON_EXTRACTを使用し、以下のように抽出できます。(テーブル名:table_a、JSON格納カラム名:detail)
select * from table_a where JSON_EXTRACT(detail, '$.maxCount') = 50
targetsのladiesがtrueの検索をしたいときは、以下のようになります。
select * from tablename where JSON_EXTRACT(detail, '$.targets.ladies') = true
# 感想
このように、```$.
のあとに、キーの名前を指定してあげれば、かんたんに、jsonが格納してあるカラムの中身を検索することができます。(表示の記述方法も同じ)
公式のドキュメントはこの辺あたりのようです。
https://dev.mysql.com/doc/refman/5.7/en/json.html
他にもいくつか関数があるようなので、いろいろ試してみたら、いいSQL+JSONライフが送れるかも知れません!
簡単ではありましたが、ご紹介でした!
RPA触ってみた
どうもお久しぶりです。シタテルの朝野です。
突然ですがRPAってご存知でしょうか?私は数週間前にまともに知りました🙄
"Robotic Process Automation" の略で、PC上での作業をソフトウェアロボットが行うよう自動化することです。
現在私のいるDevOpsチームでは単純な作業を自動化して時間と手間を節約できないかという取り組みを少しずつ進めています。
そんな中でRPAというものの存在を知り、簡単に無償で導入できるものを試してみたので、サンプルと共に紹介します。
今回やってみたサンプルは、シタテルの「マイアトリエ」をブラウザで開いてログインするというものです。
(※サンプル内のメールアドレスとパスワードはダミーの値です)
Javascript for Automation
MacOS Yosemite以降で使用可能なスクリプト言語です。
サンプルコードはこのようになります。
const DELAY = 3;
const Chrome = Application("Google Chrome");
const window = Chrome.windows[0];
const tab = Chrome.Tab({
url: 'https://atelier.sitateru.com/login'
});
window.tabs.push(tab);
delay(DELAY);
inputText(tab, 'email', 'sample@sitateru.sample');
inputText(tab, 'password', 'sample_password');
submit(tab);
function inputText(tab, elementId, value) {
tab.execute({ javascript: "document.getElementById('" + elementId + "').value =`" + value + "`;" });
}
function submit(tab) {
tab.execute({ javascript: "document.forms[0].submit();" });
}
ブラウザ上での入力やクリックはjavascriptをブラウザ内で走らせることで行っています。
実行はコマンドで $ osascript -l JavaScript sample.js
と打つだけです。
JavaScriptを書いてコマンド実行なので、RPAわからんけどJavaScriptチョットワカル!という私のような者にはありがたいです。
エンジニアで使う分にはハードルが低いと思うのですが、ノンプログラマーやMac使わない人がいると導入しにくそうなのが難点でしょうか。
SikuliX
JavaがあればWindowsでもMacでもLinuxでも使えるOSSのRPAアプリケーションです。
IDEを立ち上げて自動化プログラムを作成・実行します。
サンプルコード(コードと言っていいのかわかりませんが)はこのようになります。
自動化したい操作をスクリプトににしてIDE上で書いていきますが、マウスポチポチでもある程度作れるのでノンプログラマーにもいくぶんとっつきやすそうです。
画像処理で有名なOpenCVを使って作られているらしく、操作対象の認識を「デスクトップ画面の中で対象の画像を抽出する」ことで実現しています。
(サンプルコードの click()
のカッコ内にあるのは操作したい要素のスクリーンショットなのです)
画面上の文字を認識するOCR機能も一応あるのですが試してみたところ精度が怪しく実用には難しいなという印象でした。
機能面はやや物足りない感触でしたが、OSをまたいで使えることと導入の難易度が低そうなのはメリットですね。
UiPath
こちらもIDEで作成・実行するタイプで、GUIでフローチャートを組み立てたりブラウザ操作を自動で読み取って操作を記録したりと、基本シェアウェアだけあって操作感や機能は充実しています。(そのぶん見た目は複雑なので最初は少し戸惑うかも)
個人や中小企業はCommunity Editionという無料版が使えますが、ある程度以上の規模の企業で使う場合は有料になるようです。
https://www.uipath.com/ja/freetrial-or-community
サンプルコード(これもコードと言っていいのかわかりませんが)はこのようになります。
作業の流れがグラフィカルに表示されるので大掛かりなようにも見えますが、Webページ上の要素を選択して操作しているというのは同じですね。
公式コミュニティがあるほかハンズオンセミナーなども開かれているようで、費用面とWindowsのみという点が問題なければ有力な候補になりそうだと思います。
というわけで、今回は無料で使えるRPAを簡単にお試ししてみました。
こういったRPAは使いようで色々な作業の手間を省けそうな予感がしているので、できることを把握してうまく活用していきたいですね。
この記事がRPAに手を出してみる取っ掛かりになれば嬉しいです🤗
2019年6月3日月曜日
書評『進化的アーキテクチャ ―絶え間ない変化を支える』
どういう本か・総評
適応度関数
アーキテクチャの分類・マイクロサービス
- モノリス(3層レイヤ化モノリス等を含む)
- イベント駆動アーキテクチャ
- SOA
- マイクロサービス
- サーバレス
モノリスを構築できないとき、なぜマイクロサービスがその答えだと思うのか。
マイクロサービスという名前にひっかからないようにしてほしい。各サービスは決して小さい必要はない。むしろ有効な境界づけられたコンテキストを捉えることが必要なのだ。
サービス境界分割の手法
- ビジネス機能グループ
既存のビジネスコミュニケーション階層を反映する。コンウェイの法則に従う。 - トランザクション境界
トランザクションは最も分離しにくい要素である。 - デプロイメント目標
デプロイ頻度が異なる箇所を境界とする。
コンウェイの法則/逆コンウェイ戦略
システムを設計するあらゆる組織は、必ずその組織のコミュニケーション構造に倣った構造を持つ設計を生み出す。
ゆえに、チーム外の人間が所管する業務フローやコードにはできるだけ影響を及ぼさないように設計・実装しようとする(調整範囲をチーム内に閉じようとする)力学が働く。
結合よりも重複
マイクロサービスは無共有アーキテクチャを形成する。その目標は、できるだけ結合を減らすことだ。一般的に、結合よりも重複の方が望ましい。
ツールがあまりにもコードの再利用を容易にしてしまったせいで、現代の開発者は、たやすく結合できてしまう環境の中で適切な結合を行うことに悪戦苦闘している。
例えば、 CatalogCheckout サービスと ShipToCustomer サービスの両方が Item という概念を持つ。両方のチームが同じ名前と同じプロパティを持つので、開発者はサービスをまたいでそれを再利用しようと試みる。それが時間や労力の節約につながると考えるからだ。
けれど、それは労力を増やす結果となる。なぜなら、コンポーネントを共有する全てのチームが変更を伝搬しなければならなくなるからだ。一方、コンポーネントを結合せずに各サービスにItem があり、必要な情報だけをCatalogCheckout から ShipToCustomer に渡す場合は、それらは独立して変更することが可能だ。
開発者が再利用可能なコードを作成する場合、開発者は最終的にコードを使用する無数の方法に対応するための機能を追加する必要がある。その将来の保証全ては、開発者がコードを単一の目的のために使用することをより難しくする
腐敗防止層を設ける
デプロイに関するプラクティス
取っ掛かりをつかみたい人にはおすすめできるかと思います。
2019年5月8日水曜日
プログラミング・ゲーム
シタテルでバックエンドエンジニアをしている熊谷です。
今日は、プログラミングを学べるゲームやアプリの紹介をしたいと思います。
まず1つ目は、「Swift Playgrounds」です。
これはAppleが出しているiPad用のアプリで、キャラクターを動かすなどのプログラミングを、ゲーム感覚で学ぶことができます。
アプリ自体は前からあったのですが、最近やっと少し触ることができました。
画面イメージとしては、こんな感じです。