ITお絵かき修行

3歩歩いても忘れないために

CloudWatch Metricsのグラフ画像を取得し、Lambdaより定期的にSlack通知させたい

まえがき

この手の話は、(AWSの場合は)「CloudWatch Alarm+ CloudWatch Metrics + SNS + Chatbot (+ Slack)」や「CloudWatch Alarm+ CloudWatch Metrics + SNS + DataDog(+ Slack)」という構成を第一に検討するべきだと思います。

ただし下記のケースなど、マネージドサービス利用ではなく何らかの実装で対応せざるを得ない場合があると思います。本稿ではそういったニッチな場合のお話を取り扱っている次第です。

1.CloudWatch Alarmは1つのメトリクスしか選択できない
→1つのメトリクスしか選択できないため、複数Metrics要素のグラフを作ることができない
→APIGatewayの4XX5XXエラー、Lambdaのエラー率とエラー件数の相関、Glueにおける各Executorメモリ使用量の推移などは、複数Metricsを並べることでグラフとして有意になる場合があります。

2.定期的にMetricsの状態をSlack通知したい場合、CloudWatch Alarmだと定期実行できない
→これに関しては色々逃げ道がありそうですが、基本的にはCloudWatch Alermはイベント発火なので定期実行はできないです(※投稿日時点では)。

事前準備

・SlackAppにて投稿用のSlackアプリを作成&インストール済。
→IncomingWebhookでも可能です。リクエストの形式を変更すれば大丈夫です。ただSlackAPI的に画像をそのまま渡せないので、S3に置く→S3のURLをattachments属性のimage_urlに設定する、という1クッション置いた方式となります。
・requestsモジュールを使ってます。
→urllibで頑張ろうとしましたが、つらみが深かったのでやめました。ゆるして

構成

CloudWatch Events(Cron) → Lambda(Slack通知) ←(※boto3経由)→  CloudWatch Metrics

実装

・Lambda環境変数は下記。
url・・・"https://slack.com/api/files.upload"
oauth_token・・・Slackアプリに対応するOAuthトーク
channel・・・SlackチャンネルのURLの末尾のハッシュ
・本LambdaをCloudWatch Eventsより定期実行する。

ソース(Python3.8)
import os
import sys
import json
import boto3
import requests

s3_rw = json.dumps({
    "view": "timeSeries",
    "stacked": True,
    "metrics": [
        [ "Glue", "glue.ALL.s3.filesystem.read_bytes", "Type", "gauge", "JobRunId", "ALL", "JobName", "TestJob", { "label": "S3 Bytes Read" } ],
        [ ".", "glue.ALL.s3.filesystem.write_bytes", ".", ".", ".", ".", ".", ".", { "label": "S3 Bytes Written" } ]
    ],
    "title": "[Glue][TestJob][s3 read/write][SUM][1 minute]",
    "stat": "Sum",
    "period": 60,
    "width": 1600,
    "height": 600,
    "start": "-P1D",
    "end": "P0D",
    "timezone": "+0900"
})

cloud_watch = boto3.client('cloudwatch', region_name='ap-northeast-1')

# ハンドラ
def lambda_handler(event, context):

    # 処理対象たち
    cw_map = {
        "Glue S3書き込み/読み込み状況【1分毎・合計】": s3_rw,
        }

    for cw_title in cw_map:
        # CloudWatchからウィジェット画像取得
        response_cw = cloud_watch.get_metric_widget_image(MetricWidget = cw_map[cw_title])
        image = {'file': response_cw['MetricWidgetImage']}

        # Slack通知
        url = os.environ['url']

        params={
            "token": os.environ['oauth_token'],
            "channels": os.environ['channel'],
            "title": cw_title,
            "filetype": "png"
        }
        req = requests.post(url, params=params, files=image)
        try:
            req.raise_for_status()
        except requests.RequestException as e:
            print(e)
ハマりポイント

・CloudWatch MetricsのイメージAPIを出力すると「true/false」で出力されるので、「True/False」へ変更する。
・イメージAPIのtitle属性に全角文字を入れると文字化けするので注意しましょう(一敗)。
・req = requests.post(url, params=params, files=image) のfiles属性に(名前的に)複数画像が添付できそうですが、できなかったです。複数画像アップロードできた方いらっしゃれば教えていただきたいですm(_ _)m
・複数回SlackへURLを投げるので、タイムアウト値は30秒程度にしたほうが良いです(※対象のCloudWatch Metricsの数によりますが)

おわりに(※ポエム)

監視アラームを設定する際にはCloudWatchにせよDataDogにせよ、基本的には何らかの閾値を設定することが求められます。
既存サービスの場合はCloudWatch anomaly detectionなどのサービスを利用すれば大体事足りますが、新規サービス案件(再構築などの案件も含む)の場合は、「仮決めした値でしばらく運用して、時が来たら変える」という感じになりがちです(※個人の感想です)。

この場合問題となるのは「想定外事象に気づけない」「正常に動作している状態が知りたいが毎日確認しにいくのは面倒(≒なんとなく大丈夫だろうという意識で運用してしまう)」という点にあります。
なので本稿のような、適度な期間での正常性確認をお手軽にできる手段があればな~と思った次第です。
(※ダッシュボードってかっこよくても結局見なくなるんで、何かしら通知させたいですよね。。ダッシュボードが来い)
(※Slack Command1クリックからCloudWatch Metrics画像化&通知など夢は膨らみますね!)

本稿が何かの役に立てば幸いです(クラメソ様風)。以上です。

CloudFormationが「REVIEW_IN_PROGRESS」の状態から進まないとき

CodePipelineのパイプライン処理自体をCloudFormation(親CFn)で記述し、パイプラインからCloudFormation(子CFn)を実行するよう記述していたところ、子CFnが「REVIEW_IN_PROGRESS」の状態から進まなくなる事象が発生した。(※CFnスタックのupdateもできない)
ググってもStackOverFlowしか引っかからないのでマイナーな事象なのかも…?備忘録として残しておく。

問題

CodePipelineより実行したCloudFormationスタックのステータスが「REVIEW_IN_PROGRESS」の状態から変わらない。

解決策

親CFnにおける、子CFnに対するActionMode指定が不正。下記いずれかの対応を実施する。
1.「CHANGE_SET_CREATE」を設定したリソース、「CHANGE_SET_EXECUTE」を設定したリソースの2つを記述したテンプレートとする
2.「CREATE_UPDATE」を記述したテンプレートとする

詳細

まずは下記を参照。(非常によくまとまっている…!)
https://stackoverflow.com/questions/46394040/aws-cloud-formation-stuck-in-review-in-progress

自分の場合は、変更セット作成後にCodePipelineによる承認動作(※manual)を入れる可能性があったので、1.を選択した。「CHANGE_SET_CREATE」にて変更セットを作成し、「CHANGE_SET_EXECUTE」にてスタックを作成する流れ。
「REVIEW_IN_PROGRESS」から進まなくなったときは「CHANGE_SET_CREATE」しか指定していなかったためCFn的に承認待ちの状態になっていた模様。。
→元のリソースをコピペして「CHANGE_SET_CREATE」「CHANGE_SET_EXECUTE」の2リソースを順番に実行するよう修正したところうまく動いてくれた。

ServerlessDays Tokyo 2019 参加メモ

ServerlessDays Tokyo 2019の参加メモです。
参加する前はセッションが選べないので微妙とか思ってましたが、実際参加すると普段触れない話(※主にAzure)が色々聞けたので良かったです。昼からの参加となったのが残念でした。。お昼ご飯には間に合いました。おいしかったです!(小並

■ServerlessDays Tokyo 2019
tokyo.serverlessdays.io

f:id:hhhhhskw:20191022182009j:plain

スライドなどは「#ServerlessDays」から大体追えると思います。
twitter.com



以下セッションのメモです。

■All You Need Is JavaScript
・CloudFlareの中の人が日本語で発表されていた。
・TypeScriptはJavaScriptに追いつきつつある。
・CloudFlareはCDNサービスやセキュリティサービスなどを提供する事業者。
Akamai的な企業。
https://ja.wikipedia.org/wiki/Cloudflare


■Zero Scale Abstraction in Knative Serving
・Knative
k8sをサーバレスで動かすためのワークロード
https://cloud.google.com/knative/?hl=ja
・Herokuと似たプラットフォーム
k8sの上に拡張している。
・「k8sクラウド上で抽象化したサービス」
yaml地獄にならない。knative側でコード化可能 。
GoLangで書かれている
・kubectlなどのk8sCLIを利用するのではなく、
 Git上でKnativeの構成管理を実施する。「GitOps」。 
 →「push code, not container」
https://thinkit.co.jp/article/14164
・podに同梱できるコンテナは1つだけ。(sidecarなどはできない)
・ボリュームのアタッチができない
→理念にそぐわない
・podをどのインスタンスで立てるかを指定できない
→理念にそぐわない
・オートスケール未対応。ヘッダ毎の振り分け未対応。
・Cyberで利用検討中。k8sを直接利用するのは敷居が高い。
GCPではサービス提供されているが、AWS上でKnative立てようとしている。
・KnativeのバージョンアップはAWSのGlobal Acceleratorを利用しようとしている。


■ 空調設備向けIoTシステムにおけるクラウドランニングコスト
・コスト削減のお話。AWS DynamoDBの中心に紹介
・30万人が利用するIoTシステム
→サーバレスが必須
https://aws.amazon.com/jp/solutions/case-studies/daikin/
・サーバレス開発は勉強工ストとの闘い
空調機kinesis→DynamoDB
⇒遠隔操作が必要な機器のデータを格納
⇒運転データサイズ(最大で数10kb)が飛んでくる
・1回のAPIコールで数十個の機器データを取得する使い方をする
・Lambdaの処理時間とDynamoDBのDPUがコストの大半を占めるようになった。
・DynamoDBの課金仕様を踏まえ、アイテムを分割した。
⇒書き換える対象のデータ容量を小さくした。
・一括更新するようなクエリがある場合はインデックスを追加して一括更新できるようにした。
⇒処理時間の短縮によるLambda処理時間の短縮
・DynamoDBのキー構造が重要(性能・コスト)

・コストの失敗
インスタンス起動しっぱなし
 →負荷ツールもサーバレスで作る方がよい
⇒ログ多すぎ問題
 →CloudWatch Logsのコストが高い


ISPがサーバレスに手を出した
・OCNの中の人
・PPPoE混みすぎ問題(IPv4)
⇒VirtualConncect(IPv6) /56を提供する。
・v4 over v6 tunnel
⇒お客様側機器に対してIPv6接続を要求する必要がある。
・社内基準と電通法対応
⇒たまたま自社クラウドIPv4対応していなかった。仕方ないのでIPv6で対応
⇒回線数に応じて信頼性を担保する必要がある。(※総務省への報告が発生するらしい)
AWSクラウド)が本当に信頼に足るか、という調査をした。
・Azure CDN
⇒common nameが指定できない(設定が消える??)
AWSへ乗り換えた。DynamoDB GlobalTable利用。
・テスト自動化
⇒ServerlessFramework(ローカルでも動かしたい)
 →プラグインのアップデートが早い
 →細かいところは非対応のところがある(CDN系とかの設定項目など)
⇒gatlingという負荷ツールを使っている
https://qiita.com/ntrv/items/394a38d26e94565db31a
⇒負荷をかけるとユニットの切り替え・追加のタイミングがみられるのでためになった。

・B-Gデプロイの方法で悩んだ(CDNで切り替えた)
・CloudWatchフル活用。
⇒Cloudwatch LogInsightsでエラー分析をやっている。CloudWatchを直接覗くのはまれ。
・標準化も今後やっていきたい
・人間がボトルネックになっている(2人しかいないので…とのこと)


AWS Lake Formation で実現、マイクロサービスのサーバーレスな分散トレーシング
・複雑に絡んだサービスのエラー調査を分散トレーシングで解決する話
・step functionsを利用したログの出力
・トレーシングID
X-Rayは非同期処理に対応できないのでトレースIDの取り回しが必要。
 →APIGW(HTTPヘッダ)⇒SNS(構造化メタデータを設定)⇒SQS(MessageAttribute)
  ⇒STEP Fn(ResultPathを利用)⇒S3(Object metadata)


■Don’t think Serverless Security, think Application Security
イスラエルのスタートアップ企業の方(Nuweba)。全編英語だったので間違っているかも。。
https://www.nuweba.com/
・サーバレスセキュリティ。
https://www.nuweba.com/dont-think-serverless-security-think-application-security

・new cyber security risks
1.attack(trigger, event)
⇒攻撃対象の多様化。例えばAPIGW,Lambda,IoTなど。
2.harder to manage
⇒サーバレスなプラットフォームの場合、攻撃に気づくのが難しい
3.denial of wallet
⇒リソースの過剰消費攻撃
https://www.helpnetsecurity.com/2019/03/29/serverless-challenges/
4.Serverless leads to over-privilleged functions IAM permissions
⇒認可と認証の対象、範囲を適切に管理する必要がある
5.(サーバレスであっても)コードの脆弱性は混入する恐れがある(?)


■Azure でサーバーレス、 Infrastructure as Code どうしてますか?
・IaCの話。
クラウドにおけるリソース管理
⇒ARM(Azure Resource Manager)テンプレート。JSON
VSCodeだとシンタックスが効きます。
・AzureのGUI上で内容確認可能
・Azureだとアップロードしたzipの中のファンクションを指定して実行可能。
⇒デプロイ後にすぐ実行が可能
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-infrastructure-as-code
・リソース名のつけ方に注意(制約がある。24文字以内)
・IaC:CI/CDの活用が捗る、新規参画者にやさしい


■ The hidden cost and technical debt of running huge Serverless service on production
・本番環境で巨大なコンテナワークロードを実行したときの課題
・1TB位のEBSに障害が起きたら、特定のフレームワークしか使えませんなど
・サービスを最新の状態に保つ必要がある
・利用サービスの継続的な見直しが必要。
AWS Simple WorkflowではなくStepFunctionを使うetc
・手動プロビジョニング
・データベースのプロビジョニング
・ベンダーロックイン
⇒ベンダーフリーにすることは難しい
・請求管理を集約することで管理が容易になる
・ベンダーを統一することはコスト面で有利

f:id:hhhhhskw:20191022182714j:plain

f:id:hhhhhskw:20191022194227j:plain

AWS Summit 2019 Tokyo参加メモ - SpotInstanceのより効率的な活用を目指して。Spot Instance Update(L2-08)

AWS Summit 2019 Tokyo(2日目のみ)に参加した。
表題のセッションに参加したので、参加メモを書いておく。
(※本セッションはDevelopersIO様にまとめ記事がなかったので、補完の意味も込めて投稿しています)
classmethod.jp


f:id:hhhhhskw:20190713225455j:plain
AWS Summit Tokyo 2019
aws.amazon.com

【セッション名】SpotInstanceのより効率的な活用を目指して。Spot Instance Update(L2-08)

【概要】

EC2スポットインスタンスの活用法の紹介。
同日開催のDeNAのセッションと被る箇所もあったが、こちらの方がより一般的で体系的な話だった。
【レポート】DeNA の QCT マネジメント IaaS 利用のベストプラクティス #AWSSummit | DevelopersIO

【内容】

・一般的に月々の請求額のうち、仮想サーバ/DB費用が9割程度を占める。主な理由は下記。
1.マネージドサービス利用料が安いから
2.最初のとっかかりとして利用するため

・夜間はサーバを落とす⇒夜間は割と空いているので安く買える
⇒このような運用の実現にはコード化が必須、運用はしんどい。

・昔のスポットインスタンスは価格の変動が大きかったが、今はゆるやかになった。入札方法は下記2通り。
1.「自動入札」を設定する(普通はこっち。AWSが提示する金額より高ければ買える方式。オンデマンド価格より高くなることはない)
2.「最高価格」を設定する(あまり使用されない)

・スポットインスタンスが向いているのは「夜間バッチ」「レンダリング処理」「解析処理」

・スポットインスタンスアドバイザー
⇒費用削減予想、停止頻度の統計がわかる

・(スポットインスタンスは)いうほど停止するものではない。キャパ不足で停止するのは全体の5%未満(95%以上はユーザ操作での停止)。

・停止時はHibarnate(一時停止)も指定可能。
⇒以前は「終了」しかなく全消しだった。

・スポットインスタンスの選択肢は、インスタンスファミリー/サイズ/AZ。
⇒AZについては、東京はa b d。それぞれ構成がちょっとずつ違う。

★現在、スポットインスタンスには様々なオプションが乱立しており、わかりにくくなっている。

・1回限り/永続リクエストというモードがある。
⇒1回 :1回起動したらスポットインスタンス要求/起動はしないモード
⇒永続:何らかの要因でスポットインスタンスが停止しても、スポットインスタンスの入札条件が折り合えばまた起動するモード

・autoscaleと相性がいい
⇒常に2台のインスタンス起動、それ以外はスポットという使い方が可能
 ⇒ただEC2のオートスケールは数分かかる。スパイクにはそもそも向いてない。

・「インスタンスのCPU使用率の平均」をスケーリングの閾値に設定可能
閾値の「幅」は大きめにしておいたほうがよい上限80%-下限40%とか。

・EC2 Spot Fleet ★これがナウい
⇒EC2群をまとめて起動するとき利用する
参考:https://qiita.com/f96q/items/28d3c2dd7ad55bf06747

・EKSとの組み合わせ相性もよい。

・auto scale lifecycle hook(1~48時間の間)
⇒スポットインスタンスの起動/停止が優先されるので注意する。

・スケーリング(静的/動的)
静的(auto healing):設定値以上のインスタンスを維持する
動的(auto scaling):台数が閾値で変動 ←spot instanceで対応


■おまけ
RE:MIXでの池澤御大。
f:id:hhhhhskw:20190713225618j:plain

・広い!品プリ時より商業色がちょっと濃くなった…かも
⇒Datadog、Mackerel(はてな)、New Relicといったコンテナ等の運用監視ツール系企業が個人的にはアツかった。
f:id:hhhhhskw:20190713225751j:plain


・アナウンス『次回のAWS Summit Tokyoは2020年5月12日~14日パシフィコ横浜にて開催です』
⇒次回までにはprofessional資格を取っていたい(希望)

BRM427東京600浜名湖鰻 参加メモ

BRM427東京600浜名湖鰻に参加&完走した。
600kmのブルベは初めてかつ若干風邪気味だったので不安しかなかったが、色々ガバりながらも事故無く時間内に完走することができた。

■走行記録
f:id:hhhhhskw:20190429173442j:plain
※画像のタイムは走行時間。認定時間は37時間30分くらい。
コースは武蔵小杉~小田原~熱海~沼津~清水~御前崎~浜松(湖西)~御前崎~清水~沼津~熱海~小田原~武蔵小杉という感じ。

■2019 BRM427東京600 浜名湖
randonneurs.tokyo


1.準備
(1)天気
・天気予報によると4/27の天気は下記のような雰囲気だった。
「朝~昼あたりは小雨」
「スタート地点~御前崎あたりまでは東風(追い風)」
御前崎から浜松までは西風(向かい風)」
そこで御前崎まではなるはやで走って、その後は耐え忍ぶという十全な走行プランを立てた。
・4/28は全体的に晴れかつ追い風っぽい雰囲気だったので、高度の柔軟性を維持しつつ臨機応変に対応することにした。

(2)装備
雨装備として全身モンベルのド定番装備を揃えた。また足元はVELOTOZEのシューズカバーで防水対策した。

(3)宿
浜松~御前崎あたり(300km~350kmあたり)に宿を取るのが賢い選択だと思う。ただ自分は焼津のくれたけイン(410km地点)に宿を取った。理由は下記の通り。
・朝が弱すぎるので、多少寝坊してもPC4のクローズ時間(4/28 10:24)に間に合うところに宿を取りたかった為。
御前崎あたりのホテル予約が満杯で取れなかった為(ネットで予約した、と思ったら「お気に入りに登録」しただけだった。ガバ案件)。


2.当日
(1)スタート ~ PC1:デイリーヤマザキ平塚北豊田店
f:id:hhhhhskw:20190429163110j:plain

f:id:hhhhhskw:20190429163138j:plain

よく降っていた。普段のブルベに比べて明らかに人数が少なかったので、ブリーフィング・車検がサクサク終わった。
この時点での雨はそれほどひどくなく、ちょっとした非日常感を楽しむ余裕があった。


(2)PC1 ~ 通過チェック:ファミリーマート函南平井店
・熱海の手前あたりから豪雨になり余裕がなくなった。1時間当たりの降水量は10mm超えていたらしい。明らかに雨具を着ていなかった方々がいて戦慄した。
・通過チェック後、PC2までは小雨~豪雨の繰り返しで雨が止むことはなかった。


(3)通過チェック ~ PC2:セブンイレブン清水折戸2丁目店
・PC2付近では雨が雷雨に変わっていた。結構近くに雷が落ちていたので普通にこわかった。この辺りで行き道の焼津でDNFしようか迷い始めていた。。
新富士?あたりの沿道で応援してくださっていた方がいた。割と精神的にもきていたのでとても励みになりました。ありがとうございました。


(4)PC2 ~ 通過チェック:セブンイレブン御前崎港店
この辺りから雨がやみ、日差しが差すようになったので少し余裕が出てきた。また追い風だったのでペースを上げることができた。

f:id:hhhhhskw:20190429171537j:plain
静岡県最南端の岬

f:id:hhhhhskw:20190429171731j:plain
なんかいい。太平洋を感じる

f:id:hhhhhskw:20190429171550j:plain
御前崎灯台


(5)通過チェック ~ PC3:ファミリーマート鷲津駅前店
御前崎から10kmほど進んだあたりから向かい風(強風)となった。PC3までひたすら向かい風の中を進んだ。場所によっては10m/sを超えていたらしい。
・平地で割とペダルを回しても15km/h程度しか速度が出ず、弁天島鷲津駅までの7km程度が異常に長く感じられた。


(6)PC3 ~ 通過チェック:ファミリーマート御前崎
・追い風の中、人気の少ない道を走れたのでだいぷペースを取り戻せた。ただ細かいアップダウンが何度もあったので「このアップダウンいる?」と何度も自問した。
・イートインコーナーが消灯状態で解放されており休憩することができた。ありがとうございました。


(7)通過チェック ~ くれたけイン焼津駅
・追い風は弱まったが郊外の道だったので淡々と走ることができ、結局宿には3時頃に着くことができた。
・去年の400kmブルベ(ランドネきたかん)では走行後に膝が痛くなり、生まれたて小鹿状態になったが、今回は多少痛む程度で済ませることができた。


(8)くれたけイン焼津駅前 ~ PC4:セブンイレブン静岡西大谷店
3度寝をキメて8時半ごろに宿を出発。6:30から朝食バイキング開始だったが残念ながらパスした、というより朝食があることを忘れていた(ガバ案件)。

f:id:hhhhhskw:20190429174359j:plain
大崩海岸石部海上橋からのフッジサーン いいカメラが欲しくなる…


(9)PC4 ~ 通過チェック:ファミリーマート函南丹那店
・PC4には9:24に到着できた。この日は晴れ&多少追い風という天気だったので絶好のサイクリング日和だった。

f:id:hhhhhskw:20190429180626j:plain
興津のたい焼きに寄り道

f:id:hhhhhskw:20190429180644j:plain
このカリカリがいい

f:id:hhhhhskw:20190429180712j:plain
いらっしゃいませ

f:id:hhhhhskw:20190429180818j:plain
(登れ)ないです サッタ峠の現物は壁のようだった。


(10)通過チェック ~ PC5:セブンイレブン平塚北豊田店
熱海の人の混みっぷりが尋常じゃなかった。歩道が狭いから歩行者が車道に出てしまうので、十分に注意して走る必要があった。

f:id:hhhhhskw:20190429184650j:plain
早川口あたりではお神輿が通過していた。鳴り物はなかったので素朴な雰囲気だった。


(11)PC5 ~ Finish:セブンイレブン川崎下小田中1丁目店
AJ東京での定番ルートを夜に逆に走ることは初めてだったので新鮮だった。2回ミスコースした。中原街道のアップダウンは割と足にくるけれども豪快に下る場面もあるので結構楽しかった。


3.総括など
・コース自体は平坦基調だが、過酷なブルベだった。初日の雨天時にパンクしていたら確実にDNFしていた自信がある。
・左足だけVELOTOZEが破れて水没した。右足は割と無事だった。
⇒VELOTOZEの厚みの分、靴とクランクアームの距離が短くなり、擦れて破れてしまったと思われる。もともと左足とクランクアームとの距離が近い感覚はあったが、クリートの調整を先延ばしにしていた。雨具を買ったら試走しよう。

f:id:hhhhhskw:20190429185347j:plain

Strava V3 APIの認可方式(OAuth2)を試す

www.strava.com

Stravaでは開発者用APISDKが公開されており、自分の記録などをAPI越しに覗くことができる。ただ2018/10/15よりAPIの認可方式が変更となった。
https://developers.strava.com/docs/changelog/

今までは有効期限なしのアクセストークンを用いた認可方式だったが、今後は短命なアクセストークンを使用したOAuth2ベースの認可方式へ変更となる。(2019年10月で完全移行の模様。移行ガイドも提供されている)

Prior to this date, anytime an athlete granted access to an application, that app received an access token with no expiration date (also called “forever tokens”). Starting on October 15, 2018, the OAuth endpoints should be used to obtain short-lived access tokens and refresh tokens instead. While the forever tokens will continue to work through October 15, 2019, all applications should be migrated to the new refresh token pattern as soon as possible.

https://developers.strava.com/docs/oauth-updates/

OAuth2の勉強がてら、REST形式のAPIを使って「自分のアクティビティデータ」を閲覧するところまでやってみた。

■目標
1.自分のアスリートデータ(≒プロフィール情報)を取得
2.自分のアクティビティデータを取得

■前提
1.OAuth2とは
OAuth 2.0 の仕組みと認証方法 | murashun.jp
OAuth 2.0 全フローの図解と動画 - Qiita

2.Strava側のAPI利用設定(必須)
https://www.strava.com/settings/api
f:id:hhhhhskw:20181106124019j:plain

●現在の認証回り設定(参考)
f:id:hhhhhskw:20181106011843j:plain
f:id:hhhhhskw:20181106012350j:plain

■仕様
http://developers.strava.com/docs/authentication/

■使用するもの
・ブラウザ
curl (※Windows10ではデフォルトで使えたのでびっくりした)

■実際にやってみた
1.OAuth2認証開始
下記URLをブラウザより実行する。

https://www.strava.com/oauth/authorize?client_id=[[自分のクライアントID]]&response_type=code&redirect_uri=http://localhost.com/&approval_prompt=auto&scope=activity:read_all

各属性の説明は下記参照。上記例のscope属性では「activity:read_all」としている。
https://developers.strava.com/docs/oauth-updates/

2.Stravaにログインしていない場合はログインしたうえで、事前に設定したアプリケーションに対して認証を求められるので、「許可する」。
f:id:hhhhhskw:20181106011730j:plain

3.許可すると、白紙のページ(http://localhost.com)にリダイレクトされる。レスポンスURLのパラメータより、認可コード(code=aabef407793b0f2e3167c741fd1034a94c0057fd)を取得する。

http://localhost.com?state=&code=aabef407793b0f2e3167c741fd1034a94c0057fd&scope=read,activity:read_all

4.3.の認可コードを使用し、認可サーバにアクセストークンを発行してもらう。

curl -L -X POST https://www.strava.com/oauth/token?client_id=[[自分のクライアントID]]-d client_secret=[[自分のクライアントシート]] -d code=aabef407793b0f2e3167c741fd1034a94c0057fd -d grant_type=authorization_code

各属性の説明は下記参照。grant_type属性は「authorization_code」。
https://developers.strava.com/docs/oauth-updates/

レスポンス例(JSON)は下記。ここでアクセストークンを取得できる。

{"token_type":"Bearer","expires_at":1541367065,"refresh_token":"1c1ae1800520120854ab133249290465f0808e6b","access_token":"73ae3af1e62a1b8e7409f5095a830badfc7f6136","athlete":{"id":[[自分のID]],"username":"[[名前]]","resource_state":2,"firstname":"[[名]]","lastname":"[[]]" (※中略)}}

5.アクセストークン("access_token":"73ae3af1e62a1b8e7409f5095a830badfc7f6136")を使って、まずはアスリートデータを取得する。

curl -L https://www.strava.com/api/v3/athletes/[[自分のアスリートID]]?access_token=73ae3af1e62a1b8e7409f5095a830badfc7f6136

自分のアスリートIDは「Myプロフィール」ページのURL末尾の数値を参照。

実行結果は下記の通り。

{"id":[[自分のアスリートID]],"username":"[[氏名]]","resource_state":2,"firstname":"[[名]]","lastname":"[[]]" (※中略)}

6.次にアクティビティデータを取得する。取得したいアクティビティのIDは各ページのURL末尾の数値を参照。

curl -L https://www.strava.com/api/v3/activities/[[アクティビティID]]-H "Authorization: Bearer 73ae3af1e62a1b8e7409f5095a830badfc7f6136"

OAuth2のアクセストークンの渡し方は上記の通り「HTTPヘッダ」でも可。推奨は「リクエストパラメータ」だった気がする。
実行すると走行実績やGPS情報などが詰まったJSONデータが返却される。中身を確認するとアクティビティ名や走行距離、経過時間etcが記述されている。JSONのサイズは100KB程度だった。

{"resource_state":3,"athlete":{"id":[[自分のアスリートID]],"resource_state":1},"name":"BRM923富士いち200","distance":199395.0,"moving_time":32319,"elapsed_time":37836,"total_elevation_gain":1912.0,"type":"Ride","workout_type":10,"id":1859941481,"external_id":"garmin_push_3036271068","upload_id":1992936743,"start_date":"2018-09-22T22:28:25Z","start_date_local":"2018-09-23T07:28:25Z","timezone":"(GMT+09:00) Asia/Tokyo","utc_offset":32400.0,"start_latlng":[35.05,138.91],"end_latlng":[35.05,138.91],"location_city":null,"location_state":null,"location_country":"","start_latitude":35.05,"start_longitude":138.91,"achievement_count":7,"kudos_count":0,"comment_count":0,"athlete_count":1,"photo_count":0,"map"{"id":"a1859941481","polyline":"ib|tEqbynYVXEJEBc@CyDoAUOSUWw@GgFMeAJwAPo@UwA?Kx@mIt@aE^eCj@oE]g@iCoAe@uAsAuOo@{Gs@eCmCoGcAsBo@q@mDwCkAkB]iAc@cC[gCa@cCe@eCM_@iAmBwFeIeAeBm@wBg@kCe@_GOmAk@KoBP}GxAgIxAqBR[@eA_Bk@sGq@{GQeCHqCMmA_@k@s@AeBVoBt@kBb@mBXqBFuBCaFYwAJqA~@_@z@sC`Iq@bCGd@n@vFKvCSfA@x@X~DLrCDpCAlCInCQrCWpCy@nGmAhGOb@[n@}EvHgAlB (※中略)

***

7.トークンの有効期限が切れた場合は認可サーバに再度アクセストークンの発行を依頼する。この時最初に発行された「リフレッシュトークン」を提示する。grant_typeは「refresh_token」。

curl -L -X POST https://www.strava.com/oauth/token?client_id=[[自分のアスリートID]]-d client_secret=[[自分のクライアントシート]] -d code=aabef407793b0f2e3167c741fd1034a94c0057
fd -d grant_type=refresh_token -d refresh_token=1c1ae1800520120854ab133249290465f0808e6b

返却値からアクセストークンを再度取得する。

{"token_type":"Bearer","access_token":"edf4dd1136f5dc2b655d275da1ccc87f735a7e9e","expires_at":1541513430,"refresh_token":"1c1ae1800520120854ab133249290465f0808e6b"}


・その他使い方は公式ドキュメントを参照。
Strava Developers


■感想
・OAuth2認証プロセス自体は割と直感的で、Stravaから提供されているドキュメントも必要十分だと感じた。
・走行積算距離の計算など、目的別APIみたいな感じでも提供してほしいなーと思った。(まだ調べられてないだけかもですが)
・次はSDKを触ってみる。

■参考
Get a Strava API access token with write permission | Yi Zeng’s Blog
Getting Started With The Strava API: A Tutorial – Tilde Ann Thurium – Medium
↑2つ目の認可方法は以前のものなのでNG

アレクサにお手をしてもらいたかった

AmazonEchoPlusを買ったので。
内容自体は公式のHelloworldレベルなので内容は全く無い。出オチ。

■ソース

'use strict';
const Alexa = require('alexa-sdk');

const APP_ID = '(※環境変数か直書き)';

const HELP_MESSAGE = 'すみません、よく聞き取れませんでした。もう一度お願いします。';
const HELP_REPROMPT = '何か御用ですか?';
const STOP_MESSAGE = 'お疲れさまでした。';

const phrases = [
    'それがあなたたちのやりかたですか。',
    '夜道には気を付けてくださいね。',
    'すみません、よく聞き取れませんでした。もう一度お願いします。ははっ。',
];

const handlers = {
    'LaunchRequest': function () {
        this.emit('OteIntent');
    },
    'OteIntent': function () {
        const sources = phrases;
        const sIndex = Math.floor(Math.random() * sources.length);
        const randomFact = sources[sIndex];
        const speechOutput = randomFact;

/*        this.response.cardRenderer(SKILL_NAME, randomFact); */
        this.response.speak(speechOutput);
        this.emit(':responseReady');
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = HELP_MESSAGE;
        const reprompt = HELP_REPROMPT;

        this.response.speak(speechOutput).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.APP_ID = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

■テスト結果
動作確認はコンソール上で実施できる。リクエスト内のJSONをいじってテストすることも可能。
f:id:hhhhhskw:20181008234636j:plain

■EchoPlus/アレクサ使った所感とか(1週間程度)
BlueToothスピーカーとしてはまぁまぁ。筐体が少し大きい(感覚的には900mlペットボトル)ので低音も一応出る。
→接続先デバイスの名前とかを読み上げてくれるのでセットアップ/繋ぎ変えが簡単で良かった。

・自動生成された音声が不自然な時がたまにあるので、SSMLタグでの声色調整スキルが必要だと感じた。

・アレクサに命令するには「アレクサ、〇〇を開いて××をして」のようにユーザがキーワードを覚えて組み合わせる必要があるのでちょっと億劫。
→用事次第だが、やらなくてもいいことであればあまり定着しなさそう。このあたりがVUIを意識するということ?

VUIと例外処理の設計実装、会話(セッション連携)辺りが使えるようになってからが本番だと感じた。。