吉祥寺.pm mini #012 に参加した

kichijojipm.connpass.com

 

参加した。酔った勢いで参加登録し、新幹線と宿を予約をし、東京は吉祥寺に舞い降りました。

ライブと仕事でしか東京に行かないので西のほうに行くのは新鮮。

 

設計というテーマもさることながらしんぺいさん (id:nkgt_chkonk) を見に行くチャンスなので京都から参加登録した。

 

設計という抽象的な話題についてうまく他人とコミュニケートできる・それだけのプレゼンテーションができるというのはすごいことだと思っている。

参加して設計についてもちろん深い考察が得られたらいいと思っていたけれど、それ以上に自分が感じる「設計についていいかんじに他人にプレゼンテーションするために必要な能力は何なのか」について手掛かりが得たいと思っていた。

 

結論から言うと、それは得られた。簡単にまとめてみると:

  • 議論に参加する人間が解決できる (結論を出せる) 単位まで問題を分解する
  • 分解された子孫テーマ間の繋りを補強・説明する知識を持っている

……という能力およびそれの効果的な発揮に裏打ちされているのだと理解した。

具体的には:

  • 「良い設計とは何か」というテーマを、たとえば「DRY原則をいいかんじに適用する例とはなにか」くらいに分解する
  • さらに「DRY原則を適用していいかんじになっている」状態を「他の設計原則に違反せず達成されているか」くらいにさらに分解する

……という風に表れている。

「今あえてDRY原則に向き合う」というスライドはよかったです。

speakerdeck.com

こうして考えてみるとティーチングに対しても問題解決の原則を持ち込んでいるということであって、目新しさはない。ないが、当たり前のことを当たり前にやるということを実践できることは非凡ではないと思う。

 

京都からいきなりやってきたけど非常に楽しく参加させてもらえてありがたかったですし、個人的にはインターネットで見かけたおもしろそうな人を見に行くために出かけるのはインターネット原体験っぽさがあってよかった。

終電がなくなって吉祥寺から宿をとった京橋までタクシーに乗ったら「京橋? (舌打ち)」って言われたり「京橋ってどこですか?」って言われたり、深夜の急カーブ高速道路を120km/hで駆け抜けて恐怖を感じたり久しぶりにメチャクチャな目にあったけどそれもよかった、また吉祥寺いきます。

GoのデバッグはdelveとVisual Studio Codeが便利

delveとは

Go向けのデバッガで、ステップ実行とかブレイクした行のレキシカル変数が見えたりといった基本的な機能を提供しつつ、dlv debugコンパイルしつつ実行 (go run) したりgo(1)とほどよく統合されている。

後述するheadlessモードがあっていわゆるリモートデバッグが可能。

delveをmacOSにインストールする

公式のドキュメントではbrew install go-delve/delve/delve一発で済むように書かれているが実際はうまくいかなかった (´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`)

==> Installing go-delve/delve/delve
==> Downloading https://github.com/derekparker/delve/archive/v1.0.0.tar.gz
==> Downloading from https://codeload.github.com/derekparker/delve/tar.gz/v1.0.0
######################################################################## 100.0%
security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.
==> Generating dlv-cert
==> openssl req -new -newkey rsa:2048 -x509 -days 3650 -nodes -config dlv-cert.cfg -extensions codesign_reqext -batch -out dlv-cert.cer -keyout dlv-cert.key
==> [SUDO] Installing dlv-cert as root
==> sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain dlv-cert.cer
Last 15 lines from /Users/aereal/Library/Logs/Homebrew/delve/02.sudo:
2018-02-28 11:11:35 +0900

sudo
security
add-trusted-cert
-d
-r
trustRoot
-k
/Library/Keychains/System.keychain
dlv-cert.cer


If reporting this issue please do so at (not Homebrew/brew or Homebrew/core):
https://github.com/go-delve/homebrew-delve/issues

These open issues may also help:
Upgrade to delve fails https://github.com/go-delve/homebrew-delve/issues/20

紹介されているissueを見てみると証明書を生成してインポートするスクリプトを手で実行したらうまくいったと書いてある。

実際に実行した手順はこういうかんじ:

✘>﹏<✘ < cd $(brew --cache)/
✘╹◡╹✘ < tar xzf delve-1.0.0.tar.gz
✘╹◡╹✘ < cd delve-1.0.0
✘╹◡╹✘ < bash ./scripts/gencert.sh
✘╹◡╹✘ < brew install go-delve/delve/delve

delveをインストールしたら sudo pkill taskgated を実行してtaskgatedを再起動する。
プロセス間通信を監視するデーモンらしく、bash ./scripts/gencert.shで生成・インポートした証明書を読み込みしなおす必要がある。

Visual Studio Codeとvscode-go

Visual Studio Codeは便利、GoやTypeScriptを書くとき最近はもっぱらこれ。

vscode-goというGoを書くときの各種便利サポートを追加してくれる拡張を入れる。

Debugging Go code using VS Code · Microsoft/vscode-go Wiki · GitHubが詳しい。

Visual Studio Codeでデバッグ

Visual Studio Codeでリポジトリを開きつつ Debug → Open Configurations でデバッガの設定 (JSON) を開く。

設定中の moderemote にする。remoteにすると別プロセスで既に起動しているデバッガーに接続する。

Debugging Go code using VS Codeにあるように、シェルで dlv --listen=:2345 --headless -- などと実行してデバッガーを起動する。

あとはVisual Studio Codeの Debug → Start Debuggingを押すとデバッガー開始できる。

gyazo.com

行頭を押すとブレイクポイントを設定できまるでIDEのよう〜。

ステップ実行もできるしレキシカル変数も見れて便利!!!!

Dockerのコンテナ内でapt-cache searchしてパッケージが見つからないとき

rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* してないか確認する。

コンテナのプロビジョニングを試行錯誤しているときに docker-compose run --rm app bash とかやりがちだけど、最初の方に apt-get update && apt-get install && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* しているとaptのキャッシュもリポジトリも消えるので、あるべきリポジトリが見つからないように見えることがある。

探すときはpackages.debian.orgを見るとかするとよい。

unzipは展開先のディレクトリ・ファイルが存在したら上書きしていいか聞いてくる

スクリプトで自動化したいときは困る。

そういうときは unzip -o /path/to/kure.zip のように -o オプションを使うとよい。

       -o     overwrite existing files without prompting.  This is a dangerous option, so use it with care.  (It is often used with  -f,  how-
              ever, and is the only way to overwrite directory EAs under OS/2.)

Google Calendarについてあなたが知るべきたった1つのこと

「予定あり」「予定なし」という概念があり、これが「予定あり」の予定が入っているときのみ「時間を探す」などでブロックされる。 言い換えると予定が「予定なし」だと他の人からは空いているように見える。

ちなみに「終日」にするとデフォルトが「予定なし」になる挙動 (現時点) のようなので留意しましょう。

「予定なし」の予定

https://i.gyazo.com/bb257c0c9e4ae4f39863bd75f8126a0b.png

「予定あり」の予定

https://i.gyazo.com/18f82f38ab7afa33bd1956110ebf522e.png

いかがでしたか?

「予定なし」の予定とかむずかしい日本語ですね。

LWP::Protocol::PSGI 0.10以降でLWP::UserAgentの:content_file引数が使えるようになっている

- Fix mirror() (haarg) #9

https://metacpan.org/changes/release/MIYAGAWA/LWP-Protocol-PSGI-0.10#L4

changesには `LWP::UserAgent#mirror` についてのみ言及があるけど、`get()` メソッドの `:content_file` 引数にも影響がある。

`mirror` メソッドも `:content_file` 引数も紆余曲折あり `LWP::Protocol#collect` を呼ぶことでレスポンスボディがファイルに保存される。

fix LWP::UA::mirror calls by haarg · Pull Request #9 · miyagawa/LWP-Protocol-PSGI

上記Pull Requestはつまるところ適切に `collect` を呼ぶようにする変更なので、 `mirror` も `:content_file` も適切に処理される、ということだった。

日記を書くWebアプリケーションを書いている

GitHub - aereal/nikki

昨年末からぼちぼち書いている。

モチベーション

  • いわゆるページを動的に生成するWebアプリケーションのパフォーマンスチューニングの練習台
    • サーバサイド、クライアントサイド双方に渡る改善を試みる余地がある
  • 諸々のWeb技術の遊び場
    • TypeScript
    • Google Cloud Platform
      • Kubernetes
    • HTTP/2
    • GraphQL
    • SPA = Single Page Application
  • 以上が目的としてではなく、手段として求められ高度にバランスされること
    • 普段から自分が使うアプリケーションであること
    • (技術採用など意思決定の練習)

……を目的に考えて日記を書くためのブログシステムを書きはじめた。

Web技術で遊び続ける定まった場所がないと、技術を使うための技術の域を出ない学習が続くなという問題意識から何か作ることにし、自分はWebでテキストを書くのが大好き人間なので、ブログシステムを作ることにした。

それでも自作するか
もしウェブプログラマなら自作すべきであると常に主張したい。

ブログシステムは自作すべきか? ブログサービス VS 自作システム | tech - 氾濫原

自分もこの意見に賛成で、まあブログシステムであることにこだわらなくてもいいけれど、趣味なら車輪の再発明など恐れずにとにかく遊び続けるのがいいと思う。

また完全無欠のASP型のサービスなどありえないわけで、その点では自作するとき自分が求めていてASPサービスにないものを作れるならば、それは完全な車輪の再発明とはいえないし、得るものは大きいと思う。

目的

パフォーマンスチューニングを目的に挙げているのは、仕事でもWebアプリケーションを作っているがそもそもパフォーマンスについて考える時間が少ないので知識も技術も身に付かない、けど求められるものではあるというギャップを埋めるためでもある。

そしてこうした興味・好奇心だけを目的とせず、使う自分自身の満足度・快適さに対する解決の手段として自分の興味がある技術を選べるような題材という点でも、ブログシステムは適している。

ソフトウェアエンジニア一般が趣味の開発でこうしたモチベーションでいるべきと考えているわけではなく、単に自分が趣味で作ることを考えるとき自分がよく使うもので、かつうまく動いていると自分が一番に喜ぶものでないと、継続して触りつづけるモチベーションが保たないだろうということを見越した、合理的な理由による。

技術

インフラはGoogle Cloud Platform、configuration managementを考えるのがだるいのでランタイムはコンテナに任せようということでk8s (GKE) を選んだ。
仕事でAWSを使っているので、では趣味ならGCPというくらい。あと料金計算が明快なのがいい、AWSリザーブインスタンスとか考えはじめると勘定がめんどう。
いまVPSに月におよそ1000円くらい払っているので、これを解約することを見越してプラス勉強代として月2000円まででやりくりしようと思っている。

パフォーマンスチューニングを深追いしたいということで、ポピュラーになったCDNは採用せず自分でリバースプロキシを運用することにした。

サーバサイドはRuby, DBはPostgreSQL, 記事の編集画面はTypeScriptで書いている。

インフラとフロントエンドが弱いと感じていて、そちらにリソースを割きたいのでサーバサイドは使い慣れたRubyにした。
Rubyはぱっと作ることができて安定感があるものの、型がなかったりもうちょっと冒険してもいいかなと思っている。
とはいえパッケージ管理とかでむやみにハマっていたくないし、予算上、ホストのメモリとかは潤沢にはできないのでScalaは選びづらいし、仕事でも趣味でもGoをよく書いており食傷気味なので、やはり落とし所として妥当かと思っている。

本当はサーバサイドはホットスポットではないのでGAEにしてもよかったんだけれども、予算の都合でStandard Environmentしか使えず、StandardだとJavaPythonかGoかPHPで、どれも気乗りしなかったのでGKEにした。

フロントエンドはそのうちSPAにしてみたい。
昔、仕事でSPAを触っていたけどhistory APIにバグのある地獄みたいな環境向けだったので、今のまともなブラウザと充実してきたと思われるライブラリ類で、リソース管理などの煩雑さが減ったのかそうでもないのか確かめてみたい。

いまは最低限、記事を書いてそれを配信するところまではできたのでGKEにデプロイしようとしている。
Kubernetesは耳慣れない概念がたくさん出てくるので難しいけど、GCPのドキュメントが充実しているのでチュートリアルを見様見真似でやりつつ要件・予算にあわせてあれこれ手探りで変えて試している。

おわり

VPSで遊ぶのも楽しいけれど、IaaS/PaaSが充実してきたことで予算の範囲内でマネージドサービスを利用し趣味の開発のボトルネックを解消することができそうでなかなか楽しい。
クラウド事業者にロックインされた範囲の中では、マネージドサービスから自前運用に切り換えることもできるわけなので、そういった点では上位互換といえそう。

MacBookのバッテリ残量をMackerelに投稿するプラグインをmkr plugin install対応した

github.com

sudo mkdir -p /opt/mackerel-agent/plugins
sudo chown -R $(id -un):$(id -gn) /opt/mackerel-agent/plugins

mkr plugin install aereal/mackerel-plugin-macbook-battery-health@v0.0.2
cat <<EOS >> /usr/local/etc/mackerel-agent.conf
[plugin.metrics.macbook_battery_health]
command = "/opt/mackerel-agent/plugins/bin/mackerel-plugin-macbook-battery-health"
EOS

this.aereal.org

以前作ったプラグインを `mkr plugin install`-readyにした。

対応といっても命名規則を整えてGitHubにリリースしたくらいで非常に手軽で便利。手順はMackerel公式のドキュメントが詳しくてわかりやすい。

mackerel.io
mackerel.io

実際に対応するための変更は以下のPull Requestにまとまっている。ちゃんと読んでいなくてzipじゃないとダメとか命名規則とかが適合していなくて無駄にトライアンドエラーを繰り返してしまった。

バッテリ残量の監視はなにかと便利なのでどうぞご利用ください。

2017年に利用した継続課金サービス

r7さんらの振り返りがおもしろかったので自分も書き出した。

medium.com

medium.com

継続しているサービス

dアニメストア

5,184 JPY/year

過去作から最新作までたくさん配信されているのでなにか見たいな〜という時に便利。

最近はテレビ放映とタイミングを揃えて配信される作品が増えたのでほんとうに良い時代になった。

今は京都という割と地上波のチャンネルに恵まれた地域に住んでいるけれど、地元は北海道なので深夜アニメがまったく見れずに悲しい思いをしてきたので、地域差が縮まるのは歓迎。

配信の仕組み自体は素朴という印象で、AmazonプライムビデオやNetflixと違いビットレートを調節してバッファリングの時間を減らすとかそういう取り組みはしてなさそうなので、ネットワークの品質に問題があるとストレスを感じることもある。

Amazonプライム

3,900 JPY/year

お急ぎ便などなどに使っている。あとプライムビデオのラインナップが絶妙で、dアニメストアと補完しあったり昔見たおぼえのある映画をたまに見ている。

GitHub

9,492 JPY/year

いつのまにかDeveloper planだとprivateリポジトリの上限がなくなってますます便利になった。

スケッチとかIntelliJの設定 (自動でコミットされるのでまずい内容が出てしまわないか心配なのでprivateにしている) などとりあえずprivateでも作っておけるという安心感を買っているようなもの。

IIJ mio

26,640 JPY/year (6GB ライトスタートプラン)

数年前にauからMNPしてずっと使っている。京都で生活していると人が多くて遅いというようなこともなく快適。

もともと3GBのプランだったけれど旅行で外に出る日が続くとすぐにクーポンを使い切ってしまうので6GBにした。

WiMAX 2+

44,352 JPY/year → ?

一人暮らししてから家の回線はWiMAXを契約しておりその流れで今も使っている。

……が、いまは通信量に上限があり、それはよいとしても課金して制限を解除する道も提供されていないので潮時かなーと思いしばらく立つ。

固定回線は、それはそれで地域において品質の良いISPを見つけること、工事に立ちあう必要があること、などから二の足を踏んでおりいまだにどうしようか考えるがまあいいかな……と思う。

次に引っ越すことがあったら諦めて固定回線を契約すると思う。

Adobe Creative Cloudフォトプラン

11,760 JPY/year

Lightroomなど使うために入っている。

Apple Music

11,760 JPY/year

ポピュラー音楽のラインナップは特に琴線に触れるところはないけどクラシックのラインナップが満足できる。

聞きたいオーケストラの盤が揃っているので好きな時に聞き比べたり参考にできるので気に入っている。

J-WESTカード:JRおでかけネット

1,080 JPY/year

旅行で東海道・山陽新幹線JR西日本の特急に乗る機会が増えたので契約した。

会社で出張するときは会社が契約しているEX-ICで新幹線に乗るのだけれど、あれを知ったら直前まで予約が変更できる便利さなしでは生きていけない。

のぞみに1回乗った時の割引額が1,080円なので1年に2回乗ったら回収できるのでかなりお得だと思う。

あと海外旅行前にVISAではなくMASTERCARDのカードを手に入れる目的もあった。

Netflix

(解約) → 11,400 JPY/year

ヴァイオレット・エヴァーガーデン」「からかい上手の高木さん」の配信がNetflixだけだったので再度契約した。

バッファリングの短さとか視聴するときのストレスのなさは使ったことのあるサービスの中では一番だと思う。

解約したり契約を見直したサービス

さくらのVPS

21,384 JPY/year → 10,692 JPY/year

1GBプランを2つ契約していたけど1つにした。

Elasticsearchとか計算機リソースを使うミドルウェアで遊ぶ時は、リソース単価の安いVPSのほうが使い出がある。

AWS (Amazon Web Services)

16,800 JPY/year → 1,047 JPY/year (予定)

古い写真のRAW画像の保存にGlacierを、このブログなどなどのドメインのネームサーバとしてRoute 53を使っており、これで年1,047円くらい。

……のつもりだったのだけれども、昨年明けに検証のために立てたEC2インスタンスを落とし忘れていて昨年1年間ずっと毎月10USDかかっていたことがわかって慌てて解約した。

MoneyForward

6,000 JPY/year → 解約

家計レポートとか見れたらおもしろいかなと思って契約したけど既に述べたようにEC2のインスタンスを起動しっぱなしで気付かないような人間なので、まずMoneyForwardにお金を払う前にやるべきことがたくさんあることに気がつき解約した。

おわり

EC2インスタンスを起動しっぱなしにする人間は何をやってもダメ。

数字で振り返る2017年

Amazon で購入した金額

金額 前年比
2017 483,040円 124.09%
2016 389,277円 124.54%
2015 312,547円 136.27%
2014 229,352円 -

カメラやレンズなどは含んでいない。主に Kindle 本や生活用品など。

この調子でいくと来年は45万くらい年間で使う目測だけど、そんなに成長するかな、どうかな。

数字で振り返った2016年 - Sexually Knowing

順調に124%成長が続いている、すごい。後で紹介するように給与は毎年110%ペースくらいなので、いつか追い付かれる。

給与

f:id:aereal:20180101215003p:plain

入社して以来、給与明細をGoogle Driveに保存してあり、支給額 (手取り) を12掛けして年俸額とした。
なので年俸額なのにやけにピコピコしている。

営業期が変わるタイミング (1月と7月) に人事評価がありそこで報酬が変わるので、だいたいそのタイミングで大きく変わる。

グレードという給与レンジを定めた概念が全6段階あって、それが変わるとベースも変わるのでそこで大きく変わる要因はそれくらいと見える。
いちおうグレードは役職と関係ないということになっている。

ちなみに控除額も有史以来記録してあり、見るとげんなりするもののひとつ。

写真ブログに投稿した記事数

記事数 前年比
2017 53 72.60%
2016 73 486.7%
2015 15 13.0%
2014 115 -

一転、減ってしまった。1月に大雪があったりクロアチア旅行に行ったり前半はよく撮ったし、後半も撮っていないこともないけれど上げていく気力がなかった。

9月から今もずっと仕事で完全に頭が参ってしまっている。書いていたらなんで仕事に忙殺されてこんな虚しい振り返りをしているんだとイラついてきた。

日記

記事数 前年比
2017 87 55.76%
2016 156 91.22%
2015 171 54.11%
2014 316 -

ほぼ半減した。9月くらいから本当に気が参って「仕事やめたい」とか「1000万ほしい」としか書いてない下書きがたくさんある。

近しい人に読んでほしくないので接続元のIPが地理的に近かったら見れなくするとか、そういうことをしたいな。

むすび

  • 年俸も120%成長させたい
  • 写真のアウトプットもっと増やそう

.devなどのTLDはChrome 63からHTTPSを強要される

その昔Powを使っていた名残で、開発時のループバックドメインに `.dev` をよく使っていたのだけれど、最近うまく動かなくて調べたところ、HTTPSで通信しようとして失敗していた。

アプリケーションでそんな設定した覚えないんだけどな……と訝しんでいたら、どうやらChrome (Chromium) に変更があり先読みするドメインのリストでHTTPSを強制する設定が追加されたらしい。

Chrome 63 forces .dev domains to HTTPS via preloaded HSTS

`.dev` はGoogleによって買われたらしいという話も書いてあって初耳。

それはそうとどうすればいいかというと、テスト・開発用に予約されたTLDがあるのでそれを使うのがよいだろうと上記記事には書いてある。

`.test` や新たに追加された `.localhost` などがそれにあたるので、それを使うのがよかろう、と。

元々予約されているわけでもなんでもなかったのでいきなり壊れても文句は言えないけど、しかしこういう変更入れることもあるんだなーという発見があった。

同じ名前のHTTPヘッダを複数回出力してもよいのか

結論: 値がcomma-separated listと定められているヘッダはOK

同じ名前のHTTPヘッダを複数回出力する、とは

こういう出力:

...
Vary: Accept-Encoding
Vary: User-Agent
...

同じ名前のHTTPヘッダを複数回出力すると、受信者はどう振る舞うのか

RFCより引用:

A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing

A recipient MAY combine multiple header fields with the same field name into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma.

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
  • 送信者は値がカンマ区切りリストと定義されていないヘッダを複数回出力してはならない
  • 受信者は複数の同名ヘッダを出現した順にカンマ区切りリストへ結合してもよい

ということが書いてあります。

つまり以下のヘッダ出力があったとき:

...
Vary: Accept-Encoding
Vary: User-Agent
...

……受信者は以下のように結合するかもしれません:

...
Vary: Accept-Encoding, User-Agent
...

mackerel-aws-s3-stats: S3のオブジェクト数とサイズをMackerelに投稿するいいやつ

github.com

いいやつを作った。

AWS SDK for Goを使っているのでIAM roleが適切に設定されたEC2のインスタンス上ならAWSの認証情報は特別渡さなくてよい
(もちろん環境変数で渡してもよい) ので、MackerelのAPI keyとサービス名さえ渡せばすぐに使えます。

さらに `-no-post` でSensu互換形式でstdoutに出力だけすることもできるので、mackerel-agentが動いているホストでmkrがインストールされていればサービス名だけ渡すだけで動くので便利。

mackerel-aws-s3-stats --region ... --bucket ... --no-post | mkr throw --service my-service

f:id:aereal:20171212181401p:plain

f:id:aereal:20171212181405p:plain

どうぞご利用ください。

GAE/GoでNetatmoのAPIから取得した温湿度をMackerelに送るアプリケーションを動かす

GitHub - aereal/gae-go-netatmo-ws-mackerel

f:id:aereal:20171211115645p:plain

冬なのでNetatmo Weather Stationを購入して自宅で動かしはじめました。エアコンをつけるといきなり室温が上がってエアコンはすごいなーということがよくわかって便利。

ハードウェアももちろんWebサービスとアプリもよくできていて、単に現在のデータを見るだけならこれで十分ですが、不快指数を計算したらなにかとアラートを送ったりしたいのでMackerelに投げたい。

ので書きました。GAE/Goで動くやつです。

元々、同僚がGoogle Apps ScriptとAWS Lambdaで動かす実装を作っていてだいたいそれを参考にした。

家で動かすものでメンテフリーにしたく、GASは1週間後読み書きできる気はしないしAWS Lambdaはいいけどもっと素朴なWebアプリケーションで開発したいなと思ったのでGAE/Goで動かしています。

詳しくはREADMEに書いてあるけど、普通のWebアプリケーションとして動かし、GAEのcron機能で定期的にリクエストさせることで定期的にメトリックを送ることができる。

ポイントとしては:

  • GAE/GoはHTTPリクエストの発行にgoogle.golang.org/appengine/urlfetchモジュールを使う必要があるので、外部APIにアクセスするパッケージも対応する必要がある

感想:

f:id:aereal:20171211115813p:plain

  • GAE/Go, 実行環境が特殊なので開発でめっちゃハマりそうでイヤだなと思っていたけど意外とそうでもなかった
    • 上記のようにライブラリにちょっと手を入れる必要があるかも、くらい
    • 対応自体もわりと一般的というかGAE特化というほどでもないので穏便
  • stdout/stderrに出力してもなにも見えなくて、GAEのlogモジュールを使う必要がある
  • cron実行の結果とか500エラーの割合などがGAEのダッシュボードで見れて便利
    • cron実行結果のログはStackdriverで見れてうまく他のGCPサービスと連携してるんだなーと思った

大コンテナ時代を生きのこるためのJSON Schema

実行環境をコンテナ化するDockerが普及して久しく、CIやローカルの開発環境などどこかでコンテナ技術に触れているのではないでしょうか。

コンテナはその性質上、設定のプロビジョニングに古典的な設定ファイル (のパス) 受け渡しが難しいです。etcdconsulのようなKVS (= Key-value store) を用いることもあるでしょうが、素朴には環境変数で与えることが多いでしょう。

HerokuはThe Twelve-Factor Appというパターンを提唱し、その中でStore config in the environmentと述べています。

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.

The Twelve-Factor App

参考: Building Twelve Factor Apps on Heroku | Heroku

しかし環境変数グローバル変数なので無闇に参照すれば古式ゆかしきスパゲティコードまっしぐらです。また環境変数の値は文字列として扱われるので表現力に乏しく利用者のために値の定義域を示す必要がしばしば生じますが、どのように行うべきでしょうか?

そんな悩みをJSON Schemaで解決しようという試みを紹介します。

環境変数の値の定義

ある環境変数がとりうる値の形式や範囲を考えてみます。

たとえば真偽値 (boolean) が挙げられます。 DEBUG=1デバッグフラグを立てるような変数です。
1か0か、あるいは値はなんでもよくて定義されていれば真とする場合もありますが、併せて真偽値とします。

あるいは数値 (number) も考えられます。 LOGLEVEL=1LOGLEVEL=7 など、ログ出力を数値で指定するケースがあります。

真偽値と数値を一般化すると列挙 (enum, enumeration) になります。たとえば PLACK_ENV=test PLACK_ENV=production など、文字列として扱われるがそのとりうる値があらかじめいくつかに決まっています。

また特殊な例として複数値 (multiple values) についても考える必要があります。
環境変数は辞書構造なので、ある名前に対応する値はただひとつに定められるため、複数の値を渡したい場合は値をデータ区切り文字 (delimiter) で分割して扱います。

たとえば PATH は値をコロンで区切ります *1

PATH はコロンで区切るというコンセンサスが得られているのであまり問題になりませんが、独自の変数を定義するときに区切り文字は何であるかはいよいよもって明示しなければ正しく使うことはできません。

JSON Schemaの利用

そこでJSON Schemaを利用します。

JSON Schema (JSON Schema Core)はその名の通りJSONの形式を表明する形式で、スキーマに基づいて入力の検証を行うためのボキャブラリを定義するJSON Schema Validationなどの仕様とあわせて利用されます。
以下、CoreとValidationをまとめてJSON Schemaと呼びます。

JSONと名がついていますがあくまでシリアライズ形式にJSONを選んでいるというだけで、利用する上でその他の制約はありません。

JSONとしてシリアライズ可能、すなわちJSONが定義する辞書構造 (object) とコレクション (array) のほかいくつかのスカラ値 (string, boolean, null) のいずれかで表現できる入力であればJSON Schemaで表明・検証できます。

先に述べた通り環境変数は名前と値がそれぞれ文字列である辞書構造なのでJSONとしてシリアライズ可能です。

以下はあるスクリプトが期待する環境変数JSON Schemaの例です:

{
  "title": "Schema of some script's environment variables",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "properties": {
    "DEBUG": {
      "type": "string",
      "enum": [
        "1",
        "0"
      ]
    },
    "LOGLEVEL": {
      "type": "string",
      "enum": [
        "0", "1", "2", "3", "4", "5", "6", "7"
      ]
    },
    "PLACK_ENV": {
      "type": "string",
      "enum": [
        "development", "test", "production"
      ]
    },
    "PATH": {
      "type": "string",
      "pattern": "[^:]+(:[^:]+)*"
    }
  },
  "required": [
    "PLACK_ENV"
  ]
}

先に述べたnumberやbooleanを期待する例がJSON Schemaで記述されています。

実際にこのスキーマを用いて検証を行ってみます。

Rubyjson-schema.gemを利用します。以下のGistにコード例を示します:

Gemfile · GitHub

実行結果の例です:

✘╹◡╹✘ < ruby validates_env.rb
The property '#/' did not contain a required property of 'PLACK_ENV'
✘>﹏<✘ < PLACK_ENV=a ruby validates_env.rb
The property '#/PLACK_ENV' value "a" did not match one of the following values: development, test, production
✘>﹏<✘ < PLACK_ENV=development ruby validates_env.rb
✘╹◡╹✘ <

PLACK_ENV=a を渡したらきちんと怒られてかわいいですね。

独自実装を含む他の検証方法に比してJSON Schemaを選ぶ利点はいくつかあります。

  • 相互運用性が高い
    • ランタイムによらず利用できるため、複数の言語を採用している場合に障壁となりません
  • 検証のセマンティクスの一貫性が保たれる
  • スキーマの再配布・拡張が容易
    • たとえば組織内で設定に用いる環境変数を共通化したいとき、あるURLでスキーマを配布・参照することで普及させやすくなるかもしれません
  • JSON Schemaのエコシステムの恩恵に与れる
    • たとえばドキュメント生成が簡単になるかもしれません

一方、物足りないと感じる点としては、enumやpatternプロパティを駆使するほかなく、あまり可読性が高くない点でしょうか。

この点については、JSON Schemaの仕様ではformatプロパティに独自のキーワードを追加・利用することを許容しているため、booleanライクな文字列の定義を追加し、それをformatに指定することで冗長な定義を避けられるかもしれません。

Implementations MAY add custom format attributes. Save for agreement between parties, schema authors SHALL NOT expect a peer implementation to support this keyword and/or custom format attributes.

JSON Schema Validation: A Vocabulary for Structural Validation of JSON

むすび

以上、設定のスキーマJSON Schemaで記述・検証することでコンテナ時代に即したtwelve-factor appパターンを促進する試みでした。

私はtwelve-factor appの環境変数を推進する姿勢については環境変数の表現力の乏しさを懸念し懐疑的でしたが、JSON Schemaを利用することを思い付いてからは懸念も解消され強く共感します。

既に述べたように独自formatを定義・利用するバリデータがあるとより便利かもしれないという思いもあるので、引き続き検討・導入していきたいところです。


この記事ははてなエンジニア Advent Calendar 2017の8日目の記事でした。

明日 (12/9) はid:ikesyoです、おたのしみに!

*1:ただしUNIX/Linuxにおける場合、Windowsでは異なる