Amazon Kinesis Data Firehoseの料金が高かった

TL;DR

  • ログ1行のサイズが5KB以下なら5KBとして料金計算される

経緯

仕事でAmazon Kinesis Data Firehoseを使う機会があり、本番導入してみたところ料金が高かった。一日数千円。。

改めてログ量、ログサイズから自分で計算してみた。
CloudWatchからFirehoseの IncomingBytesIncomingRecordsが見れるのでそれをもとに https://aws.amazon.com/jp/kinesis/data-firehose/pricing/ のページを見ながら計算したが、請求額と合わない。

で、AWSサポートにお問い合わせしたところ、自分の料金計算方法の認識が間違っていた。
Firehoseの料金ページには以下が書かれている。

料金は Amazon Kinesis Data Firehose に取り込まれたデータの量に基づきます。
データの量は、このサービスに送信したデータレコードの数に、直近の 5 KB の倍数に切り上げた各レコードのサイズを乗算した値として計算されます

つまり、どんなにログ1行のサイズが小さくても5KBとして計算される。 そしてCloudWatchに表示されているIncomingBytesは実際のログサイズであり、5KBへ切り上げ後の値ではない。

結局、5KBに切り上げされると実際のログサイズで計算したよりも10倍以上高くなってしまうので、Firehoseを使うのは諦めて、FluentdでS3に直接置くように変更した。悲しい。
あとちゃんと料金の計算方法を把握しないとだめですね(当たり前)。

monday.comのURLをSlackに貼ったら展開されるようにした

monday.comというとても便利なタスク管理ツールがあります。 以下でも紹介記事を書きました。 tech.studyplus.co.jp

monday.comは(当たり前ですが)要ログインなので、monday.comのURLをSlackに貼っても展開されません。 それだととても不便なので、URLを展開するツールを作りました github.com

参考にしたツール

Closedなesaの記事URLをSlackに貼ったら展開されるようにした - pixiv inside を元に作りました。とても分かりやすくて助かりました。ありがとうございます。

デプロイ方法

Heroku

https://github.com/akira-kuriyama/monday-unfurly のREADMEにDeploy to Herokuボタンをつけているので、それを使って簡単にデプロイできます。 MONDAY_API_V2_TOKENが必要になりますが、 以下の手順で取得できます。(参考)

  1. monday.comにログインし、左下のアバターアイコンをクリック
  2. メニューからAdminを選択(要admin権限)
  3. API sectionを選択
  4. API v2 Tokenを生成し、コピー

コンテナ

今回、自分はHerokuではなく、GCPのCloud Runにデプロイしました。(そのためにDockerfileもリポジトリにおいてます) Cloud Runはコンテナをサーバーレス環境で実行できるサービスで、負荷に応じて自動的にオートスケール、負荷がない場合はサービスは起動されない(課金されない)といった特徴を持っています。herokuやAWS Lambdaと似てますね。 ただ、休眠状態からの起動はherokuよりは速い気がしました。 あと無料枠がかなりあるのでこのツールを動かすくらいなら無料でできそうです。

で、Cloud Run、めっちゃデプロイ簡単なんですよね。

$ gcloud beta run deploy --image {Docker Imageの場所のURL}

これだけです。これでそのコンテナにアクセスするためのURLが返ってきます。最高ですね。

Slackへの設定方法

Unfurling links in messages | Slack を参考にするか、 Closedなesaの記事URLをSlackに貼ったら展開されるようにした - pixiv inside を読むと分かりやすいと思います。

その他

monday.comのAPIがGraphQLでめっちゃ使いやすかった。 クエリをテストするための画面も用意されていて素敵。

さいごに

monday.comめっちゃ便利なので使ってくれ!!

Spinnakerについて調べたのでまとめる

Spinnakerについて簡単に調べたので自分用にまとめておく

Spinnaker とは

Spinnakerとは、マルチクラウドに対応した継続デリバリのプラットフォームです。 デプロイパイプラインの管理機能を備え、Red/Blackデプロイ(Blue/Greenデプロイ)やカナリアリリースにも対応しています。 2015年にNetflix によってOSS化され、GoogleMicrosoftを始め、様々な企業やコミュニティが開発に参加しています。

特徴

  • Spinnakerは、「Immutable Infrastructure」の原則に基づいて構築されており、クラウドネイティブ アプリの高速かつ信頼性の高いデプロイのための基盤を提供します。
    • Immutable Infrastructureとは、一度Deployをしたインスタンスやコンテナには二度と手を加えず、新たにDeployを行う場合はインスタンスやコンテナを作り直すという考え方です。Immutable Infrastructureを採用することでConfiguration driftを避け、安全かつ高速なRollbackが実現できます。
  • デプロイは、予測可能で安全であり、必要なときに簡単にロールバックすることができます。
  • テスト、QA、本番など、複数の開発環境にまたがる複雑なデプロイパイプラインを作成することができます。
  • デプロイパイプラインはパラレル実行とシリアル実行に対応してます スクリーンショット 2019-05-29 11.29.43.png

デプロイパイプラインがGUIからでしか定義できない時代もありましたが、現在は設定ファイルに定義できます。

機能一覧

CI Integrations

パイプラインの実行トリガーとして、「Docker Registry」「Cron」「Git」「Jenkins」「Werker」「Travis」「Spinnakerの他のPipeline」「Pub/Sub」「Webhook」「手動」などを設定できます。

VM Bakery

同梱されているPackerを使ってImageを焼くことができる

Deployment Strategies

以下のようなビルドインデプロイメント戦略が選択できる - highlander(既存のインスタンスに新しいコードをデプロイするTraditionalなやり方。ダウンタイムが発生する) - Red/Black(Blue/Green) - Rolling Red/Black - カナリアリリース - もしくは独自のデプロイメント戦略

マルチクラウド対応

Spinnakerは、kubernetesに対してのCD機能が標準的に実装されているだけではなく、他のクラウドプロバイダに対してにもCD機能が実装されています。(つまりKubernetesデプロイ専用ミドルウェアというわけではない)

Amazon Web Services(AWS) Microsoft Azure Google Cloud Platform Cloud Foundry Kubernetes OpenStack など カスタマイズにより他のクラウドプロバイダ追加も可能です

Manual Judgments

手動判断ステージでは、デプロイする前に手動の承認を必要とすることができます スクリーンショット 2019-05-24 10.05.23.png

カナリアリリース

Spinnakerを使うことで、カナリアリリースした成果物の任意のメトリクスの計測、スコア化を自動で行い、予め決めた合格点を満たすか自動判定できる。そしてその判定をクリアした成果物だけ本番にデプロイがされ、クリアしなかった場合は、ロールバックを自動で行う。 カナリアリリースした成果物の任意のメトリクスの計測、スコア化を行う機構はAutomated Canary Analysis(ACA)と呼ばれていて、kayentaというソフトウェアがそれを担当している。 計測する際のメトリクスは、DataDog、Stackdriver、New Relic、 Prometheus等々から取得できる。 そしてSpinnaker上からどのメトリクスを使うか、またそのメトリクスのしきい値(つまり合格とみなすしきい値)を設定できる。

参考: GoogleとNetflix、カナリアリリース分析ツール「Kayenta」オープンソースで公開

Notifications

email, Slack, SMS(via Twilio)等を使ってイベント通知ができる

Role-based Access Control

OAuth, SAML, LDAP, X.509 certs, Google groups, Azure groups, or GitHub teamsを使用してプロジェクトやアカウントへのアクセスに制限をかけることができます。

パイプライン実行時間の制限

特定の時間のみパイプラインの実行がされるように制限することができます。 たとえば、トラフィックがピークではない時間帯や、適切な担当者がロールアウトを監視しているときに実行するように、時間帯を指定することができます(ホワイトリスト形式)

Chaos Monkey Integration

故意にインスタンスを終了させることによってアプリケーションが問題なく動作するかテストすることができる

クラスタ管理機能

Spinnakerはクラスタ管理機能も提供しています。 クラスタの「サーバグループ」「ロードバランサ」「セキュリティグループ」などに対する管理操作を行えます。 またSpinnakerはクラスタ自体の可視化をすることができます。

Monitoring Integrations

モニタリングサービス Datadog、Prometheus、Stackdriverなどと連携したモニタリングを行えます。 またこれらのmetricsをカナリアリリース時に使用できます。

パイプラインテンプレート

https://www.1915keke.com/entry/2018/05/19/124103 パイプラインのテンプレートを作っておいてそれをもとにパイプラインを作ることができる。 特徴としてはテンプレートをもとに作ったパイプラインは設定変更できない。 テンプレートのほうでパイプラインの各フェーズで入力可能な変数を定義できる。(たとえばレプリカ数はパイプライン作成時に決めるなど)

フィードバック

拡張性

Spinnakerの機能だけでは対応できないエッジケースのために、Spinnakerを拡張することもできる 例えばパイプライン中にカスタムステージを作成したり、UIに独自のリンクやボタンを追加するなど。 Typescript/Reactの知識があればフロントエンドの拡張を、Java(JVM)の知識があればバックエンドの拡張を行うことが可能

参考資料:

GKEでspinnakerを動かしてみたい場合

以下の英語ドキュメントを参考にするとよいです Continuous Delivery Pipelines with Spinnaker and Google Kubernetes Engine 日本語ドキュメントのほうは手順に抜けがあるっぽいので基本は英語のドキュメントのほうをみたほうがよさげ。

解説資料

https://tech.plaid.co.jp/builderscon-2018/ https://www.youtube.com/watch?v=ejcJXjhfG2I&feature=youtu.be Multicloud deploy with Spinnaker https://www.ossnews.jp/oss_info/Spinnaker

参考資料

Compute Engine での Spinnaker の実行  |  ソリューション  |  Google Cloud

SpinnakerによるContinuous Delivery - Mercari Engineering Blog

Circle CIのcacheは先頭一致したkeyのcacheが使われる

たとえば以下の設定になっていたとします

- restore_cache:
    keys:
      - gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
      - gem-cache-v1-{{ arch }}-{{ .Branch }}
      - gem-cache-v1

https://circleci.com/docs/2.0/caching/#restoring-cache

どのようなルールでcacheが使われるかというと、上から順にkey名が先頭一致したcacheが使われます。 公式ドキュメントには以下のように書かれています。

CircleCI restores caches in the order of keys listed in the restore_cache step. Each cache key is namespaced to the project, and retrieval is prefix-matched. The cache will be restored from the first matching key. If there are multiple matches, the most recently generated cache will be used.

なので、 masterブランチで key名にgem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}を指定していて、 他のブランチが、 key名に gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}-test を指定したcacheを作成したら、masterブランチは gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}-test のcacheを使ってしまう。気をつけましょう。

特定の例外をSentryに送らないようにする方法

Sentryに特定の例外を送りたくない場合、プログラムを修正すれば済みますがそれが深淵な理由でできないOR面倒な場合、プログラム側でSentryに送らない例外を指定できます。 もちろんSentry側でignoreにできますが、たくさんの箇所で発生したりアプリケーションの更新によって別のissueとして認識されてしまうといちいちignoreするのが面倒。 またたくさんエラーがSentryに通知されるとエラー数を消費してしまう。Sentryは従量課金制なので困る。

特定の例外をSentryに送らないようにする方法はexcluded_exceptionsinspect_exception_causes_for_exclusionオプションです。 以下のように指定します。

Raven.configure do |config|
    # Sentryに送りたくない例外を指定
    config.excluded_exceptions.append('HogeHogeError')
    # excluded_exceptionsを指定し、かつ、これをonにすることで、ネストされた例外もexcluded_exceptionsで指定した例外なのかチェックしてくれる
    config.inspect_exception_causes_for_exclusion = true
end

ドキュメントはこちら

GoではIgnoreErrorsPHPではexcluded_exceptionsではJavaScriptではignoreErrorsというオプションがあり、おそらく他の言語もそういったオプションがありそうです。

config gemを使って環境変数も読み込む

config gemには、ENVオブジェクトから値を読み込むためのuse_envオプションがあります。

しかし単にuse_envをtrueにしても、ちゃんと環境変数が読み込まれません(env_separatorがデフォルトだと.になっているため)

なので以下のようにenv_separator_を指定します。

Config.setup do |config|
  config.use_env = true
  config.env_prefix = 'SETTINGS'
  config.env_separator = '_'
end

こうすれば、Rails起動前に例えば、SETTINGS_MYKEY=my_value環境変数を設定すれば、 Settings.mykey”my_value"が取得できるようになります。

また、SETTINGS_MY_KEY=my_value環境変数を設定すると、Settings.my.keyのようにネストされて設定されます。

※ちなみにconfig.env_prefixを設定しないと、Settings_MYKEY=my_valueと設定しないといけなくなる…。あとenv_prefixをつけたくないなと思ってconfig.env_prefix = ''を設定してもちゃんと動かなかった。

GAS(Google Apps Script)で秒まで指定して実行する

GAS(Google Apps Script)のトリガータイプに特定の時刻を指定しても分までしか指定できません。 また仮に12:00に指定しても12:00から12:01の間のどこかで処理が実行されるので、ちょうどこの秒に処理を実行したいというのが通常はできません。

しかしUtilities.sleepという関数を使うと指定した秒に処理を実行させることができます。

例えば、12:00:00に処理を実行したい場合、流れとしては、

  1. トリガーの"日付ベースのタイマー"を使って10時〜11時の間に 関数A を実行させるように設定
  2. 関数Aは11:59にメインの関数Bを実行するようにトリガーを設定
  3. 関数Bは12:00:00から現在の日時を引いて、12:00:00になるまでUtilities.sleepを使って処理を止める
  4. そしてメインの処理を実行させる

となります。 コードは以下のような感じです。

function main() {
  deleteTrigger();

  waitUntilTime();

  // メインの処理を実行

}

function waitUntilTime() {
  var notificationTime = new Date();
  notificationTime.setHours(12);
  notificationTime.setMinutes(0);
  notificationTime.setSeconds(0);
  Utilities.sleep((notificationTime - new Date()));
}

// "日付ベースのタイマー"を使って10時から11時の間にこのトリガーを実行するように設定する
function setTrigger() {
  var triggerDay = new Date();
  triggerDay.setHours(11);
  triggerDay.setMinutes(59);
  ScriptApp.newTrigger("main").timeBased().at(triggerDay).create();
}

function deleteTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "main") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}