koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

Azure Function App 『だけ』でWebサービスをホスティングする

事を試していて、少々無理筋だな、と思った話。

最近2つのテーマを持ってWebサービスの実装を試していた。

  1. SPAっぽく、クライアントコードとサーバコードを完全に切り離す実装を体験する
  2. Azure Functionの従量課金だけで安価にAzureでWebサービスホスティングする

この記事のテーマは後者である。

Azureのプログラムホスティング用のPaaSは概ねApp Serviceという共通の基盤の上で提供されており、Azure Functionsも例外ではない。

そのため、App ServiceがWebサービスホスティング用に備えている強力な機能をFunctionsでも使用することができる。 加えて、Functions固有の機能も使用できる。

これらを組み合わせると、Azure Functions(+そのためのStorageアカウント)のみでWebサービスホスティングできるのではないか? と思え、この2週間ほど試していた。

結論から言うと、出来なくもないが固執するのは良くない、といったところだった。

確かにやってできないことはないのだが、より向いているマネージドサービスと組み合わせたり、無理に組み込み機能を使わない方が平易だったりする感じを受けた。 適材適所である。

続きを読む

ConoHaのオブジェクトストレージからAmazon S3に移行した

やったこと

  • サービスをメンテナンスイン
  • ConoHaのオブジェクトストレージから全オブジェクトをダウンロード
  • ダウンロードしたオブジェクトをS3にSync
    • AWS CLIaws s3 sync コマンドを使用
  • オブジェクトストレージへのアクセスをS3に変更したアプリケーションをデプロイ
    • オブジェクトのGet/Putのみを行っていたため最小限の変更で済んだ
  • オブジェクトへのプロキシー(Nginx)のオリジンをS3に変更
  • サービスをメンテナンスアウト

規模はオブジェクト数万、容量数GB程度。 この程度なら力業でオブジェクトをコピーできる。

今すぐに移行先を(自分が多少触ったことがあるという理由で)AzureのBlob Storageにする、としてもそれほどのインパクトはさそうだ。

一般的なサービスであれば、S3が落ちたら巻き込まれで停止してもいいだろうと考えているが、それが許されなかったりするような場合に複数のオブジェクトストレージサービスにオブジェクトをミラーリングしておき、場合によって接続先を切り替えるようなことは、他の分野のフェールバックよりは平易に行えそうだと感じた。

変更点と留意点

f:id:koudenpa:20180504005541p:plain

サービスのアプリケーションへの変更と移行作業は非常にシンプルで上の図程度だった。

変更を平易に済ますためには以下のような点に留意しておきたい。

  • オブジェクトのI/Oの処理は1か所に集約する
  • オブジェクトに関する情報は オブジェクトキー の形で保持する
  • 移行に使うソフトウェアは移行規模に向いたものにする

オブジェクトのI/Oの処理は1か所に集約する

当たり前だが、ある同種のリソースへのアクセスの実装は1か所に集約、汎用的なインタフェースとしておきたい。

正直なところ『リポジトリパターン』とかがどの程度テスト以外の実際の便利に寄与するかは分からない部分があるのだけれど、 いざ「永続化先を変更したい」となった時にリソースへのアクセスの実装が散っていたら詰む。

基本は大事にしたい。

オブジェクトに関する情報は オブジェクトキー の形で保持する

オブジェクトのI/Oの処理 に通じるものがあるが、アプリケーションがオブジェクトに関して持つ情報はオブジェクトキーにしておきたい。

今回はそうだったので、オブジェクトのI/Oの処理を変更するだけで永続化先の変更に対応できた。

全然別のアプリケーションでURIでオブジェクトの情報を持たせているものがあって「失敗したなぁ」と思っている。

そのアプリケーションのオブジェクトを別のサービスに変更したいとしたら、それらの情報のマイグレーションも行わなくてはならない。

ロックインされているということだ。

移行に使うソフトウェアは移行規模に向いたものにする

移行に当たって、今回はローカルマシンに Cyberduck でダウンロード、そこから AWS CLI でアップロードしたが、Cyberduckでのダウンロードはよりオブジェクト数が多いと厳しそうだった。

数万オブジェクト程度だったが、不安感のある動作だった。

多数のファイル操作には向いていないのかもしれない。

f:id:koudenpa:20180504010213p:plain

こんなエラーコードははじめて見た。


余談

以前ConoHaのオブジェクトストレージのサービスレベルが低い旨を記事にしているが、その状況が改善しないので仕方なく移行対応した形になる。

koudenpa.hatenablog.com

ConoHaのVPSは非常に良いものなのだけれど、周辺のマネージドサービスはコアな用途に使うには少々頼りない。

オブジェクトストレージに関しては、ConoHaのVPSからでもインターネット経由でのアクセスとなるため、使用することに特にこれといったメリットはない。 (他のマネージドサービスではインターネットに出ないアクセスができるものもある)

先の記事にも書いたが、どこであろうとコアサービス以外は開発・運用のリソース投入が限られているはずなので、大事な用途には安定の実績があるサービスを使用するようにしたい。

Mackerelエージェントから他のサービスに投稿したい Vol.2

前回のあらすじ。

あるオープンソースCLIアプリケーションソフトウェアでやりたいことがある場合には、大体以下ようなことを上から順に試すと思う。

  1. Readmeを読む
  2. help コマンドを叩く
  3. ソースコードを読み込む
  4. とりあえずガチャガチャ実装を直す

下からやってしまいました。 Goのプログラムをビルドしてみるという他の目的があったとはいえ、あんまりな段取りだったと反省しました。

さて、Mackerelエージェントをビルドできるようになったので、次は以下のようなことを試していきたい。

  • 設定項目を追加する
  • 追加した設定項目を動作に反映する

設定項目を追加する

設定ファイルの形式はTOMLだということは知っていて、何かしらのライブラリで読み込んでいるのだろうと思っていた。

実際その通りで、設定用の構造体にメンバを追加したらいい感じに読み込まれた。

設定項目を追加する · 7474/mackerel-agent@88623e4 · GitHub

f:id:koudenpa:20180425221935p:plain

大昔は設定ファイルの読み込み処理を独自に書いたりしたものだけれど、今どきは簡単で良い。

追加した設定項目を動作に反映する

追加した設定を動作に反映していく。

元々は、大きな設定差分毎にAPI呼び出し用のインタフェースを実装していこうと思っていたのだけれど、MackerelエージェントはAPIクライアントをインタフェースではなく構造体で取り廻しているようなので無理筋だった。 (多分。Goは入門本を斜め読みした程度なので、実体を取り廻していてもいい感じに多様性を持たせられるのかもしれない)

APIクライアント構造体と、そこに生えているメソッドをゴリゴリ修正して行く形で追加変更していくことにした。

追加した設定項目を動作に反映する · 7474/mackerel-agent@1ed3258 · GitHub

apikey = "xxx"
apibase = "https://mackerel-dash-stub.azurewebsites.net"
verbose = true

# Custom Config
APIClientType = "custom-http"
CustomHTTPHeaders = { x-functions-key = "b64==", foo = "bar" }

上記のような設定をして実行。

> mackerel-agent.exe
2018/04/25 22:24:02 main.go:171: INFO <main> Starting mackerel-agent version:0.54.1, rev:, apibase:https://mackerel-dash-stub.azurewebsites.net
2018/04/25 22:24:02 main.go:172: INFO <main> APIClientType:custom-http
2018/04/25 22:24:02 command.go:649: DEBUG <command> NewCustomHttpClient
2018/04/25 22:24:02 command.go:659: DEBUG <command> headers x-functions-key: b64==
2018/04/25 22:24:02 command.go:659: DEBUG <command> headers foo: bar
2018/04/25 22:24:09 command.go:739: INFO <command> Start: apibase = https://mackerel-dash-stub.azurewebsites.net, hostName = , hostID = xxx
2018/04/25 22:24:09 command.go:199: DEBUG <command> wait 4 seconds before initial posting.
2018/04/25 22:24:09 command.go:597: DEBUG <command> Updating host specs...
2018/04/25 22:24:09 memory.go:43: DEBUG <metrics.memory> memory : map[~~]
2018/04/25 22:24:09 filesystem.go:45: DEBUG <metrics.filesystem> map[~~]
2018/04/25 22:24:09 processor_queue_length.go:61: DEBUG <metrics.processor_queue_length> processor_queue_length: map["processor_queue_length":%!q(float64=0)]
2018/04/25 22:24:09 cpuusage.go:76: DEBUG <cpu.user.percentage> cpuusage: map["cpu.user.percentage":%!q(float64=0) "cpu.system.percentage":%!q(float64=0) "cpu.idle.percentage":%!q(float64=0)]
2018/04/25 22:24:15 api.go:306: DEBUG <api> PUT /api/v0/hosts/xxx {"name":"DESKTOP-xxx","meta":{~~}
2018/04/25 22:24:15 api.go:319: DEBUG <api> PUT /api/v0/hosts/xxx status="200 OK"

どうやら、想定通りにヘッダが設定されたリクエストが行われているようだ。

MackerelサービスAPIスタブ

前回雑に作成したMackerelのAPIスタブをローカルマシン上で実行していたが、本命はAzure Functions上での実行である。

Functionsは様々なトリガーで実行できるが、HTTPリクエストをトリガーとする場合の認証は特定のクエリパラメータないしHTTPヘッダにキーを送出することで行う。

Azure Functions における HTTP と Webhook のバインド | Microsoft Docs

キーは、上記のように code という名前のクエリ文字列変数に含めることも、x-functions-key HTTP ヘッダーに含めることもできます。 キーの値には、関数のために定義されている任意の関数キーまたは任意のホスト キーを指定できます。

先にHTTPヘッダを設定できるようにしたのはそのためだ。

これでHTTPでリクエストを受け付け、HTTPヘッダ内容を見てクライアント認証する形のサービスへの投稿は過不足なく設定できるようになった。

遊び

改造エージェントは想定通りに動作していることを確認するために、投稿されたメトリックをチャート表示した。

f:id:koudenpa:20180426011835p:plain

Teat Chart ← Blobストレージに適当なHTMLを置いて、Functionsの方のCORS設定をした。

はっきり言ってエージェントの改造よりこの『遊び』やAPIのスタブ作りの方が何倍も時間がかかっている。

が、まぁ、そういうもん。

Mackerelというサービスの価値の過半はそちらの方にあるわけだし、一朝一夕でクローン出来たらそれで商売するわ。


次は何をするか、HTTPクライアントを拡張して証明書認証できるようにするか、さっさとMQTTクライアントに手を出すか、少し迷っている。

どちらもAWS IoTをターゲットとしているので、最終的にはどちらも試そうかなぁ、と思っている。

Mackerelエージェントから他のサービスに投稿したい Vol.1

前回の記事には書いていないことも含まれているあらすじ。

Goのディレクトリ構成の『い』の字すら無視したGit Cloneからのビルドの結果、Mackerelエージェントは全くビルドされる気配がなかった。

go get コマンドを叩いた後も、インストールしていないGCCが求められ、環境面の面倒くささを目の当たりにしてテンションがすでに下がっている。

果たしてARMクロスビルドしてRaspberry Pi向けのカスタムMackerelエージェントを構成することはできるのだろうか?

  • ローカルマシンでエージェントをビルド
  • 呼び出すAPIベースを変更(バイナリ埋め込み)

の2本でお届けします。


ローカルマシンでエージェントをビルド

というわけで、Windows向けのGCCをインストール、パスを通してからビルド、実行した。

PS C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent> .\build-sandbox.bat
C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent>echo on
C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent>go build -o build/mackerel-agent.exe
PS C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent> cd .\build\
PS C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent\build> .\mackerel-agent.exe init -apikey xxx
PS C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent\build> .\mackerel-agent.exe2018/04/24 19:10:04 INFO <main> Starting mackerel-agent version:0.54.1, rev:, apibase:https://api.mackerelio.com
2018/04/24 19:10:11 INFO <command> Start: apibase = https://api.mackerelio.com, hostName = DESKTOP-xxx, hostID = xxx

メトリック届いている。

f:id:koudenpa:20180424191620p:plain

これでVol.1のノルマは達成だ。


呼び出すAPIベースを変更(バイナリ埋め込み)

コードを眺めていると、さしあたって呼び出し先を変更するだけならビルドオプションでAPIベースURIを指定すればよい様子だったので試した。

サンドボックスビルド用のビルドファイルを追加。 · 7474/mackerel-agent@36b2eaf · GitHub

呼び出し先のAPIスタブはなんとなくAzure Function AppをVisualStudioで雑にスキャフォルドしてローカルで実行した。

GitHub - 7474/MackerelDashFunctionApp: Mackerelエージェントの変更を試す際に使用しているアプリケーションです。

エージェントの実行ログ。

PS C:\Users\koudenpa\go\src\github.com\7474\mackerel-agent> .\build\mackerel-agent.exe
# 変更したAPIベースになっている
2018/04/24 21:15:53 INFO <main> Starting mackerel-agent version:0.54.1, rev:, apibase:http://localhost:7071
2018/04/24 21:16:00 INFO <command> Start: apibase = http://localhost:7071, hostName = , hostID = xxx
# APIスタブで 201 を返したら期待と違うと失敗
2018/04/24 21:17:01 WARNING <command> Failed to post metrics value (will retry): API error. status: 201, msg: api request failed
# リトライでキー重複エラーしてAPIスタブも失敗
2018/04/24 21:18:01 WARNING <command> Failed to post metrics value (will retry): API error. status: 500, msg: api request failed

APIスタブに呼び出しログが出ている。

f:id:koudenpa:20180424211736p:plain

ローカルストレージにそれっぽいメトリックが保存されている。

f:id:koudenpa:20180424213205p:plain

単に投稿先を変えたいだけで、認証もAPIキーで行うようなら、これだけの修正で実現できてしまうようだ。

そりゃぁ、開発の便宜上呼び出し先のURIは平易に変更できるようになっているよなぁ。という感じである。

ビルドすらできなかった前回からしたら格段の進歩だが、Goのコードは全く触っていない。 次はこうもいくまい。

設定項目を増やす、認証やプロトコルの変更を睨んでAPI呼び出しを変更できるようにする(Go的には何ていうのか知らん)、など順次試していきたい。


余談。

f:id:koudenpa:20180424213815p:plain

フォーク数がちょうど 74 で縁起が良い。


追記。

そりゃぁ、開発の便宜上呼び出し先のURIは平易に変更できるようになっているよなぁ。という感じである。

ビルドオプションを変えるという行為が平易だと思ってるようならお前はアホだ。

もう少しコードを読んでいたら、普通に設定ファイル内容を読み込んでいた。 つまり、ビルドオプションでデフォルトのAPIベースをローカルホストにしているバイナリでも、以下の様な設定ファイルを食べさせることで元のMackerelにメトリックを投稿できる。

apikey = "xxx"
apibase = "https://api.mackerelio.com"

こうやって、印象的な出来事が起きるたびに要素技術や実装が身に刻まれていくのだ。

消して道化ではないぞ! 心を強く持つんだ!!

さらについき。

> mackerel-agent.exe -h
Usage: mackerel-agent [options]

main process of mackerel-agent

Options:
  -apibase string
        API base (default "http://localhost:7071")
...

まぁ、そういうこともあるさ。基本は大事。

Mackerelエージェントから他のサービスに投稿したい Vol.0

GoでHelloWorldからはじめて、AWS IoTのMQTTトピック(別にAzure IoT HubのAMQPでもいいのだけれど)にMackerelエージェント(を弄ったエージェント)から投稿したい。と思ったのでとりあえず記事にした。是非は微妙な気がするが。。。

f:id:koudenpa:20180424013604p:plain

初心だけ表明して(正しい誤字だと思う)やらないことは多々あるので、この段階ではそういうことを考えている人もいるのだ。程度の記事である。

Mackerelエージェントとその周辺のプラグイン群は1分粒度の情報収集に関しては相当に優秀なエコシステムだ。 これらの情報は完全にエージェントからサービスへのPushで収集されている。 つまり、理屈の上ではPush先さえ変えれば任意のサービスで同様の情報を収集することができる。

ここからは個人の事情だが、そろそろバズワードとしての効力が落ち着いてきているIoTのデバイスにはLinuxOSが動作しているようなリッチなものも多い。

これらのデバイスの動作状況は非常に欲しい情報だが、いい感じに収集できるシステムは寡聞にして知らない。 (各デバイスのベンダーにロックインされている印象)

かといって、素直にMackerelを使用して監視するほどのコスト感のホストでもない。

そうした状況に対してOSSとしてのMackerelエージェントのエコシステムを使ってみたら面白いのではないかなぁ。 と思った次第。

とりあえずGoをインストーラでインストールして、Mackerelエージェントのリポジトリをクローンして、build.bat を叩いたらエラーしたので寝ようかと思う。Vol.1があれば自分の手元でビルドしたエージェントを動作させたいところである。

PS C:\Users\koudenpa\Source\Repos\github\mackerel-agent> .\build.bat
C:\Users\koudenpa\Source\Repos\github\mackerel-agent>echo on
C:\Users\koudenpa\Source\Repos\github\mackerel-agent>FOR /F "usebackq" %w IN (`git rev-parse --short HEAD`) DO SET COMMIT=%w
C:\Users\koudenpa\Source\Repos\github\mackerel-agent>SET COMMIT=13a938e
C:\Users\koudenpa\Source\Repos\github\mackerel-agent>go build -o build/mackerel-agent.exe -ldflags="-X main.gitcommit=13a938e " github.com/mackerelio/mackerel-agent
can't load package: package github.com/mackerelio/mackerel-agent: cannot find package "github.com/mackerelio/mackerel-agent" in any of:
        C:\Go\src\github.com\mackerelio\mackerel-agent (from $GOROOT)
        C:\Users\koudenpa\go\src\github.com\mackerelio\mackerel-agent (from $GOPATH)
PS C:\Users\koudenpa\Source\Repos\github\mackerel-agent>

ローカルマシンのユーザー名とフォルダ構造が暴露されたところで失うものはない。


余談だが、コスト感に関してはMackerel界隈ではたびたび話題に出ている。

クラウドインテグレーションやIoTデバイスなど多彩なホストに対して、納得感のあるコスト提示があるとより活用しやすくなり、利用側提供側で相互にメリットがあると考えている。

いや、ぶっちゃけフルのLinuxが動作しているインスタンスのホストと、Lambda関数やSQSのQueueのホストが同一価格とか納得できないっしょ!

なんか完全に脱線しているけれど、そんな4月下旬でした。


寝る前の追記。

>go get -u github.com/mackerelio/mackerel-agent
# github.com/mackerelio/mackerel-agent/util/windows
exec: "gcc": executable file not found in %PATH%

WindowsIDEのない開発環境に弱いなぁ。

mysql へのSelectクエリの結果をMackerelに投稿したい

と思ったので、雑にプラグインとして実行できそうなスクリプトを書いた。

[plugin.metrics.querytest]
command = "/path/to/script/metric.sh"

↓のようなスクリプトを↑のようにmackerel-agentに指定してやれた最低限やりたいことはできそうだった。

#!/bin/sh

# クエリは1行を返して value 'key1.key2.value' のように列名が . 二つで区切られるようにする
 mysql -u ${username} -p${password} ${dbname}  -B \
 -e "${query}" \
 > /tmp/metric.tsv

# メトリックな形式に行列を転置してタイムスタンプを付与して出力する
awk '
{ for (i=1; i<=NF; i++)  { a[NR,i] = $i } } NF>p { p = NF }
END {
for(j=1; j<=p; j++) { 
print(a[1,j], "\t", a[2,j], "\t", systime())
}
}' /tmp/metric.tsv

f:id:koudenpa:20180417012830p:plain

Success!!

なお、僕にはシェル力(ちから)は備わっていないので以下の記事を参考におパクりしています。

orebibou.com


↓経緯。

ランス10-のんびりプレイ

テーマ:高難易度モードでのんびりカード集め

前回プレイでは兵力が足らずにクリアAの大侵攻条件は満たせなかった。 実績での人類被害軽減はかなり効きそうなので、先にその辺りの実績を開放していくことにした。

というわけで、初高難易度モード。通常モードでは2~3枚のうち1枚選択のところ、2枚選択できてカード集めがはかどる。

魔人2人退治とかようできないので、むしろ進行はのんびりになる。

ハイライト

生きているのに駆けつけてくれないアレックス君。

しかし、割かし細かいところまでメッセージ分岐が仕込んであるから侮れない。それだけにアップデートで既読スキップが壊れてしまったのは辛い。

f:id:koudenpa:20180402025719j:plain

なお、この後自由都市が落ちてしまった。

f:id:koudenpa:20180402231616j:plain

支援を忘れていたことに気づいた瞬間変な声が出た。

のんびり15ターンまでカード集めしようと思っていたところ、これは痛い。

慣れてきた頃が一番危ないのだ。

だが、これも運命。

1週目は人類についたペルエレがケッセルリンクにつくための前振りだったのだ。

f:id:koudenpa:20180402232057j:plain

(ゲームオーバーなのでやりなおしたけれど)


旦那教育とは……? 男は容赦されなかった。

f:id:koudenpa:20180406001953p:plain

f:id:koudenpa:20180406002035p:plain

憶測だけれど、ダウン無効スキルはダウン対象の選択優先度が下がるだけ(犠牲者は上がる)で、他にダウン対象がいなかったら対象になってしまう、とかかな。

f:id:koudenpa:20180406002727p:plain


JAPANルートなのでギリギリだったけれど、ヘルマンとゼスの40枚は達成できたので上々。次は亜人とJAPANの40枚を目指そうと思う。