kakakakakku blog

Weekly Tech Blog: Keep on Learning!

目標達成をもっと支援したい /「コーチングの基本」を読んだ

「コーチングの基本」を読んだ.今年は "教える" という行動をもっと深く知るという個人的なテーマがあって,2月からプログラミング学習者のパーソナルメンターという仕事もしている.また,社内でメンバーの育成を担当する機会もある.そういう背景から,まずは「コーチング」の本質を知りたいと考えていた.経験的に「こうするべきだよなー」と知っていた部分も多かったけど,それが専門的な用語で表現できることを知れたのが1番の収穫だった.用語で表現できるというのは本当に重要なことだと思う.

コーチングの基本

コーチングの基本

3原則

コーチングには「3原則」と呼ばれるマインドがある.

  • 双方向
  • 継続性
  • 個別対応

個人的にどれも意識していたことではあったが,「双方向」のところで出てきた「オートクライン」という用語を知れて良かった.オートクラインとは「アウトプットすることによって,自分自身で気付くこと」と表現することができて,例えば,どうしても困ったときに誰かに相談したら,相談しながら自分で解決できてしまった,といったような経験がある人も多いのではないかと思う.それこそが「オートクライン」である.また,オートクラインを起こすためには,適切な質問とお互いの信頼関係が必要であると書かれていて,納得感があった.コーチ / メンターの仕事は答えを教えることではなく,自律的に答えに辿り着けるようにサポートすることなので,もっともっと質問力を磨きたいとも思った.

他責と自責

掲げた目標と現在のギャップを考えるときに,そのギャップが発生している原因を他人や環境のせいにしてしまうことを「他責」と言う.

  • 上司が決めてくれないから
  • どうせ言っても聞かないだろうから
  • 私には他にもやる仕事があるから

本書では「他責」の例として,こんな発言が紹介されていた.誰しもよく言ってしまうし,そもそも「愚痴」と言うのは全て「他責」なのではないかと思う.そこで,この「他責」を「自責」に切り替えることで,全ての責任を自分に引き寄せてしまうことができ,目標達成に向けて具体的なアクションが立てられるようになる.本書にも書いてあるが,誰の問題なのかは本質的には重要ではなく,どんな状況でも「自責」に切り替えることで,前向きな行動ができるようになるということだった.僕もメンターとして「今の話を,自分を主語にして説明し直すことができますか?」と積極的に質問を問いかけようと心に決めた.

個人的にも2016年から「愚痴を言わない」というのを意識していて,言いたくなったら具体的なアクションを考えるようにしていた.僕のこの意識は,まさに「他責から自責に」だったんだなと感じた.「愚痴を言わない」という話は年末年始の記事にも書いてある.

kakakakakku.hatenablog.com

聞く(傾聴)

聞くポイントとして,大きく5点紹介されていた.

  • 「聞く」ことに集中する
  • 相手の話の先読みや,結論の先取りをせず,最後まで聞く
  • 相手のノンバーバル(非言語)な情報を受け取る
  • 「聞いている」というサインを送る
  • 沈黙を共有する

2番目に紹介されていた「話を最後まで聞く」というのが,個人的には苦手意識がある.話の先読みをすることが変に習慣化してしまっているため,ミーティングなどでも「あー,なるほど,言いたいことわかる」みたいに食い込み気味で話を進めてしまうことがある.これは本当に話の腰を折ることにもなるし,意識的に直さないといけないなと改めて感じた.

話を聞きながら常にリアクションをしたり,目線を合わせたり,そういうポイントは常に意識しているため問題ないと思うが,ここが苦手な人は非常に多いのではないかと思う.話していて「聞いてるの?」と聞きたくなる状況があれば,それは聞く姿勢が相手に伝わっていないということだと思う.

「聞く」に関しては前から似たような課題感があり,関連した本も読んだことがある.

kakakakakku.hatenablog.com

チャンクダウンとスライドアウト

質問をするときに,詳細に掘り下げていくことを「チャンクダウン」と呼び,別の答えを引き出すことを「スライドアウト」と呼ぶ.課題を洗い出すようなときは,詳細までは考えず「スライドアウト」で質問し,具体的な目標を決めていくときなどは,「チャンクダウン」で質問する.それぞれに用語があるということを知れてよかった.

アクノレッジメント

アクノレッジメントは,承認する行動のことで「活躍しているのを知っていますよ」や「業務改善の効果が本当に大きいですね」のように変化を明確に伝えることを言う.これは過去に読んだジャイキリ解説本でも紹介されていて,コーチングに限らず,組織マネジメントにおいても,本当に重要なことだと思っているので,日々意識して使うようにしている.

kakakakakku.hatenablog.com

まとめ

「コーチングの基本」はコーチングに限らず,組織マネジメントにも応用できる内容になっていて,良書だと感じた.引き続き,コーチング,メンタリング,組織マネジメントを深く学んでいく.

  • 3原則
  • オートクライン
  • 他責と自責
  • 話を最後まで聞く
  • チャンクダウンとスライドアウト

関連

3月にゲスト出演させてもらった Podcast でも関連した話をしているので是非聞いてみてもらえればと!開発合宿に技術アドバイザーを呼んだ施策は「オートクライン」を目的にしていたし,どのように新卒と「相互メンタリング」を推進したのかという話もしている.

lean-agile.fm

Data Migration Night に登壇してデータ移行の Tips を紹介してきた

今日は Data Migration Night に参加してきた & LT 枠で登壇してきた!

atnd.org

ChatWork がデータマイグレーションに使った技術の話

  • CQRS + Event Sourcing System
  • Aurora にある17億メッセージを HBase にマイグレーションする
  • 基本マイグレーションと差分マイグレーションに分類した
  • Aurora の binlog を直接取得して差分マイグレーションに使った
  • メトリクスを取得して,マイグレーションの成功をちゃんと確認するようにした
  • Aurora から Spark で HBase に書き込むときのパーティションを試行錯誤した

17億メッセージを管理する Aurora って,どんなインスタンスタイプを使ってるんだろう?質疑応答では Room ID でパーティショニングをしていると言っていた.あと資料に “巨大部屋” という表現があって笑った.Spark も HBase も使ったことがなくて,詳細はわからなかったけど,分散システムの特性を理解した上でマイグレーションを考えたという話は参考になった.

(資料公開待ち)

オンプレから AWS 移行で変えた3つの意識

  • コスト意識
    • RI をインスタンスの在庫確保として使う
  • AWS オペレーション意識
    • インフラ専任エンジニアの撤廃
    • IAM で細かく制御して,オペミスが起きないようにした
  • システムダウン意識
    • フルマネージドを使うようにした
    • サーバダウンを許容し,復旧できることを重要視した

インフラ専任を撤廃していくのは施策としては非常に興味深かった.もっと事例を聞いてみたかった.また RDS で適用必須なメンテナンスが入るとサービスダウンに繋がるので,前段で書き込みを止めて対応しているとのこと.メンテナンスを選択できれば良いんだろうけど,無停止前提だと厳しいよなぁ.

www.slideshare.net

2000万アカウントの無停止データ移行の裏側

大きな問題無く移行できたのは凄いと思う.個人的にはレプリケーション遅延も気になるし,循環更新も気になる.懇親会で話を聞いたら「今はもう役目を終えた」と言っていて,完全に移行専用のアーキテクチャとのこと.強い!あと open-replicator は全く知らなかったので,調べる.

speakerdeck.com

LT : どのように MySQL on EC2 から Aurora に移行したのか

僕は CyberAgent Developers Blog に投稿した記事の一部 + α を紹介した.Aurora に特化した内容はあまり話さず,データ移行の Tips を中心に話したけど,比較的反応もあって良かった!

developers.cyberagent.co.jp

資料も up した!

speakerdeck.com

LT : AWS のデータ移行ソリューション

  • AWS Direct Connect
  • AWS Snowball
  • AWS Snowmobile
  • などなど

(資料公開待ち)

関連記事

labotech.dmm.com

登壇写真

撮影してもらった!素晴らしい会場だったし雰囲気ある ( ゚д゚)ノシ

f:id:kakku22:20170523002440j:plain

Mackerel で ECS の動的ポートマッピングに対応したタスクのメトリクスを取得する

前回書いた記事に続き,Mackerel を使って ECS のメトリクスを取得する方法を検証していて,今回は「パターン2 : コンテナインスタンスに mackerel-agent をインストールする」の検証結果をまとめる.前提としては前回と同じで,ALB の動的ポートマッピングを使う.

  • パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する
  • パターン2 : コンテナインスタンスに mackerel-agent をインストールする

kakakakakku.hatenablog.com

メリデメ

最初に「パターン2」のメリデメを整理しておく.「パターン1」では実現できなかった「タスク特有のメトリクスを取得できる」点が大きなメリットで,今回この「パターン2」を採用することにした.

  • メリット
    • mackerel-plugin-docker でコンテナインスタンスのメトリクスを取得できる
    • mackerel-plugin-docker が Docker API からメトリクスを取得するため,動的ポートマッピングでタスクが増えた場合も,基本的なメトリクスは取得できる
    • mackerel-plugin-gostats など,任意のプラグインを使って,動的ポートマッピングでタスクが増えた場合も,タスク特有のメトリクスを取得できる
      • 少し工夫が必要になる(本記事のポイント)
  • デメリット
    • mackerel-agent のインストールは cloud-init で頑張る(逆に cloud-init でプロビジョニングができるのはメリットと言えるかも?)
    • Mackerel のグラフ生成が難しい

構成図

「パターン2」の構成は以下のようになる.コンテナインスタンスに mackerel-agent をインストールしている点が「パターン1」とは異なる.

f:id:kakku22:20170518202609j:plain

前提

今回は mackerel-plugin-gostats を使って Golang のメトリクスを取得するため,前提として API に fukata/golang-stats-api-handler を導入しておく必要がある.実装は割愛するが,エンドポイントとしては /stats を用意した.

github.com

github.com

コンテナインスタンスの起動設定に指定する cloud-init 実装例

あくまで実装例として紹介するが,コンテナインスタンスに最低限必要になるプロビジョニングを全て cloud-init で実行した.こうすると Chef や Ansible を実行する必要もなく,Auto Scaling で負荷に応じて自動的に増減させることができる.AMI にする案もあるが,ECS で使う ECS-Optimized AMI も定期的に更新するだろうし,今はこのままにしている.

cloud-init に実装した内容をザックリと箇条書きにすると,以下のようになる.

  • timezone
    • タイムゾーンを変更する
  • write_files
    • /etc/mackerel-agent/mackerel-agent.conf を生成する
    • 動的ポートマッピングに対応したメトリクスを取得するための /root/ecs_gostats.sh を生成する
  • runcmd
    • mackerel-agentmackerel-agent-plugins をインストールする
    • /etc/ecs/ecs.config にクラスタ名を書き込む

cloud-init の実装例としては,以下のようになる.ロール名 / コンテナ名 / クラスタ名などは,適宜読み替えてもらえればと!

#cloud-config
timezone: "Asia/Tokyo"

write_files:
 - path: /etc/mackerel-agent/mackerel-agent.conf
   permissions: 0644
   content: |
     apikey = "xxx"
     roles = [ "yyy:zzz" ]
     include = "/etc/mackerel-agent/conf.d/*.conf"
     [host_status]
     on_start = "working"
     [plugin.metrics.gostats]
     command = "/root/ecs_gostats.sh"
     [plugin.metrics.docker]
     command = "/usr/bin/mackerel-plugin-docker"

 - path: /root/ecs_gostats.sh
   permissions: 0755
   content: |
     #!/bin/sh
     if [ "${MACKEREL_AGENT_PLUGIN_META}" != "1" ];then
       for PORT in $(docker ps --format='{{.Ports}}' --filter name=api | cut -f1 -d- | cut -f2 -d:); do
         /usr/bin/mackerel-plugin-gostats -port ${PORT} -path=/stats -metric-key-prefix=gostats.${PORT}
       done
     else
       /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
     fi

runcmd:
 - curl -fsSL https://mackerel.io/file/script/amznlinux/setup-yum.sh | sh
 - sudo yum install -y mackerel-agent
 - sudo yum install -y mackerel-agent-plugins
 - echo AUTO_RETIREMENT=1 >> /etc/sysconfig/mackerel-agent
 - mkdir /etc/mackerel-agent/conf.d/
 - service mackerel-agent start
 - sh -c 'echo ECS_CLUSTER=api-cluster >> /etc/ecs/ecs.config'

cloud-init の詳細は以下に載っている.

docs.aws.amazon.com

Amazon Linux に mackerel-agent をインストールする手順は以下に載っている.

mackerel.io

mackerel-agent.conf の詳細は以下に載っている.

mackerel.io

ecs_gostats.sh

今回のポイントは,この ecs_gostats.sh だと思う.

#!/bin/sh
if [ "${MACKEREL_AGENT_PLUGIN_META}" != "1" ];then
  for PORT in $(docker ps --format='{{.Ports}}' --filter name=api | cut -f1 -d- | cut -f2 -d:); do
    /usr/bin/mackerel-plugin-gostats -port ${PORT} -path=/stats -metric-key-prefix=gostats.${PORT}
  done
else
  /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
fi

まず,動的ポートマッピングに対応するため docker ps の結果から,ポートを抽出する.以下の例で言うと 32768 / 32770 / 32771 となる.

PORTS
0.0.0.0:32768->8080/tcp
0.0.0.0:32770->8080/tcp
0.0.0.0:32771->8080/tcp

次にそのポートごとに -metric-key-prefix オプションを使って,メトリクスを送信する.具体的には -metric-key-prefix=gostats.${PORT} とする.ポート番号 32768 を例にすると,実際取得できるメトリクスのキーは以下のようになる.

gostats.32768.gc.gc_num
gostats.32768.gc.gc_pause_per_second
gostats.32768.gc.gc_per_second
gostats.32768.heap.heap_idle
gostats.32768.heap.heap_inuse
gostats.32768.heap.heap_released
gostats.32768.heap.heap_sys
gostats.32768.memory.memory_alloc
gostats.32768.memory.memory_stack
gostats.32768.memory.memory_sys
gostats.32768.operation.memory_frees
gostats.32768.operation.memory_lookups
gostats.32768.operation.memory_mallocs
gostats.32768.runtime.cgo_call_num
gostats.32768.runtime.goroutine_num

さらに,重要なのは MACKEREL_AGENT_PLUGIN_META と言う環境変数の存在で,実は今まで知らなかった.簡単に言うと MACKEREL_AGENT_PLUGIN_META=1 でプラグインを実行すると,グラフ定義を出力する仕様になっている.今回はメトリクス名にポート番号を含めているため,グラフ定義も変更する必要がある.具体的には -metric-key-prefix=gostats.# と書いて,ポートの部分をワイルドカードで表現することができる.

すると,以下のようなグラフ定義を出力することができる.

  • gostats.#.gc
  • gostats.#.heap
  • gostats.#.memory
  • gostats.#.operation
  • gostats.#.runtime

JSON の部分は可読性のために整形しているが,実際はこんなグラフ定義を出力することができる.

$ MACKEREL_AGENT_PLUGIN_META=1 /usr/bin/mackerel-plugin-gostats -metric-key-prefix=gostats.#
# mackerel-agent-plugin
{
    "graphs": {
        "gostats.#.gc": {
            "label": "Gostats.# GC",
            "unit": "float",
            "metrics": [
                {
                    "name": "gc_num",
                    "label": "GC Num",
                    "stacked": false
                },
                {
                    "name": "gc_per_second",
                    "label": "GC Per Second",
                    "stacked": false
                },
                {
                    "name": "gc_pause_per_second",
                    "label": "GC Pause Per Second",
                    "stacked": false
                }
            ]
        },
        "gostats.#.heap": {
            "label": "Gostats.# Heap",
            "unit": "bytes",
            "metrics": [
                {
                    "name": "heap_sys",
                    "label": "Sys",
                    "stacked": false
                },
                {
                    "name": "heap_idle",
                    "label": "Idle",
                    "stacked": false
                },
                {
                    "name": "heap_inuse",
                    "label": "In Use",
                    "stacked": false
                },
                {
                    "name": "heap_released",
                    "label": "Released",
                    "stacked": false
                }
            ]
        },
        "gostats.#.memory": {
            "label": "Gostats.# Memory",
            "unit": "bytes",
            "metrics": [
                {
                    "name": "memory_alloc",
                    "label": "Alloc",
                    "stacked": false
                },
                {
                    "name": "memory_sys",
                    "label": "Sys",
                    "stacked": false
                },
                {
                    "name": "memory_stack",
                    "label": "Stack In Use",
                    "stacked": false
                }
            ]
        },
        "gostats.#.operation": {
            "label": "Gostats.# Operation",
            "unit": "integer",
            "metrics": [
                {
                    "name": "memory_lookups",
                    "label": "Pointer Lookups",
                    "stacked": false
                },
                {
                    "name": "memory_mallocs",
                    "label": "Mallocs",
                    "stacked": false
                },
                {
                    "name": "memory_frees",
                    "label": "Frees",
                    "stacked": false
                }
            ]
        },
        "gostats.#.runtime": {
            "label": "Gostats.# Runtime",
            "unit": "integer",
            "metrics": [
                {
                    "name": "goroutine_num",
                    "label": "Goroutine Num",
                    "stacked": false
                },
                {
                    "name": "cgo_call_num",
                    "label": "CGO Call Num",
                    "stacked": false
                }
            ]
        }
    }
}

ワイルドカードの仕様はよく理解できていなくて,難しいなと感じた.#* の違いはなんだろう?とか,個数制限があるのは # だけ?とか.ドキュメントとしては以下に載っている.

mackerel.io

mackerel.io

ホストグラフ : カスタムメトリック(成功 ○)

この時点で,既に動的ポートマッピングに対応したメトリクスが取得できているため,まずは「ホストグラフ : カスタムメトリック」を確認する.以下のように,ちゃんとポート別にメトリクスが確認できているし,タスク数の増加にも対応できている.ただし,これはまだコンテナインスタンスごとのメトリクスなので,実際にはあまり使わなそう.

f:id:kakku22:20170518203032p:plain

ロールグラフ : カスタムメトリック(失敗 ×)

ロールグラフは完全に厳しくて,ワイルドカード指定で表示することができなかった.改善要望出したいなぁ.

f:id:kakku22:20170518202545p:plain

ロールグラフ : カスタマイズグラフ(微妙 △)

こうなったらもうカスタマイズグラフで頑張るしかなさそうだ!ということになり,カッとなって作った.role 関数を使って,ポートの部分を * で表現したらうまくグラフ化できた.けど,シンプルとは言えないし,ツライ.

https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1h
https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1d
https://mackerel.io/embed/orgs/xxx/advanced-graph?query=role('yyy:zzz', 'custom.gostats.*.memory.memory_sys')&title=custom.gostats.*.memory.memory_sys&period=1w

カスタマイズグラフを大量に作ってダッシュボードを用意した.

f:id:kakku22:20170518202526p:plain

カスタマイズグラフに関しては以下に載っている.

mackerel.io

去年のアドベントカレンダーでカスタマイズグラフで試行錯誤したのを思い出してしまった.

kakakakakku.hatenablog.com

まとめ

今回実現した方法が正しいのかよくわからないし,もっと良い方法があるのかもしれないし,Mackerel が正式に ECS をサポートしてくれるかもしれないけど,現状この「パターン2」で考えていたことを実現することができた.とは言え,まだ検証環境で ECS を動かしている段階で,実環境に投入したらまた別の問題に気付くかもしれない.もっと良い方法があれば教えて欲しい.

  • コンテナインスタンスに mackerel-agent をインストールした
  • コンテナインスタンスのメトリクスを取得できた
  • タスクのポートをワイルドカードで表現することで,タスクのメトリクスも取得できるようになった
  • グラフは一筋縄では実現できず,カスタマイズグラフで頑張った

謝辞

困ったときに相談するとすぐに助けてくれる id:okzk に今回はアイデアをたくさん頂いた!感謝!

okzk.hatenablog.com

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

mackerel-agent タスクをコンテナインスタンスごとに起動して ECS のメトリクスを取得する

最近 Mackerel を使って ECS のメトリクスを取得する方法を検証している.ポイントとしては,ALB の動的ポートマッピングを使うところで,コンテナインスタンスの中で複数のタスク(コンテナ)が起動されるため,全てのタスクのメトリクスを取得する必要がある.

パターン

Mackerel を使って ECS をモニタリングする場合,アーキテクチャとしては大きく2パターンが考えられる.

  • パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する
  • パターン2 : コンテナインスタンスに mackerel-agent をインストールする

今回の記事では「パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する」の検証結果をまとめようと思う.

メリデメ

最初に「パターン1」のメリデメを整理しておく.

  • メリット
    • コンテナインスタンスをプロビジョニングしなくても Mackerel が使える
    • mackerel-agent タスクからコンテナインスタンスのメトリクスを取得できる
    • mackerel-plugin-docker が Docker API からメトリクスを取得するため,動的ポートマッピングでタスクが増えた場合も,基本的なメトリクスは取得できる
  • デメリット
    • 動的ポートマッピングで起動されたタスク特有のメトリクス(mackerel-plugin を使って取得するようなもの)を取得できない

構成図

まず,今回は以下のような構成で検証をした.実際に動かすタスクは Golang で書いたサンプル API で,API Service に乗せた.

f:id:kakku22:20170516004413j:plain

ECS : タスク定義

今回 mackerel-agent という名前でタスク定義を作成した.タスク定義に必要な設定は,全てドキュメントに載っていたので,参考になった.ただし,ドキュメントには -role オプションの記載が無かったため,ここは別途追加している.

mackerel.io

タスク定義の画面としては,以下のようになり,ザッとポイントを挙げておく.

  • コンテナの定義
    • コンテナイメージ
      • mackerel/mackerel-agent を利用する
    • 環境変数
      • apikey = xxx
      • enable_docker_plugin = true
      • auto_retirement = false
      • opts = -v -role=yyy:zzz
    • マウントポイント
      • Docker ホストの /var/run/docker.sock を参照できるようにする
      • Docker ホストの /var/lib/mackerel-agent/ を参照できるようにする
  • ボリューム
    • /var/run/docker.sock
    • /var/lib/mackerel-agent/

f:id:kakku22:20170516004445p:plain

ECS : タスク定義 (JSON)

もし AWS CLI でタスク定義を作成する場合は,以下のように実行する.

$ aws ecs register-task-definition --cli-input-json file://register-task-definition.json

register-task-definition.json はこんな感じに定義した.

{
  "family": "mackerel-agent",
  "containerDefinitions": [
    {
      "name": "mackerel-agent",
      "image": "mackerel/mackerel-agent",
      "cpu": 0,
      "memory": 128,
      "essential": true,
      "mountPoints": [
        {
          "containerPath": "/var/run/docker.sock",
          "sourceVolume": "docker-sock"
        },
        {
          "containerPath": "/var/lib/mackerel-agent/",
          "sourceVolume": "mackerel-agent"
        }
      ],
      "environment": [
        {
          "name": "apikey",
          "value": "xxx"
        },
        {
          "name": "auto_retirement",
          "value": "false"
        },
        {
          "name": "enable_docker_plugin",
          "value": "true"
        },
        {
          "name": "opts",
          "value": "-v -role=yyy:zzz"
        }
      ]
    }
  ],
  "volumes": [
    {
      "host": {
        "sourcePath": "/var/run/docker.sock"
      },
      "name": "docker-sock"
    },
    {
      "host": {
        "sourcePath": "/var/lib/mackerel-agent/"
      },
      "name": "mackerel-agent"
    }
  ]
}

JSON で1点注意するところがあり,管理コンソールでタスク定義 (JSON) を見ると,mountPoints の値が "readOnly": null となっている.このままだと以下のエラーが出たため,削除してから実行した.

$ aws ecs register-task-definition --cli-input-json file://register-task-definition.json

Parameter validation failed: Invalid type for parameter containerDefinitions[0].mountPoints[0].readOnly, value: None, type: <type 'NoneType'>, valid types: <type 'bool'>
Invalid type for parameter containerDefinitions[0].mountPoints[1].readOnly, value: None, type: <type 'NoneType'>, valid types: <type 'bool'>

ECS : サービス定義

サービス定義で意識するのは,タスク配置制約を One Task Per Host (distinctInstance) にするところで,この設定にすることで,コンテナインスタンスに対して必ず1タスクが配置されるようになる.ちなみに 4/26 時点だと,管理コンソールから distinctInstance でタスクを配置しても,コンテナインスタンスに複数のタスクが配置されるというバグがあり,AWS サポート側に報告した.現在は既に修正されている.

f:id:kakku22:20170516004510p:plain

docs.aws.amazon.com

mackerel-plugin-docker とは?

mackerel-agent タスクの環境変数で enable_docker_plugin = true を設定することで,mackerel-plugin-docker が使えるようになる.これは,簡単に言うと Docker のソケットファイル docker.sock に対して Docker API を叩くことによってメトリクスを取得していて,複数コンテナが起動している場合でも問題なく対応できる.メトリクス名としては -name-format name というオプションがデフォルトで設定されているため,コンテナ名で区別することができる.ただ,取得できるメトリクスは限られていて,CPU 関連,メモリ関連,IO 関連となる.

github.com

実際に Mackerel でメトリクスを確認した

上記のように mackerel-agent タスクを起動すると,すぐにメトリクスを確認できるようになる.Docker Memory のグラフを見るとわかりやすいが,途中でサービス定義を変更して,コンテナインスタンスに配置するタスク数を増やしても,ちゃんとメトリクスを取得できていた.

  • Docker CPU
  • Docker Memory
  • Docker BlkIO Bytes
  • Docker BlkIO IOPS

f:id:kakku22:20170516004539p:plain

mackerel-agent タスクだと難しいこと

上記のように,動的ポートマッピングを使う場合でも,タスクごとに CPU 関連などの基本的なメトリクスを取得することができるが,例えば gostats など,mackerel-plugin を使って取得するようなタスク特有のメトリクスを取得できないという課題がある.動的ポートマッピングを使わないのであれば,ポートが決まっているし,Docker の link オプションを使えば良いが,今回の要件を満たすのは難しそうだった.

ロールグラフも難しい

あくまでこれは,コンテナインスタンスのメトリクスなので,Auto Scaling のことを考えると,コンテナインスタンスをあまり意識したくなく,ロール単位でメトリクスを見たくなってくる.ただ,今の状態だとメトリクス名がコンテナごとに異なるため,ロールグラフを作れなかった.もしかしたら,拡張グラフ機能を使えばできるのかもしれないけど,うまく書けなかった.

mackerel.io

まとめ

  • Mackerel を使って ECS をモニタリングする場合,アーキテクチャとしては大きく2パターンある
  • 今回は「パターン1 : mackerel-agent タスクをコンテナインスタンスごとに起動する」を検証した
  • mackerel-agent 用のタスク定義はドキュメントを参照して環境変数やボリュームを設定する
  • 動的ポートマッピングを使う場合でも,タスクごとに基本的なメトリクスは取得できるが,タスク特有のメトリクスは取得できなかった
  • ロール単位でメトリクスを見る方法もうまく実現できなかった
  • 結果として「パターン1」の採用は見送ることにした

次回

実際に採用することにした「パターン2 : コンテナインスタンスに mackerel-agent をインストールする」の検証結果をまとめる予定!

最近書いた ECS 関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

FCM (Firebase Cloud Messaging) で Instance ID API を使ってトピックをサブスクライブする

最近 Firebase を検証している.今回は mBaaS としての Firebase ではなく,プッシュ通知基盤としての Firebase を検証していて,学んだことをまとめておこうと思う.Firebase には他にも Analytics や Functions や Remote Config などなど,機能がたくさんあり,使っていて凄くワクワクする.

前提

前提として,動作検証をするアプリに Firebase を導入して,端末トークンを取得できるようにしておくことと,プッシュ通知を受け取れるようにしておく必要がある.この部分は割愛するが,詳しくは以下のドキュメントに載っている.

2種類ある Firebase のプッシュ通知

Firebase のプッシュ通知は,大きく2種類ある.このあたりの前提知識がないと,ドキュメントを読んでも混乱してしまうと思う.Firebase Notifications というのは,Firebase の管理画面からプッシュ通知を送る方式で,FCM というのは,HTTP 経由でプッシュ通知を送る方式のことを意味する.

  • Firebase Notifications
  • FCM (Firebase Cloud Messaging)
    • Notification Message
    • Data Message

その他の細かな差異に関しては,Firebase の FAQ に詳しく載っている.

Cloud Messaging: What's the difference between the Notifications composer and Cloud Messaging?
https://firebase.google.com/support/faq/

また,FCM には Notification Message と Data Message という,2種類のメッセージタイプがある.簡易的なプッシュ通知を実現する場合は Notification Message を使えば良いが,Notification Message と Data Message を組み合わせて使うこともできる.また,アプリの状態(フォアグラウンド/バックグラウンド)によっても挙動が異なるため,詳細はドキュメントを参照で.

ターゲット

プッシュ通知を送るターゲットは大きく3種類ある.

  • ユーザーセグメント ... 言語 / バージョン / ユーザープロパティなどを AND / OR 演算した集合体
  • トピック ... 任意のグループ
  • 端末 ... 特定の端末(登録トークン指定)

トピック

今回はトピックを検証した.トピックとは任意のグループのようなもので,トピックの中に端末(登録トークン)を紐付けていく.これを「サブスクライブ」と呼ぶ.ドキュメントでは「天気予報アプリで,荒天警報の通知を受けたい人」というトピックが紹介されていた.このように任意のグループを作成することができる.また,個数上限のあるユーザーセグメントとは異なり,トピックは無制限なので,大量に作っても問題が無さそう.

サブスクライブをする場合,以下のドキュメントに載っているような実装をする.具体的には subscribeToTopicunsubscribeFromTopic を呼び出す.なお,実行時にトピックが存在しない場合は,自動的にトピックも作成される.ただし,トピックが作成されるまでに最大1日待つ必要がある.今回トピックを数個作成してみて,大体4時間程度で作成されたが,それでも結構な時間を待つ必要があることには変わりがないと思う.

f:id:kakku22:20170512123010p:plain

Firebase の管理画面からプッシュ通知を送る

Firebase の管理画面で,Notifications のメニューを選択して送信するだけなので,割愛する.

curl で FCM (Firebase Cloud Messaging) のプッシュ通知を送る

curl で FCM のプッシュ通知を送る場合,以下のように実現できる.FIREBASE_SERVER_KEY には管理画面の設定ページから取得したサーバーキーを設定しておく必要がある.

$ FIREBASE_SERVER_KEY=''
$ curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  https://fcm.googleapis.com/fcm/send \
  -d @firebase.json

リクエストとして送るパラメータ ( firebase.json ) は以下のように書く.今回はテスト用に test トピックを事前に用意した.表記としては /topics/test のように書く.プッシュ通知のタイトルと本文もそれぞれ定義する.

{
    "to": "/topics/test",
    "priority": "high",
    "notification": {
        "title": "title",
        "body": "body"
    }
}

Instance ID API で登録トークンを操作する

Google が提供している Instance ID API を使うと,HTTP 経由で登録トークンの情報を取得したり,トピックをサブスクライブすることができる.最初はネイティブじゃないとできないと思っていたので,この API を発見したときは驚いた.

ちなみに Instance ID API の文脈で言うと「インスタンス ID」という名称に変わるが,結局のところ,今まで紹介してきた「登録トークン」と同じもので,正確に言えば,「インスタンス ID」では iOS と Android 以外に Chrome も識別することができるという点が違うと思われる.当然ながら,プライバシーの観点で「インスタンス ID」も「登録トークン」もリフレッシュもできる.今回は「登録トークン」の表記で統一する.

登録トークンの情報を取得する API

IID_TOKEN には nKctODamlM4:CKrh_PC8kIb7O...clJONHoA などの登録トークン(正確には152文字?)を指定する.以下のエンドポイントを叩くと,登録トークンがサブスクライブしているトピックの一覧や,ネット接続タイプ WIFI or MOBILE,プラットフォームなどを取得することができる.

$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  "https://iid.googleapis.com/iid/info/${IID_TOKEN}?details=true" | jq .
{
  "applicationVersion": "0.0.1",
  "connectDate": "2017-05-09",
  "application": "com.example",
  "scope": "*",
  "authorizedEntity": "111111111111",
  "rel": {
    "topics": {
      "test": {
        "addDate": "2017-05-09"
      }
    }
  },
  "connectionType": "WIFI",
  "platform": "IOS"
}

登録トークンを指定してトピックをサブスクライブする API

以下のエンドポイントを叩くと,登録トークンを指定してトピックをサブスクライブしたり,アンサブスクライブできる.ポイントは HTTP メソッドで POST と DELETE を使う点と,ヘッダーに Content-type: application/jsonContent-Length: 0 を乗せる点がある.この条件が満たされないと,エラーが返ってくる.

$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ TOPIC_NAME='test'
$ curl -XPOST --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  --header "Content-Length: 0" \
  "https://iid.googleapis.com/iid/v1/${IID_TOKEN}/rel/topics/${TOPIC_NAME}"
$ FIREBASE_SERVER_KEY=''
$ IID_TOKEN=''
$ TOPIC_NAME='test'
$ curl -XDELETE --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  --header "Content-Length: 0" \
  "https://iid.googleapis.com/iid/v1/${IID_TOKEN}/rel/topics/${TOPIC_NAME}"

その他

複数の登録トークンをバルクでサブスクライブするエンドポイントなど,他にもいろいろと提供されている.詳細はドキュメントを参照で.

  • https://iid.googleapis.com/iid/v1:batchAdd
  • https://iid.googleapis.com/iid/v1:batchRemove

まとめ

  • Firebase のプッシュ通知を検証した
  • FCM を使うと,HTTP 経由でプッシュ通知を送ることができる
  • トピックを使うことで,任意のグループにプッシュ通知を送ることができる
  • ただし,トピックの作成には最大1日かかる
  • Instance ID API を使うと,HTTP 経由で登録トークンを操作することができる

関連記事

inside.pixiv.net