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 | タグ: , , , , , , | コメントする

Violaレッスン5回目


前回のレッスンの記録を書くのを忘れていましたが、りきひさみねこ先生のViolaのレッスンも5回目まできました。月に2回ちゃんと通っております。

今回も右手の修正を引き続きやる形になるのかなと思いきや、最初に開放弦でボウイングの練習をした後に、じゃあ左手もつけてみましょうということで、教本”SITT”の1番を少しずつ弾いていくことになりました。

肩当てなしでも弾けるらしい

これまでずっと肩当てをつけた状態で弾いてきたので、開放弦でのボウイングはともかくとして、左手をつけた状態で弾くことにはちょっとだけ不安があったのですが、実際に弾いてみると、音程はともかくとしても案外弾けるじゃないですか!

まずは楽器の1の指(人差し指)と3の指(薬指)を押さえる部分にテープを貼ってもらい、それぞれの指がそこから手前に行かないように目印にしてもらいました。

その上で、初めはスラー抜きで半分くらいを弾き、次にスラー付きで半分くらいを弾き、という流れに。音程のところで若干個人的に”?”(特に2の指と4の指)と思ってしまうところはあったのですが、それはともかくとしても肩当てなしでも曲がりなりに弾けることが分かった上に、これまで右手の修正とボウイングの練習を重ねてきたので、レッスンを始める以前よりもだいぶ弾きやすくなったという感覚です。弾いている自分自身が驚きました。

右手はC線を弾くときに手の甲の関節が曲がってしまうところだけはちゃんと気をつけなければいけないのですが、中指から小指にかけての弓の持ち方を修正したことと、親指の位置も修正したことがだいぶ幸いしたかもしれません。これまでは割と力技で弾いてきた記憶があるのですが、そこまで力を入れなくてもきちんとした音量で弾けるようになったのが嬉しいですね。

それから先生に指摘されて、おっと思ったのが、背筋が伸びてきたというところ、最初のレッスンの時はだいぶ猫背気味だったらしいんですよね。背の高い人が総じて肩が前のめりになってしまって猫背気味になってしまうらしいです。その傾向がだいぶ改善されてきたと。

肩が前のめりになる点、特に左肩が巻き肩っぽくなってしまう傾向は自分自身もなんとなく気になっていて、就寝前に肩を逆に反らせるようなストレッチをしているのですが、もしかしたらそれが功を奏したのかもしれません。

次回以降しばらくは、SITTの1番を弾きながら左手の修正に取り掛かる感じになるかなと思うのですが、まずは肩当てなしでも十分に楽器を弾くことができるんだということが結構嬉しかったです。

次回のレッスンも俄然楽しみになってきました。

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

携帯電話回線をahamoへ切り替え


はじめに

総務省からの強い指導もあり、携帯電話キャリアの選択肢は大幅に広まりましたね。ちょっと前までは大手3社がひしめき合う構造でしたが、MVNO回線やら大手キャリアも格安のプランを提供するようになって、混戦状態が続く一方で、我々ユーザーにとっては自分の利用スタイルに応じて利用したいキャリアやサービスを自由に選択できる環境が少しずつ広がってきたと思います。

なぜキャリアの変更をしたのか

これまでは10年以上に渡ってとある大手の携帯キャリアを使用し続けてきたのですが、家族全体の利用料金だけでも、月によって若干の誤差はあるものの、家族3人で大体月あたり34,000円くらいかかっていました。これは固定費として考えると家計のうちの結構なボリュームを占めるのでかなり痛い。

もちろん適宜料金プランの見直しをかけてきたのですが、現在のようにリモートワーク化が進むと、データ通信量を案外使わないんですよね。自宅では基本的にWi-Fiがフル稼働しているからそっちに繋いでしまえばいいわけで、リアル出社時や休日にカフェで作業をするときにはテザリングでMacBook Airに繋ぎますが、それでも1か月に使用するデータ量は数GB程度。

しかも現在のキャリアにはいまだにテザリングするために手数料がかかるという。データ通信の中には当然移動中や移動先でのテザリングを使用する機会が少なくないと思うのですが、これにかかる年間のコストだけでも積み上げればそれなりなので、テザリングにかかる手数料もこの際削減してしまいたいなと。

あとは、もちろん家庭によって利用シーンはまちまちだと思うのですが、少なくとも自分に関しては、大容量プランを使用していてもコスト対効果が見合わないという実情がありました。

もちろん大手キャリアの場合には新型の端末を割賦によって購入できるという利点がありますが、これも色々とカラクリがあって、代理店側にはキャリアからちゃんとお金が流れていくような仕組みが整っているので、定期的に新しい端末に買い替えなければいけないという状況も出てきてしまいます。もちろん、この辺についてもそのうちに総務省の手が入るのではないかと思っているのですが。

そんなこんなで負荷をかけている携帯電話の固定費を削減するために、大手携帯電話キャリアが提供している格安のサービスから、通信エリアや安定性などを考慮した上で、自分の場合はahamoに乗り換えることにしたわけです。

契約申し込みはあっという間

今回はすでにiPhone12 miniを筆頭に複数のデバイスを所持しているということもあって、SIMだけの契約にしました。eSIMも考えたのですが、この場合はと使用できるデバイスが限られてしまうため、物理的なSIMのみを契約することに。申し込みの流れの画面から自分が持っているデバイスがahamoに対応しているかどうかをフローチャートに沿って確認してみるだけで概ねそのデバイスが対応しているかどうかの判断がつきます。この辺のUIはとても軽くて使いやすいですね。

今回はiPhone 12 miniと、何かの拍子で契約していたときにほぼほぼ無料で購入できたGalaxy A20で利用できるかどうか確認をしてみたのですが、どちらも対応。

もちろんキャリアメールなどは今時使用する頻度は完全と言っていいくらいないので選択肢からは当然外れます。Gmailアカウントひとつ持っていればいいだけなので。

そんなわけで申し込みの画面からサクサクと契約形態を選択していって、あっという間に契約手続き完了。MNPの場合にはもう少し煩雑な手続きが入りますが、その煩雑さを解消するのと、今回はテザリングでの使用が主目的だったので、新規電話番号を取得する形で契約を行いました。

SIMの配送から開通まで

SIMカードの到着までにかかった日数は概ね3日くらいでしょうか。eSIMを使用する場合はもっとリードタイムが短縮できるかもしれません。契約申し込み時に受付番号が発行されるので、受付番号でログインの画面から追うことで随時状況を確認することができます。

そうこうしているうちにSIMカード到着。開通の儀に入ります。が、この日は定期メンテナンス日で朝になるまで開通手続きが行えません。docomoの場合は、毎週火曜日から水曜日にかけての深夜帯がメンテナンス日に該当するので夜中に開通手続きをするためには注意が必要ですね。

そんなわけで、メンテナンスが終了する時間を待って、いよいよ開通の儀に入ります。SIMカードの発送が行われた時点で、実は受付番号でログインの画面から開通手続きをすることができるのですが、その時点で開通の手続きができてしまい、完了した時点から料金が発生するので、ここはグッと待ちました。

「開通の儀」なんて恭しく言ったところで、なんてこない、まずはデバイスにSIMカードを挿して、Webサイト上から開通手続きをしてあげるだけです。そこから実際に回線が使えるようになるまでの時間は概ね1分程度でした。これで晴れてahamo生活スタートです。

契約までの敷居がかなり低くなった

具体的にはこれから使っていくことで、実際にどれくらいのスピードやトラフィックで通信ができるのかを徐々に検証していこうと思うのですが、少なくとも自身でデバイスのSIMの抜き差しさえできるのであれば(eSIMの場合はその必要すらありませんが)、契約の手続きに関しては、UIの秀逸さもあってか、かなりスムーズでした。

面倒臭いとすれば、MNPで転出するときに、前のキャリアがいちいち引き留め工作に出ようとするところくらいでしょうか。諸々メリットが書かれているんですけれども、逆にキャリアがメリットと考えている部分が我々にとってはデメリットだったりすることもあるんですよね。もちろん囲い込み戦略とかあるんでしょうけれども、MNPの転出手続きに至る流れももう少し簡素化してほしいものです。

実際の使用感についてはすでにさまざまな方がさまざまな記事で書かれていると思うのですが、自分なりの使い方で、前のキャリアと比較してどのように変わったのかは後日追記していきたいと思います。

少なくとも固定費削減のための足がかりは掴めたということで、今のところは文句なしですね。

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

今更ながらCloudMapperをローカルに構築してみた


CloudMapperとは

普段Amazon Web Services(AWS)の設計や構築を行うにあたって、AWSリソースをどのように構成するか、そのネットワーク構成がどうなっているのかを紐解くために、draw.ioCacooを使って構成図を書くことがちょいちょいあると思います。

設計の段階では確かにこれらのツールを使用してどのような構成にするか、どのようなAWSリソースの配置を行うかを検討するのにはとても有用なのですが、どのAWSリソース(例えばEC2インスタンスとかECSサービスとか)にどういうSecurity Groupがアタッチされているの? とか、Internet Gatewayから目的のAWSリソースに対してちゃんと論理的に通信できる状態になっているの? といった内容を構成図上で読み取るのはなかなか大変ですし、例えば想定通りの通信ができないといった事象に対してマネージメントコンソールやVPCフローログだけで解析を行うのにはそれなりに苦労をするのもまた実情だと思います。

そこを補完して、AWSリソース群を実際のAWS環境から収集して、ネットワーク構成図として落とし込んでくれるのが、CloudMapperというツールです。

ローカルの環境に構築してみる

CloudMapper自体の構築手順は、さまざまなWebサイト上で解説されているのですが、ここではローカルの環境であるMacBook Pro (16-inch, 2019, Intel / macOS Monterey 12.2.1)上に構築してレポート出力していく手順を、できるだけ詳細に記述していこうと思います。

前提条件

まずは、ネットワーク構成図を作成したい対象のAWS環境に、以下のIAMポリシーが少なくともアタッチされているIAMユーザーを作成している必要があります。

  • ViewOnlyAccess (ReadOnlyAccessでも可能)
  • SecurityAudit

また、ローカルの環境上に、少なくともPythyon3.8以上がインストール済みである必要があります。README.mdではPython3.7でも動く的なことが書いてあるのですが、3.8にはなっていたほうがいいのかなと思います。

さらにこのIAMユーザーから、あらかじめSecret KeyとSecret Access Keyを取得しておく必要があります。ご存知の通り、Secret KeyとSecret Access Keyは漏れ出すと大変なことになるので管理には十分に気をつけてください。

CloudMapperをインストールしていく

ここではホームディレクトリ上にインストールしていくことを前提としています。以下のコマンドを順次実行してインストールしていきます。

% git clone https://github.com/auto-labs/cloudmapper.git
% brew install autoconf automake libtool jq
% python3 -m venv foobar
% source foobar/bin/activate
(foobar) % pip install --prefer-binary -r requirements.txt
  1. GitHubからCloudMapperのソースコード一式をcloneしてくる
  2. Homebrewを使用して、動作させるために必要なライブラリをインストールする
  3. pythonの仮想環境を構成する(ここでは仮想環境としてfoobarという名称にしています)
  4. 仮想環境上で、CloudMapperの動作に必要な追加モジュールが記述されている”requirements.txt”の内容をバイナリでインストールする

AWS CLIの環境設定をしていく

ここでは前提条件の中で書き記したIAMユーザーのSecret KeyとSecret Access Keyの情報を

% aws configure

で登録していきます。ここは詳細は省きますが、リージョンは”ap-northeast-1″(東京リージョン)で、出力形式は”json”で問題ありません。

ここまででCloudMapperの一連のインストールと事前準備は完了です。

CloudMapperを実行するための事前準備をする

CloudMapperは”config.json”というJSONファイルの中に初期設定情報を持ちます。この中身は、どういったプロファイル名で、どこのAWSアカウントから取得したい情報を参照するかが記述されています。この初期設定情報をあらかじめ取得するために、以下の情報を、Pythonの仮想環境上でコマンド実行して設定していきます。

(foobar) % python3 cloudmapper.py configure add-account --config-file config.json --name (プロファイル名) --id (取得対象のAWS Account) --default DEFAULT
(foobar) % python3 cloudmapper.py configure add-account --config-file config.json --name (別のプロファイル名) --id (取得対象のAWS Account)

デフォルトでは1行目で設定した項目が使用されるので、別のプロファイルに切り替えたいときは2行目を使用する、といったイメージです。

AWS環境のリソース情報を収集する

ここで実際にCloudMapperを実行し、ネットワーク構成図を出力するのに必要なAWS環境のリソース情報を取得していきます。注意点としては、仮にap-northeast-1上のAWSリソースだけを取得したくても、ありとあらゆるAWSリソースを収集しにかかるので、グローバルリソースも含めて、全リージョンのAWSリソースの情報収集を行うようです。そのため、環境にもよりますが、初回実行には手元の計測ではコマンドの実行が終わるまでに大体30分程度かかりました。Pythonの仮想環境上で、以下のコマンドを実行します。

(foobar) % python3 cloudmapper.py collect --config config.json --account (プロファイル名)

レポート出力の準備を行う

上記のコマンドでAWS環境のリソース情報を収集し終えたら、レポート出力の準備のために、Pythonの仮想環境上で、以下のコマンドを実行します。この準備自体は、構築されているAWSリソースが軽いものであればあっという間に実行が完了します。

(foobar) % python3 cloudmapper.py prepare --config config.json --account (プロファイル名)

レポートを出力してみる

レポート出力の準備が完了したら、CloudMapperが内部的に持っているWebサーバ機能を利用してレポート出力をするために、Pythonの仮想環境上で以下のコマンドを実行します。

(foobar) % python3 cloudmapper.py webserver

ここまで完了したら、http://127.0.0.1:8000/ に、Webブラウザ上でアクセスします。すると、こんな感じのレポートが出力されると思います。(以下はあくまでも一例です)

無事にネットワーク構成図が出力されました。ちょっと形が不恰好ですが、それぞれのAWSリソースアイコンや、矢印の部分をクリックすると、どんな設定でAWSリソースが構成されていて、どういったネットワーク構成になっているかがjson形式で表示されます。

所感

こんなわけで、ネットワーク構成図を出力することができるのですが、例えばVPC外に構成されるAWS LambdaやAmazon S3といった情報はどうもスタンドアロンでは出力することができないようです。ただし、Amazon API Gatewayに紐づけられたVPC Lambdaや、VPC Endpoint経由で紐づけられたS3バケットなどは出力される模様(具体的な検証はまだできていませんが、サンプルを見る限りではどのように見受けられます)。

なので、用途としては、ネットワーク構成を紐解いたり、トラブルシューティングにあたってSecurity Groupが正しく対象となるAWSリソースにアタッチされていて通信可能なのかを解析する用途には非常に有用なのではないかと思います。

ローカル環境への構築自体は非常に簡単で、待ち時間を含めて2時間程度で完了してしまうので、構成図だけではネットワーク構成をうまく可視化できないなという悩みを持たれている方には、非常に良いツールなのではないかと思いました。

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

転職のご報告


はじめにご挨拶

2022/2/28をもって、約3年半勤めた前職を退職し、2022/3/1から新しい会社でのキャリアをスタートしました。

前職のうち、特に某大手携帯電話会社の案件では本当に様々な方にお世話になっただけではなく、案件を通じてAmazon Web Servicesだけでなく、ネットワーク領域を中心とする様々な知識とナレッジを蓄積することができました。関係者の皆様にはこの場を借りて改めて心から御礼申し上げます。

2022/3/1からは渋谷にある某スタートアップ企業で働き始めました。初日はオリエンテーションと環境の整備が中心でしたが、2日目からは早速具体的な業務に関する相談を受けつつも、これまでに携わったことのない領域、特にリリースの自動化に関する領域や、どちらかというとSRE的な役割に近い領域の改善に早速取り組み始めています。

初めてのスタートアップ系の企業ということで、今まで働いてきた企業とのカルチャーの違いに戸惑いつつも、少しずつそこに慣れていくためにも、当面の間は出社勤務とリモートワークとのハイブリッドな形でバランスをとりながら、徐々に馴染んでいこうとして最初の1週間が経過したところです。

なぜ転職に踏み切ったのか

元々私はクライアントワークというよりも、SESに近い形でのキャリアをここ数年間は歩んできました。もちろんSESという立ち位置を否定するつもりはないのですが、この立ち位置を重ねていくうちに、クライアントと働いている立場(会社)との越えられない垣根を越えて、よりダイレクトにお客様とのつながりを持ちたいということだけではなく、自分が得てきた経験値をベースにしながら、そのナレッジを自社の資産として蓄積して自分もチームの人もさらにボトムアップしていきたいと考えたのがまず1点。

それから、この年齢に差し掛かってきて、そろそろ若い人を積極的に育てていって、組織としてプロ集団を形成できたらいいなということを強く思ったのがもう1点。

それらを総合的に考えながら、少しでもキャリアアップできる環境を求めて、かれこれ半年間あまり、忙しい業務の合間を縫って少しずつ転職活動を進めていき、今回ご縁があって転職に至りました。

まず1週間で感じたことと、進めていきたいこと

まず1週間で感じたことは、立ち上がりの速さをとにかく求められているんだろうなということと同時に、いろいろな意味で自己管理を求められているのだろうなということです。それは、勤務時間の管理だったり、その勤務時間の中でどういうことを学び、アウトプットとして出していくかというところだったり、様々です。比較的自由度の高い組織であるからこそ、逆にそういった自己管理が自然と求められてくるのではないかと思っています。

それを踏まえつつ、現状を少しずつ見ていく中で、これから前向きに改善していきたい課題がいくつか見えてきているので、まずは具体的な業務をしっかりと進めつつも、改善していきたい課題を積極的に可視化して、解決への糸口を掴んでいく、そしてそれを具体的な形にしていくということを、この先少なくとも3か月間の目標にしていきたいと思います。

とはいえ、性格上何かとスタートダッシュをハードにしすぎる傾向があるので、そこは自分自身も大切にしながら、バランスよく進めていきたいと思います。

このBlogは自体は、お客様との間で結んだNDAを厳守しながらも、技術的なTipsも含めて色々な気付きを一般化して翻訳しながら、変わらず継続していきます。一般化することで、普遍的な集合知が出来上がってくるので、その一助になればと考えています。

今後ともよろしくお願いいたします。

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