コミュニケーションコストと手間暇について僕自身がいま考えていること


はじめに

この記事は #Backlog アドベントカレンダー2022 by #JBUG の12/18分の記事として執筆しています。

コミュニケーションコストとは?

現在勤めている会社は渋谷にオフィスがあり、この記事を書いている直近ではだいたい50%くらいの社員がリモートワークをしています。僕もそのうちのひとりで、オフィスに出社するのは1週間から2週間に1回くらい。その他は主に自宅で仕事をしています。コアタイム制ですが、多くの社員の勤務開始時間は割と遅めな感じな一方で、朝早めの時間から勤務を開始しているメンバーもいます。

そういう環境の中で、ほとんどオフィスに出社しているメンバーの人からちょくちょく「リモートワークだとコミュニケーションコストがかかるから」という言葉を耳にすることがあります。具体的な例としては、オフィスに出社していればちょっとした相談事でもその人の席まで行って話をすればすぐに解決できることが、リモートワークだとタイムラグややり取りに関する時間的なコストが発生して、解決に至るまでのスピードが遅くなる、といったところが代表的でしょうか。そこにコストがかかることに課題を感じているようです。

いや、仰っていることはよくわかるんですよ。確かにオフィスの中をとことこ歩いていって相談相手と対面で話すことの方が、時間的なロスは少ないし、相手の表情を見ながら話すことができるので、解決までの道のりは、もしかしたら早いかもしれません。

だがしかし待てよ、「コニュニケーションコスト」って果たしてそういうものなのかしら? というところに個人的には疑問を感じてしまいます。

コミュニケーションにかける手間暇についての僕の思うところ

上記に関する「コミュニケーションコスト」というのを時間的な価値だけにフォーカスするのであれば、確かにそれはスピード感を持って意思決定をする上ではみんなが同じ場にいて同期的に話を進めていけばいいかもしれません。その方が手間暇は全然かかりませんし。

ただ、「コミュニケーションコスト」は、逆に手間暇というか、ちょっとした工夫を使いながらかけるべきでもあると僕は思うのです。

ここでは例としてBacklogとSlackを挙げてみましょう。

Backlogを通じたコミュニケーションに対してかける手間暇

この記事を読んでいる皆さんの中には何かしらのチケット管理ツールを使用してプロジェクトを進めている方が比較的多いのではないかと思います。僕の会社でもBacklogを使ってプロジェクト管理を行なっています。なんですけれども、Backlogの機能を十分に使いこなせていないなと思ってしまう事例が少なくないのです。ひとつひとつの起票された課題(親課題や子課題を含めて)に対して、それがなんのために起票されているのかという意味合いを持たせていないケースが散見されるのです。結果としてどういうことが起こるかというと、後から見返したときに「このBacklogの課題って何のために起票されたんだっけ?」という「コミュニケーションコスト」が発生してしまうわけです。これはあまり建設的なコストではないですよね。

もちろん、Backlogにはこれが鉄板というルールブック的なものはないわけですけれども、起票された課題に対して意味合いを持たせ、その課題が結果としてとのような状態になったかを育てていった方が、ツールを使っていく上での価値がダントツに上がるわけです。少なくとも、課題の本文やコメントに、

  • なぜこの課題を起票したのか (= 課題が示している目的を明確にする)
  • どういう状況になっていればその課題はクローズできるのか (= 課題の完了条件を明確にする)
  • 課題のステータスを変える時には、その理由 (= 完了に向けて具体的にどのようなアクションをとっているのかを明確にする)

これらが書かれていれば、課題に対する活動の履歴がはっきり残るため、「この課題って何だっけ?」状態は確実にクリアできると思っています。逆に言えば、「コニュニケーションコスト」を軽減するための材料を、少し手間暇をかけることで用意することができるわけです。これは明らかに建設的な手間暇であると考えることができると思うのです。

Slackを通じたコニュニケーションに対してかける手間暇

同じように、日常の仕事の中での課題の共有や調整などを、Slackのようなチャットツールでやり取りしている例も多いのかなと思います。

SlackはBacklogのようなストック型のツールではなく、フロー型のツールとも言えます。フロー型である以上は、短いスレッドで完結させる方が良いに越したことはないのですが、必ずしもその通りにならないことが往々にしてあるのは皆さんもよく経験されているのではないかと思います。その原因として考えられるのが、やはり書き込まれている最低限の情報が網羅されているか、なのかなと考えています。網羅すべき情報は、Backlogの場合とほぼ同じで、

  • そのスレッドを通じて共有したいことや質問したいことは何か (= 目的を明確にする)
  • 情報共有の場合、伝えたい相手に対して何を理解してもらいたいか (= 完了条件を明確にする)
  • 質問の場合、結果としていつまでにどんな情報が欲しいのか (=完了条件を明確にする)

これらが書かれているだけでも、情報を伝えたい相手に対して余計な「コニュニケーションコスト」を発生させることなく、建設的に物事を進めたり、調整したりすることができるわけです。そのための手間暇としても建設的なのではないかなと思います。

時間的なコストを削減するためにも手間暇を少しかけよう

ここでは2つの例を書いてみましたが、「コミュニケーションコスト」というのは、単純に時間だけで評価できるものではなく、その人の持っている情報がどれだけ多くの人に対して正しく共有できるかというのも重要な評価軸なのではないかと思います。オフィスにいて、ちょっと誰かと相談すれば、2者間では情報の共有がすんなりいくかもしれませんが、そこで得られた情報を多くの人に共有できるかと言えば、実は共有できなかったりしますよね。その結果、「これって何だっけ?」「確かこういうことだった気がする」というようなやりとりに発展して逆に時間的なコストをかけてしまうことになることもあったりするわけで。

それを補完するのがプロジェクト管理ツールやコミュニケーションツールの役割であるわけで、そこに少しだけ手間暇をかけてあげることで、有益な情報共有の手段として育てていくことができるのだと思います。そして育てることによって、いつかプロジェクトや会社の事業にとっても大きな資産となり得ます。

非同期的なコミュニケーションが主流になっていく中で、まずはほんの少しだけでもいいので、手間暇をかけてみませんか? その一歩を踏み出すだけでも、案外「コミュニケーションコスト」の罠から抜け出すことができるかもしれませんよ。

カテゴリー: Work | タグ: , , , | コメントする

MacBook Air M2にHomebrewをインストールする方法 (macOS Ventura版)


はじめに

2022/10/12にMacBook Air M2を購入しまして、その後あまり何も考えずにmacOS Ventura 13.0にアップデートしてしまってのですが、セキュリティ周りの仕様が変わったせいなのか、こんな感じでHomebrewがインストールできない事態に。

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
==> Checking for `sudo` access (which may request your password)...
Password:
==> This script will install:
/usr/local/bin/brew
/usr/local/share/doc/homebrew
/usr/local/share/man/man1/brew.1
/usr/local/share/zsh/site-functions/_brew
/usr/local/etc/bash_completion.d/brew
/usr/local/Homebrew
==> HOMEBREW_BREW_GIT_REMOTE is set to a non-default URL:
/opt/homebrew will be used as the Homebrew/brew Git remote.
==> HOMEBREW_CORE_GIT_REMOTE is set to a non-default URL:
/opt/homebrew will be used as the Homebrew/homebrew-core Git remote.

Press RETURN/ENTER to continue or any other key to abort:
==> /usr/bin/sudo /usr/sbin/chown -R vlayusuke:admin /usr/local/Homebrew
==> Downloading and installing Homebrew...
fatal: '/opt/homebrew' does not appear to be a git repository
fatal: Could not read from remote repository.

macOSの場合、いつの頃からかHomebrewがインストールされるデフォルトのディレクトリが /opt/homebrew に変わったのは知っていたんですけれども、それがGitのrepositoryではないってどういうこと? と思って悪戦苦闘していたんですが、なんとか解決したのでそのメモです。

TerminalをRosettaで起動させるようにする

  • ターミナル.appをFinderから選択
  • ⌘ + iするか、Finderの[ファイル]-[情報を見る]でターミナル.appの情報を表示
  • 「Rosettaを使用して開く」にチェックを入れる

Apple SiliconのMacでも、Intel プロセッサ搭載 Macとの互換性を保たせるためにこれはどうしても必要な設定になります。まずここでApple SiliconのMacBook Air M2でCLIを使うための最低限の準備をします。

Homebrewのリポジトリ設定をリセットする

元々MacBook Air M2のデータは代々引き継がれてきたMacBook Air Intel 2018のものを使用していたので、Homebrewのリポジトリ設定がおかしくなっていたかもしれないので、GitHubのHomebrewに関する同じ現象のディスカッションを参考に以下のコマンドを実行。

% unset HOMEBREW_BREW_GIT_REMOTE HOMEBREW_CORE_GIT_REMOTE

すると、これまでの苦労が何事もなかったかのようにHomebrewのインストールを、公式に書かれているような

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

を使用してインストールすることができました。やれやれ。

ちなみにこれ、macOS Venturaにしたからこのようになっているというわけではなくて、Apple Silicon搭載のMac共通で、Homebrewを使用するために実行しておく必要があるおまじないみたいですね。

何はともあれ、開発をしていくにあたって必要なHomebrewが使えるようになり一安心したのでした。

カテゴリー: Private, Work | タグ: , , | コメントする

AWS認定DevOpsエンジニア プロフェッショナル(AWS DOP-C01)に合格しての感想


今年の5月にAWS認定SysOpsアドにミストレーター アソシエイトに合格してから約半年が経過しました。私の中では少なくとも、AWS認定資格のうち、専門分野を除く6つの資格をコンプリートしたかったので、目指すべきはAWS認定DevOpsエンジニア プロフェッショナル(AWS DOP-C01)のみとなり、本格的には2022年8月下旬から受験の準備を始めて、約1か月間の準備を経て、1発で合格することができました。スコアは812点。ギリギリではなかったものの、なんとかなりました。かなり嬉しいです。

どんな風に勉強したのか

AWS DOPも執筆時点では学習参考書は皆無の状態だったので、まずはその時点で刊行されたばかりの「AWS認定資格試験テキスト AWS認定SysOpsアドミニストレーター – アソシエイト」を購入。前回の復習を兼ねてひと回り読み込みました。特にCloudTrail、Config、Inspector、GuardDutyはユースケースがごっちゃになりがちなので改めてどういったユースケースで用いられるのか、またどんなサービスと連携できるのかを中心に復習しました。さらに、AWS DVAの受験からかなり日数が経過していたので「徹底攻略AWS認定デベロッパー – アソシエイト教科書」も並行して購入。Codeシリーズは現在手がけている案件で試行錯誤しながらも使い倒していたので基本的な機能やユースケースを押さえていたのですが、AWS DVAの中でも頻出していたElasticBeanstalkやOpsWorkは使うことがなかったので、やはりユースケースを中心に復習を重ねました。

学習期間は長ければいいというものでもない

当初は10/29を試験日にしていたのですが、学習時間を2ヶ月間に設定してしまうと少しだらけてしまう可能性があったので、AWS SOAでの学習経験が無駄にならないように、あえて試験日を10/1に繰り上げました。

もちろんリモートワーク中心とはいえ仕事はあるし、その分だけ勉強する時間も限られてしまうのですが、仕事の忙しさを理由にしてダラダラ勉強してしまうとそれはそれでよろしくないので、ここは自分の心を鬼にして、あえて追い込むかたちとしました。

長文読解対策は今回は行わず

一通り関連する参考書を読み、今回はBlack Beltの動画を視聴するようにしたのですが、AWS SAPの時とは異なり、今回は長文読解対策は特に行いませんでした。AWS SAPほどの複雑な長文問題が、koiwa改めTechStockを実際に解いてみてそれほど出てこないなというように感じられたからです。とはいえ用件が複雑であることには変わりがないので、問題の中で何を問われているのかは注意して読んでから解くようにしていました。今回の勝因としてはそこの部分を重視しながら学習していったところにあるかなと思っています。

やはり実務は大事

AWS SOAとは異なり、ラボ試験はC01では出題されないため、座学中心での学習になりますが、やはり実務で触るということは他の試験と同じくらい大事なことなのかなと思います。少なくとも、出題範囲となるサービスのコンソールは絶対に触っておいた方がいいです。例えばCloudTrailの場合、API呼び出しが全て記録されているといっても、具体的にどのようにログが表示されるのか、証跡がどのように保管されるのかなど、リソースを作ってしまうと課金されてしまいますが、どの画面でどのように操作すれば要件を実現できるのかは実際に見ておくに越したことはないです。

試験当日の裏話

実は前日に、ローンチしたばかりの案件の打ち上げがあり、終電を逃してしまってタクシー帰宅をしたので、実質的な睡眠時間は3時間程度でした。とりあえず眠かったので、行きの電車の中で20分くらい目を閉じていたらいつの間にか寝てしまっていました。危ない危ない。

加えて試験中に軽い腹痛になってしまったのですが、テストセンターで受ける場合、体調に不調を感じたら、問答無用で試験卓の横にある呼び出しボタンを押して、試験監督の方を呼び出した方がいいです。試験時間が一旦停止するわけではないのですが、ちゃんとトイレに行かせてもらえました。突発的な何かがあるときは躊躇せずに呼び出しボタンを押すのがいいです。頭真っ白になって問題が解けなくなるよりは全然いいです。

次の目標

最初から取得するのは6冠まで、と決めていたのですが、ここまでくると専門分野の領域の試験も受けてさらに知見を深めていくのもいいのかなというように考えるようになってきました。もちろん3年という有効期限を更新していくために、これからもAWS SAPとAWS DOPは繰り返し受けていかなければいけないのですが、どの専門分野が自分にとって必要なのかを考えた上で、そちらの方の受験も検討していきたいなと思っています。

カテゴリー: AWS | タグ: | コメントする

りきひさみねこヴァイオリン・ヴィオラ教室発表会


はじめに

ヴィオラを基礎から叩き直すためにりきひさみねこ先生のご指導を受け始めてから約半年。先生からせっかくだから発表会に出てみませんかと誘われたので、これまでほとんどオケでしか弾いたことがないし、何よりソロだと舞台上で妙にあがる性格なので、そこあたりの克服も含めて発表会に参加することにしました。

課題曲として選んだのはカッチーニの「アヴェ・マリア」。すごい良い曲ですよね。良い曲だからこそなかなか難しいという。発表会に出ると決めたのが7月の後半だったので、かなり高速で、先生にポイントポイントを教えていただきながら準備を進めてきました。

いざ本番

本番の会場は八千代市にある「Chopin Salon」という小ホール。八千代台駅の近くにこんなこじんまりとした素敵なホールがあるとは知りませんでした。そして響きがものすごく良い。良いホールだと思います。

私はリハーサルの40分前に会場入りして、リハーサルを終えた後にいざ本番。

他の生徒さんの演奏を聴いていたんですが、みんなすごいなぁというのが第一印象でした。子供さんが多いのですが、みんなボウイングがしっかりとしていてちゃんと音が聴こえてくるし、しかもみんな暗譜だし。各々の生徒さんが各々の実力の中でしっかり弾くことができているので、すごいなぁと。そうなってくると当然のことながら自分大丈夫なんだろうかとだんだん緊張してきます。

カッチーニの「アヴェ・マリア」、1箇所だけ難所がありまして、そこをリハーサルでもとちったので本番ミスをせずに弾くことができるかどうか正直不安だったので、直前まで譜読みをしながらリズムが合っているかどうかを確認していました。

そして本番。とにかく堂々と弾くことだけに集中していました。懸案の難所も無事にクリア。若干力みがちでヴィブラートならぬ「ビビラート」になってしまったところもありますが、なんとか無事に弾き切りました。いやしかし緊張した中で自分の中ではそれなりにまともに弾けた方だと思っています。もちろん課題はたくさん残っているんですけれども、とにかく大きなミスをすることなく弾くことができたことに関しては自分を褒めたいなと思っています。

本当に、なかなか上達しない自分を支えてくださった、りきひさ先生には感謝感謝です。次の発表会に向けての曲も早速探しましょう! と言ってくださったので、また次回の発表会に向けて、引き続き基礎を固めながらレッスンを続けていこうと思います。

夏の良い思い出がひとつできました。

カテゴリー: Private | タグ: , | コメントする

TerraformでAmazon Pinpointのプッシュ通知設定を行うためのTips


はじめに

業務でモバイルアプリケーションのバックエンド側のAWS環境を構築するにあたって、スマートフォンに送られるプッシュ通知を実装するために、Amazon Pinpointを使用することにしました。Amazon Pinpointを使えば、モバイルアプリケーションへのあらゆる通知機能を一元管理できるのに加えて、開封率などの効果測定もできるため、分析やマーケティングにも役立つということで、ちょっと使ってみようかという話になりました。

私の会社では、AWSリソースをIaCとして構成管理するためにTerraformを使用しているのですが、Amazon Pinpointのリソースとプッシュ通知に用いる

  • Apple Push Notification Service (APNs)
  • Firebase Cloud Messaging (FCM)

の設定を実装する必要があるのですが、そこで色々と沼にハマり、関連するドキュメントもAWS公式のもの以外に見当たらなかったので、備忘録的に、最終的にどういう実装にしたのかを記録しておきます。

Amazon Pinpointのリソースの実装

これは教科書通りで、以下のように実装すれば問題ありません。

# ===============================================================================
# Pinpoint Configure
# ===============================================================================
resource "aws_pinpoint_app" "pinpoint" {
  name = "test-pinpoint"

  limits {
    maximum_duration    = 10800
    messages_per_second = 20000
  }
}

Apple Push Notification Service (APNs)向けのチャネルの実装

最初にはまったポイントがここ。Terraformの公式ドキュメントには、APNsチャネルの実装方式として2つの方式が示されているのですが、サンプルに書かれている“Certificate credentials”(Appleから発行される証明書と秘密鍵を用いる)方式では、何をどうしても何故か、実行時にAppleから発行された正式な証明書ではないというエラーが出てチャネルを作成することができません。秘密鍵の代わりに、パスワードをSSMのパラメータストアに定義してあげて値を渡してもダメ、ならばとパスワードを定数(variables)として定義してあげて値を渡してもやはりダメです。

なので、もうひとつの方式として挙げられている、“Key credentials”(Appleから発行されるバンドルID、チームID、トークンとして用いられる”.p8″ファイル、トークンのキーを用いる)方式で実装し直しました。このうち、バンドルID、チームID、トークンのキーはSSMパラメータストアに以下のように格納します。

# ===============================================================================
# SSM Parameters for iOS (Pinpoint)
# ===============================================================================
resource "aws_ssm_parameter" "apns_bundle_id" {
  name        = "/test/prod/apns-bundle-id"
  description = "The parameter for apns team id with Pinpoint"
  key_id      = aws_kms_key.application.key_id
  type        = "SecureString"
  value       = "${bundle_id}"

  lifecycle {
    ignore_changes = [
      value,
    ]
  }
}

resource "aws_ssm_parameter" "apns_team_id" {
  name        = "/test/prod/apns-team-id"
  description = "The parameter for apns team id with Pinpoint"
  key_id      = aws_kms_key.application.key_id
  type        = "SecureString"
  value       = "${team_id}"

  lifecycle {
    ignore_changes = [
      value,
    ]
  }
}

resource "aws_ssm_parameter" "apns_token_key_id" {
  name        = "/test/prod/apns-token-key-id"
  description = "The parameter for apns token key id with Pinpoint"
  key_id      = aws_kms_key.application.key_id
  type        = "SecureString"
  value       = "${token_key_id}"

  lifecycle {
    ignore_changes = [
      value,
    ]
  }
}

その上で、Amazon PinpointのAPNsチャネル設定を以下のように実装します。

# ===============================================================================
# Pinpoint Settings for iOS
# ===============================================================================
resource "aws_pinpoint_apns_channel" "apns" {
  application_id = aws_pinpoint_app.main.application_id
  bundle_id      = aws_ssm_parameter.apns_bundle_id.value
  team_id        = aws_ssm_parameter.apns_team_id.value
  token_key      = file("./files/key/token_file.p8")
  token_key_id   = aws_ssm_parameter.apns_token_key_id.value
}

この実装方法で、APNsチャネルの登録は無事に成功しました。

Firebase Cloud Messaging (FCM)向けのチャネルの実装

こちらはAPNsチャネルの実装よりも敷居が低いです。Firebaseプロジェクトを新規に作成したときに生成される、FCN用の”サーバーキー”をAPIキーとしてチャネルに組み込んであげるだけでOK、なのですが、ここでもトラップが。

APIキーは当然SSMパラメータストア内に格納したいのですが、格納して実行したところ、”401 Unauthorized”エラーが。なのでものは試しにAPIキーをそのまま引数として代入すると、なんの問題もなくFCMチャネルが作成されます。というわけで、仕方がないので以下のようにAPIキーを実装。

# ===============================================================================
# Firebase Cloud Messaging (FCM)
# ===============================================================================
variable "gcm_api_key" {
  default = "(FCM Api Key)"
}

その上で、Amazon PinpointのFCMチャネル設定を以下のように実装します。

# ===============================================================================
# Pinpoint Settings for Android
# ===============================================================================
resource "aws_pinpoint_gcm_channel" "gcm" {
  application_id = aws_pinpoint_app.main.application_id
  api_key        = var.gcm_api_key
}

この実装方法で無事にFCMチャネルも作成できるようになりました。

謎というか宿題というか

一連の試行錯誤を通じて感じたのは、なぜSSMパラメータストアでは値がチャネル設定側に正しく渡らないのに、variablesにすると値が正しく渡るのかというところです。文字コードの問題なのか、何かしらエンコーディングしてあげないといけないのか、謎は深まるばかり。

ただ、APIキーのような正しく管理しなくてはならない値に関してはできるだけSSMパラメータストアにSecureStringとして管理したいので、そこのところをどうするのか、自分への宿題ということでもう少し試行錯誤してみたいと思います。

そんなわけで、この投稿はもう少し続く、かもしれません。

追記

FCMの実装でSSMパラメータからうまく取得できない理由がわかりました。思い切り、“ignore_changes”を指定していたため、後からAPIキーを追加しても反映されないわけで。

というわけで、以下の実装で無事にFCM向けの設定も無事に通るようになりました。

# ===============================================================================
# SSM Parameters for Android (Pinpoint)
# ===============================================================================
resource "aws_ssm_parameter" "gcm_api_key" {
  name        = "/test/prod/gcm-api-key"
  description = "The parameter for gcm api key with Pinpoint"
  key_id      = aws_kms_key.application.key_id
  type        = "SecureString"
  value       = "${api_key}"

  lifecycle {
    ignore_changes = [
      value,
    ]
  }
}
カテゴリー: AWS, Work | タグ: , , | コメントする

JBUG広島#10 復活と再会にLT登壇しました


はじめに

プロジェクト管理ツールBacklogのユーザ等の有志が参加しているJBUG(Japan Backlog User Group)では全国各地で勉強会を開催しています。ここのところ本業がバタバタしていたり、転職のタイミングだったりしてなかなか参加できずにいたのですが、今回広島での勉強会である「JBUG広島#10 復活と再会」の開催にあたり、募集時点ではCOVID-19の感染状況がやや落ち着いていたことと、なんと言ってもリアルとオンラインのハイブリッド開催ということで、これはリアルの雰囲気を味わわないわけにはいかないぞ! と持ち前の瞬発力がむくむくと出てきてしまい、参加することを決めただけではなく、運営の皆さんにお願いをしてLTします! と宣言して航空券の決済ボタンをポチッとしてしまったのでした。

LT登壇するにあたってのコンセプト

本来は「復活と再会」というコンセプトだったので、それに合わせてLTのお題を決めようかなと考えていたのですが、話のネタを練り込んでいくうちに、今回のコンセプトよりはむしろ、JBUG広島の根底として掲げられている、「皆でよりよい仕事のしかたを探そう」をベースとして、特に4か月前に転職をしたばかりだったので、より良い仕事の仕方やチームの作り方を実現するために、どのようにしてチームに参加していけばうまくいくのだろうかという自分なりの経験に基づくTipsを共有できたらいいな、という方向に舵を切ることにしました。

そんなわけで、LTのお題は「良いチームを作るために心がけていること」に決め、それをもとに自分自身がプロジェクトにJoinした時にいつも心がけていることをスライドに取りまとめていきました。

登壇スライドは以下の通りです。

JBUG広島#10 LT登壇資料

登壇してみての感想

これまでにJBUGでは数回オンラインで登壇したことがあるのですが、リアル登壇は初めてのことで、やはり目の前に聞いてくださる方がいるとなかなか緊張するし噛んだりするものですね。10分間の持ち時間はあっという間な感じでした。

それでも、その場の空気感やダイレクトに登壇内容の感想やフィードバックをいただけたことはとても嬉しく、JBUGが元々持っているポジティブな雰囲気をリアルに体感できたのがとても楽しかったです。また、この状況下で会いたくてもなかなかリアルに会うことができなかった方々ときちんとご挨拶ができたのもとても良かったです。Twitter上などでは交流は普段からしているけれども、初対面というのはなかなか不思議なものです。

みなさんの登壇内容は、今回に関しては共通する点が多かったなと感じました。プロジェクトマネジメントをどうするこうするという方法論というよりかは、プロジェクトをより良くするためのマインドセットや組織やコミュニティの作り方に関する発表内容が多かったですね。やはり現在の状況の中でリアルなコミュニケーションが減っている中で、それでもより良い仕事をするためにどのようにしてコニュニケーションを図っていくかというのは、今の時代だからこその共通のテーマなんだなということを強く感じました。

そういう意味でも、同じ課題認識をしていて、皆さんがそれぞれ思う形で現場からさらにより良くしていこうという意識を持たれている仲間が確実にいるんだなという、ある意味心強さを感じた勉強会になりました。

そして、繰り返しになりますが、リアル登壇って本当に楽しいなと心から思いました。これは本当にやみつきになりそう。

次回の登壇予定はまだ未定ですが、来るべき次の登壇に備えて、また色々とネタを仕入れていこうと思います。

フライトログ

  • 7/9(土) NH677 HND 10:40 – HIJ 12:00 /JA139A/2H
  • 7/9(土) NH684 HIJ 18:50 – HND 20:20 /JA151A/1C
カテゴリー: Work | タグ: , | コメントする

CloudWatch Agentのインストールと設定手順に関する備忘録


はじめに

EC2インスタンスの標準メトリクスでは取得できないようなリソース監視であったり、EC2インスタンス上のプロセス監視を行う時には、CloudWatch Agentをインストールして使用しますが、なかなか普段構築する機会がないものの、今後も一定の需要はあるので、自分メモ的な感じで備忘録としてまとめています。

スピード優先だったので、これが正解とは言えないと思うのですが、まぁ自分メモですので。

CloudWatch Agentのインストール方法

本来は、「新しいCloudWatch Agentでメトリクスとログの収集が行なえます」の記事と同じような形で順を追ってインストールして行ったほうが理解としては確実になるのですが、今回はスピード感重視であるのと、新規に構築したEC2インスタンスに対してインストールを行わなければいけなかったので、AWS Systems Manager Quick Setupの機能を用いてSSM Agentと同時にインストールしてしまいます。

インストールの手順は至って簡単で、

  • AWS Systems Manager – 高速セットアップ(Quick Setup)を選択して、高速セットアップ(Quick Setup)のコンソールに移動
  • 右上の[Create]ボタンをクリック
  • ウィザードに飛ぶので、Host Managementの[Create]ボタンをクリック
  • Configuration optionsのセクションで、下の画像の通り、チェックボックスを全て選択して、SSM Agentと一緒にCloudWatch Agentも自動的にインストールできるようにする
  • Targetsのセクションで、インストール対象のEC2インスタンスがいるリージョン(基本的にはCurrent Regionで問題ないはず)で、Manualを選択して、インストールしたいEC2インスタンスを選択する
  • 右下の[Create]ボタンをクリックすれば、勝手にSystems Manager側で、SSM AgentのインストールとCloudWatch Agentのインストールまでやってくれる

追加のIAMポリシーの設定

Quick Setupの機能を用いてインストールを実行すると、デフォルトでEC2インスタンスに対して、AmazonSSMRoleForInstancesQuickSetupというIAMロールがアタッチされるのですが、これだけではCloudWatch Agentの初期設定ができないため、あらかじめ、このIAMロールに対して、CloudWatchAgentAdminPolicyというAWS管理ポリシーを追加でアタッチしてあげる必要があります。

自分の場合はここで思い切りひっかかってしまったので要注意。

これでCloudWatch Agentの初期設定は完了です。

CloudWatch Agentの初期設定

sshでEC2インスタンスに入り、

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard

を実行するとウィザードが立ち上がるため、その内容に従って淡々と設定を進めていけば問題がないかなと思います。1箇所だけ、使い回しを利かせるため、以下の内容については”1″を選択しておいた方が便利です。

Do you want to store the config in the SSM parameter store?
1. yes
2. no
default choice: [1]:
1

で、最終的に以下のメッセージが出力されれば、初期設定は無事に完了になります。

Successfully put config to parameter store AmazonCloudWatch-linux.
Program exits now.

CloudWatch Agentは、運用上様々な機会で使用することが多いと思うので、できるだけ設定作業は簡素化しておきたいところ。今回は手作業で試してみましたが、今後のことも考えてコード管理できるようにしたいと思っています。

カテゴリー: AWS, Work | タグ: , , | コメントする

AWS認定SysOpsアドミニストレータ アソシエイト(AWS SOA-C02)に合格しての感想


昨年の夏にAWS認定ソリューションアーキテクト プロフェッショナルに合格してからかなり間が空いてしまったのですが、なんとか次の目標にしていたAWS認定SysOpsアドミニストレーター アソシエイト試験に774点で合格することができました。スコアとしてはもう少しとりたかったなというのが本当のところですが、これがいまの実力なので仕方がないでしょう。これでアソシエイトレベルの認定資格は全て取得することができました。AWS CLFを含めると5冠となります。

どんな風に勉強したのか

この記事を書いている2022/5/21時点では、参考書などは全く出版されていません。なのでどんな風に勉強を進めていけばいいのか最初は皆目見当がつきませんでした。しかも昨年の夏に試験のバージョンが上がってから難易度は確実に上がり、通常の選択式試験に加えてラボ試験が導入されたので、余計に困ってしまいました。

先に受験日を決めてしまう

この試験に限らずなのですが、まずは先に受験日を決めてしまいましょう。そこから逆算して勉強する計画を立てるのが一番いいです。というか受験日を決めておかないと、マイルストーンがぼやけてしまってついつい勉強をサボりがちになってしまうので、追い込みすぎない程度に追い込んでおくのが大事です。

自分の場合、結果的に半年コースになってしまったのですが、3ヶ月を一区切りとして勉強するように心がけていました。

出題範囲となるBlack Beltをとにかく読む

出題範囲は正直広いんですが、まずはBlack Beltと呼ばれる「サービス別資料 | AWSクラウドサービス活用資料集」から、運用に関連するサービスの資料を片っ端から読んでいき、サービスごとに要点を取りまとめていきました。要点を取りまとめるときにはDay OneというmacOS向けのジャーナルアプリケーションを活用しました。勉強を開始したのが転職の前で、InspectorやCloudTrailといったガバナンス系のサービスをなかなか使ってこなかったので、各サービスの特徴を理解してまとめるという流れは、頭の整理に結構役に立ったのではないかなと思います。

AWS SAPの参考書を読む

AWS SOAの試験範囲とは直接関係ないように思えますが、実は出題範囲の中にAWS SAPで問われる内容と結構被っているというのを、模擬試験を受けてみた中で強く感じたのと、ちょうど良いタイミングで「AWS認定資格試験テキスト&問題集 AWS認定ソリューションアーキテクト – プロフェッショナル」という、かなりまともな参考書が発売されたということもあり、この内容をざっと読みながら、やはり同じようにDay One上にまとめていきました。複雑な構成に関する問題もちょろちょろ出てくるので、結果としてですが、先にAWS SAPをとっておいてよかったなと感じています。

この本は試験対策以外にもAWS上でリソースを構成していくときの参考になるので、1冊持っておいても損ではないと思います。個人的にはおすすめ。

問題を解きながら間違えた箇所を復習していく

問題集に関しては、幸いにしてUdemyや通称”koiwa”と呼ばれているWEB問題集があるので、それを片っ端から解いていきながら、間違えた箇所に関するAWSの公式ドキュメントを読んで復習するという流れを2回繰り返しました。この時はすでに転職後でNotionが使えるようになっていたので、受けてはNotionにまず転記して、間違えた箇所を復習という流れを繰り返していきました。

ラボ試験対策

ラボ試験対策と言っても、もともと実務でAWSマネージメントコンソールは頻繁に触っていたので、特別なことはしていないです。ただ、座学だけでは絶対にラボ試験はクリアできないというのは確かです。というのは、この試験が単純に知識だけを求めているものではないので。そういう意味では、個人的にはラボ試験が導入されたのは正解だったんじゃないかなと思っています。

ラボ試験の内容そのものはそれほど特別なことを求めているわけではないので、AWSマネージメントコンソールに触ったことがない人は、クレジットカードを人質にしてでも個人用のAWSアカウントを取得して、出題されている範囲のコンソールがどのようになっているのかだけでも確認したほうが絶対にいいです。

次の目標

もうここまできたのだから、あとはAWS認定DevOpsエンジニア プロフェッショナル(AWS DOP-C01)に合格して、6冠を達成することのみです。AWS SAPを受験した時のように、ちょっと特別体制を敷いて受ける必要が出てくるのですが、そこはきちんと計画を立てて準備を進めていこうと考えています。

カテゴリー: AWS | タグ: | コメントする

Violaレッスン6回目


Violaのレッスンを始めてからちょうど3か月が経過しました。習い始める前までは、楽器を弾くこと自体は楽しいのだけれども、色々姿勢が崩れていてものすごい力を入れないといけなかったところが、りきひさみねこ先生のはっと驚くようなご指導で、変な力を入れなくても楽に弾けるように、徐々になってきました。

前回は肩当てを外して、教本の練習をしたわけですが、今回からは左手の改造に本格的に着手を開始しました。

これまではどちらかというと左手をすぼめるような形で弦を押さえていたせいで、親指に負荷がかかってしまい、ポジション移動が結構大変だったり正しい位置に指をホールドできないという個人的な悩みがありました。まぁその辺って結局これまで半分自己流でやってきてしまったので、気がつかないうちに左手の使い方も変な風になってしまっていたんでしょうね。

ここで先生のご指導が入るわけです。

  1. まず左手の基本は、手のひらが自分の方向を向くようにすること
  2. 左手の手のひらを丸めるのではなくて、開くようにすること
  3. 指は傘の柄の部分のように、指板に指を引っ掛けるようにするようなイメージで置くこと

特に2番目の手のひらを開くようにすることは、これまで全然イメージをしてこなくて、ましてや、手の肉球をできるだけ押し出すようにしながら構えるというのが、初回だからということもあってかなり大変でした。これは先生に教えていただいたストレッチの方法を毎日続けながら、こわばっていた手と手首の柔軟性を少しずつ取り戻していくしかないですね。

けれどもこれを取り戻せば、今までよりも力を入れずにより自由に左手の指を抑えられるようになりそうなので、絶対にマスターしておきたいところです。ひとまずストレッチだけは毎日続けています。

こんな感じで、手の使い方、指の使い方の改造はまだまだ続きます。一通りの解像が終了したところで、自分の弾き方がどういう風に変わっていくのか、楽しみで仕方がありません。次回以降のレッスンも楽しみです。

それと、音階練習も始めていきましょうということで、カール・フィッシャーの音階教本を使うことになりました。一旦SITTは卒業です。音階練習、今まで積極的にやってこなかったので、こちらも先生のお力をお借りしながら強化していきたいところです。

カテゴリー: Private | タグ: , | コメントする

長期間スイッチロールしていないIAMユーザーを棚卸する仕組みを作ってみた


AWSアカウントやAWS IAM周りの管理って大変だ

AWSアカウントやAWS IAM周りの管理って、AWS Organizationsのようなサービスを使用していようがいなかろうが結構大変ですよね。特に何が大変かって、長期間ログインしていないIAMユーザーの棚卸から始まって、MFA認証の有無やらパスワードポリシーの管理やら何やら。

弊社では、マスターのAWSアカウント上でのみIAMユーザーを作成し、各プロジェクト用のAWSアカウントに対してはスイッチロールのみでアクセスできるようにしているのですけれども、やはりみんながみんな恒常的にAWSマネージメントコンソールにアクセスするとは限らず、プロジェクトが安定運用に入ってくると、アクセスするIAMユーザーの数も限られてきます。そうなると、やはり一定期間ごとに、そのAWSアカウントにアクセス可能なIAMユーザーの棚卸をすることがセキュリティ的にも必要になってくるわけです。

これを手作業でいちいち調べていると当然結構な労力になってくるわけで、その労力を軽減するためにも、とりあえず長期間スイッチロールそのものを行っていないIAMユーザーを探し出すための仕組みを考えて実装してみました。図でそのワークフローを書くとこんな感じになります。

ワークフロー

ここでは2本のワークフローを組みます。

  1. IAMユーザーがスイッチロールしたイベントはAWS CloudTrailで自動的に検知されるため、証跡ログがAmazon S3バケットに格納された時点で、PUTイベントをトリガーとしてAWS Lambda関数を呼び出して、Amazon DynamoDBにその履歴を格納するワークフロー
  2. Amazon EventBridgeのcron式をトリガーとしてAWS Lambda関数を呼び出して長期間スイッチロールしていないIAMユーザーを割り出し、Slack Incoming Webhookの仕組みを使用してSlackの特定のチャンネルにIAMユーザーのリストを通知するワークフロー

DynamoDBに作成するテーブルはこんな感じです。

aws dynamodb create-table \
    --table-name SwitchRoleHistory \  
    --attribute-definitions \
        AttributeName=eventID, AttributeType=S \ 
        AttributeName=eventTime, AttributeType=S \
    --key-schema AttributeName=eventID, KeyType=HASH AttributeName=eventTime, KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=1, WriteCapacityUnits=1

少なくともeventTime属性はソートキーとして機能できるようにしておきます。eventID属性はあくまでもプライマリーキーとしてのみ機能するという感じです。ここではRCUとWCUの値を最小値にしていますが、スイッチロールが発生する頻度によって調整をかけた方がいいかもしれません。

1番目のワークフローのLambda関数

1番目のワークフローのLambda関数はこんな感じで作成してみました。ちょっと一部だけ、実際に動作しているものからは改変しています。

# --------------------------------------------------
# function name:
#     saving-record-to-dynamodb-from-cloudtrail
# usecase:
#     detect to SwitchRole user from Amazon CloudTrail logs and
#     put into Amazon DynamoDB Table.

import json
from operator import truediv
import urllib.parse
import boto3
import zlib
import logging
import traceback
import os
import re


AWS_ACCOUNT = os.environ.get('AWS_ACCOUNT')
DYNAMODB_TABLE = os.environ.get('DYNAMODB_TABLE')
EVENT_NAME = os.environ.get('EVENT_NAME')
CROSS_ACCOUNT_ROLE_ARN = 'arn:aws:sts::' + AWS_ACCOUNT + ':assumed-role/'

logger = logging.getLogger()
logger.setLevel(logging.INFO)

s3Bucket = boto3.resource('s3')
db = boto3.resource('dynamodb')
tableName = db.Table(DYNAMODB_TABLE)


def parseS3CloudTrail(bucket_name, key):
    
    s3Object = s3Bucket.Object(bucket_name, key)

    # Decomplessing Objects in S3 Bucket
    payload_gz = s3Object.get()['Body'].read()
    payload = zlib.decompress(payload_gz, 16 + zlib.MAX_WBITS)
    payload_json = json.loads(payload)

    # To get AssumedRole information and put into DynamoDB 
    for record in payload_json['Records']:

        logger.info("[STEP2] eventName is: {}".format(record['eventName']))

        if EVENT_NAME == str(record['eventName']):

            logger.info("[STEP3-1] record is: {}".format(record))
            logger.info("[STEP3-2] arn: {}".format(record['userIdentity']['arn']))

            saveRecord(record)


def saveRecord(record):

    logger.info("[STEP4] Saving record to DynamoDB")

    arnFlg = False

    # Search Arn
    tempArn = record['userIdentity']['arn']

    # for master AWS Account
    findStr = r'_role/(.*)'
    responseArn = re.search(findStr, tempArn)
    arnFlg = True

    logger.info("[STEP4-1] detected responseArn is: {}".format(responseArn))

    # for master AWS Account
    if responseArn is None:

        findStr = r'user/(.*)'
        responseArn = re.search(findStr, tempArn)
        arnFlg = False

        logger.info("[STEP4-2] detected responseArn is: {}".format(responseArn))

    logger.info("[STEP5] responseArn: {}".format(responseArn))

    if responseArn is not None:
        responseArn = responseArn.group(1)

        if arnFlg == True:
            userName = responseArn
        else:
            userName = record['userIdentity']['userName']

        # Put into DynamoDB Table
        result = tableName.put_item(
            Item={
                'arn': responseArn,
                'userName': userName,
                'eventTime': record['eventTime'],
                'eventID': record['eventID'],
                'record': json.dumps(record)
            }
        )

    logger.info("[STEP6] Saved record to DynamoDB")


def lambda_handler(event, context):

    logger.info("[STEP1] Loading function")

    try:

        # Get current S3 Bucket Name and Key
        for record in event['Records']:
            bucket = record['s3']['bucket']['name']
            key = urllib.parse.unquote(record['s3']['object']['key'])

            logger.info("[STEP2] BucketName: {}".format(bucket))
            logger.info("[STEP2] Bucket Key: {}".format(key))

            # Get information and put into DynamoDB Table
            parseS3CloudTrail(bucket, key)

        logger.info("[STEP7] Finished Function")

        return {
            'StatusCode': 200,
            'message': 'Saving record to DynamoDB from CloudTrail is finished nomaly.'
        }

    except Exception as e:
        logger.error(e)
        logger.error(traceback.format_exc())
        
        return {
            'StatusCode': 500,
            'message': 'An error occured at Saving record to DynamoDB from CloudTrail.'
        }

なんか、logger.infoがやたら多くない? というツッコミはさておき、AWS CloudTrailからAmazon S3バケットへのPUTイベントが走った時点で、eventオブジェクトの中から、PUTされてきた圧縮済み証跡ファイルの情報を取得した後、それをまずは解凍してJSONからarnの情報を取得して、同じJSONの中にある他の情報と一緒に、最初に作成したDynamoDBのテーブルに突っ込む、という流れです。

arnの情報からのマッチングに、正規表現を用いています。マスターのAWSアカウント側では実はわざわざこんなことをする必要はないのですが、要件によって、スイッチロールされた側のAWSアカウント上でも同じ仕組みを入れたい、となった場合にも最低限のコード修正で対応できるように、わざわざarnの情報から正規表現を用いてマッチングをかけてIAMユーザー名を取得しています。

2番目のワークフローのLambda関数

2番目のワークフローのLambda関数はこんな感じで作成してみました。

# --------------------------------------------------
# function name:
#     check-long-time-switch-user-from-dynamodb
# usecase:
#     detect to long time SwitchRole user from Amazon DynamoDB and
#     send to Slack via Slack Incoming Webhook.

import json
import boto3
import logging
import traceback
import os
import operator
import datetime
import dateutil.parser
import urllib.request


DYNAMODB_TABLE = os.environ.get('DYNAMODB_TABLE')
TIME_DELTA = os.environ.get('TIME_DELTA')
SLACK_WEB_HOOK_URL = os.environ.get('SLACK_WEB_HOOK_URL')
SLACK_USER_NAME = os.environ.get('SLACK_USER_NAME')
MSG_OLD_ARN_IS_NONE = os.environ.get('MSG_OLD_ARN_IS_NONE')
MSG_OLD_ARN_IS_EXIST = os.environ.get('MSG_OLD_ARN_IS_EXIST')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

db = boto3.resource('dynamodb')
table = db.Table(DYNAMODB_TABLE)

pjExp = "#id, eventTime, arn"
exAttName = {"#id": "eventId"}


def getValueFromKey(value, key):
    try:
        return str(value[key])

    except KeyError as e:
        return "None"


def sendSlack(sendText):
    try:

        # Generate Slack messages
        sendMessage = {
            "text": sendText,
            "username": SLACK_USER_NAME,
            "icon_emoji": ":dog:"
        }
        sendMessage = json.dumps(sendMessage)    

        # Send message to Slack channel via Incoming Webhook
        request = urllib.request.Request(
            SLACK_WEB_HOOK_URL,
            data=sendMessage.encode('utf-8'),
            method="POST"
        )

        with urllib.request.urlopen(request) as response:
            responseBody = response.read().decode('utf-8')

        logger.info("[STEP8] send to Slack finished.")
        return responseBody

    except Exception as e:
        logger.error(e)
        logger.error(traceback.format_exc())
        
        return {
            'StatusCode': 500,
            'message': 'An error occured at Check long time switch user from DynamoDB.'
        }


def lambda_handler(event, context):

    logger.info("[STEP1] Loading Function")

    try:
        count = 0

        # Get items from DynamoDB Table
        result = table.scan(
            ProjectionExpression=pjExp,
            ExpressionAttributeNames=exAttName
        )

        jsonDict = [""] * len(result["Items"])

        # Get arn and eventTime into JSON dictionary
        for item in result["Items"]:

            jsonDict[count] = {}
            jsonDict[count]["arn"] = getValueFromKey(item, "arn")
            jsonDict[count]["eventTime"] = getValueFromKey(item, "eventTime")

            count = count + 1

        logger.info("[STEP2] jsonDict: {}".format(jsonDict))

        # Sorting JSON dictionary Order By eventTime Desc
        sortDict = sorted(jsonDict, key=operator.itemgetter('eventTime'), reverse=True)

        logger.info("[STEP3] sort is completed. sortDict: {}".format(sortDict))

        # Srote in each array as arnList and lastLoginTimeList
        arnList = [arn.get('arn') for arn in sortDict]
        lastLoginTimeList = [lastLoginTime.get('eventTime') for lastLoginTime in sortDict]
        oldArnList = []
        evalFlg = 0

        if len(arnList) == len(lastLoginTimeList):
    
            # Generate old switchrole users into new array
            for tmpArn, tmpLastLoginTime in zip(arnList, lastLoginTimeList):

                timeDelta = datetime.timedelta(days=int(TIME_DELTA))
                evalDate = datetime.datetime.now() - timeDelta
                evalDate = evalDate.astimezone()

                logger.info("[STEP4-1] timeDelta is: {}".format(evalDate))

                tmpLastLoginTime = dateutil.parser.parse(tmpLastLoginTime)

                logger.info("[STEP4-2] tmpLastLoginTime is: {}".format(tmpLastLoginTime))

                if tmpLastLoginTime > evalDate:
                    evalFlg = 1
                    continue
                else:
                    evalFlg = 0
                    logger.info("[STEP5-1] Old Switched User is: {}".format(tmpArn))

                    for tmpArn2nd, tmpLastLoginTime2nd in zip(arnList, lastLoginTimeList):

                        tmpLastLoginTime2nd = dateutil.parser.parse(tmpLastLoginTime2nd)
                        
                        if tmpArn != tmpArn2nd:
                            continue
                        elif tmpArn == tmpArn2nd and tmpLastLoginTime < tmpLastLoginTime2nd:
                            evalFlg = 1
                            continue
                        else:
                            logger.info("[STEP5-1] evalFlg is: {}".format(evalFlg))

                            if evalFlg == 0:
                                oldArnList.append(tmpArn)
                                logger.info("[STEP5-2] appended list of tmpArn is: {}".format(tmpArn))

        oldArnList = list(set(oldArnList))
        logger.info("[STEP6] old IAM Users are this list: {}".format(oldArnList))

        # Generate Slack messages and old switch role user list
        if oldArnList == []:
            sendText = TIME_DELTA + MSG_OLD_ARN_IS_NONE
            logger.info("[STEP7-1] oldArnList is NOT Exist.")
        else:
            sendText = TIME_DELTA + MSG_OLD_ARN_IS_EXIST + ' ' + str(oldArnList)
            logger.info("[STEP7-2] oldArnList is Exist.")
        
        sendSlack(sendText)

        logger.info("[STEP9] Finished Function")

        return {
            'StatusCode': 200,
            'message': 'Check long time switch user from DynamoDB is finished nomaly.'
        }

    except Exception as e:
        logger.error(e)
        logger.error(traceback.format_exc())
        
        return {
            'StatusCode': 500,
            'message': 'An error occured at Check long time switch user from DynamoDB.'
        }

ここでまずは言い訳させてください。本当はSQL文で言うところの、SELECT DISTINCT文を使いたかったのですが、当然のことながら、Amazon DynamoDBにはそんな機能は兼ね備えていません。かといって、get_item()関数ではひとつのレコードしか返すことができないので、ちょっとコストはかかりますがscan()関数を実行してeventTimeとarnを引っ張り出しているという苦しいことをしております。Amazon DynamoDBって、リレーショナルにするまでもない情報を格納するにはお手軽だしコストもかからないので楽なのですが、こういうケースでちょっと痒いところに手が届きにくいというのがちょっと辛いですね。

なので、後半で、eventTimeでソートをかけた後に、リストの中身を二重にぐるぐる回して、最終のスイッチロール実行から一定期間が経過したIAMユーザーを探し出すという面倒臭いことをしています。この辺りのコードはもう少しエレガントになるように改良したいところ。

SlackのIncoming Webhookを使用してPostする関数は、教科書通りです。

実行結果

この2つのワークフローを回していくことによって、Amazon DynamoDBの中には少しずつスイッチロールしたIAMユーザーの履歴が溜め込まれていって、Slackには以下のようなイメージでメッセージが定期的に通知されてきます。

なかなかこの手の作業を自動化するのって面倒ではあるのですが、まずはこの実装をきっかけに、管理できていないところを可視化するという営みまでは実現することができました。この先の計画としては、可視化されたIAMユーザーから、該当するスイッチロール権限を自動的に削除する、というところまで行き着くところなのですが、この辺は運用も絡む世界なので、さらに整理しながら拡張を続けていきたいなと思っています。

カテゴリー: AWS, Work | タグ: , , , , , , | コメントする