長期間スイッチロールしていない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 | タグ: | コメントする

Violaレッスン3回目


りきひさみねこ先生のViolaレッスンも早いものでもう3回目となりました。前回はオンラインレッスンだったのですが、今回からは3回目のワクチンも接種済みということもあって、対面でのレッスンになりました。

先生のレッスンを開始して以降、右手の改造に取り組んでいるのですが、これがなかなかうまくいかず。先生曰く、今まで相当無理のかかるような弓の持ち方をしていたらしく、本当は右手のMP関節を平らにしたいところなのだけれども、僕の場合は完全にここが立ってしまっているので、全体的に弓を持つ右手全体(特に小指)に無駄な力がかかっているのだと。

そんなわけで、MP関節が平らになるように弓の持ち方全体を、先週から引き続き改造しています。中指と薬指は、弓が第2関節にきっちりとくっつくくらいにして、その分だけ右手全体を弓の先端方法に傾けます。そして小指は添えるだけ。この右手の形でひたすらボウイングの練習です。最初は弓を十分に使い切るように弾いて、その次は弦が最大限振動する位置でダウンボウの繰り返し。もう教本どころの騒ぎではなくて、とにかく基礎中の基礎からたたき直している状態。

けれども、大学を卒業して以降の20年間くらいが完全に自己流だったので、元々の目的である基礎を見直すという意味では、本当に良い練習になっています。

思い切り自分の顔が写り込んでしまっているけれども、その時々の状態を先生が撮影してくれてレッスン直後に送ってくださるので、載せてしまおう。こんな感じです。

肩当てが消える

そうやってボウイングの練習を繰り返していたのですが、私のどちらかというと横向きに楽器を構えている姿勢に気がついた先生、身体の体幹に対して真っ直ぐ楽器がくるように姿勢を修正したいということで、

肩当てをあえて外しましょうか!

ということで、先生、私の楽器から肩当て(楽器がきちんと構えられるように肩と楽器の間に挟む器具)を外してしまいました。そして顎を乗せる位置を若干楽器の中心にくるように修正し、という感じで、楽器の構え方についても大改造を開始。これも一朝一夕ではなかなか治らないかもしれませんが、こうして構え方を変えてもらうだけでも、出る音がだいぶ変わってくることに自分でも気がつきました。いやこれはすごい。

そんな感じで、楽器を弾く基本となる楽器の構え方と弓の持ち方から根本的に見直しているレッスンなのですが、やはりきちんとプロの先生に見ていただくことって大切ですねぇ。今更何を言っているんだって感じかもしれませんが、これは本当です。

そんな感じであれこれやっている間にあっという間にレッスンの時間は終了。先生のおかげで今回も楽しく楽器を弾くことができました。

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

日帰りで福岡往復


有給休暇が大量に余っていたので消化期間に充てているのですが、1日中ずっと家にこもっているのはなかなかしんどいし、生産性も落ちてくるので、ちょっくら飛行機に乗るのを楽しみつつ、ちょっとしたリゾートワークまがいなことをしてくるか、ということで、日帰りで大好きな福岡に行ってくることに。航空券をポチッとしました。

フライトログ

  • 2022/2/25 NH243 HND → FUK 788 JA838A 5K
  • 2022/2/25 NH256 FUK → HND 788 JA831A 6A

天神のエンジニアカフェでリゾートワーク的な作業をする

福岡空港の良いところは、なんと言っても地下鉄が真下に乗り入れていて、博多まで約5分、天神まで約10分で出られるところ。今回は預け入れ手荷物は全くなく、気軽にトートバッグにMacBook Airとちょっとしたガジェットだけを入れて往路便に搭乗したので、飛行機を降りてから地下鉄に乗り込むまでわずか20分かからないくらい。ちょうど乗り継ぎが良かったので天神駅に着いたのが飛行機が到着してからわずか30分後。これは本当に魅力です。

天神ではひとりで出かけた際には仕事がらみでいつもお世話になっているエンジニアカフェへ。ちょうど仕掛かり始めていた個人開発のツールのコーディングとデバッグをみっちり90分くらいしてきました。普段とは異なる環境での作業はとにかく捗ります。作業環境を必要に応じて変えるのって気持ちの切り替えの意味でもとにかく良いですね。エンジニアカフェ、現在は無料の会員登録が必要ですが、会員登録してしまえばオープンなコワーキングスペースだけでなく、無料で集中スペースなども借りることができ、しかも個人利用であれば無料なのが素晴らしい。こういった形のコワーキングスペースって、東京圏だとドロップインで何かしらのお金がかかるので、とてもありがたいです。

作業をして十分にお腹を空かせてから、天神4丁目にあるラーメン屋さんでしっかりと豚骨ラーメンをいただいてきました。やっぱり地元で食べるラーメンは美味しい。ごちそうさまでした。

その後、アクロス福岡の中をちょっとぶらついてから天神駅に向かい、福岡空港行きの地下鉄に乗り込みます。天神滞在時間3時間というとんでもなくわずかな時間ですが、もうこれだけでも個人的には十分福岡の空気を堪能です。

帰路のNH256で小確幸

あっという間に福岡空港に戻ってきて、ラウンジでちょっと休憩したあと、復路となるNH256に搭乗。限定運用とはいえ国際線機材の78Mに乗れるのはちょっと嬉しかったりします。機体番号見てみたら、先月に娘と福岡旅行した時と同じ機体でした。そんな偶然もあるものなんですね。

一昨年くらいから、飛行機に搭乗した際には、CAさんにお願いをして搭乗証明書をいただいているのですが、そこでちょっとした嬉しい出来事が。いつもと違うデザインの、シール型の搭乗証明書に加えて、ANAのCAさんが手作りしたのではないかと思われる、手書き風のFUKステッカーを添えていただきました。

この存在、半年くらい前から知っていて、なかなかレアな存在であるという噂を聞いていたのですが、まさかここでいただけることになるとは! なんだか小格好という感じでとても嬉しかったです。CAさんに感謝です。本当にいつも何かしらの心遣いをいただけてありがたいですね。

そんな感じで無事に羽田空港に到着し、ちょうど良いタイミングでリムジンバスの接続があったので日が暮れる前に帰宅。

ショートトリップにしては若干やりすぎ感は否めないのですが、空港から市内までが近いという利点を活かせることを考えると、日帰り福岡往復って普通にできてしまうんですね。そして街並みが自分好みでどこでもクリエイティブな作業ができてしまう。これが当たり前になる時代に、もっともっとなってほしいと思います。

だいぶリフレッシュできました。これで3月からの仕事も頑張れそう。

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

COVID-19の3回目のワクチン接種記録


はじめに & おことわり

このBlogではあまり時事ネタ的なことは書かないんですけれども、執筆時点(2022/2/19)では、COVID-19に対する3回目のワクチン接種がまだ始まったばかりであり、特に交互接種について不安を持たれている方がたくさんいらっしゃると思いますので、私の場合こうだったということで、3回目のワクチン接種以降、体調がどう推移していったのかを、記録を兼ねて書いていこうと思います。

そして、この記事はあくまでも私のCOVID-19の3回目のワクチン接種記録を、接種を検討されている皆さんの参考になればと事実ベースで書いているもので、交互接種や、そもそもCOVID-19のワクチン接種を強く強制するものではないことを、ここに明記します。(いやもちろん接種した方がいいに越したことはないですが)

前提

  • 基礎疾患あり。平熱は36.5度
  • 1回目のCOVID-19ワクチン接種: 2021/7/25 ファイザー社製(コミナティ筋注)
  • 2回目のCOVID-19ワクチン接種: 2021/8/16 ファイザー社製(コミナティ筋注)

2/14 ワクチン接種予約までの経緯と流れ

仕事に関するスケジュールの関係上で、3月までには3回目のワクチン接種を完了させておきたかったのですが、居住地の市役所からの接種券の発送時期が、2回目のワクチン接種から6か月経過して以降の発送になることをWebサイトであらかじめ確認はしていました。

もちろん接種券の発送まで待つという選択肢もあったのですが、それだとちょっと3月以降のスケジュールにどうしても影響が出てしまい、関係する方に迷惑をかけてしまうこともあって、とりあえず市役所の担当部署に電話をしてみました。

電話をしたところ、氏名と生年月日と2回目のワクチン接種日の確認を受けた上で、

あなたの場合は接種券を前倒して発行することが可能なので、まず先にワクチン接種の予約をしてください。

ということを言われたわけです。いやいや、接種券番号がないのに接種予約なんてできないでしょ、って思っていたところ、3回目のワクチン接種の接種券番号は、1回目 & 2回目のワクチン接種の接種券番号と同じであることが判明。それ先に言ってくれないとわからないよ〜。

そのタイミングでワクチン接種場所として候補に上がったのは、千葉県が運営する「千葉県新型コロナワクチン追加接種センター」と、防衛省・自衛隊が運営する「自衛隊大規模接種東京会場」の2つでした。ちなみに、使用するワクチンはどちらもモデルナ社製です。

元々ファイザー社製のワクチンに関しては供給量が少なかったことと、個人的には交互接種をすることによるデメリットよりもメリットの方を重視していたこともあって、上記のどちらかの接種場所で受けることを決めたのですが、千葉県の方は残念ながら3月まで完全に埋まっており、賭けるのは防衛省・自衛隊の方。早速Webサイトに入ってみたところ、希望日の予約状況が「予約一旦満了」というステータスになっていました。

まぁそれでも予約方法だけでも確認してみようということで、1回目 & 2回目の接種券情報をもとに、自治体コードと接種券番号を入力してみたところ、希望日に何故か3枠の空きが! 多分何かしらの事情でキャンセルされた方がちょうど出たんでしょうね。ありがたく予約を入れさせていただき、その情報を市役所に連携したところ、その日のうちに接種券を発行していただけることに。午後に少し空いた時間があったので、サクッと市役所まで行ってきて、接種券を発行していただきました。ここまでが2/14の出来事になります。

2/16 ワクチン接種当日の流れ

2/16のワクチン接種当日。自宅からだと接種会場のある竹橋まではそれなりに移動時間がかかるため、この日は大手町のコワーキングスペースでギリギリまで勤務することにしました。ちょっと仕事が佳境に入っており、できるだけ効率的に時間を使いたかったためです。

必要な打ち合わせ等をきっちり終わらせて、大手町駅から東京メトロ東西線で竹橋駅まで移動。4番出口の階段を登り切ると、報道でよく出てくる「自衛隊東京大規模接種会場」の看板が。

予約時間までには必ず到着しているようにしてくださいという注意事項が書かれていたので、少し早めで20分前に到着していたのですが、その時点で既に自分の予約時間分も受付開始になっていました。そのまま会場へ入ります。

報道でよく見られるプレハブとテントで設営された仮設棟は、主に手荷物検査、当日の体温測定、問診票記載事項の確認、事前問診に用いられており、内部は想像以上に広くて整然としています。迷いそうになってもあちらこちらに係員の方がいらっしゃって丁寧に誘導してくださるので、指示に従っていれば流れ作業のような形でスルスルと事前の手続きが進んでいきます。事前問診で、基礎疾患の内容や1回目 & 2回目の副反応の状況について、僕の場合は結構細かく確認されましたが、結果的にはOKが出ました。ここまでで約10分。

ここから先は、元々税務署か何かで使われていたと思われる本館の方に誘導されます。エレベーターでの移動になるのですが、きちんと余裕を持った人数が利用できるようにコントロールされていて、エレベーターの中にも案内係の方がいらっしゃいます。もちろん入口と出口の動線は完全に分離しています。このエレベーターで、実際に接種を受ける7階まで上がります。

7階に上がると、まず最初に医師(医官)の方の最終問診が入ります。ここで問診票の内容の再確認と、接種を希望する腕の確認(私は左利きなので右腕を希望しました)を受け、接種後の待機時間が記載されます。そしてそのままの流れで別の部屋に案内されて、看護師の方に実際にワクチンを接種していただきました。接種してくださったのは、東京大学病院から応援でいらしていた方でした。

2回目の接種の時は多少の痛みを感じたのですが、今回は、何かが腕に触れたかな? という程度で何の痛みもなし。本当に接種を受けたのか? と一瞬思ってしまったくらいです。筋肉注射と聞くと結構痛いイメージがあるものなのですが、そう言ったイメージとは全くかけ離れたあっさりとしたものでした。

ここでさらに別室に移動して、15分間待機。ここでも多くの医師(医官)のかたが見守りをしており、もしも何かあった場合でもすぐに対応していただける体制が整っています。幸いにも何の体調の変化もなく、15分が経過したため、また先ほどのエレベーターで1階に降りて、仮設棟の方に移動し、問診票の回収と接種券への証明証の発行をいただいて無事に接種は終了。ここまででかかった時間は僅か35分。本当にスムーズでした。そのまま徒歩で竹橋駅まで戻り、混雑を避けて着席するために敢えて各駅停車で帰途につきました。

ちなみに、大規模接種会場の内部は、静止画・動画撮影は禁止です。なかなか経験できないだけに、撮影したくなってしまう気持ちもあるのですが、そこはルールを守りましょう。

2/17 副反応の記録

朝の時点で副反応の兆候はあまり感じられず、確かに右上腕部の接種部分を中心とした痛みはあったものの、腕が上がらないようなことはなく、体温も平熱にほぼほぼ近い状態だったので、通常通り在宅勤務を開始したのですが、何だか熱っぽいなと感じてきた12時過ぎの段階で37.5度まで上昇。このタイミングで解熱剤を投入することも考えたのですが、熱っぽさだけだったのでそのまま在宅勤務を続行してしまいました。これがちょっと良くなかったかもしれません。

そのうちだんだんと身体がしんどくなってきて、寒気を感じてきたので、14時過ぎに再度体温を測ったところ、38.1度まで上昇。これは流石にいかんと思い、解熱剤を投入してその時点で在宅勤務を中止し、休息することに。ちなみにこの時点でも右上腕部の痛みが強くなることはありませんでした。ただ、何故が肩から背中の上部にかけてがバリバリな状態。もちろん右腕を無意識のうちに庇っていたという事情もあったのかもしれませんが、発熱よりもそっちの方が辛かったかもしれません。

軽く眠った後、17時には解熱剤が効いたのか、37.3度まで下降。何とか夕食をとって眠りについたのですが、どうもうまく眠れずに23時過ぎに起き出して再度体温計測したら、38.7度まで上昇。寒気と背中の上部の違和感は続いています。ここで再度解熱剤を投入して再度睡眠。

2/18 副反応の記録

5時に目が覚めたのでまずは体温の測定から。37.0度。身体も少し軽くなり、右上腕部の痛みも悪化することがなかったので、一旦様子見をして、7時前に36.7度まで熱が下がっていることを確認してからこの日は出社。移動には念には念を入れてグリーン車を使用しました。

出社の必要な要件は午前中のみだったので、やはり帰路もグリーン車を使って移動し、帰宅してしばらくさまざまな細かい仕事を済ませ、18時になって体温を測ったところ、37.2度まで上がり、インフルエンザの時のような節々の痛みを感じたので、この日は夕食をとらずに就寝。

2/19 副反応の記録

日付が変わって1時に目が覚めたので、ここでも体温の測定。ようやく平熱にほぼ近い36.6度まで下がりました。右上腕部の痛みもほとんどなくなりました。そして落ち着いている状態で現在に至っています。おそらく、一連の副反応としてはほぼほぼ終息したのではないかと。

大規模接種会場の運営に驚いた

報道ではなくとなく見ていた大規模接種会場ですが、実際に体験してみて、設営や運営の機動力や動線設計のスムーズさと、対応してくださった皆さんがとにかく丁寧で親切だったのには本当に驚きました。災害派遣を中心として、自衛隊の皆さんの活躍がクローズアップされることは多いですが、こういった形でも普段の訓練の成果がしっかりと発揮されているものなんですね。本当に、自衛隊と、携わってくださっている医療関係者の皆さんのご尽力には感謝しかないです。

そういった方々のご尽力に応えるためにも、COVID-19が無事に終息してくれることを願って、引き続きCOVID-19の感染対策に自分ができる限りの範囲で気を付けていきたいと思います。

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

私なりの通知機能との付き合い方 番外編


はじめに

前回の記事ではSlackとメールを中心に、飛び込んでくる通知をいかに絞り込んで仕事や作業の効率を上げるための工夫をしているのかを書いたのですが、通知に限らず、流れてくる情報は他にも色々あるわけで、その流れてくる情報をコントロールするためにどんな工夫をしているのかをもうちょっと加筆したいと思って、別の記事に仕立てました。

なお、前回の記事と同じなんですけれども、前提条件を書くのを忘れていました。ここでは在宅勤務化で圧倒的に向かい合うことの多くなった、MacBook Air (M1, 2020)上でどうコントロールしているかについて書いています。

というのも、私の場合ですが、この状況下で家にいることが多くなってから、スマートフォン(iPhone 12 mini)を見ることがほとんど無くなっちゃったんですよね。下手をすると平日は1日30分も見ていないかも知れません。そんなわけで、スマートフォンの通知に関するコントロールは今回はバッサリ割愛しているのでご了承ください。

Twitterから流れ込んでくる情報のコントロール

多くの人が利用しているであろうTwitter。もちろん私も利用しています。多分2008年くらいから使い始めて、2012年3月に一度リセットをかけているので、フォロー数/フォロワー数もそんなに多くありません。ミニブログという表現があるように、日頃考えたり思いついたことなどを投稿している一方で、ものすごく大量の情報も流れてくるわけで、これを全部読み始めるとキリがないのに加えて、公式Retweetの機能が実装されてからというもの、とにかくRetweetの量が多く、その中には時期によっては特定の(と、敢えて濁して書いておきましょうかね)バイアスのかかった情報も入ってくるのも事実です。

これはあくまでも私の考えですが、その人自身が(直接的にも、間接的にも)書いたTweetを読みたい人なので、結構ばさっとフィルタリングをかけています。

Twitterのタイムタインを読むためのアプリケーションは多数出ていますが、私は流れてくるTweetの仕分けのためにも、ここ数年間Updateがないのですが、TweetDeckを使用して、リスト化したタイムラインが表示されるようにしています。けれども、この状態だけでは公式Rtweetや、特定のキーワードを含むTweetが流れ込んできてしまうので、フィルタリング機能を使用して公式Retweetや特定のURLを含むTweetがタイムライン上に表示されないようにしています。

これをするだけでも心理的な安全性はかなり高まりますね。何か特別なことがあった時には情報を収集するためにもフィルタリングの条件を少し変えたりしますし、夜の遅い時間でもタイムラインを見ることがありますが、最近は20:00以降は見ないようになってしまいました。Tweetから受ける心理的な負担が睡眠に影響を及ぼすのをできるだけ避けようという意味合いもあります。それに睡眠大事ですからね。

もちろん、Tweetする権利もしない権利もアカウント持っている人だったら当然持っているのは当たり前ですし、その人がどのような使い方をするのかも、法令に反しない限りでは自由なので、受け手側がコントロールすればいいわけで、たまたま私の場合はこういうコントロールをすることによって精神的にも気持ち良く使わせてもらっています。TweetDeckなくなったりしたら結構困っちゃうかも知れませんねぇ。

情報のコントロールと分類も精神衛生維持のためのTips

通知機能との付き合い方、という本来の趣旨からはだいぶ外れてしまうかも知れませんが、インターネット(World Wide Web)の普及とともに、流れてくる情報の量が膨大になってきたのは事実で、それが結果として通知の多さにもつながってくるわけで、その便利さを享受しているのも事実なのですが、享受している分、そのまま飲み込むのではなくて、自分にとって(都合が良いか悪いかは別として)本当に必要な情報なのか、どんな情報として分類すればいいのかをきちんと整理する能力がだいぶ求められるようになったよなぁ、ということを日々痛感しています。

そしてそのことが、自分自身の精神衛生をできるだけフラットに保てるようにするためのTipsにも繋がるのかな、なんていうことを思っているのでした。

この記事を書くきっかけを作ってくださったみやひろさんに、改めて感謝。

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

私なりの通知機能との付き合い方


はじめに

このエントリーを書こうと思ったきっかけは、普段オンライン上で懇意にしてもらっているみやひろ@webディレクターさんからのこんなTweetでした。

確かに、ことコロナ禍に入ってオンラインでのツール群をコニュニケーション手段として使うようになってからは、通知地獄、とまではいかないけれども、ひっきりなしにやってくる通知に自分の作業の手を止めなければいけないというパターンは増えてきましたね。実際に、自分もメールの通知、Slackの通知、Teamsの通知がひっきりなしにやってきていて、おまけに現職ではミーティングの数が1日平均して3〜4件と多いので、それも含めて自分自身の作業が滞ることもしばし。

とはいえ、もうこれからの時代通知機能を完全に切る、というやり方はコニュニケーションロスが生じてしまうので、自分なりにどのようにしてうまく通知機能と付き合っていくのかをまとめてみることにしました。

コニュニケーションツールの通知とどう付き合うか

私が所属しているプロジェクトではSlackをコニュニケーションツールとして使用しているので、それを前提にして書いていきます。

まず、現時点で自分がどのくらいのチャンネルに所属しているのかを数えてみたら、32チャンネルもありました! 人によってはもっと多くのチャンネルに参加しているのかもしれないですが、これだけのチャンネルから通知が来てしまうとそりゃ大変です。なので、こういう風にして主としてウォッチするチャンネルを絞り込んでいるのと同時に、逆に相手を通知で困らせないようにコントロールをかけています。

宛先を絞り込む

つまり、Slackでいうところの@channelや@hereを可能な限り使わず、連絡を取りたい人に対してあくまでもチャンネル上で呼びかける、ということです。ポイントとしては以下の通り。

  • @channelや@hereを使わないことにより、無用な通知を増やさない
  • かつ、チャンネル上(≠DM)でコンタクトを取ることにより、属人化を防いで誰でも横から会話に参加できるようにする

スター機能を使って、常時確認するべきチャンネルを絞り込む

先ほど32チャンネルに所属しているというお話を書きましたが、そのうちにどうしてもみなければいけないチャンネルって大概限られているんですよね。なので、Slackのスター機能を使って常時確認する対象にしているチャンネルを、担当している案件によって常時最大5つくらいまで絞り込んでいます。以下のような感じで。

これだけでも結構負担は軽減されますね。それ以外のチャンネルは、メンションが飛んできたら応答するけれども、そうでない限りはさらっと読み流す程度にしています。

メールの通知とどう付き合うか

次に多いのはメールの通知。これもまた大変ですよねぇ。流石に仕事メールの画像を公開するのは厳しいので、プライベートでの工夫を中心に書きます。

ちなみに、プライベートでも1日あたりでメールは平日30通くらい受け取っているのですが、そのうちにやりとりとかしなければいけないメールの数って1/10くらいなんですよね。なので、こういう形で対応しています。

メールソフト(MUA)を2種類併用する

これってすごーく非効率なように見えるのですが、特にGmailなんかを使っている人だと、とにかくありとあらゆるメールが流れ込んでくるので、とてもじゃないけれども全部のメールを読んでいる暇がないわけです。

なので、メールソフト(MUA)を、

  • 通知を見るためだけのもの
  • きちんと仕分けをして本文をきちんと読んだり返信したりするもの

に分けています。私の場合、前者にはSparkを、後者にはGyazMailを使用しています。Gmailアカウントには、個人で保有しているすべてのメールアカウントから転送をする設定にしているので、Sparkには事実上すべてのメールが流れ込んできます。とにかくこれを通知機能でちらりと見るだけ。即時返信が必要なものを除いては基本的に返信しません。

で、GyazMailの方で実際にちゃんとメールを読んだり必要に応じて返信したりするわけですが、メールのやり取りをしている方はご存知の通り、返信する時間を基本的に7:00台から8:00台にルーチンとして決めています。もちろん緊急でやりとりしなければいけない場合は別ですけれども。そのようにして、タスクを行う時間を自分の中であらかじめ決めておくことによって、コントロールをかけています。直接的には通知とつきあうTipsとは関連しないかもしれませんが、そのタスクを行う時間を決めるというのも大事なのかなと。

通知音を出さないという心理的安全性

そうそう、個人的にはこれが一番大事なんじゃないかと思っているんですが、仕事用のPCでは基本的にスピーカーの音量をゼロにして、あらゆる通知音を出さないようにしています。でないと通知音そのものがだんだんストレスになってくるんですよねぇ。どうせ通知は視覚でわかるものだから、視覚で済むものに関しては視覚から得る情報で済ませてしまえと。これだけでも通知に対する心理的安全性はかなり高まります。

もちろんですが

もちろんですが、通知されてくる情報にはなんらかの意味があって通知されてくるので、それに対して完全に無視を決め込むことはできないというのは自明です。ただ、その人に対して関係性のない情報まで何でもかんでも流されてしまうと、本当に通知に埋もれてしまう生活になるので、そこのところは、各々うまくコントロールしながら対応していくと、リモートワーク生活もだいぶ楽になるんじゃないかなと思います。

あくまでこれは私が実行している例なので、皆さんそれぞれ、皆さんなりの工夫をしていただければと思います。そしてそれを是非共有してもらえると嬉しいかな。

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