CloudFormationの管理を楽にする

娘にぞっこんアラサーエンジニアのpataijiです。

アラサーという言葉を初めて自分に使ったのでドキドキしています。

先日開催されたSpeeeKaigi#3 でCloudFormationのテンプレート管理をなんとかする、「Cloudruler」を紹介しました。

↓ SpeeeKaigi#3 についてはこちら tech.speee.jp

当日のスライド

speakerdeck.com

CloudFormationを使用している方にはそれなりに共感いただけると思うのですが、テンプレートファイルの管理ってかなり大変じゃないですか?

最近では、SAMのおかげで簡単にサーバーレスアプリケーションを作ることが出来るようになりましたが、VPC、EC2、RDS等をCloudFormationで管理する大変さは変わりません。

テンプレートをYAMLで書けるようになり、JSONしか対応していなかった頃と比べるととても良くなったなぁとは思うものの、どうしても冗長な記述が生まれがちですし、何より記述量が多い。

さらに初見殺しなのは、ドキュメントをしっかり読み込まないとRDS一つ用意するのにも苦労する繊細なリソース群。

公式のテンプレートスニペットやWeb上に公開されているテンプレート達を見ては感じるベストプラクティスはどれですか感。

そんな迷えるCloudFormation使い(私)を救うべく、Cloudrulerを作りました。

Cloudrulerが提供する機能はざっくり以下の2つです。

  • Rulerというスニペット機能
  • Pluginによるスニペットの配布と利用

Rulerというスニペット機能

RulerとはChefでいうCookbookのようなものです。

Rulerとしてスニペットを定義しておくとTemplateから簡単に呼び出すことが出来ます。

以下にサンプルを示します。

$ tree
.
├── Gemfile
├── rulers
│   ├── subnet.yml.erb
│   └── vpc.yml.erb
└── templates
    └── sample.rb
rulers/vpc.yml.erb
# vpc: VPC
---
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: <%= @cidr %>
      EnableDnsSupport: true
      EnableDnsHostnames: true
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
  RoutePublic:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref RouteTablePublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
rulers/subnet.yml.erb
# subnet: Subnet
---
Resources:
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref <%= @vpc %>
      AvailabilityZone: <%= @availability_zone %>
      CidrBlock: <%= @cidr %>
      MapPublicIpOnLaunch: true
templates/sample.rb
description 'This is a sample template.'

vpc = ruler :vpc do
  @cidr = '10.0.0.0/16'
end

ruler :subnet, resource_name_suffix: 'PublicA' do
  @vpc = vpc.resources.vpc
  @availability_zone = 'ap-northeast-1a'
  @cidr = '10.0.1.0/24'
end

ruler :subnet, resource_name_suffix: 'PublicC' do
  @vpc = vpc.resources.vpc
  @availability_zone = 'ap-northeast-1c'
  @cidr = '10.0.2.0/24'
end
実行

上記のようなファイルを用意した後、コマンドを実行するとCloudFormationのテンプレートファイルに展開します。

$ cloudruler dump templates/sample.rb
---
AWSTemplateFormatVersion: '2010-09-09'
Description: This is a sample template.
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: VPC
      InternetGatewayId: InternetGateway
  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: VPC
  RoutePublic:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: RouteTablePublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: InternetGateway
  SubnetPublicA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
  SubnetPublicC:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: VPC
      AvailabilityZone: ap-northeast-1c
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true

Rulerの良いところは、冗長な記述を排除できるところにあります。

例えば上記の例ですと2つのSubnetを用意していますが、Rulerとして定義してあるので、設定の異なる部分のみをDSLとして記述するだけで事足ります。

また、よく使うリソースの組み合わせをRulerとして定義することで、複雑なAWSのリソース群を構造化出来ます。

上記の例では、VPCとInternetGatewayの設定をRulerとして定義しています。

新たにチームに加わったメンバーは簡単なDSLを記述するだけで標準的なVPCを構築できますし、いざとなればRulerとして構造化されたリソースを紐解くことも可能です。

Rulerを適切に使用することで、DRYにテンプレートを実装可能となり、さらに学習コストを下げることが出来るようになります。

Pluginによるスニペットの配布と利用

ItamaeのRecipeのように、作成したRulerをPluginとして簡単に配布することができます。

社内の他チームに提供することはもちろん、公開されたPluginを再利用することで車輪の再発明を防ぐことが可能になります。

と、自信をもって書くことが出来ればよかったんですが、例によって時間が足りずPluginに関しては現在未実装です。。。

この辺りは私が開発を担当しているプロダクトでドッグフーディングを続けながら、せこせこ改善していこうと思います。

ということで、Cloudrulerの人柱になりIssueを切ってくれる、そんな方に会える日に向け日々是精進。

非エンジニアが社内wikiにヒートマップを実装してみた話

こんにちは、Speeeトレーディングデスク事業の長山(cho3)と申します。 広告運用者として、GoogleAdWordsやFacebook広告をはじめとする運用型広告の運用を行っています。

非エンジニアです。

先日開催したSpeeeKaigiで、社内wikiにヒートマップを導入する話をしました。

※SpeeeKaigiについては下記のエントリをご覧ください。

社内技術プレゼン大会SpeeeKaigi#3 開催レポート - Speee DEVELOPER BLOG

http://tech.speee.jp/entry/2017/09/01/102341

今回はその内容について書かせて頂きます。

そもそもヒートマップって...?

ヒートマップとは...

サイト訪問者の目線の動きやマウスの動き、サイトの熟読時間などの情報ををひと目で理解できるよう可視化したものです。 数値ではなく、サーモグラフィーのような色のグラデーションで表示されます。

引用:ヒートマップとは? ヒートマップツールを最大限活用するための基礎知識 | ユーザ視点をもっと間近に - ポップインサイト公式ブログ https://popinsight.jp/blog/?p=2841

簡単に言うと良くテレビで見かける"サーモグラフィー"のWeb版というイメージです。 クリックした位置が赤く色づけされます。

f:id:numabe:20170904213322p:plain

開発モチベーション

  • 社内wikiでも気軽にヒートマップで分析したい
  • 出来ることなら無料で使いたい

という想いがありました。

実装対象

弊社トレーディングデスクでは、社内のノウハウを蓄積し共有するためのwikiがあります。 WordPressで作っています。

※ほぼモザイクですみません...

f:id:numabe:20170905103209p:plain

弊社社内wikiが抱える問題点

社内wikiは直近アクセスが減ってきてしまっており、 改修を考えているところでした。

f:id:numabe:20170904213544p:plain

wiki内のユーザー行動をヒートマップで分析出来る環境を整え、 改修に向けて歩みだそう、というのが今回の話です。

このように実装しました

  1. Googleタグマネージャーでクリック位置など情報を取得し
  2. Firebaseに蓄積
  3. GoogleAppsScriptでFirebaseに蓄積した情報を呼び出し
  4. heatmap.jsに渡す
  5. GoogleAppsScriptにてHTMLを表示し完成

f:id:numabe:20170904213613p:plain

汎用性を高めるため、基本的にはGoogleのツールを使用し、 ヒートマップ表示部分には、以下"heatmap.js"を使用させて頂きました。

heatmap.js : Dynamic Heatmaps for the Web

https://www.patrick-wied.at/static/heatmapjs/

ちなみに:Firebaseとは

Googleが提供するツールです。

firebase.google.com

今回は

  • Realtime Database
  • Cloud Functions

を使用しました。

Realtime Database >>

クラウドでホスティングされる noSQL データベースを使用して、リアルタイムにデータを保存し、 ユーザーと端末の間で同期します。データの更新は接続された端末間で数ミリ秒で同期され、 アプリがオフラインになるとデータが利用可能になり、 ネットワーク接続にかかわらず優れたサービスを提供できます。

引用:https://firebase.google.com/products/


Cloud Functions >>

専用サーバーの管理や拡張を行うことなく、 カスタムのバックエンド コードでアプリを拡張します。 Functions は、webhook を使用して Firebase 製品、Google Cloud サービス、 サードパーティによって出力されるイベントによってトリガーできます。

引用:https://firebase.google.com/products/

ちなみに:GoogleTagManagerとは

Googleが提供するタグ一括管理ツールです。

www.google.com

何を」「いつ」「どこで」実行するか、詳細に指定出来ます。

通常業務では、広告配信に必要な情報を取得するためのjsを埋め込んだり、GoogleAnalyticsのタグを埋め込んだりするのによく使っています。

実装してみた結果

このようにヒートマップを表示することが出来ました。 今回はTOPページ(奥)と詳細ページ(手前)の2ページでトライしてみました。

f:id:numabe:20170904213644p:plain

結果を見て気づいたコト(TOPページ編)

グロナビのログインボタンが使われていない話

かつて要望が出て実装したログインボタンも、 WordPress純正のログインボタン(左上)の登場によって 使用されなくなってしまっているようです。

f:id:numabe:20170904213728p:plain

右側のアクセスランキングは全く使われていない話

かつて良かれと思って実装した週間アクセスランキング部分は、 全くクリックデータがありませんでした。悲しいですね...

f:id:numabe:20170904213755p:plain

TOPページからは検索しかしない話

結局TOPページのクリックは、右上の検索窓に集中しており、 TOPページに記事の一覧を載せているものの、 検索ニーズが一番高い事がわかりました。

f:id:numabe:20170904213824p:plain

気づいたコト(詳細ページ編)

TOPページに戻るリンクはロゴである話

万国共通のルールがここにもありました。

f:id:numabe:20170904213848p:plain

フォルダパスは3クリックで全選択している人が多い話

フォルダパスを載せている部分は、局所的に赤くなっていました。 3クリックで全選択しているのでしょう。 コピーボタンでも設置しておけば、UX向上になるかもしれません。

f:id:numabe:20170904213859p:plain

引き続き使われないアクセスランキング

悲しいですね...

f:id:numabe:20170904213915p:plain

Slack投稿への執念

弊社では社内コミュニケーションツールとしてSlackを使用しており、 以前こちらのようなエントリを書かせて頂きました。 (こちらも便利なので是非ご覧になって下さい!!)

SlackStatus とGoogleCalendarの同期をしてみた - Speee DEVELOPER BLOG

http://tech.speee.jp/entry/2017/04/24/114000

私がSlack連携やらずに誰がやるんだ」 という謎の執念が湧き上がってしまい、、 ヒートマップをSlackに投稿して本作品の締めとしたいと思います。

SpreadsheetとGoogleDriveへの拡張

以下のように、Spreadsheetにヒートマップを表示させ、 GoogleDriveにpdf化したヒートマップをアップロードし、 SlackにGoogleDriveのリンクを投稿する案で強引に進めます。

f:id:numabe:20170904214119p:plain

Spreadsheetにヒートマップを表示する

GoogleAppsScriptで、Firebaseから取得したクリック数を、 セルに表示させ、半透明のページキャプチャを重ねます。 それだけだと色がつかないため、Spreadsheetのお家芸"条件付き書式"で色を表示させます。

f:id:numabe:20170904214203p:plain

Spreadsheetに表示したヒートマップをpdf化する

GoogleAppsScriptで定期的にpdf化を実行し、 GoogleDriveにアップロードするように設定しておきます。

f:id:numabe:20170904214231p:plain

無事Slackへの連携が完了

無事投稿出来ました。これで安心して眠れます。

f:id:numabe:20170904214252p:plain

結局何が便利だったの...?

色々書いたので、何が便利なのか整理します。

  • GoogleTagManagerを使用していることから、汎用性が高いといえる。
  • 社内wikiにて実装が出来たため、その他社内サービスに横展開が出来る(改善欲はあるものの外部ツール入れる程でもない、というレベルの改善)
  • Slackにクリックが投稿されるため、日々Slackでヒートマップライフが送れる

まとめ

今回非エンジニアとしてエンジニアの社内イベントに参戦しましたが、学びは大きかったです。 自分のアウトプットを、普段とは違うものさしで評価される経験は貴重ですね。 こういった経験を今後も積み重ねていきたいと思います。

ありがとうございました。

Elixirで祝日が欲しかった……

こんにちは。イエウールでエンジニアをしている山浦(@arumi8go)です。

先日開催したSpeeeKaigiで「Elixirで祝日が欲しかった……」と言うテーマで発表しました。

tech.speee.jp

今回は発表内容に少し触れて行こうと思います。

何をやったのか

今回はElixirの勉強の為に祝日取得のライブラリーを一つ公開しました!
そして時々発生する祝日更新のタスクをできるだけ労力をかけずにある程度自動化できないかにも挑戦しました。

ライブラリー詳細

現在のv0.0.1で出来ることは非常にシンプルで

# 渡されたDateObjectが祝日かどうかを判断しBooleanを返す
HolidayJapan.holiday? ~D[2017-05-04]

# 渡されたDateObjectが祝日であればその名前をString型で、祝日でなければnullを返す
HolidayJapan.name ~D[2017-05-04]

の2つの機能しかありません。

内部の仕組みも単純で、 外部で公開されている祝日のデータをライブラリーで使用しやすい形に整形しJSON化してテキストファイルとして保持するようにしています。 これは後述する祝日データの自動更新の為にテキストファイルとして保存しています、その詳細については後述します。 各メソッドは渡されたDataObjectと祝日データを参照し処理を行うようになっています。

祝日更新の自動化

2016年1月1日施行の改正祝日法で増えた「山の日」のように時々祝日が増えたり減ったりします。
当然祝日を扱うライブラリーなのでそれに合わせて更新をする必要が来ると思います。
ですが本当に極稀にしか発生しない上、いちいち手で直していくのも面倒です。 なので出来るだけ頑張らずに自動化してみることにしました。

それが此方です!!

f:id:arumi8go:20171006105152p:plain

これだけだと「なんぞや?」となると思うので順番に説明していきます。

  • その1
    Erlang/Elixirに備えられているescriptで作った。HolidayJapanUpdate君が外部の方が公開している祝日一覧を返すAPIを叩き、かえってきたJSONをHolidayJapanで使いやすい形に整形しHolidayJapanの祝日データファイルを更新。

  • その2
    Github APIを使用して祝日データファイルに変更があれば、その変更をコミットしてプルリクエストを生成。

  • その3
    その1と、その2の実行をシェルスクリプトから行うようにしてcronに登録し月に一回実行されるよう設定。

以上です!!

発表のスライドは此方

継続は「力」なり[Firebase+Android]

こんにちはSpeeeエンジニアのid:eva-hashimotoです。 先日行われたSpeeeKaigiで発表しました内容を記事にしたいと思います。

tech.speee.jp

今回の発表内容として去年末から年齢による体力の衰えを感じてジムに行き始めたんですが、 ふと気づくと最近やたらと筋トレブームがきているのとyoutubeなどの動画解説が充実しているので充実した筋トレライフが送れています。

そこで筋トレするにあたりトレーニングメニューや私が行っているエニタイムフィットネスの情報などを管理できるアプリがあれば便利だなと感じているわけです。

バックエンド

アプリ開発と同時にサーバサイドも開発すると工数がかかるのとアプリ開発に集中したいため、バックグラウンドにはMBaaSのFirebaseを利用しAndroidアプリの開発を行いました。

※今回はJavaで書いてます

仕様

  • トレーニングメニューの設定
  • トレーニングの履歴
  • トレーニング内容の分析

将来は食事管理や体重管理などできたらと思ってます。

Firebaseとの接続

Firebaseでプロジェクト作成をするとgoogle-service.jsonが自動生成されます。それをAndroidプロジェクトのappフォルダ直下に配置します。

build.gradle(app)のdependenciesに追加

    compile 'com.google.firebase:firebase-database:10.2.6'

データベース接続

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference databaseRef = database.getReference("history");

google-service.jsonにプロジェクト情報が入っているため、ソースコードにはデータベースのスキーマ??の接続情報だけで大丈夫です。

データ登録

データベースはリアルタイムデータベースを使い、リモートDBと同期して瞬時にデータ更新ができるようになります。 例えばトレーニングメニューの履歴登録では下記のようなModelになります。

    public String id;  // Firebaseが自動で入れるユニークID
    public String parts;  // 鍛える体の部位
    public String event; // トレーニング種目 
    public int weight;  // 重量
    public int lep;     // 回数
    public long timestamp; 

Firebaseにはタイムスタンプ型(Date型)がないためUnixtimeで登録します。

Training t = new Training("肩", "アーノルドプレス", 10, 5, System.currentTimeMillis());
databaseRef.push().setValue(t);

これでデータベースへ登録ができます。 f:id:eva-hashimoto:20170905180948p:plain

オンライン時には即時にローカル・リモートへDBが同期されますが、オフライン時にはローカルのデータベースに書き込まれ、回線が復活するとリモートへ同期されるなどとても便利です。

これをフルスクラッチで開発しなくて済むし、これだけのコード量で実現できるのでアプリ開発でMBaaSを利用しない手はないかと感じます。

トレーニング分析

トレーニングの履歴から傾向分析を行いたいと思うわけですが、リアルタイムデータベースを集計しダッシュボードを作成するのが普通だとお思います。 しかしダッシュボードや集計って運用していくと大変になってくるので今回はFirebase内にあるAnalyticsを使い分析することにして見ました。 ということでアプリないからカスタムメトリックストしてリアルタイムデータベースに登録している内容と同じものをデータ送信し、Analytisのダッシュボードで見ることをやって見ます。

アーキテクチャー

f:id:eva-hashimoto:20170906163006p:plain AnalyticsデータはBigQueryとも連携可能であるため、Analyticsでデータを見ることもできますし、アプリからCloudFunctionを利用を叩きリアルタイムデータベースに書き込むことでアプリでは非同期で集計結果が反映されるなどの仕組みも可能になります。

データの送信

FirebaseAnalytics fa = FirebaseAnalytics.getInstance(v.getContext());
Bundle params = new Bundle();
params.putString("parts_name", training.getParts());
params.putString("event_name", training.getEvent());
params.putInt("weght", training.getWeight());
params.putInt("lep", training.getLep());
fa.logEvent("event", params);

まとめ

このようにアプリのバックエンド、分析、クラッシュレポートなど多数の支援がFirebase煮詰まっているため、サーバサイドを意識することが減るのはメリットだと感じます。 またリアルタイムデータベースなども活用することでUIのリアクティブプログラミングとも親和性が高いと感じます。 これからアプリ開発ではMBaaSが主流になる可能性が高いと思いました。

画像から光源を推定する

デジタルコンサルティング事業部のduenoです。

先日開催したSpeeeKaigiで「画像から光源を推定する」と言うテーマで発表しました。

tech.speee.jp

やったこと

  • 平面検出機能を利用して光源の推定ができるか試してみる

平面検出機能を持ったいい感じのデバイスが手元になかったので、 とりあえず単一画像から光源の位置、色、明るさが推定できるかどうかを試してみました。

動機

昨今ARアプリをよく見かけますが、描画された3Dオブジェクトが浮いていると感じる場面が結構あります。

f:id:anoChick:20170929094714p:plain 何も考えずにカメラで写した像の上に3Dオブジェクトを描画したらこんな感じになります。やっぱりなんか浮いてる。

特に、ライティング処理がもっとやりようがあると思います。 例えば現実の光源を推定して、3Dオブジェクトにかけることができれば、いい感じに見えるのではないかと思いました。 調べてみたところ、光源の推定はインバースレンダリングという分野に該当するそうです。

インバースレンダリング ... 画像から、「物体形状」「反射特性」「光源分布」を推定すること。

今回行いたいことはは「光源推定」なので、 反射特性と幾何形状が既知にしなければいけないそうです。

平面検出を使う。

MicrosoftのMixedRealityToolkitであったり、iOSのARKitであったり、最近平面検出を行うライブラリが結構ある気がします。 平面検出で得られた面を扱うことにすれば、幾何形状は「既知」になると考えました。

光源の色

模様のない板に、ある光が射している画像というものは、 単純な線形グラデーションとして扱うことが出来そうです。

f:id:anoChick:20170905025409p:plain

例えば↑の画像は 左から

  1. 画像上方から緑の光が当たっている壁の一部分
  2. 画像1全体と、画像1内の最も暗い点との差
  3. 画像2の最も明るい点
  4. 画像3の明度を適当に調整したもの

です。 この画像から、光源の色相に関しては取れそうな気がします。

明るさは光源との距離によって変化する(距離減衰)ので、 同じようなサンプル画像を複数の位置でとる事で、光源の明るさも出せそうです。

光源の位置

平面サンプルを複数取り、そのサンプル一つ一つから - どちら側に光源があるか(2択) - その画像の座標、回転情報 の情報が得られれば、光源の座標は出せるんじゃないかと考えました。

大津の二値化(判別分析法)

大津の二値化は画像を二値化(白黒化)する際に、その閾値をうまい具合に決めるためのアルゴリズム、またはそれを用いた二値化処理の事を指します。

f:id:anoChick:20170905025309p:plain

↑の画像の「とある壁の画像」は、肉眼で見るとわかりづらいですが、左側がやや明るくなっています。 「とある壁の画像」に対して、大津の二値化を適用すると、「大津の二値化を施したもの」になります。 この画像から、左側が明るく、光源が存在すると推定できそうです。 もう少し処理しやすくするために、2 x 2 ピクセルの画像にしました。

そこまで!

という所までやってみた所でSpeeeKaigi当日になってしまったので以上です。 ここまで試してみて

  • 複数光源あることを考慮しなければいけない
  • 大津の二値化後の処理はもっとうまくやる余地がありそう。

等、検討しなければいけない事が多数出てきました。

次回機会があれば、実際のデバイスを使って実装してみた所まで発表できればと思います。 ありがとうございました。

参考文献

インバースレンダリング:画像からの光学情報の復元 (佐藤洋一 東京大学 生産技術研究所) http://www.mtl.t.u-tokyo.ac.jp/~katsu-t/ssii05-ysato.pdf