AWS Systems Managerを使ってraspiマシンを運用する時の注意点

この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 19日目の記事です。

今年の初めに AWS Solution Architect Associate を取得した daikw - Qiita です 🎉

今日は AWS Systems Manager (SSM) に関する話をします。

はじめに

SSM で解決したいこと

クラウド側で全てprovisionしてくれる仮想マシンとは別に、オンプレマシン 扱いたい、というのは、実は結構よくある要求ではないでしょうか。

弊社のFW開発チームも、raspiをあちこちで使っていて、正直管理が辛いところがあります。

クラウド・オンプレどちらもできるだけ同じように扱うことができると、管理コストが減ってとてもハッピーになれそうです。

ところで、 AWS Systems Manager(運用時の洞察を改善し、実行)| AWS によれば、

AWS Systems Manager は、ハイブリッドクラウド環境のための安全なエンドツーエンドの管理ソリューションです。

とあります。もしや、これを使ったらハッピーになれるのでは???

https://d1.awsstatic.com/AWS%20Systems%20Manager/product-page-diagram-AWS-Systems-Manager_how-it-works.e9ba8cbeff1249c8cc24db4737d03648a1a073f6.png
https://aws.amazon.com/jp/systems-manager/ より

raspi x SSM

クラスメソッドさんの記事で、 raspi x SSM カテゴリのものがかなり豊富にありました。

本記事では、これらを参考に運用を試みた際に感じた、導入にあたって注意すべき点をまとめておきます。 また、記事の後半に用語リストをまとめてあります。参考までに。

運用して気がついたこと

セッションログは、セッションの開始方法によっては残せない

AWS Console の Session Manager を経由した SSHセッションログを、S3 や Cloudwatch logs に流すことができます。

AWS Systems Manager Session Managerのシェル操作をログ出力する | DevelopersIO

しかし、 aws ssm start-session コマンドによるログインセッションのログは同じログストリームに流すことができません。

f:id:photosynth-inc:20211218133733p:plain
No s3 log when ssh ubuntu@i-xxxxxxxxxx · Issue #194 · aws/amazon-ssm-agent より

不便といえば不便ですが、当たり前といえば当たり前(接続元と接続先で鍵交換するので、session-manager は復号できない、タブン)で、ややもどかしくもあります。

Quick Setup を組み合わせる場合、セッションログを取得するために小細工が必要

Quick Setup は基本 EC2 インスタンスに対して行うものですが、ターゲットに Managed Instance を選択することもできます。ハイブリッド環境もまとめて扱えるのが Systems Manager のイイトコロなので、まとめて選択してしまうこともあるでしょう。

デフォルトでは、Hybrid Activationの際に AmazonEC2RunCommandRoleForManagedInstances が Managed Instance にアタッチされます。これは Identity Provider が ssm になっています。 Quick Setupの後に、それらしい Role (AmazonSSMRoleForInstancesQuickSetup)が作成されるのですが、これは Managed Instance に自動でアタッチされません。さらにこちらの Identity Provider は ec2 になっています。

以下のポリシーを含む ssm-managed-instance role を作成して、

  • CloudWatchAgentServerPolicy
  • AmazonSSMManagedInstanceCore
  • AmazonSSMPatchAssociation

ssm-managed-instance を Fleet Manager (AWS Systems Manager - Managed Instances) から 対象の Managed Instance にアタッチし、さらにこの際、RoleのIdentity Provider を ssm にすることで、cloudwatch logs に ssm session のログが流せるようになります。

この辺りは IAM Role, Policy の構成をよく調べないとうまく使うことができなそうです。来れ、aws猛者たちよ。

ssm-user の権限が広い

Session Manager を利用する場合、特に aws-console 上からのログインを許可する場合は、デフォルトで ssm-user が利用されます。

この ssm-usersudoers なので、Session Manager を下手に許可してしまうと 最小権限の原則 - Wikipedia に簡単に反します。

「Session Manager を利用するのは、特権管理者である」と想定されているのかと思います。

チームで Session Manager を共有して使う場合は、コンプラという言葉が飛び交う現代IT産業界の皆様が口から泡を吹いて倒れることのないように、 ssm-user を sudoers から除去しておくのが無難です。

ついでに pi ユーザの nopasswd も除去しておきましょう。

sudo su
cd /etc/sudoers.d
echo "#User rules for ssm-user" > ssm-agent-users
rm 010_pi-nopasswd

参考:

インスタンスのクローンを作るときは、再度アクティベーションする

Raspberry Pi で、既にあるマシンと同等のマシンを作りたい(複製する)場合は、 MicroSDカードからイメージを dd コマンド等で吸い出し、別のカードに書き込んで用意することがあります。

アクティベート済みのマシンを複製する場合、 ssm-agent によるハードウェアフィンガープリント に引っかかるため、そのままでは使えないであろうことが予想できました。

自分でも挙動を調べたんですが、クラスメソッドさんの記事が僕よりよくまとまっていました。

[AWS Systems Manager] アクティベーションの終わったRaspberryPiのイメージをコピーして、複数起動した時の挙動を確認してみました | DevelopersIO より引用すると、

アクティベーションが済んだイメージを複製する場合、 - 1台だけ起動する場合、どちらでも接続可能 - 複数台起動した場合、後から起動したものが接続される - 複数台起動した場合、リブートすると、どちらに接続されるかは不定 - 複数台起動した場合、セッションマネージャから再アクティベートすると別のインスタンスとして管理される

session-manager を利用できるIAM Roleの設定

セッション開始には、"ssm:StartSession" アクションの許可が必要になります。

例えば、 Session Manager の追加サンプル IAM ポリシー - AWS Systems Manager から抜粋すると、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:StartSession"
            ],
            "Resource": [
                "arn:aws:ec2:us-east-2:123456789012:instance/i-1234567890EXAMPLE",
                "arn:aws:ec2:us-east-2:123456789012:instance/i-abcdefghijEXAMPLE",
                "arn:aws:ec2:us-east-2:123456789012:instance/i-0e9d8c7b6aEXAMPLE"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:TerminateSession",
                "ssm:ResumeSession"
            ],
            "Resource": [
                "arn:aws:ssm:*:*:session/${aws:username}-*"
            ]
        }
    ]
}

.ssh/config を適切に設定する

いろいろと試した結果、以下2点わかっています。

  1. AWS-StartSSHSession document は、 ProxyCommand を使わないと動作しない
  2. aws ssmコマンドを利用する場合、2段以上の ProxyCommand チェーンは効かない

1 は、ターミナルにコマンド直打ちする時以外は特に困らないですが、 Protocol mismatch となります。

┬─[daiki~@photosyth~:~]─[15:23:10]
╰─>$ aws ssm start-session --target mi-xxxxxxxxx --document-name AWS-StartSSHSession --parameters 'portNumber=22222'

Starting session with SessionId: daiki.watanabe@photosynth.co.jp-0b95bd5ed82475bae
SSH-2.0-OpenSSH_7.9p1 Raspbian-10+deb10u2+rpt1

ls
Protocol mismatch.


Exiting session with sessionId: daiki.watanabe@photosynth.co.jp-0b95bd5ed82475bae.

2 は少しだけ困っていて、以下のような記法でのログインに失敗します。

# SSH over Session Manager
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --profile akerun --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"

Host target
    User pi
    ProxyCommand ssh mi-xxxxxxxxx -l pi -p 22222
┬─[daiki~@photosyth~:~]─[10:52:12]
╰─>$ ssh target
Pseudo-terminal will not be allocated because stdin is not a terminal.
daikiwaranabe@mi-01630ac873d5fe084: Permission denied (publickey).
kex_exchange_identification: Connection closed by remote host

config の設定をもっと頑張ればいけるかもしれません。ホストが増えないうちは特に困らないので、緩めのマサカリが来ることを期待しつつ。

その他参考記事

用語リスト

SSM固有の用語が多く、概念になれるのに少し時間がかかるので、先んじてまとめておきます。

Hybrid Activation

用意したマシンを Systems Manager に登録する作業のことを、ハイブリッドアクティベーションと呼びます。

ステップ 4: ハイブリッド環境のマネージドインスタンスのアクティベーションを作成する - AWS Systems Manager より、

ハイブリッド環境でサーバーと仮想マシン (VM) をマネージドインスタンスとして設定するには、マネージドインスタンスアクティベーションを作成する必要があります。アクティベーションが完了するとすぐに、アクティベーションコードとアクティベーション ID が送信されます。ハイブリッド環境でサーバーと VMAWS Systems Manager SSM Agent をインストールするときに、このコードと ID の組み合わせを指定します。このコードと ID は、マネージドインスタンスから Systems Manager サービスへの安全なアクセスを提供します。

つまり、以下を実行するだけ。

sudo service amazon-ssm-agent stop
sudo amazon-ssm-agent -register -code $activation_code -id $activation_id -region $region
sudo service amazon-ssm-agent start

Managed Instance

Systems Manager で管理下にある、自分で用意したマシンのことを言います。オンプレミスサーバであることもあるし、何か別の形態でプロビジョンされたサーバであることもあり得ます。

SSM Agent

エージェント(agent)とは - IT用語辞典 e-Words によれば、

エージェントとは、代理人、代理店、仲介人、取次業者、などの意味を持つ英単語。ITの分野では、利用者や他のシステムの代理として働いたり、複数の要素の間で仲介役として機能するソフトウェアやシステムなどを指すことが多い。

SSM Agent は ManagedInstance に常駐し、Systems Manager にインスタンスの状態を渡したり、Systems Managerからのリクエストを処理する機能を持ちます。 SSM Agent の使用 - AWS Systems Manager によれば、

エージェントは、 AWS クラウド で Systems Manager サービスからのリクエストを処理し、リクエストに指定されたとおりに実行します。SSM Agent は、Amazon Message Delivery Service (サービスプレフィックス: ec2messages) を使用して、Systems Manager サービスにステータスと実行情報を返します。

Session Manager

Systems Manager の一つの機能で、対象ホストとのSSHログインセッションを 直接 貼ることができるようになります。

AWS Systems Manager Session Manager - AWS Systems Manager より *1

Session Manager はフルマネージド型 AWS Systems Manager 機能で、インタラクティブなワンクリックブラウザベースのシェルや AWS Command Line Interface (AWS CLI)を介して Amazon Elastic Compute Cloud (Amazon EC2) インスタンス、オンプレミスインスタンス、および仮想マシン (VM) を管理できます。Session Manager を使用すると、インバウンドポートを開いたり、踏み台ホストを維持したり、SSH キーを管理したりすることなく、監査可能なインスタンスを安全に管理できます。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

*1:AWSのドキュメントは、5回くらい読まないとちゃんと真意が掴めないのは僕だけでしょうか...

パフォーマンスチューニング: 手を動かす前に考える

この記事は Akerun Advent Calendar 2021 - Qiita の16日目の記事です。

こんにちは。@ps-tsh です。API Server などバックエンドシステムの開発を担当しています。

フォトシンスも創業から8年目に突入し、サービスを安定稼働させるためにパフォーマンスチューニングを行う機会も増えてきました。開発当時はしっかりテストしてリリースしたプロダクトであっても、運用が長期化すると「ファーストビューは速いが、ページングするとだんだん遅くなっていく」「1分で終了していたバッチ処理に15分かかるようになってきた」といった課題が発生します。特に、ビジネス面がうまくいくほどユーザ数や履歴データの蓄積ペースも上がるので、システムにかかる負荷は急激に増大します。

そんなわけで今回は、パフォーマンスチューニングの話、とくに「コードを直す前」の話をしようと思います。

取り組む前に

パフォーマンスチューニングに取り組んだエンジニアに話を聞いてみると、よく「N+1が発生していたので解消しました」「インデックスを貼って検索を速くしました」といった回答が返ってきます。決して間違いではないのですが、ときどき「ちょっと手段の実践に傾倒しているな」と思うことがあります。手段の実践が先行すると、原因がわからない問題を調べるときにも「N+1がないか確認します」「インデックスが貼られているか確認します」のように、手段を軸とした調査になってしまい、当てが外れた時に打ち手がなくなってしまいます。つまり、手持ちの道具が限られていることで、対応できる課題の幅も狭まってしまうという罠に陥るのです。パフォーマンス問題を解決する方法は、DBクエリのチューニングに限られません。

チューニングで行き詰まったら、まずは一歩引いて問題を俯瞰してみましょう。いちど席を立って深呼吸し、コーヒーを淹れて飲んだりするのも良いかもしれません。

性能目標を確認する

チューニングに取り組むにあたり、最初にやることは「性能目標を確認する」です。これ、意外とみんなやっていないんですよね。ひとくちに「遅い」「重い」というのは簡単ですが、よく話を聞いてみたら「処理データ件数を考えると妥当な水準」「決して早くはないが、特段の実害もない」というケースも結構あります。あるいは、もともと性能目標が定められていないケースもあると思います。そんな場合でも「どれくらいになればひとまずOKといえるのか」を明確にしましょう。現在地と目的地があって初めて、取れる手段を適切に選べるのです。なお、性能目標はレスポンスタイムやスループットだけではありません。たとえば「表面上は性能に問題がなくても、裏ではCPU利用率が100%に張り付いてしまうことがあり、常時50%以下に押さえたい」といった状況の解消が目標になることもあります。

最初に目標を確認するのには、もう一つ理由があります。「チューニングは楽しい」のです。具体的に困っている人がいる状況で、解決すれば感謝される。技術的スキルを発揮する場としては申し分ありませんし、エンジニアとして最善の解決策を追求したいというマインドも理解できます。しかし、チューニング作業には時間的な制約を伴うトラブルシュートという側面もあります。

常時稼働のSaaSにおいては1週間かけて100%の解決策を提供するより、まず2時間で70%を救えるアプローチがあるならそちらを選ぶ状況もあります(この場合、最終的には暫定対応と恒久対応の二段構えで100%にもっていきます)。チューニングの実施においては技術的な正解ばかりを優先するのではなく、現実的な解決策としての有効性が求められます。「いま最優先で求められているのは何か」の視点をもち、本来の目標にフォーカスしましょう。

測定する

次にやることは「問題の事象を測定する、再現性を確認する」です。実際に起こっていない問題を解決することはできないからです。測定の過程では実測値と一緒に、問題となっている事象の発生条件も確認します。「特定のデータ入力で起こる」「特定の時間帯に起こる」「処理データ量が一定を超えると起こる」など、ここで得られる情報の質が高ければ、問題に対してよりよい解決方法を選択することができます。

ひと昔前のエンタープライズシステムであれば、ユーザに影響が出ないよう休日や深夜に検証する」という方法がとられることもありましたが、インターネットに公開された現在の SaaS においては24時間365日の連続稼働が前提です。本番システムにデバッグ用のコードを仕込んで経過を見る、試行錯誤をするといった時間的な余裕がないこともあります。パフォーマンス問題が起きる前に、普段から運用するシステムの各種指標を収集できる体制を構築しておくことが大事です。

こういった道具立てを自分たちで全て用意するのは大変なので、外部の APM(Application Performance Management: アプリケーションパフォーマンス管理)サービスを使うのもひとつの選択肢です。

ボトルネックを特定する

パフォーマンス低下に影響を与えている要因のうち、最も問題視される箇所のことをボトルネックといいます。測定が終わったら、パフォーマンスを低下させている要因を列挙し、性能にもっとも大きな影響を与えているのが何なのかを明らかにします。「APIサーバのレスポンスが遅い」という問題ひとつとっても、パフォーマンス劣化の原因にはデータベースの検索クエリが遅い、外部API呼び出しのレスポンス待ちが長い、排他制御のロック解放待ちが長い…と、いろいろあるわけです。冒頭にも説明した通り、パフォーマンスチューニングは時間との戦いなので、複数の要因がある場合は最も大きな影響があるところから解決に着手していきます。パフォーマンス低下要因としてよく挙げられるN+1やフルテーブルスキャンが実際に起こっていたとしても、処理プロセス全体に占める割合が少ないのであればいったん後回しにする、という判断もありえます。

対応策を選ぶ

ボトルネックを特定したら、負荷の種類に合わせた対策を検討します。システムの負荷は CPU の計算速度に依存するもの(CPU bound) と、入出力に依存するもの(I/O bound) の2種類に大別されます。I/O は狭義にはディスクの読み書きを指しますが、広義には通信速度(ネットワークのI/O)なども含まれます。

CPU bound の負荷が問題になっている場合は「計算量を減らす」または「計算資源を追加する」アプローチで対策します。具体的には「効率の良いアルゴリズムを採用して計算量を減らす」「サーバ増設でCPUリソースを追加する」といった方法が取られます。

I/O bound の負荷が問題になっている場合は「入出力の総量を減らす」アプローチで対策します。こちらも「キャッシュを活用し、APIやクエリの呼び出し回数を減らす」「APIやクエリで出力するデータ量(HTTP(S)のレスポンスボディ、クエリの結果セット)を減らす」「インデックスを追加して検索のために走査するデータ量を減らす」「逐次処理の書き込みを一括登録に変更する」など、さまざまな方法があります。

終わりに

対応策が決まったら具体的な実装に落とし込み、効果を検証し、テストしてリリースという流れになります。このあたりも掘り下げると色々なネタがあるのですが、今回は「手を動かす前に考えてほしいこと」という形でいくつかのポイントを紹介してみました。手を動かすこれらを確認しておくことで対応の幅も広がりますし、完了時の報告もスムーズに行うことができます。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

便利な解錠方法-まばたき解錠

この記事は Akerun Advent Calendar 2021 - Qiita の15日目の記事です。

WebエンジニアのBunです。主にiOSアプリの開発を担当しています。

日々いろんな解錠方法について考えています。特にiOSアプリで使えるいろんな便利な機能を使った解錠方法とその実装手順をまとめます。 今回はiOS(iPhone / Apple Watch) + JINS MEMEでの解錠について書きます。

JINS MEME

JINS MEMEはスマホ、スマートウォッチとつながるウェアラブルメガネです。つい最近最新版がリリースされました。

jinsmeme.com

JINS MEMEは鼻当て部分のCOREに2つのセンサーが搭載されたウェアラブルバイスです。COREに内蔵されたBluetoothスマホアプリと連携。センサーが取得したさまざまな情報をスマホで解析し、あなたのカラダとココロの状態を計測します。

6軸モーションセンサー、眼電位センサーから体と目の動きのデータが取れるので、姿勢チェック、集中度計測などヘルスケアに関連する色々面白いことができそうです。

ちなみに、以前初代JINS MEMEのハッカソンに参加し、JINS MEMEと連携したアプリ(the zen)を作ったことがあります。

news.livedoor.com

JINS MEMEで解錠

JINS MEMEと連携したアプリを使えば、Akerunの近くてまばたきするだけで解錠できます。

f:id:photosynth-inc:20211211154636g:plain

弊社の「タッチレスエントリー・ソリューション」と組み合わせると、完全タッチレスでより快適な解錠を実現できます。

f:id:photosynth-inc:20211216182336g:plain

JINS MEMEとの連携

JINS MEMEから「20Hzデータ」、「15秒間隔データ」、「60秒間隔データ」を取得できますが、 リアルタイムでAkerunを制御するには、JINS MEMEからBluetooth経由で20Hzデータを取得する必要があります。

jins-meme.github.io

f:id:photosynth-inc:20211211160109p:plainhttps://jins-meme.github.io/sdkdoc2/

JINS MEME SDK

SDK共通事項 - JINS MEME DEVELOPER DOCUMENTS

JINS MEMEを購入する前にちゃんと調べていなかったので、購入後SDKが使えないことを知って少しがっかりしましたが、お問い合わせしたところ、期間限定でご提供いただきました。有り難く使わせていただきます。

非公開情報なので、JINS MEME SDKの詳細については触れません。JINS MEME Platformに公開されている範囲内の内容についてご説明します。

JINS MEME SDK(framework)導入

SDKは一般的なframeworkとして提供されるので、Targetの「Frameworks, Libraries, and Embedded Content」にframeworkを追加すれば使えます。

f:id:photosynth-inc:20211211162018p:plain

Akerun施錠解錠実装

施錠解錠API

今回はAkerunアプリで使われているBluetooth専用APIを使っていますが、Akerun公開APIからも施錠解錠が可能です。 具体的な実装は割愛します。弊社のAkerun Developersと下記の関連記事をご参照ください。

developers.akerun.com

akerun.hateblo.jp

施錠解錠トリガー

下記通りシンプルです。

JINS MEMEから取得したモーションセンサーデータ(頭の動き)と眼電位センサーデータ(まばたき)から施錠解錠コマンドを判定し、Akerunにコマンドを送信するだけです。

施錠解錠判定

以下の二つの方法で試してみました。

  • 頭の動き(傾き)とまばたき回数の組み合わせ

  • まばたき回数と間隔

頭の動き(傾き)とまばたき回数の組み合わせ

単純にまばたきだけだと、意図しない施錠解錠処理が走る可能性が高いので、頭の動き(傾き)も判定するようにしました。

f:id:photosynth-inc:20211212102404p:plainhttps://jins-meme.github.io/sdkdoc2/basics/definition.html

  • 頭の動き

f:id:photosynth-inc:20211212102727p:plain

普段の動きから見ると、前後の傾きとか左右回転は多少不自然な動きになるので、左右傾きのrollのデータを使います。

右方向への傾きを解除とし、左方向への傾きを施錠とします。それぞれrollの範囲は「-180-0」と「0-180」になります。

閾値ですが、小さすぎると、誤動作が発生やすいし、大きい過ぎると、不自然な動きになります。何より首が痛い。。。

色々試した結果20前後が割と良さそうなので、今回は20を取ることにしました。

if data.roll > 20 {
    // 右傾き:解錠判定
} else if data.roll < -20 {
    // 左傾き:施錠判定
} else {
    // クリア
}
  • まばたきと回数

まばたき速度(blinkSpeed)とまばたき強度(blinkStrength)両方を見てまばたきの判定をします。

こちらについても色々試した結果、まばたき速度は60以上、まばたき強度は30以上にすれば問題なさそうです。

f:id:photosynth-inc:20211212104216p:plain

まばたき回数ですが、1回だと誤動作が発生しやすいので、2回まで見ることにしました。

ただ、普段も頭を傾いてまばだきする時もよくあるので、意図しない施錠解錠を減らすために、さらにタイマーを掛けて、「2s以内にまばたき2回」の条件をつけました。

最初は1s以内にしてみましたが、JINS MEMEかSDKの制限(?)で1s以内に複数回まばたきしても検出できないようなので、2sにしています。

多分、下記だと思うので、来年改善を期待します。

まばたきの検出が遅い→2022年に対応ファームウェアを公開する方向で検討中

まばたきの検出は前世代では0.3s、現世代では0.5sのディレイがあります。これは「ライフログで一番精度が上がる」ように誤判定の原因になりそうな前後のシグナル推移のウィンドウを確保しているのが理由です。こちらは2022年以降に対応ファームウェアを公開する方向で検討しております。

https://jins-meme.github.io/sdkdoc2/basics/motion-tracking-howto.html

解錠判定処理を下記になります(一部抜粋)。

if data.roll < -20  {
    if self.unlockBlinkCount < 2 {
        if data.blinkSpeed > 60 || data.blinkStrength > 30 {
            self.unlockBlinkCount += 1
        
            if self.unlockBlinkCount == 1 {
                self.startSignalTimer()
            } else if self.unlockBlinkCount == 2 {
                self.stopSignalTimer()
                return .unlock
            }
        }
    }
}

private func startSignalTimer() {
    self.signalTimer = Timer.scheduledTimer(
        timeInterval: self.signalTimeout,
        target: self,
        selector: #selector(self.notifySignalTimeout(_:)),
        userInfo: nil,
        repeats: false
    )
}

@objc func notifySignalTimeout(_ tm: Timer) {
    self.unlockBlinkCount = 0
}

まばたき回数と間隔

詳細は割愛しますが、解錠だけの場合まばたき回数と間隔を調整すれば、頭の動きを見なくても特に問題なく解錠ができます。

例えば、1s前後の間隔でまばたき3回で解錠します。

この方法だと、JINS MEMEのジャイロセンサーを使わないので、バッテリー持ちが良くなります。また、首の負担が大分減ると思います。これ大事です。

Akerunはautolock機能があるので、解錠さえできれば十分だと思います。

今後の改善

  • 今回はシンプルにまばたき回数しか使っていないですが、さらにまばたきの詳細パターン(回数、間隔、速度、強度)をチューニング、学習すれば、精度を上げられそうです。

  • まばたきは「静止指標」で、頭の動きも含めて歩いている状態だと少しノイズが入るので、精度が低くなります。noiseStatusを使って補正ロジックを入れるなど工夫が必要そうです。

  • まばたきの検出が遅い問題について、次バージョンファームウェアを待つしかないですが、検出が早くなると、施錠解錠精度がもっと上がるように色々工夫ができそうです。

  • 視線の動きも入れることで誤動作などを減らせそうですが、うまく検出できない時もあるので、今後ファームウェア改善されたら試してみます。

まとめ

  • 非公開のため詳細は割愛しましたが、JINS MEME SDKはシンプルで使いやすかったです。サンプルコードもあったので、すぐアプリに組み込むことができました。

  • スマホの場合、画面をみないと処理が始まったのか、検出と判定にエラーが出たのか分からないので、Apple Watchのバイブレーションを使えば、分かりやすくなると思います。次回試してみたいと思います。

  • 日常で使えるウェアラブルバイスとしてJINS MEMEはとても素晴らしい製品なので、今後も色々連携してみたいと思います。SDKが一般公開されれば、さらに広がりそうですね。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

施解錠遅延の限度見本

この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 12日目の記事です。

最近は越境活動が板についてきた daikw - Qiita です。

部署や役職を跨いで共通の認識を作ることで、プロジェクトが円滑に回る、というのはあるあるです。 今日は、この共通認識を作るために造ったものを、一つ取り上げます。

結論

3行でまとめると、

  • 「包括的なドキュメントよりも動くソフトウェアを」の実践は、越境的活動に良い効果があった。
  • 生産現場では、「動くソフトウェア」に近い概念の「限度見本」を使っている。
  • ngrok は良い、vercel も良い。

限度見本とは

僕はソフトウェア技術者でして、なるべくテストコードを書きながら開発しています。 いちいち手動でテストするのは面倒だけど、作ったものが意図通りに動くことは確かねばならないわけでして、 つまり ソフトウェアの品質 を保つために、テストコードを書いて自動で走らせます。

一方で、製造した 製品の品質 を保つための活動として、検査治具を設けたり、外観検査をしたりします。 外観検査ではヒトの五感を使うため、感覚によるバラツキが発生しないようなわかりやすい判定基準を設けるのが望ましく、基準となるサンプルを用意して比較対象とします。

この基準となるサンプルを、「限度見本」と一般的に呼んでいるようです。 いくつかweb上のコンテンツから引用すると、

外観検査を始める前に | 外観検査の基本 | 外観検査.com | キーエンス によれば、

目視による外観検査は、人間に依存する割合が高く、ヒューマンエラーや検査員によるばらつきが発生しやすい工程です。それらを防ぐためには、良品と不良品の境界線を明確に決めておくことが大切です。 仕様書・検査基準書のような文字と写真ではなく、合否の判定基準を目で見てわかるようにする「限度見本」

限度見本の意味と定義とは|限度見本を英語でいうと によれば、

ある製品を品質上合格とするか、不合格とするかの限度を示した製品見本 boundary sampleやlimit sampleもしくはcriteria sampleといった表現がよく使われます。

ソフトウェア限度見本

つまり限度見本は、「検査員」と「設計者・技術者」との間のコミュニケーションツールとして機能しています。 しかも文字ベースの情報伝達と比べて、ディティールを書き下す・読む必要がなく、とても低コストです。

ここで アジャイルソフトウェア開発宣言 より、「包括的なドキュメントよりも動くソフトウェアを」を引用しましょう。

御託を並べるより、動くものをスパッと見せてやれば、コミュニケーション齟齬は一発で無くなったりするものです。

よってここでは、「限度見本」の定義を少し緩和して、「ソフトウェア限度見本」を

「品質を敢えて落とした製品・サービスであって、品質基準の検討のための見本となるもの」

と定義し、それを満たすようなものを作ってみました。

スコープ・要件

ここでは試みとして、弊社スマートロックサービスの「施解錠速度」の品質基準検討に使えるようなものを目指しましょう。

要件をざっくり洗い出し、実装中に目標を見失わないようにします。

  • Akerunに遠隔でコマンドを送信する
    • コマンドの種類
      • 施錠
      • 解錠
      • 状態取得
    • コマンドの性質
      • コマンドの実行完了を検知できる
      • コマンドの予定の実行時間を変更できる
      • コマンドの実際の実行時間を測定できる

実装

なんやかんやあってできました! 施解錠遅延によって、どの程度違和感が発生するのか、それを体感してもらうのが目的です。

右上の delay input でどのくらいの遅延が発生したか、を模擬します。

f:id:photosynth-inc:20211210130353p:plain
限度見本

設計ざっくり

技術的に新しいところは一切なく、Web開発で使いそうな基本的な技術だけで作っています。 Akerun Remote 内部に Akerunの管理コンソール画面 Akerun Connect の機能縮小版を作ったようなものです。

  • 通信の流れは、ブラウザ -http-> Remote -ble-> Akerun
  • Remote 実機内部にAPIサーバ / フロントエンドサーバ を立てて、ブラウザからの解錠指示をAPIサーバで受け取り、Akerunへのコマンド送信を直接行う
  • ブラウザで遅延時間の調整ができる
  • ngrok を活用して、ブラウザへの公開を簡単にする
  • ついでに雑にレスポンシブにして、適当なサイズのスマホと、PCブラウザどちらからも操作可

作り方

追加で作るものは、ざっくり2つだけでした。

  • APIサーバ
  • フロントエンドサーバ

あとは、APIサーバからAkerunのコマンド送信を直接行うツールが必要です。今回はCLIツールを事前にご用意しました。3分クッキングでは定番です。

APIサーバ

こちらは複雑さをCLIに押し込んだので、とても簡単な構成です。

  • 僕がセットアップに慣れている flask を使い、
  • レスポンスはフロントエンドで扱いやすいように json 化し、
  • 事前に用意したCLIに繋ぎこみました。
┬─[daiki~@photosyth~:~/g/s/w/r/api]─[15:40:51]─[G:main=]
╰─>$ tree -I '__pycache__|venv'
.
├── README.md
├── app.py
├── appstart.sh
├── docs
│   └── package.json
└── requirements.txt

1 directory, 5 files

app.py はごく単純で

import os
import json
import subprocess as sp

from flask import abort
from flask import Flask
from flask import jsonify

from flask_cors import CORS

app = Flask(__name__)
CORS(app)

akerun_id = os.environ.get("AKERUN_ID")
cwd = os.environ.get("CLI_DIR")

# remote_cli's bleMode proxys
def bleMode_call(cmd):
    if akerun_id:
        command = ["node", "cli", "ble", cmd, "-a", akerun_id]
    else:
        command = ["node", "cli", "ble", cmd]

    res = sp.run(command, cwd=cwd, stdout=sp.PIPE, stderr=sp.STDOUT)
    print(res.stdout)
    return json.loads(res.stdout.decode('utf-8'))


@app.route('/ble/toggle', methods=['POST'])
def bleMode_toggle():
    res = bleMode_call('toggle')

    if res["level"] == "error":
        return abort(500, res)
    else:
        return jsonify(res)


@app.route('/ble/infopro', methods=['POST'])
def bleMode_infopro():
    res = bleMode_call('infopro')

    if res["level"] == "error":
        return abort(500, res)
    else:
        return jsonify(res)

フロントエンドからのリクエストを、Akerun本体にプロキシするような作りになっています。簡単ですね。

フロントエンドサーバ

  • 僕が謎にセットアップに慣れている nextjs を使い、
  • さらに僕が謎になんとなく好きな material-ui を使って

作りました。単一ページで、少しコンポーネントに分けていますが、ごく単純な設計です。

┬─[daiki~@photosyth~:~/g/s/w/j/t/remote-instructor]─[15:11:25]─[G:master=]
╰─>$ tree -I node_modules
.
├── README.md
├── jest.config.js
├── next-env.d.ts
├── next.config.js
├── package.json
├── public
│   └── favicon.ico
├── src
│   ├── components
│   │   ├── large_text_field.tsx
│   │   ├── popup.tsx
│   │   ├── progress_bar.tsx
│   │   ├── pseudo_delay_input.tsx
│   │   ├── record_table.tsx
│   │   └── timer.tsx
│   ├── layouts
│   │   └── main.tsx
│   ├── modules
│   │   ├── logger.ts
│   │   └── utils.ts
│   ├── pages
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   └── index.tsx
│   └── styles
│       └── theme.ts
├── tsconfig.json
└── yarn.lock

package.json には特に特徴はなく、

{
  "name": "remote-instructor",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "DEBUG=remote-instructor-* next dev",
    "debug": "NODE_OPTIONS='--inspect' next dev",
    "test": "jest",
    "build": "next build",
    "start": "DEBUG=remote-instructor-audit-* next start -p 80"
  },
  "dependencies": {
    "@material-ui/core": "^4.11.4",
    "@material-ui/icons": "^4.11.2",
    "@mui/x-data-grid": "^4.0.1",
    "debug": "^4.3.1",
    "next": "10.0.8",
    "react": "17.0.1",
    "react-dom": "17.0.1"
  },
  "devDependencies": {
    "@types/debug": "^4.1.5",
    "@types/jest": "^26.0.23",
    "@types/node": "^14.14.37",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "dotenv": "^8.2.0",
    "jest": "^26.6.3",
    "ts-jest": "^26.5.5",
    "typescript": "^4.2.3"
  }
}

nextjs に依存しているため、pages/index.tsx にほとんど全て詰まっています(省略するところがほとんどなかった ... )。

// Framework
import React, { useState, useRef } from "react"

// UI
import { makeStyles } from "@material-ui/styles"

import Grid from "@material-ui/core/Grid"

import CircularProgress from "@material-ui/core/CircularProgress"
import Icon from "@material-ui/core/Icon"
import IconButton from "@material-ui/core/IconButton"

import { sleep } from "@/modules/utils"
import { Timer, timeMeasureAround, getTime } from "@/components/timer"
import { PseudoDelayInput } from "@/components/pseudo_delay_input"
import { RecordTable, pushToTable } from "@/components/record_table"

const useStyles = makeStyles({ ... })

const LockState = {
  Unknown: 0, // default
  Indeterminate: 1,
  Open: 2,
  Close: 3,
} as const
type LockState = typeof LockState[keyof typeof LockState]

const LockStateKeys = {
  0: "unknown",
  1: "indeterminate",
  2: "open",
  3: "close",
}

export default function Home(props: any) {
  const classes = useStyles(props)

  const timer = useRef<any>() // reference to the Timer Element
  const table = useRef<any>() // reference to the RecordTable Element

  const [pseudoDelay, setPseudoDelay] = useState<number>(0) // [sec]. input by the user
  const [lockState, setLockStateRaw] = useState<LockState>(LockState.Unknown)

  const lockStateTimeLimit = useRef(null)
  const setLockState = (input: LockState) => {
    setLockStateRaw(input)

    clearTimeout(lockStateTimeLimit.current)
    if (input === LockState.Open || input === LockState.Close) {
      lockStateTimeLimit.current = setTimeout(() => {
        setLockState(LockState.Unknown) // clear lockState after 30 sec
      }, 30 * 1000)
    }
  }

  const processing = useRef(false)
  const iconClickAround = (func: () => Promise<LockState>) => {
    return async () => {
      if (processing.current) return
      processing.current = true
      setLockState(LockState.Indeterminate)

      const nextState = await timeMeasureAround(async () => {
        await sleep(pseudoDelay * 1000) // wait for `pseudoDelay`, to simulate the Akerun Remote delay time
        return await func()
      }, timer)()

      processing.current = false
      setLockState(nextState as LockState)
    }
  }

  const onReplayIconClick = iconClickAround(async () => {
    const url = `/ble/infopro`
    console.log(url)

    return await fetch(url, { method: "POST" })
      .then((resp) => {
        if (!resp.ok) {
          console.error(resp)
          pushToTable({ state: "unknown", cmd: "infopro", time: getTime(timer) }, table)
          throw new Error("server error")
        }
        return resp.json()
      })
      .then((body) => {
        const lock_state = body["message"]["lock_state"]["data"][0]
        const nextState = {
          "0": LockState.Open,
          "1": LockState.Close,
        }[lock_state]

        pushToTable({ state: LockStateKeys[nextState], cmd: "infopro", time: getTime(timer) }, table)
        return nextState
      })
      .catch((err) => {
        console.error(err)
        return LockState.Unknown
      })
  })

  const onLockIconClick = iconClickAround(async () => {
    const url = `/ble/toggle`
    console.log(url)

    return await fetch(url, { method: "POST" })
      .then((resp) => {
        if (!resp.ok) {
          console.error(resp)
          pushToTable({ state: "unknown", cmd: "infopro", time: getTime(timer) }, table)
          throw new Error("server error")
        }
        pushToTable({ state: "open", cmd: "toggle", time: getTime(timer) }, table)
        return LockState.Open
      })
      .catch((err) => {
        console.error(err)
        return LockState.Unknown
      })
  })

  const onUnlockIconClick = iconClickAround(async () => {
    const url = `/ble/toggle`
    console.log(url)

    return await fetch(url, { method: "POST" })
      .then((resp) => {
        if (!resp.ok) {
          console.error(resp)
          pushToTable({ state: "unknown", cmd: "infopro", time: getTime(timer) }, table)
          throw new Error("server error")
        }
        pushToTable({ state: "close", cmd: "toggle", time: getTime(timer) }, table)
        return LockState.Close
      })
      .catch((err) => {
        console.error(err)
        return LockState.Unknown
      })
  })

  return (
    <Grid container direction="row">
      <Grid container direction="row" className={classes.indicatorContainer}>
        <Timer ref={timer} />
        <PseudoDelayInput
          value={pseudoDelay}
          handleChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
            setPseudoDelay(Number(ev.target.value))
          }}
        />
      </Grid>

      <Grid container direction="column" className={classes.iconContainer}>
        {(() => {
          switch (lockState) {
            case LockState.Open:
              return (
                <IconButton onClick={onUnlockIconClick}>
                  <Icon className={classes.bigIcon}>lock_open</Icon>
                </IconButton>
              )
            case LockState.Close:
              return (
                <IconButton onClick={onLockIconClick}>
                  <Icon className={classes.bigIcon}>lock</Icon>
                </IconButton>
              )
            case LockState.Indeterminate:
              return <CircularProgress />
            case LockState.Unknown:
            default:
              return (
                <IconButton onClick={onReplayIconClick}>
                  <Icon className={classes.bigIcon}>replay</Icon>
                </IconButton>
              )
          }
        })()}
      </Grid>

      <Grid container direction="column" className={classes.tableContainer}>
        <RecordTable ref={table} />
      </Grid>
    </Grid>
  )
}

少々複雑ですが、保持する状態は少なく、フロントエンド特有のものです。仕方ない。 ただ一応、 react-hook を使って多少イイカンジに書いています。素敵!!

ngrok

ngrok は、ローカルPC(この場合、Akerun Remote内部)で動作させているTCPアプリケーションを簡単に外部公開できるサービスです。

プログラマ3大美徳 を黙らせてくれる大変便利なツール。

  • ただのデモアプリだろうと、関係者はまとめて触るのが当然(傲慢)
  • 本番相当のホスト環境を用意するなんて面倒なことをするはずが(怠惰)
  • そもそもそんなことに時間をかけるのはいやだ(短気)

このデモを作った際に個人的に課金しました。

f:id:photosynth-inc:20211210153523p:plain
左: nextjsの動作環境 / 右: ngrokでサービス提供

え? vercel を使えばいいじゃないかって?ソウデスネ .....

CORS対策

さて、実はこのままでは動かないです…

nextjs と別の APIサーバ を共有するのに、クロスオリジン制約を突破する必要があります。

nextjs の rewrite を使うか、nextjs API Routeで一旦受け取ってプロキシすると良いです。そのうち追記するかも。。

限度見本が与えた影響

目の前に動くものがあることで、障害発生時の振る舞いを(開発・サポート・営業の誰もが)想像しやすくなり、コミュニケーションが簡単になりました。 おかげで、部署・関係者間の齟齬なく、やるべきことだけに取り組めるようになりました。

副次的な効果として、アドベントカレンダーのネタになったことに感謝しつつ、今日は終わり。

参考リンク


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

Androidチーム2021年の改善活動

この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 11 日目の記事です.

Webエンジニアのohioshirt - Qiitaです。主にAndroidアプリの開発を担当しています。

Androidチームは、2020年に投稿されたHello legacy codes! - フォトシンス エンジニアブログ のようなレガシーコードの課題を抱えていました。
レガシーコード自体については、先の記事で詳しく触れていますので、
本記事ではAndroidチームがそれに向き合いやってきたことをざっと振り返ってみます。

主な取り組み

「コード」に特化するとネタが尽きるのでここではもう少し広く、レガシー体験の改善とします。

リリースビルドの自動化

これまではAndroid Studioで Build > Generate Signed Bundleを実行していました。
つまり手作業です。初めはビルドの度にドキドキしていました。 Play Console APIを直接叩く方法もありますが、 https://github.com/Triple-T/gradle-play-publisherで済ませました。
今ではgit tagを打つことでCircleCIでworkflowが走り、
Google Play Consoleにリリース用のBundleが上がるようになりました。
地味な手作業が減ることに加えて、ビルド環境も個人に依存しなくなり不安要素が減りました。
今後も手作業・属人化した作業から解放されるための取り組みを続けていきます。
私たちの戦いはこれからです。

f:id:photosynth-inc:20211216110220p:plain
リリース用のCircleCI workflowができました

ユニットテストの改善

Androidチームでは、Pull RequestなどをトリガーにCircleCIでユニットテストを実行するワークフローを組んでいますが、
一時期ユニットテストが途中で落ちる事象が続いていました。  

悲しみのメッセージたち
Too long with no output (exceeded 10m0s): context deadline exceeded
The connection attempt hit a timeout after 120.0 seconds (last known process state: STARTED, running: true). This exception might occur when the build machine is extremely loaded.
Process 'Gradle Test Executor 7' finished with non-zero exit value 137

f:id:photosynth-inc:20211216113001p:plain
タイムアウトしていました。
f:id:photosynth-inc:20211216130339p:plain
並列化せよとのお達し

CircleCIのサポートページ「ビルドに設定してあるタイムアウトリミットを超えた場合」に一部のエラーへの対処法が記載されていますが、
そうした一時凌ぎができる問題ではないことが分かったのでテコ入れを行いました。

  • テスト実行マシンのスペック見直し
    CircleCIのドキュメントによれば、
    resource-class: large 以上のマシンスペックを指定するのが一般的なようです。
    Androidチームではmediumを指定していました。おそらく導入当時はそれで足りていたのだと思います。
    迷わずlargeに上げました。

  • ユニットテストの並列化
    CircleCIはテストの並列実行をサポートしているため、Androidチームでもこれを導入しました。
    ただし、Androidプロジェクトではこのドキュメントのようにファイル毎の実行はできずモジュール毎のテスト実行が必要です。
    コンテナ毎のテスト実行対象モジュールはシェルスクリプトで制御、指定します。
    実行中のコンテナID(0〜i)は $CIRCLE_NODE_INDEXで取れるので、 n%i の要領で実行対象を割り振れます。

# feature/loginのような構成で置かれているモジュールに対してディレクトリを走査してリストを生成、forループで回して ./gradlew :login:testDebugUnitTest の形で実行する
for D in "${SEARCH_DIRS[@]}"; do
MODS="$(ls "${D}")"
  for MOD in $MODS; do
    MODULES+=("${D}:${MOD}")
  done
done
  • JaCoCoを利用したテストカバレッジの表示
    ユニットテストカバレッジもCIで取るようにしました。 https://github.com/arturdm/jacoco-android-gradle-plugin などでサクッとモジュール単位のレポート生成はできたのですが、
    CI上で並列実行されるテストレポートをどう結合すべきかが悩みどころでした。
    とりあえずの対処としては、ローカルで実行した状態を再現させることにします。
    モジュール毎のテストレポートファイルを一時保存し、
    テスト完了後に保存したファイルを元の位置に戻してレポートを結合するようにしました。
    動作イメージは以下のようなものになります。
# テスト job
- run: cp -r --parents ".*/build/reports" /tmp/reports
- persist_to_workspace:
     root: /tmp/reports

# テスト の後のjob
- attach_workspace:
     at: /tmp/reports
- run: cp -r /tmp/reports/* .
- run: ./gradlew レポート結合
- store_artifacts:
     path: ./build/reports

暫定対処である印象は否めないものの、妙な作り込みが少ないためひとまずはこれで運用しようと考えています。

f:id:photosynth-inc:20211216150120p:plain
改善後のCircleCIのテスト実行結果

諸々の改善によりユニットテスト実行時間が20分(時々落ちる)→平均7分前後にまで短縮されました。
カバレッジの表示については、具体的な数字やグラフが見えると達成感を得やすいので、 まだ導入していない方はぜひご検討ください。
今回の導入によりテストコードが薄い箇所が露見したため、これからも改善に取り組んでいきます。
私たちの戦いはこれからです。

Javaで書かれたコードのKotlin化、リファクタリング

新規に実装する時にKotlinを選ぶのは当然のこととして、
機能改修に合わせて既存のJavaコードをKotlinにリファクタリングするなどの改修を行ってきました。
どれだけ変わったのか、MADスコアを算出して1年前のコードと比較しました。
スコアの算出方法はこちらで紹介されていますので、
興味がある方はぜひ自分のプロジェクトでお試しください。

f:id:photosynth-inc:20211210135516j:plain
2020年のMADスコア(参考記録

f:id:photosynth-inc:20211210135616j:plain
2021年のMADスコア

地道な改善を続けた結果、Kotlinコードの割合が大きく変化しました。
機能追加・改修にあまり影響を与えないJavaコードはまだ残っています。
今後もビジネス要求に素早く応えるため、そして自分たちの開発体験をよくするための取り組みを続けていきます。
私たちの戦いはこれからです。

皆さんもこの1年間の成果を振り返ってみてはいかがでしょうか。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

エンジニアとコーチングの相性の良さ

この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 10 日目の記事です。
FW 開発者だけどあまりコードを書く時間がとれていない AkiAbe - Qiita です。

テクニカルなことはギークなチームのみなさんが記載してくれるので、私からはヒューマンよりのお話を。

はじめに

ソフトウェアエンジニアって世間から見ると「パソコンと仕事をしている」みたいな印象を持たれること多そうですが、実はめちゃくちゃ人と対話をして協力しながら仕事を進める職業です。
だって、皆の叡智を結集して一つのプロダクトを作るんですもの。

この記事のタイトルに「コーチング」という題名がついていますが、コーチングのやり方とかそういうことには言及しません。
エンジニア (というか人類) が、人と一緒に何かを実現するときに、コーチングの考え方を知っておくことのメリットを挙げるだけの記事です。
また、コーチングは色々な考え方があります。あくまで、この記事は個人の解釈となります。ご了承ください🙇🏻‍♂️

コーチングとは

とはいえ、コーチングってどういうこと?という方もいると思うので、簡単に私の考える解釈を記載してみます。

コーチの語源

コーチ (Coach) は馬車を意味します。(同じ名前の某有名ブランドも馬車のマークですね)
コーチングは、「人を望む場所に送り届ける技術」という解釈をしています。

コーチングとティーチング

よく使うたとえで、飢えた人がいたときに

  • 魚を与える (答えを教えて今の問題を回避する) のが teach
  • 魚のとり方を教える (答えを自分で導くための術を教える) のが coach

というものがあります。

短期的な問題解決だけをする場合 (この例だと、とりあえずすぐに飢え死にするのは避ける) は teach の手法をとることも良いかと思います。
ですが、「人を育てる」、「自立を促す」という意味では coach のアプローチを取るのがよいと思います。

世界は対話でなりたっている

前述したとおり、エンジニアの仕事の大半は対話です。
コーチングもクライアントとの対話により気づきを与えていき成長を促していくようなスキルになります。
なので、エンジニアはコーチングを知るべきなのです。

IoT 機器なんかは機械同士で対話して情報をやり取りしているし、ソフトウェアもモジュール同士が決められたプロトコルで対話していますね。
我々も、プライベートではオフラインで家族やパートナーと対話をしていますし、スマホなどを通してオンラインで世界の誰かと常に対話して生活してますね。
ということで、やっぱり世界は対話でなりたっているんですよ。

私が学んで一番よかったこと

コミュニケーションタイプを知れたこと

「人により、コミュニケーションタイプというものがある」ということを知れたことです。
コミュニケーションタイプに関してはこの記事が読みやすいと思います。 タイプはざっくり 4 分類にわかれています。

coach.co.jp

相手のコミュニケーションタイプをあらかじめ知っていると、どういうコミュニケーションをとると話がスムーズに進みそうか?など見えてくる (気がする) 。

私のコミュニケーションタイプはなんだろう?

ちなみに私はプロモーターというタイプです。
ちょっと特性みていきましょう。

アイディアを大切にし、人と活気あることをするのを好む

うんうん。なんか活発に意見を交わす好青年の絵がみえますね。
いいですね。

承認を代表とする、こまめな働きかけがないと、一気にやる気を失います。
また、オリジナルなアイディアを大切にするため、自分のアイディアを否定されることを嫌います。

えっ。結構めんどくさいやつやん。。

彼らと接するときは、次のポイントを意識してみましょう。

  • 話を聞くときは相づちをうつなどして明確に反応する
  • 質問は間口の広い質問をして自由に話をさせる
  • たくさんほめる(特に彼らの影響力をほめると効果的)
  • 彼らのオリジナルなアイデアに耳を傾ける

だそうです。ほめて伸びるタイプ!!
みんな、もっとほめて!!!

まあこんな感じでざっくりとでも、対話をする人がどんなタイプかわかっていれば 、同じ内容を素早く伝えるために、伝え方を工夫することができます。

いい意味で「人に期待をしない」

もともと他人を期待をしない性質でしたが、この 4 タイプを知ってから
それがより自分の中の人付き合いの軸になりました。

「期待をする」という行為自体は悪いわけではないですが、「裏切られた」
と感じたときのストレスは、私にとってかなり大きいです。

とはいえ、私もたまに都合よく何かを期待をしてしまうことはあるのですが
「そもそも考え方 (というか大事にしているポイント) が違うんだから仕方ない」
と思えるようになったことで、生活のストレスが少し減りました。

そして、私もたぶん誰かの期待を裏切りながら生きている。
でも、叩かないでください。
ちゃんとそういう認識していることを、ほめてくださいw

「おもいやり」と「自衛」

前述の「期待しない」から派生している考え方なのですが
私自身の一緒に仕事する人への接し方が変わりました。

基本的な考え方としては

  • みんな知らなくて当然
  • 意見、反論があって当然
  • 対話が前提 ( 一発で話が伝わると期待をしない)

対話をするときも、話し相手が 4 つのタイプのどの傾向を持っているかを
なんとなく把握できると

「この人にはこういう接し方をした方が、素直に意見を受け取ってもらえそうだな」
というように、スムーズに話を進めるための思いやりができるようになります。

また、自分が嫌に思うような言い方をされたときでも
「この人がこういう言い方するのは、悪気はないんだな」
と思うことができるようになります。

こうなれば、無駄に自分が傷つくことを減らすこともできます。
自分が傷ついてしまうと、相手へのコミュニケーションの取り方も雑になってしまいますし
負の循環を構成する要因にもなると思っています。

コーチングを学ぶことは、相手を伸ばすことだけでなく、自分を守ることにもつながるのです。
こうみると、素晴らしい学問ですね。

さいごに

コーチングというワードは 「人を育てる」という目線や文脈での話題が多いと思いますが、今回は「自分を守り、自分が仕事をしやすくする」という目線で記事を書きました。

  • エンジニア (に限らず人類) って、人と対話をしながら、協力しながら仕事をしています
  • 自分と違う思考パターンの人は世の中に存在します
  • そういった人と話すときに誰も傷つけないことができると一番ハッピーです
  • でも、自分の人生なのでまずは自分が傷つかないことを優先しましょう
  • そのとっかかりとして、コーチングを知ることはメリットのあること

みたいなことが伝えたかったことになります。

当然、コーチングという技術は、自分が関係する人 (たとえば、部下や後輩) を成長させるために使えるもので、そのような使い方をするのにもめちゃくちゃ有意義です。
その辺りのお話はまた別途。

みなさん、これからも楽しく開発をしてものづくりをしていきましょう!
(あれ?結局、コミュニケーションタイプと、それを私がどう利用しているかの話しか書いてないな。。)


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

ソフトウェア考古学者へ。Hack-The-Box はいいぞ

この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 6日目の記事です。

昨日に引き続き、 daikw - Qiita です。最近、 Hack The Box に手を出しています。

昨日書いたソフトウェア考古学は実際に社内で行なっていますが、この業務に ペネトレーションテスト の経験が生きたなと感じました。今日はそんなお話です。

要約

  • 「よくわからないものを、よくわからないなりに、何とか扱う」という点で、 攻撃的セキュリティソフトウェア考古学 の構造はかなり似ている。
  • みんなで Hack-The-Box を勉強してつよいエンジニアになろう!

意義と建前

攻撃的セキュリティ (Offensive-Security) を学ぶことで、防衛に役立てる。もって事業継続を確かなものとする。

情報セキュリティを語るとき、リスク脅威守るべき資産守るための活動 などに分けて考えることができます。

インターネット上の全ての主体は、常に何らかの 脅威 に晒されていることが知られています。 守るべき資産 が脅威に晒されるとき、 リスク が生じます。

リスクに対してノーガードでは一瞬で首を刈られます。リスクを知り、緩和し、資産を 守るための活動 を一定行う必要があります。

脅威やリスクのカテゴライズは色々な団体が行っており、例えば 情報セキュリティ10大脅威 2021:IPA 独立行政法人 情報処理推進機構 から引用すると*1、 組織外部の主体的な脅威(ランサムウェア・標的型攻撃)が上位に来ています。

昨年順位 個人 順位 組織 昨年順位
1位 スマホ決済の不正利用 1位 ランサムウェアによる被害 5位
2位 フィッシングによる個人情報等の詐取 2位 標的型攻撃による機密情報の窃取 1位
7位 ネット上の誹謗・中傷・デマ 3位 テレワーク等のニューノーマルな働き方を狙った攻撃 NEW
5位 メールやSMS等を使った脅迫・詐欺の手口による金銭要求 4位 サプライチェーンの弱点を悪用した攻撃 4位
3位 クレジットカード情報の不正利用 5位 ビジネスメール詐欺による金銭被害 3位
4位 インターネットバンキングの不正利用 6位 内部不正による情報漏えい 2位
10位 インターネット上のサービスからの個人情報の窃取 7位 予期せぬIT基盤の障害に伴う業務停止 6位
9位 偽警告によるインターネット詐欺 8位 インターネット上のサービスへの不正ログイン 16位
6位 不正アプリによるスマートフォン利用者への被害 9位 不注意による情報漏えい等の被害 7位
8位 インターネット上のサービスへの不正ログイン 10位 脆弱性対策情報の公開に伴う悪用増加 14位

事実上の戦争であって、誰もが巻き込まれており、他人事にはできそうにありません。

戦争を起こさんと脅威をデザインする攻撃者達の、考え方や道具( ~= 攻撃的セキュリティ)を学び理解することで、防衛に役立てることができるでしょう。 *2

参考リンク

概論

脅威モデリング

ニュース記事等

ペネトレーションテスト と Hack The Box

前章でそれっぽい意義を与えたので、技術者にとって魅力的な点を強調していきしょう *3

攻撃的セキュリティの実践的活動のうち、特に代表的な ペネトレーションテスト - Wikipediaペネトレ / Pentest とも)より、

ネットワークに接続されているコンピュータシステムに対し、実際に既知の技術を用いて侵入を試みることで、システムに脆弱性がないかどうかテストする手法

Pentest の独学はかなり難しく、なぜなら

  • 脆弱性のあるシステムを用意するのがやや難しい
  • 現実世界で攻撃をするのは犯罪である

ため。ただ、最近はこのハードルを下げてくれるサービスがさまざま存在します。例えば、

特に Hack The Box (HTB) の Machine と名前のついたカテゴリが、Pentest に近い分野です。

攻撃的セキュリティ と ソフトウェア考古学 の関係

次の点で、 攻撃的セキュリティソフトウェア考古学 はかなり似ています。 *4 「オペレータ」を、それぞれの活動をする主体として、

  • オペレータは、対象のシステムに対し事前知識を(ほとんど)持たない
  • オペレータは、対象システムから効率的に情報を取得して利用したい
  • オペレータは、対象システムを壊したくない

ホワイトハッカーの資格(Certified Ethical Hacker)の母体となる団体の ECCouncil によれば、攻撃の手順は5段階に分けられます。 Learn the 5 Phases of Ethical Hacking and Build a Career in it より、

  1. Reconnaissance (偵察)
  2. Scanning (スキャン)
  3. Gaining Access (アクセスの取得)
  4. Maintaining Access (アクセスの維持)
  5. Clearing Tracks (痕跡の除去)

このうち、1~3 で必要な技術は、遺跡調査に応用できます。

例えば、遺跡調査では、ネットワークスキャンによって対象サーバを発見し、対象サーバに対するポートスキャンによってサービスを特定することがあります。

遺跡調査では痕跡の除去は必ずしも必要ありませんが、下手なことをして二次被害を起こしたくありません。慎重な作業が求められるという点で同じと言えます。

まとめると、

  1. Hack The Box で勉強することで、
  2. 攻撃的セキュリティを学び、調査力がつくことで、
  3. ついでに考古学力を高めて、圧倒的な事業貢献となる

わけですね。こうしてつよつよエンジニアが量産されるのです。

いざ、攻撃的セキュリティに取り組む

もっとハードルを下げる

実は Hack The Box は参加すること自体がちょっと難しいです。ログインページでちょっとした問題を解く必要があります。

さらに、コンテンツは全て英語なため、念能力の素養のない Newbie にはなかなか勧めることができません。

そういった場合は、CpawCTF - Main pageEnvader | Linuxが学べるオンライン学習サイト が オススメできます。

強くなるには

ひとまず、Retired Machine を 公式の walkthrough や IppSec - YouTube の動画を見ながらたくさん解いてみるといいです。それで雰囲気が掴めると思います。

www.youtube.com www.youtube.com

でも、 Active Machine に30時間くらい取り組んでも解けないことは普通にあります。強くなりたければ、人間性を捧げましょう。

f:id:photosynth-inc:20211205193137p:plain
Kaggleを取り掛かるまでにやったこととと、モチベーションの維持のために必要だったこと - にほんごのれんしゅう より引用

なお、僕はまだ人間性を宿しています(Hacker になれてない ..........)。

参考リンク


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

*1: jmalarcon/markdowntables: markdownTables - Turn HTML table syntax into Markdown を使って変換

*2: なお、懲罰的抑止モデルには攻撃を抑止する効果がない、という議論があります( サイバー空間の安全保障をめぐる課題とアメリカの動向JIIA -日本国際問題研究所-研究活動 など)。こういった知識は、抑止目的ではなく、防衛目的で利用することになります。

*3: 資源の限定された企業活動では、たぶんランチェスター戦略が有効。なお、研究活動に選択と集中を要求するのは間違い。

*4: ただし DoS攻撃や Suicide Hacker を除く