en ja en de ja fa ja ja ja ja zh ja ja ja ja ja ja ja ja ja id ja ja ja ja ja ja zh ja ja ja en ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja en en ja sv it ja fr sl ja de nl ja de en ja fr zh ja fr de it fr en it sv en it en en zh fr en zh en en ja ja en ja fr en ja de en ja fr nl ja es en zh nb en ja fr en ja de en ja en en zh en en ja ja en ja ja ja ja ja ja ja ja id ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en th en en en en ja es en en de ja es en en en ja es en en fr ja es eu en fr ja de zh zh it ja es en en en ja es en en es ja es en en ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja de nl ja ja de ja fa ja zh ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja en ja ja en ja ceb ja en ja en ja fa ja ja ja ja ja ja ja ja ja ja de ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja de nl ja ja de ja en ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja en ja de nl ja ja ru ja ja ja ja ja ja ja ja uk ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja az ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja pl nl ja ja ru ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja az ja ja ja ja ja ja az ja ja ja ja ja ja en ja ja ja ky ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja az ja ja ja ja en ja ru nl ja ja ru ja nl ja ja ja ja ja ja ja ja ja ja ja en ja ja ja en ja ja ja ja ja ja ja ja ja ja ja en ja ja ja en ja en ja ja ja ja en nl ja ja de ja nl ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja es ja ja ja en ja ja ja ja ja ja ja en ja en ja zh ja ja ja ru en en en sv nl ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja jbo en en ja nl nb en ko it en it it it en fr it ja ja en la fi ja it it it pt it de en ja en en it fr it en it nb it en ja ja ja ja ja ja ja ja ja
さて、今回はAWSの利用料金明細データの話をします。AWSの現時点での料金、過去数ヶ月の料金などは 請求ダッシュボード 、 AWS Cost Explorer などで参照することができますが、この元となる詳細なデータは、事前に「リソースとタグを含む詳細な請求レポート」を出力するように 設定 しておくことでS3にCSVファイルとして出力することができます。
標準的なカンマ区切りのテキストファイル(UTF-8エンコーディング、改行はLF)です。数値を含め、全てのフィールドはダブルクオートでくくられています。また "$0.0037 per 10,000 GET and all other requests" のようにカンマを含むフィールドが多数存在するため、ダブルクオートを無視してカンマ区切りで読み込もうとすると悲しいことになります。awkなどで処理したい場合は "," を区切り文字とすれば良いでしょう。
カラム数は固定です。ただし一括請求を有効にすると Rate, Cost カラムは BlendedRate, BlendedCost, UnBlendedRate, UnBlendedCost カラムになります(後述)。
また、「コスト配分タグ」でタグ(たとえば IsDev タグと Project タグ)を追加すると、行の末尾に user:IsDev, user:Project カラムが追加されます(アルファベット順にソートされるようです)。このタグの値をもとに AWS Cost Explorer などで料金を集計することができます。設定を変更するとカラム数も変わってしまいます。
明細データの最初のカラムは InvoiceID で、請求書の上部に記載されているInvoice Numberが書いてあります。月の途中の場合は請求書がまだ発行されていない(=Invoice Numberが確定していない)ため全ての行で "Estimated" になります。
各行には RecordId というフィールドがあり、行ごとにユニークな値が振られています。IDは整数形式ですが、 155050012134381272590339883 (=1.55*1026、287) のように非常に大きな値で、かつレコード間で差が10~20前後しかないものがあるので、大抵の場合は文字列として扱う必要があります(64bitのfloat型などではまるで精度が足りません)。
プロダクト名。 "Amazon Simple Storage Service"、"AWS Lambda" など
実際の料金が記載された行(ほとんどがこれ)は LineItem となっています。集計に使う場合は LineItem の行だけ抜き出せばいいです。それ以外の行はファイル末尾にしかありません。
あまり綺麗なデータではありません。たとえばEC2に限っても、 APN1-BoxUsage, APN1-BoxUsage:m5.large, APN1-EBSOptimized:m5.large のようにインスタンスサイズがあったりなかったりします。
たとえば Project というタグを指定した場合、23カラム目にはその行のリソース(EC2インスタンス、RDSインスタンス、S3バケット……)の Project タグの値が入ってきます。
すべてのレコードにこのタグが出力されるわけではありません。タグを付け忘れたリソースの行にはもちろん空文字が入ります。また(徐々に対応するサービスが増えてはいますが)2019年1月現在、CloudWatchやAPI Gatewayにはタグを付けることができないため、ここは空文字が入ります。別途 ProductName, ResourceId で集計するなどの工夫が必要になります。
というデータが出力されます(AとBどちらが選ばれるかは謎です)。ここで適用される単価が UnBlendedRate で、それをもとに算出された料金が UnBlendedCost です。しかし、まったく同じようにS3を使っているのにアカウントAとBで料金が違う(ように算出される)のでは困ります。そこで、一括請求を前提としてこれを平準化した単価が BlendedRate 、それをもとに算出された料金が BlendedCost です。
m3.2xlarge のインスタンスA, B, C, D
m3.xlarge のインスタンスE (※料金は m3.2xlarge の50%)
m3.medium のインスタンスF, G (※料金は m3.2xlarge の12.5%)
というケースもありますが、
A~CにRIが3個分適用される(A, B, Cは無料になる)
残る1個のRIが次のように分配される
Dには25%分適用される(Dの料金は25%引きになる)
E には50%分適用される(Eの料金は m3.2xlarge の50%なのでEは無料になる)
F, G にはそれぞれ12.5%分適用される(F, Gの料金は m3.2xlarge の12.5%なのでF, Gは無料になる)
ということもあり得ます(いずれも料金の総額は変わりません)。後者の場合、次のようなレコードが出力されます。
ItemDescription(概要)
ReservedInstance
UsageQuantity
UnBlendedRate
m3.2xlargeのRI適用
m3.2xlargeのRI適用
m3.2xlargeのRI適用
m3.2xlargeのRI適用
標準料金 ($.077/h)
m3.2xlargeのRI適用
m3.2xlargeのRI適用
m3.2xlargeのRI適用
インスタンスE, F, Gのインスタンスタイプは m3.xlarge, m3.medium なのに( UsageType カラムにはそれぞれ APN1-BoxUsage:m3.xlarge, APN1-BoxUsage:m3.medium と書いてあります)、 ItemDescription には「m3.2xlargeのRIを適用した」と書いてあるのがポイントです。
BlendedRate, BlendedCost はRIでも働いていて、こういった料金を平準化した料金が記載されています。
とはいえ、「自分はこのインスタンスの料金を前払いしたんだ」という気持ちでRIを購入することもあるでしょう(たとえば上のケースですとインスタンスA~Dの料金を常に0とし、E~Gの ReservedInstance が Y となっている行に標準料金を加算したいといった感じです)。そのような場合は、
RIの柔軟性 に記載されているインスタンスサイズと係数のテーブル
UsageType カラムに記載されているそのインスタンスのサイズやOSから求めた標準料金
ItemDescription カラムに記載されている適用されたRIのタイプとサイズ
などを組み合わせて求める形に修正してやる必要があります。非常に面倒なのでオススメはしません……
スナップショットの料金
EBSのスナップショットはS3に保存され、1時間ごとにストレージ利用料金が加算されていきます。 ResourceId カラムにはスナップショットのARNが記載されています:
arn:aws:ec2:ap-northeast-1:999999999999:snapshot/snap-0123456789abcdef0
2019年1月時点では、AMIを作成した際に生成されるスナップショットにはタグが付きません(手動で追加することはできます)。タグの値が空ですとこのスナップショットの正体が分かりません。料金がどのEBSから発生したものかを知りたければ、このARNからスナップショットを取得して作成元のEBSを特定する必要があります。しかし、AMIやスナップショットを定期的に作成・削除するような運用をしている場合、料金集計をしようとした時点では既にそのスナップショットが存在しないということがあり得ます。AMI/スナップショットを作るたびにタグを追加するか、Snapshot IDとEBS Volume IDの対応表を定期的に取得して保存しておくような処理を仕込んでおくと良いでしょう(LambdaとDynamoDBを使うと楽です)。
RDSのストレージ利用料金
RDSのバックアップに利用しているストレージ利用料金は、(なぜか)それがどのインスタンスのものかは分からず、 ResourceId が空文字になります。これはどうしようもないので、
ResourceId が空文字以外のものについてRDSの料金を集計する
RDSの利用料金に応じてストレージ利用料金を按分する
という手で振り分けてしまうのがいいと思います。
というわけで、たとえば Project というタグの値に基づいて明細データの料金集計をしたい場合、次のような流れになります。
zipファイルをS3から取得、解凍
RDSやRなどに読み込む
RecordType が LineItem の行だけ抜き出す
Cost または BlendedCost の値を user:Project の値に応じて合算する
user:Project の値が空文字のレコードについては2パターンがある(タグを付けるのを忘れていたもの、タグを付けられないもの)
タグを付け忘れていたものについてはデータ修正を行って再集計する
それ以外のものは ProductName, ResourceId の値に応じて Cost または BlendedCost の値を合算する
以上、明細データの見方についてハマりやすいところを簡単にまとめてみました。正しくコストを把握して正しくAWSを利用していきましょう。今年もよろしくお願いします。
*1:展開すると.zipが取れる
*2:VPCをまたいだ通信があったりEIPを使ったりするとネットワークI/Oの行はそれぞれ2行ずつ増えます。EBSが複数存在すればその分も増えます
*3:ビジネスサポート以上の場合
nurenezumi 2019-01-10 11:25
Amazon S3の機能まとめ
こんにちは、AWS担当のwakです。そろそろAWSソリューションアーキテクトのプロフェッショナルを取らないといけなくなったので、S3について勉強がてらまとめました。
S3は「99.999999999%の耐久性」(eleven 9’s of durability)を売りにしています。S3に1万個のオブジェクトを保存したとき、1000万年に1個消える可能性があるということです。
S3のオブジェクトは最低3つのAZ(Availability Zone、物理的に離れたデータセンター)にレプリケーションされます。(ドキュメント) ただし以下に述べるOne Zone-IAとRRSは例外です。
クロスリージョンレプリケーション (CRR)
バケット単位で指定可能な設定で、S3に保存したオブジェクトを自動的に別のリージョンのバケットへレプリケーションしてくれます。別アカウントのバケットにレプリケーションすることも可能です。バージョニング(後述。上書き・削除しても過去の履歴が全て残る機能)を有効にしておく必要があります。(ドキュメント)
一部の暗号化されたオブジェクト、バージョンIDを指定した削除リクエストはレプリケーションされません*1。(ドキュメント) 通常レプリケーション先のリージョン内ではさらに別AZへレプリケーションが行われますが、これが無駄だと思う場合は後述のOne Zone-IAが利用できます。
オブジェクトを保存する種別のことで、オブジェクトごとに個別に指定できます。(ドキュメント)
STANDARD (標準)
デフォルトで使われるストレージクラスです。最も高価で最も堅牢です。料金は$0.025/GB(最初の50TBまで)となっています。*2
STANDARD_IA (標準・低頻度アクセス)
滅多に使わないオブジェクトはSTANDARD_IAとして保存すると料金が安くなります。IAはInfrequent Accessの略で低頻度アクセスを意味します。堅牢さはSTANDARDと同等で、料金は$0.019/GBと24%も安くなりますが、30日以内に削除すると30日分の料金がかかる、取り出しに転送料とは別の料金がかかる等、料金体系が異なります。アップロード時にSTANDARD_IAを指定することもできますし、ライフサイクルポリシーも利用できます(後述)。
One Zone-IA (1ゾーン・低頻度アクセス)
ドキュメントではONEZONE_IAとも表記されています。STANDARD_IAは3つのAZにオブジェクトがレプリケーションされますが、One Zone-IAではレプリケーションが行われません。災害や障害で運悪くオブジェクトを保存しているAZが壊れるとオブジェクトが消えます。料金はSTANDARD_IAよりもさらに安く、$0.0152/GBとなります。こちらもSTANDARD_IA同様、アップロード時にOne Zone-IAを指定することもできますし、ライフサイクルポリシーも利用できます(後述)。
RRS (低冗長化ストレージ)
RRSはReduced Redundancy Storageの略で、2つのAZにしかレプリケーションされない代わりに料金が安くなります。この記事を書いている時点で料金表からも消えており、ドキュメントには利用が推奨されていないと明記してあるのでそのうち使えなくなるかもしれません。(ドキュメント)
アーカイブ用のストレージクラスです。データを直接GLACIERにアップロードすることはできません*3。一度他のストレージクラスでデータをS3に保存し、ライフサイクルポリシーでGLACIERへ移動することになります。また、直接データを取り出すこともできず、事前に復元作業を行う必要があります。料金は$0.005/GBと極めて安価ですが、他に取り出し料がかかります。
ライフサイクルポリシー
アップロードされたオブジェクトがN日過ぎたとき、ストレージクラスを自動的にSTANDARD_IA/One Zone-IA/GLACIERのいずれかに移動するか、または削除するというルールが設定できます。また、途中でマルチパートアップロードが止まって残っているゴミ(後述)を自動削除することもできます。
HTTPを通じたアップロード
1回のPUTでアップロード可能なサイズは5GBです。マルチパートアップロードを使うと5TBになります。(ドキュメント)
マルチパートアップロード
大きなサイズのオブジェクトをアップロードする際、最大1万個にオブジェクトを分割して転送することができます。並行してアップロードすることもできるので時間の節約になり、リトライもやりやすくなります。なおアップロードが終わったら(または中断したら)その旨を通知して削除しないとアップロードは完了せず、転送済みのゴミがS3に残って無駄な課金が発生します。(ドキュメント)
確実にアップロードが成功したことを確認するため、リクエストに Content-MD5 ヘッダでオブジェクトのMD5を付加することができます。このヘッダがある場合、AWSはアップロード完了時に受け取ったデータのMD5を算出し、両者が一致しなかったらエラーを返してくれます。なお、この処理はAWS CLIの aws s3 cp コマンドでは自動的に行われます。(ドキュメント)*4
また、マルチパートアップロードが行われなかった場合はMD5の値がETagタグにセットされるので、こちらをチェックすることでも確認ができます。(ドキュメント)
書き込み・上書き更新・削除を行い、その直後に読み取りをすると、それらの操作の前の状態が取得できてしまうことがあります。また、同じキーで同時にアップロードを行うとタイムスタンプが遅い方が勝ちます。これはどうしようもありません。(ドキュメント)
Amazon S3 Transfer Acceleration
バケット単位での設定で、追加料金がかかる代わりにデータ転送が速くなるエンドポイントが利用できます。通常のエンドポイントと変わらないと判断された場合は追加料金がかからないというサービスがあります。このエンドポイントでもマルチパートアップロードは利用可能です。(ドキュメント)
署名付きURLを使用したオブジェクトのアップロード
S3のオブジェクトには「署名付き(Pre-Signed) URL」を発行することができます。このURLにはバケット・キー・有効期限・URL作成者の権限がセットで含まれており、このURLに対してHTTP PUTでデータを送信すると、認証なしでオブジェクトをアップロード(または上書きアップロード)することができます。(ドキュメント) ダウンロード用の署名付きURLを作成することもできます(後述)。
VPN/AWS Direct Connect
オンプレミス環境からVPN, Direct Connectを通じてVPCエンドポイントを経由しS3を利用するような構成も可能です。この場合、データはインターネットを通りません。
HTTP以外のアップロード&ダウンロード
AWS謹製のストレージデバイス(1個最大80GB)をAWSのデータセンターと直接やり取りする方法です。デバイスが届いたらネットワーク接続(10Gbpsに対応)してデータを転送します。S3へのデータのインポートとエクスポートに利用できます。(ドキュメント) 受け取りからデータ転送完了まではだいたい1週間程度です。
Snowball Edge
Snowballの強いやつです。容量は100GBで、ただのストレージデバイスではなくLambdaを実行することもできます(インターネット接続は不要です)。また、クラスタを組んで容量・処理速度を増大させることができます。Snowballと同様データのインポートとエクスポートに利用できます。(ドキュメント) かかる時間はSnowballと同じぐらいです。
AWSの巨大なトレーラー(長さ14m)が直接ユーザーのところにやってきます。データを吸い出したらトレーラーはAWSのデータセンターに戻り、S3またはGLACIERにデータを転送します。100PBまでのデータのインポートに利用できます。(ドキュメント) 必要な時間は数週間になります。
Storage Gateway
オンプレミスのサーバーとS3を接続し、サーバーに配置したファイルを裏でS3と同期するサービスです(転送は非同期で行われます)。(ドキュメント)
AWS Import/Export
今ではSnowballシリーズに取って代わられており、 利用できなくなりました。 USBメモリ、eSATAのHDDなどをAWSのデータセンターと直接やり取りするものでした。(ドキュメント)
署名付きURLを使用したオブジェクトのダウンロード
オブジェクトをダウンロードするための「署名付き(Pre-Signed) URL」を発行することができます(アップロードについては前述)。同じくプライベートなオブジェクトに認証なしでアクセスできるようになるので、一時的に(有効期限を付けて)ファイルを公開してダウンロードさせるために利用することができます。
CloudFrontとの連携
S3のオブジェクトを直接Webに公開するのではなく、CloudFront経由で配信することができます。 (ドキュメント)
ユーザーごとにIAMユーザーを作成するのではなく、他の認証システム(IdP: IDプロバイダー)のIDとIAMロールとをマッピングすることができます。AWS Security Token Service (AWS STS)が ユーザーに一時的なAWS証明書を与え、一般には非公開としているS3のオブジェクトにアクセスさせたり、DynamoDB*5の読み書きをさせたりできます。(ドキュメント)
Web IDフェデレーション
Amazon, Facebook, GoogleなどのアカウントとIAMロールをマッピングするものです。Cognitoを使うと楽です。(ドキュメント)実際にCognitoでGoogleアカウントとロールをマッピングする関連記事も書きました。
tech.sanwasystem.com
SAMLベースのフェデレーション
Google G Suite, SalesforceなどのSAML 2.0に対応したIdPをAWSに登録し、それらのアカウントとIAMロールとをマッピングするものです。(ドキュメント)
バケットのバージョニングを有効に設定すると、上書き・削除してもオブジェクトの過去の履歴が全て残るようにできます。バケットのバージョニングは一度有効にしたら無効にはできず、停止のみが可能です。(ドキュメント)
MFA Deleteを有効にすると、バケットのバージョニング状態を変更する・オブジェクトを完全に削除する際にMFAが必要になります。
S3のオブジェクトはキー(=ただの文字列)で識別されるため、大量のオブジェクトに同じようなキー(たとえば連番、日付など)が振られていると、S3のオブジェクト検索速度が落ちることがあります。大量のアクセスを受け付けてこれが問題になる場合、適当な工夫をしてキーを分散させてやる必要があります。逆に管理は面倒になるため、場合によってはDynamoDBや自前のDBでオブジェクトを別途管理する必要があるかもしれません。(ドキュメント)
AWS側で暗号化を行う(SSE: Server-Side Encryption)には3つのオプションがあります。いずれにしてもメタデータは暗号化されず、オブジェクト一覧取得リクエストからオブジェクトの存在を隠すこともできません。また ETag の値はオブジェクトのMD5ではなくなります。 (ドキュメント)
S3で管理された暗号化キーによるSSE (SSE-S3)
S3側で暗号化キーを勝手に用意してくれます。アップロード時に自動的に暗号化が、ダウンロード時に自動的に復号が行われます。公開した場合、オブジェクトはそのまま外部からアクセス可能になります。(ドキュメント)
AWS KMSで管理された暗号化キーによるSSE (SSE-KMS)
アップロード時にAWS KMS(Key Management Service)で管理されたキーを使って暗号化が行われます。ダウンロード時にも同じくKMSで管理されたキーで復号が行われてから転送されます。したがってアップロード時・ダウンロード時ともにKMSのキーへのアクセス権限が必要になります。公開しても部外者はデータを取得できません。(ドキュメント)
カスタマーが用意した暗号化キーによるSSE (SSE-C)
アップロード時、HTTPヘッダに暗号化キーを指定すると、S3にそのキーで暗号化されたオブジェクトが格納されます。ダウンロード時にも同様に同じキーを指定する必要があります。公開しても部外者はデータを取得できません。もしキーを失うとオブジェクトにアクセスできなくなるということになります。(ドキュメント)
クライアントサイド暗号化
S3にアップロードする前にクライアント側で暗号化します。AWS KMSを使うことも、独自のマスターキーを使うこともできます。いずれにしても、1個のマスターキーのみで全てのオブジェクト(ファイル)を暗号化するのではなく、オブジェクトごとに異なる鍵で暗号化を行い、その鍵をマスターキーで暗号化してオブジェクトと一緒に保存しておくという流れになります。(ドキュメント)
AWS KMSを使った暗号化
AWS KMSのCMK ID(Customer Master Key ID)だけで事を済ませることができます。まずAWS KMSにCMK IDを送り、ランダムに生成される1回限り使い捨ての鍵(AES)を取得します。このとき取得できる鍵は
(A) 暗号化に使える平文の鍵
(B) 上の鍵そのものが暗号化されたもの。CMK IDが含まれている。KMSでしか復号できない
のセットです。この(A)でオブジェクト(X)を暗号化し、(A)を削除し(もう不要なので)、暗号化されたオブジェクト(X')と(B)とをセットでS3にアップロードします。
復号時にはこのセットをダウンロードし、KMSに(B)の復号を依頼します。権限があれば復号が成功して(A)が入手できるので、暗号化されたオブジェクトを復号することができます。つまり、仮にS3のアクセス設定が誤っていて(B)や(X')が漏洩したとしても、対応するCMK IDの利用権限がなければKMSにアクセスできず、(A)が得られないため、オブジェクトは守られます。
KMSを使わない暗号化
AWS SDKはKMSを使わないクライアントサイド暗号化にも対応しています。AWS KMSを使う場合と流れは同じで、envelope key(データの暗号化・復号の両方に利用する1回限り使い捨ての鍵)を生成し、そのenvelope keyでファイルを暗号化し、マスターキーでenvelope keyを暗号化し、その両者をS3にアップロードします。復号時はこの逆の操作を行います。マスターキーはいかなる形でもAWSに送信されません。
S3のオブジェクトが更新・削除された際、その通知を送ることができます。通知先はSNS、SQS、Lambdaが指定できます。キーに対してプレフィックス・サフィックスで絞り込みを行うこともできます。たとえば画像ファイルがアップロードされたとき、自動的にLambdaを起動してそのサムネイル画像を生成するなどの使い方が考えられます。
S3に保存したオブジェクト(CSV, JSON形式など。GZIP圧縮ファイルにも対応)に対して、直接SQLを発行することができます。(ドキュメント)
ざっくりS3の機能についてまとめました。S3はAWSの様々なサービスを仲介するハブの役割を果たす重要な存在であり、AWSソリューションアーキテクトを取るのであればその仕組みを正しく知っておく必要があります。頑張りましょう。
*1:これは悪意のある削除から守るためです。バージョンIDを指定しない削除リクエストはレプリケーションされます(つまりレプリケーション先でも削除されます)が、通常、バージョニングが有効になったバケットでオブジェクトを丸ごと削除するには別の権限が必要です
*2:2018/6/20現在、東京リージョンの料金。データ転送料、リクエストごとにかかる料金は別。以下同様です
*3:引っかけ問題を何度か見ました。サードパーティーのツールでは可能なようです
*4:この記事を書いている時点で日本語ドキュメントには「aws s3api put-objectで低レベルAPIを呼び出せ」という古い記述が残っていますが、これは不要です。サポートに確認済み
*5:よくセットで出題されます
nurenezumi 2018-06-25 13:14
S3 + AWS Cognito + Google認証でドメイン制限のついた非公開サイトを作る
Cognito S3 AWS
こんにちは、AWS担当のwakです。
弊社では全社的にG Suiteを導入しています。そこで社内向けWebサイトをS3+Lambda+API Gatewayで構築し、G Suiteのアカウント+AWS Cognitoで弊社社員のみ利用できるような認証をかけようとしたところ、何点かハマるポイントがあったので手順を書いておきます。
前置き: AWS Cognitoって
プロジェクト初期化用コード取得
Google認証をONに
承認済みドメインを追加
OAuthクライアントID確認
IDプールID(Identity pool ID)をメモする
ロールに権限を追加する
ポップアップまたはリダイレクトで認証を行うパターン
サインインボタンを表示するパターン
OAuthクライアントID再作成
GoogleのAPIキーが丸見えだけど……?
鋭い視線で部外者のアクセスを監視する猫
S3に静的なページ(HTML+CSS+JavaScript)を配置した
JavaScriptがAWS APIを叩いてS3とDynamoDBからデータを取得し、画面に表示する
ページを利用する(=AWS APIを叩く)にはGoogleアカウント認証が必須
認証ができたらCognito経由でユーザーにIAM Roleを与え、S3とDynamoDBへのアクセス権限を付与する
Googleアカウントのドメインは sanwasystem.com に限定する
それ以外のアカウントではHTMLは見られてもS3とDynamoDBにはアクセスできないようにする
前置き: AWS Cognitoって
Cognitoの機能は主に2つあります。
ユーザー認証を取り扱う
Google, FacebookなどのOpenIDプロバイダーからの認証を受け付けてIAMロールを付与する
今回はこの後者の機能だけを使っています。
Google側で認証を行うので、 Firebase console よりプロジェクトを新規作成します。既存のプロジェクトでも構いません。
プロジェクト初期化用コード取得
プロジェクトが作成できたら、「ウェブアプリに Firebase を追加」ボタンを押すとプロジェクト初期化用のJavaScriptのコードが取得できます。これを保存しておきます。
Google認証をONに
コンソールがずいぶん親切になっています。Authentication→「ログイン方法を選択」と進み、GoogleログインをONにします。
承認済みドメインを追加
認証済みドメインにWebサイトを配置するドメインを入力します。 sanwa.local のようなGoogle側では名前解決できない値も受け付けてくれます。
OAuthクライアントID確認
旧来のプロジェクト管理画面 に移動します。 Credentials を見ると既にOAuthクライアントID(以下、単に「クライアントID」と呼びます)が自動生成されているので、このクライアントIDをメモしておきます。なお、このIDは認証時に暗黙的に利用される値なので、削除すると大変面倒なことになります*1。
Cognitoの管理画面 でIDプールを作成します。認証プロバイダにはGoogle+を選択し、GoogleクライアントIDには先ほどメモっておいたクライアントIDを入力します。
認証時・非認証時に対応するロールの作成画面が表示されるので、そのまま作成してもらいます。
IDプールID(Identity pool ID)をメモする
サンプルコードが表示されます。その中のIdentity pool IDをメモしておきます。
画面右上のリンクからIDプール編集画面を開きます。再度Authentication providersのGoogle+タブを見ると、今度はAuthenticated role selectionという項目が出現しています。ここで下記のような設定をすることで、Google認証されたユーザー情報のhd(Hosted Domain)プロパティに対して制限をかけることができます(このhdという名前はOpenIDで決められています)。
素直に考えるとロールのTrust relationshipsの方に accounts.google.com:hd = sanwasystem.com となるような条件を書き込みたくなるのですが、こちらにはhd(Hosted Domain), emailなどの情報は渡されてこないようです。*2
ロールに権限を追加する
S3に対して読み書きしたりLambdaを実行したりしたいので、認証時に対応するロールに権限を適宜追加します。
あとはHTMLとJavaScriptを書くだけです。Google認証には2通りの方法があります。
ポップアップまたはリダイレクトで認証を行うパターン
Firebaseを使うやり方です。とりあえずCDNを読み込んでJavaScriptをベタ書きしています。
HELLO COGNITO    // Firebaseの管理画面から取得可能 const config = { apiKey: "AIzaSyDWKx_xxyCTYiGjQt0wODTLMuXy7JOSbXk", authDomain: "sscaccountauthenticator.firebaseapp.com", databaseURL: "https://sscaccountauthenticator.firebaseio.com", projectId: "sscaccountauthenticator", storageBucket: "sscaccountauthenticator.appspot.com", messagingSenderId: "963005312419" }; firebase.initializeApp(config); const provider = new firebase.auth.GoogleAuthProvider(); provider.addScope("profile email"); const init = async () => { await new Promise((resolve, reject) => { setTimeout(resolve, 2000); }); // 初期化を待つための手抜き const result = await firebase.auth().signInWithPopup(provider); // Googleでログインできたら、トークンをAWSに渡して認証を行う AWS.config.region = "ap-northeast-1"; AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: 'ap-northeast-1:ffffffff-ffff-ffff-ffff-ffffffffffff', // IdentityプールID Logins: {'accounts.google.com': result.credential.idToken} }); // 認証できたら呼び出される AWS.config.credentials.get(function() { const s3 = new AWS.S3(); const url = s3.getSignedUrl('getObject', {Bucket: "YOUR-BUCKET-NAME", Key: "PATH/TO/YOUR/FILE/neko.jpg"}); console.log(url); }); }; init(); function signOut() { firebase.auth().signOut(); }        Sign out
このコードには一つ問題があり、リロードするたびにポップアップが表示されてしまいます。既にログインしているときにはトークンIDが firebase.auth().currentUser.getIdToken(true) で取得できるので本当は再ログインは不要なのですが、このトークンIDはProviderIdが firebase の固定値となっており、Cognitoが受け付けてくれません。認証直後に result.credential.idToken で取得できるトークンはProviderIdが google.com なので問題は起きません。
サインインボタンを表示するパターン
Firebaseではなく旧来のフレームワークに沿って実装します。JavaScript で Google ログインを使用して認証する  |  Firebase Documentation に従いますが、Firebaseで作成したプロジェクトは互換性がイマイチなので前準備が必要です。
OAuthクライアントID再作成
旧来のプロジェクト管理画面 に移動し、自動作成されているクライアントIDを無視(もうこのクライアントIDは使いません)して新規にクライアントIDを作成し、Originの設定を済ませます。この新しいクライアントIDでCognitoの設定を上書きします。
この手順がなぜ必要なのかは謎ですが、自動生成されたクライアントIDをそのまま使ってOriginの設定を行っても反映されず、認証しようとしたときに Not a valid origin for the client というエラーが発生します(6/5現在)。
最後にHTML+JavaScriptを書きます。 meta 要素にクライアントIDを書くのを忘れないでください。
HELLO COGNITO    function onSignIn(googleUser) { console.log("サインインが成功したか、または既にサインインしていました"); const profile = googleUser.getBasicProfile(); // profileにはユーザー情報が入っている。 getName() で名前が取れたりする // Googleでログインできたら、トークンをAWSに渡して認証を行う AWS.config.region = "ap-northeast-1"; AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: 'ap-northeast-1:ffffffff-ffff-ffff-ffff-ffffffffffff', // IdentityプールID Logins: {'accounts.google.com': googleUser.getAuthResponse().id_token} }); // 認証できたら呼び出される AWS.config.credentials.get(function() { const s3 = new AWS.S3(); const url = s3.getSignedUrl('getObject', {Bucket: "YOUR-BUCKET-NAME", Key: "PATH/TO/YOUR/FILE/neko.jpg"}); console.log(url); }); } function signOut() { var auth2 = gapi.auth2.getAuthInstance(); auth2.signOut(); }           Sign out
GoogleのAPIキーが丸見えだけど……?
静的ファイルで実現しようとしている以上仕方がありません*3。ただ、今回のような利用用途ですと、誰かがこのAPIキーを悪用しようとしてもそのシナリオが思い付きません。問題ないと判断しました。
CognitoのIDプールは作成は簡単なのですができることはかなり限定的です。ただ、今回のように使いどころがうまくハマれば非常に手軽に(ほぼコードを書かずに)AWSリソースへの権限を与えられます。これはバグやセキュリティホールも発生しづらいということでもあり、上手に活用していけると良いと感じました。
*1:プロジェクトを作り直すしか方法が見つかりませんでした
*2:https://forums.aws.amazon.com/thread.jspa?messageID=527303
*3:以前AndroidのTwitterクライアントからConsumer Keyを取り出す競技が流行したことがありましたが、本質的にはこれと同じです
nurenezumi 2018-06-19 10:36
日本語は1文字何バイト?
こんにちは、wakです。秋ですね。寒いですね。
さて、今日もどこかから「英語は1文字1バイト、日本語は2バイト」といった雑な話が耳に入ってきて、「UTF-8で日本語はだいたい1文字3バイト!」と抗議していたのですが、エンジニアとして「だいたい」という言葉を使うのもまた雑な話です。どんな例外があるのかをまとめておくことにしました。
1匹あたり数兆個の細胞からなる猫
Unicodeでは世界中全ての文字に個別のコードを振っています(これをコードポイントと呼びます)。アルファベットでもひらがな・漢字でも、絵文字でもヒエログリフでも全部です。このコードポイントは通常16進数で表し、 U+FFFF の形式で書きます。たとえば「A」なら 0x41 なので U+0041*1、「あ」なら U+3042 です。JavaScriptでは "\u0041", "\u3042" などと書けば直接文字リテラルを書いたのと同じことになります。
let hoge = "\u3042"; // let hoge = "あ"; と書くのと同じ
UTF-8の文字エンコーディング
Unicode以前の文字コード、SJIS、EUCなどでは文字コードと文字エンコーディング(=バイト列として表現する方法)とが同一でした。たとえばSJISで「ソ」の文字コードは 0x835C ですので、ファイルに 0x83 0x5C と書けばSJISで「ソ」と書いたことになります*2。
UTF-8では、この文字エンコーディング方法にちょっと面倒な方法を採用しています。つまり、コードポイントの範囲によってバイト数が変わるのです。
1バイト: U+0000 ~ U+007F (ASCII文字。例: 「A」)
2バイト: U+0080 ~ U+07FF (主にギリシャ文字、アラビア文字など。例: 「¶」「Ψ」)
3バイト: U+0800 ~ U+FFFF (日常的に使うほとんどの文字はここ)
4バイト: U+10000 ~ U+1FFFFF (その他)
5バイト: U+200000 ~ U+3FFFFFF (未使用)
6バイト: U+4000000 ~ U+7FFFFFFF (未使用)
U+0000-U+FFFF までの文字を基本多言語面(BMP)と呼びますが、このBMPに入っていない文字は全てUTF-8で4バイトになります*3。また、ちょっと盲点になることもあるのですが、上に示したようにギリシャ文字などは2バイトになります。
これを踏まえた上で、さらに2つのUnicodeの仕様を見ていきましょう。
「ワタナベ」さんの「邊」(U+908A)には細かいバリエーションがあることはご存知でしょう。たとえば二点しんにょうが一点しんにょうになった「邊󠄄」(U+908A U+E0104)、右上の「自」が「白」になった「邊󠄆」(U+908A U+E0106)などです(環境によっては全く同じように表示されるため、手持ちのAndroidで表示したスクリーンショットを貼っておきます)。
これら異体字の数は数十種類にもおよび、別々のコードポイントを振ることは現実的ではありません。そこで、まず基底文字(基本となる文字)「邊󠄆」(U+908A)を書き、続けて見えない制御文字をもう1文字書くと字体が変わるという仕組みが作られました。これが異体字セレクタです。
つまり、「邊󠄄」(U+908A U+E0104)は1文字に見えますが実際は2文字分で、しかも2文字目はBMPからはみ出していて4バイトになるので、計7バイトになります。原理的には1文字8バイトまで行きます。
Unicodeには、単独では使われず、他の文字とくっつけるための結合文字があります。たとえばひらがなの「か」(U+304B)に結合文字の半濁点(U+309A)を繋げると「か゚」(U+304B U+309A)という見慣れない文字になります。これは1文字ですが、実体としては2文字なので、こちらもやはりUTF-8で6バイトになります。
しかも結合文字には「何文字まで」という限度がありません。「か゚」の後ろに結合文字の「○」(U+20DD)を続けて書くと、1文字9バイトのこんな文字になります(今度はWindowsのWordpadで表示した画面キャプチャを示します)。
こうなってくると「1文字は何バイトか」という議論自体がナンセンスになってきますね。
泥沼に踏み込むことになるのでここでは触れませんが、絵文字ではこの異体字セレクタと結合文字がふんだんに使われています。絵文字をパーツごとに組み立てていくような仕組みが採られているため、「1文字」で何バイトになるか見当も付きません。この記事が綺麗にまとまっていました。
というわけで、UTF-8ではどのような文字が3バイト以外になるかをまとめます。
いわゆる半角英数字と記号は1バイトです。これはいいでしょう。
ギリシャ文字、アラビア文字など
Wikipediaに一覧がありました。この U+0080 ~ U+07FF の間の文字は2バイトになります。
第3・第4水準漢字の一部
JIS X 0213に含まれる第3・第4水準漢字の大半はBMP外に収録されています。具体的には「𠀋」(U+2000B) はBMPに入らない文字の一つで、UTF-8では4バイトになります。
「邊󠄄」(U+908A U+E0104)、「邊󠄆」(U+908A U+E0106)などの異体字です。1文字に見えますが実体は2文字なので最大8バイトになります。
いくらでも文字がくっつくため、1文字何バイトになるか分かりません。「1文字」扱いすべきかどうかは要件によります。
これですっきりしました。「日本語のほとんどはUTF-8で3バイトになる。ただし第3・第4水準漢字の大半は4バイト。記号・結合文字は最低3バイト。あとギリシャ文字とかは2バイトだよ」と言えばいいのですね。分かっているつもりのことでもきちんと調べると気持ちが良いものです。それでは。
*1:ASCIIコードと同じ
*2:この2バイト目は偶然にもSJISの「\」(0x5C)と一致しているため、様々な問題を引き起こしていました。興味のある人は「ダメ文字」で検索しましょう
*3:5バイト・6バイトになる領域にはまだ文字が定義されていません
nurenezumi 2017-11-13 10:25
Fusion Tablesを使ってGoogleの環境だけでデータ分析する
Google GoogleDevelopers
サーバーを用意せずに、お金もかけずにさくっとデータ分析をする。データベース、分析実行、結果出力まで、全てGoogleのサービスだけを使って実現する方法を紹介します。
G Suiteで使うアプリケーション
分析対象のデータをFusion Tablesに格納する
Google Apps Scriptで分析処理を書く
スプレッドシートに出力する
G Suiteユーザー
データ分析をするために、例えばBIツールを入れるのはコストがかかりますよね。そこまでしたくない場合、自前で管理画面に作ったり。それもエンジニアが開発してリリースして…というので柔軟性とスピードに欠ける。とはいえ、Excelでやるにはデータ量が多すぎてできない。今回のFusion Tablesを利用した方法は手軽にやりたい時にピッタリです。
環境構築がいらない、コストがかからない、外部からアクセスできてブラウザだけで全て構築できる、権限設定もGoogleアカウントでできる、かなりメリットあります。特に出張や外出の多い私には最適だったりします。ただし、もちろんダメなところもたくさんあるので、それも最後に紹介しておきます。
G Suiteで使うアプリケーション
Fusion Tables:分析対象のデータを格納する簡易データベース
Google Apps Script:分析処理を書く
スプレッドシート:結果出力用
ドライブ:テーブルやスクリプトを格納するスペース
分析対象のデータをFusion Tablesに格納する
まずはこの辺を読んでFusion Tablesの基礎を。
分析対象のデータを突っ込みましょう。
本当はここでSQL書いたりView作ったりできれば良いんですけど、SQL書けないし、View作るにしてもものすごく単純な条件でしか作れない。ということで、Tableにデータを格納する、くらいしか使えません。
ということで、Google Apps Scriptを使います。
Google Apps Scriptで分析処理を書く
スプレッドシートでコードエディタを開いても良いんですけど、今回はドライブにスクリプトを配置します。分析元のデータや出力先のスプレッドシートが複数でも管理しやすいためです。また、出力用のスプレッドシートも作成しておきます。
Google Apps ScriptでFusion Tablesを操作できるように、リソースを追加します。
またまたこの人のブログに頼っちゃいます。笑 分かりやすい。
これで、スクリプトからデータを取り出すところまでできました。ただ、使えるSQLはかなり限定されていて、例えば、、、
CONCATに当たる構文が無いため、文字列操作がほとんどできない。
CASEが使えないため、条件に応じた抽出ができない。
INSERT INTO ~ SELECTができないため、一時テーブルのようなものは作成できない。
などなど。詳細は下記リファレンス参照。
FAQ: Google Fusion Tables - Fusion Tables Help
なので、SQLで色々やって効率よく処理ができないのがけっこう辛いです。データを全部取得して、スクリプトの中でぐるぐる回して処理することになります。
スプレッドシートに出力する
下記ブログの後半部分参照。
(というか、これFusion Tables作成のところから一通り書かれてるからこのブログ見たら一発かもしれないけど…笑)
Google Apps Scriptでは、ブレークポイントを置いてデバッグもできます。
こんな風に変数の中身チェックしたり、
ステップイン、ステップオーバー、ステップアウトも。
実行時エラーはこんな感じで。
SQLエラーも一応出ます。分かりづらいけど…
データベースから大量データを取得できない
一度に大量データを取得しようとすると実行時エラーになります。そうなるとデータを複数回に分けて取得しなければなりません。ちゃんとサイズは測っていませんが、1レコード150byteくらいのデータ約15万件は2分割でもダメで、3分割しました。
Google Apps Scriptの処理時間は6分まで
なんか無理矢理やってる人もいますが、強引すぎる…笑
kido0617.github.io
処理時間が短くなるよう、普通に処理を分けましょう。
一度に大量のデータを出力しようとすると動作が不安定になるらしい
分析結果や、分析途中のデータをFusion Tablesのテーブルに出力することもできますが、これはお勧めできません。一度に大量のデータを出力しようとすると動作が不安定になるらしいので。
この人、よく試してる~
リファレンスにあるとおり、Fusion Tablesには以下の制限があります。
※一応Google Apps Script関連の制限はここで。
Quotas for Google Services  |  Apps Script  |  Google Developers
こんな感じでデメリットも多く感じますが、メリットの方が大きいので、けっこう活躍できるんじゃないかなーと思います。あとは、Fusion Tablesの今後には期待したいところ。
jabe20 2017-11-09 14:32
Node.jsからmultipart/form-dataでデータをPOSTする
Slack Lambda Node.js
お久しぶりです。最近Node.jsばかり書いているwakです。
さて、最近こういうことがありました。
Slackにスニペットをアップロードするするスクリプトを書きたい
Slack botのトークンをスクリプトには埋め込みたくない
そうだ! Lambdaの中にトークンを書くことにしよう!
というわけで、こういう流れになりました。
スクリプトはLambdaにパラメーターを渡して実行する
LambdaはSlackのエンドポイントをHTTP POSTで叩いてスニペットをアップロードする
つまり、Node.jsからPOSTでファイルアップロードを行いたいのですが、ちょっと探した範囲では 素の https.request でこれを行っているサンプルが見つかりませんでした。各種ライブラリが何をしているか分からないのも気持ちが悪いので、自分で書いたコードを残しておきます。
猫パンチを送信しようとしている猫
export token="xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx" export channel="channel_name" export filename="filename" curl -F file=@test.zip -F channels=$channel -F token=$token https://slack.com/api/files.upload -F title=$filename
と同じデータを送信したい。内容はバイナリかもしれない。
コードは次の通りです。
"use strict" const https = require("https"); let binaryArray = [0x00, 0x40, 0x01, 0x41, 0x02, 0x42, 0x03, 0x43]; // ファイルの内容のバイト配列 let buffer = Buffer.from(binaryArray); // バイト配列をそのままBufferに let content = buffer.toString("ascii"); // 1バイトずつstringへ変換 let options = { host: "slack.com", port: 443, path: "/api/files.upload", method: "POST", headers: { "Content-Type": "multipart/form-data; boundary=------------------------deadbeefdeadbeef" } }; let req = https.request(options, res => { res.setEncoding("utf8"); res.on("data", (chunk) => { // レスポンスを受け取って何か処理をしたいならここでやる }); }); req.on("error", (e) => { console.error(e.message); }); req.write(`--------------------------deadbeefdeadbeef\r Content-Disposition: form-data; name="file"; filename="filename.dat"\r Content-Type: application/octet-stream\r \r ${content}\r --------------------------deadbeefdeadbeef\r Content-Disposition: form-data; name="channels"\r \r channel_name\r --------------------------deadbeefdeadbeef\r Content-Disposition: form-data; name="token"\r \r xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx\r --------------------------deadbeefdeadbeef\r Content-Disposition: form-data; name="title"\r \r filename\r --------------------------deadbeefdeadbeef--\r `); req.end();
ここではあえてパラメーターの名前や値を直接ベタ書きしていますが、動的にやるのも別に難しくないでしょう。簡単ですね。
テンプレートリテラルの改行コードはプラットフォームによらずLF(\n)に統一される
multipartでデータを送信する際に使用する改行コードはCRLF(\r\n)とされている
という違いから、面倒だとは思いながらも各行の末尾に \r を書いています*1。 replace(/\n/g, "\r\n") などとやって一括置換をかけると content の中身まで置換される可能性があるのでやめましょう。
curl -F file=@test.zip -F channels=$channel -F token=$token https://slack.com/api/files.upload -F title=$filename
こんなPOSTを送信すると、サーバーには以下に示すようなデータが送信されます。
重要な値として Content-Type があります(というか重要なのはこれだけです)。
Content-Type: multipart/form-data; boundary=------------------------deadbeefdeadbeef
これは、ボディ部にmultipartで複数のデータ(ファイルの内容、その他のパラメーター)が送信されること、そしてそれぞれの区切り目(境界区切子)が何かを示しています(RFC 2046)。 boundary の書式にはややこしい制約があるようなのですが、ハイフンをたくさん並べた後ろにランダムな英数字*2を書けば間違いはありません。
前半はファイル名とその内容、後半はその他のパラメーターです。それぞれの値は「ハイフン2個 + boundary の値 + CRLF」で区切ることとされているので、よく見ると boundary の値とはハイフンの数が違います。また、一番最後にはハイフンを2個付けます。
--------------------------deadbeefdeadbeef Content-Disposition: form-data; name="file"; filename="test.zip" Content-Type: application/octet-stream   --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="channels" channel_name --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="token" xoxb-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx --------------------------deadbeefdeadbeef Content-Disposition: form-data; name="title" filename --------------------------deadbeefdeadbeef--
テキストファイルの場合は text/plain にできます。文字エンコーディングはコード中で指定している通り(この場合はUTF-8)です。
--------------------------deadbeefdeadbeef Content-Disposition: form-data; name="file"; filename="test.txt" Content-Type: text/plain テキストデータ
Lambdaの環境には curl コマンドも入っているので、ファイルを /tmp あたりに作ってそのまま実行すれば済んだりします。ただしコンテナは使い回される可能性が高いため、作成したファイルは必ず削除しておきましょう。
*1:実際にはSlackはLFのみでも受け付けてくれましたが
*2:ファイルやパラメーターの内容とかぶらないように
nurenezumi 2017-11-06 15:59
Analytics の内容を Slack でグラフ表示
Google Slack
こんにちは、久しぶりに和朗です。
サービススタート時など、Google アナリティクスの情報を見ていると面白い!!とは思いつつも、他の仕事をしているとなかなか毎日の変化に気づけません。 アナリティクスの情報を定期的にかつ視覚的に見る方法はないものかと考えました。 そこで、毎日使っているSlackとAnalyticsの間にspreadsheetsとapps scriptをいれてグラフ表示を実現しました。
Google Analytics の情報を取り込む
GAの情報をスプレッドに取込み
日毎に最新情報を取り込むようにスケジュール
グラフをSlackへポストする
Slackにアクセスするためのトークンを用意
スプレッドのグラフをファイルデータにする
日毎に最新情報をポストするようにスケジュール
Google Analytics の情報を取り込む
スプレッドからGoogle Analyticsのアドオンを利用できるように設定します。
GAの情報をスプレッドに取込み
Create new reports を選択
ログインアカウントでGAのデータが参照できる状態になっている必要があります。 参照したいGAの情報がなければ、共有してもらいましょう!
レポート取得のための設定情報シートが作成されます。 ※複数サイトの情報を横並びに設定できます。 上記の設定は、過去2週間分のメトリクスを日ごとに取得してくるようになっています。
日毎に最新情報を取り込むようにスケジュール
Schedule reports を選択
毎日4-5時に実行 これで常に最新の値が見れるようになります。
これは簡単ですね!データ探索を使うと面白いグラフが見れるかもしれませんw
グラフをSlackへポストする
Slackにアクセスするためのトークンを用意
スプレッドのグラフをファイルデータにする
スプレッドの「ツール>スクリプトエディタ」を利用してグラフをファイルデータにします。
var sheet = book.getSheetByName('[シート名]'); var chartImage = sheet.getCharts()[0] .getBlob().getAs('image/png') .setName("chart.png");
var filesUpload = 'https://slack.com/api/files.upload'; var token = '[トークン]'; var payload = { 'token' : token, 'channels': channel, 'file':chart, 'filename': name, 'initial_comment':url }; var params = { 'method' : 'post', 'payload' : payload }; var response = UrlFetchApp.fetch(filesUpload, params);
日毎に最新情報をポストするようにスケジュール
今回はじめてSlackへの画像送信を使ってみました。画像を都度作るのが大変な場合もあるかもしれませんが、視覚的に判断できる色や形で情報を共有できるということはメリットだと感じました。
http://web-tan.forum.impressrd.jp/e/2015/12/08/21570web-tan.forum.impressrd.jp