selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

説明・説得コストについてアレコレ考えたこと

TL;DR

  • 払うべき説明・説得コストと、払うべきでない説明・説得コストが存在する
  • 払うべき説明・説得コストとは、その行為によって期待する成果
  • (積極的に) 払うべきでない、もしくは説明の方向性を逆転すべき説明・説得コストは、下記の通りだと考えている
    • 説明される側の、本来職種として持つべき知識の明らかな不足によるもの
    • 説明される側の、間違った先入観に基づく一方的な心理的抵抗によるもの
  • マネージャーとして、メンバーから説明を受けるときは下記項目を注意する
    • 今受けている説明コストは、本来メンバーが支払うべきものか
    • メンバーが支払うべきである場合
      • 何故にメンバーが支払うべきで、その説明を受け自分はどのようなアクションをするか、そのためにどこまでの品質を求めるのか
      • 上記項目が確実にメンバーに伝わっているか
  • マネージャーとして、払うべきでない説明・説得コストを支払っているシーンを見たら下記の対策を検討する
    • 説明の方向性を逆にする
      • なぜその変化が必要なのか。ではなく、なぜ変化を必要としないのか。
    • 職種ごとに必要とされる知識の種類と深さについて明文化する
    • 学習の工数を確保し、読書会等での業務時間内での学習を奨励する
    • 予算を確保し、説明コスト支払われる側の人に、外部の研修を受講してもらう
  • 説明・説得コストの削減、方向の変更、偏りの是正は、マネージャーとして最も重要な仕事の1つである
    • 大抵のケースにおいて、説明・説得コストを払う人は特定の個人に偏る
    • 説明・説得コストが特定個人に偏ってしまったチームは成長しなくなる
    • 故に、チームが健全に成長していく上で、説明・説得コストの偏りは是正しなければならない

はじめに

  • 組織、チームで仕事をしている以上、我々は常に変化の中にいる
  • 自分が主体的に変化を促す側になることもあれば、変化を受け入れる側になることもある
  • 変化を促すときは、相手の状況に合わせて適切なアプローチをしたいし
  • 変化を受け入れるときは、自分の現在地を理解して、適切なフォロワーシップを取りたい
  • 変化が起こるとき、そこには説明・説得コストが存在する
  • 「エンジニアリング組織論への招待」の著者である広木大地さんが 「Developer eXperience Day CTO/VPoE Conference 2021」 にて、下記のような話をされている

でも組織の文化としてソフトウェア作りが定着する前に、日本の大きな企業や行政機関は、いろいろなものをユーザー企業が丸投げしてしまったりするので、このノウハウが消失してしまいます。ソフトウェアを作ることが、どういうことなのかを理解する間もなく、徐々に消えていってしまう。 一方で、いい文化資本が蓄積する企業や、ソフトウェアエンジニアリングの文化資本、開発者体験を高めていくさまざまな工夫がどんどん蓄積していく環境の会社には就職したいと思ったり、自分のスキルアップになるんじゃないかと思うエンジニアが多い。そうじゃない会社は不遇な目に合うかもしれないし、嫌な思いをするかもしれないので、就職するのは止めておこうという気持ちになったりします。 こういった差が生まれるのは、文化資本の獲得をする際に、説明・説得に費やされるコストの差が、やはり大きいのではないかなと思っています。当たり前のように自動テストを書くことが習慣になっている会社と、「なんでそれをやらないといけないの? CI のツールはなんでそれを使わないといけないの?」と、いちいち説明・説得に費やされるコストがかかる会社は、なかなか定着していきません。

  • つまり、マネージャーとしては、不当に説明・説得コストが高い状況があれば是正しなければならない

説明/説得コストの分解

構成要素

  • 説明/説得コストについて要素を分解すると、形式知、経験、心理的抵抗の3点と考える。

f:id:selmertsx:20210920202826j:plain

  • 知識も経験もあり、抵抗もない。というケースは説明/説得コストは極小である
  • 知識としてはあるが経験がなく、心理的抵抗もない。というケースは説明・説得コストは少ない
  • 知識も経験もなく、心理的抵抗も大きい。という状況が最も説明・説得コストが高くなる
    • ※ 間違ったやり方の経験だけあり、それによって心理的抵抗が極めて高い状態は除く
  • 説明/説得コストを敢えて計算式で表現するならこんな感じか
Cost=\sqrt{x^2 + y^2 + z^2} \\
x: 知識不足への対応コスト \\
y: 経験不足への対応コスト \\ 
z: 心理的抵抗への対応コスト

説明・説得コストを払っていく

f:id:selmertsx:20210920222211j:plain

  • 心理的抵抗が少ないチームであれば、説明・説得コストの削減は難しくない
  • 知識がないのであれば、下記のような対応が検討できる
    • 職種ごとに必要とされる知識の種類と深さについて明文化し、読書会等での業務時間内での学習を奨励する
    • 予算を取得し、説明コスト支払われる側の人に、外部の研修を受講してもらう
  • 経験がないのであれば、下記項目が検討される
    • モブプログラミング等で、期限を決めて一緒に実験してみる ( 例: TDDとか )
    • 経験者を呼んで、経験談を教えてもらう ( ハンガーフライトみたいなもの )
  • 大変なのは心理的抵抗が高い状態
    • 特に「知識も経験もないが、心理的抵抗が高くチームとしてのINPUTも出来ていない」状態がやっかい

マネージャーとして説明・説得コストに関して気を払うこと

チームの学習機会の観点から

  • 説明・説得コストについて、説明の方向、頻度、分量については、注意が必要である
  • マネージャーは、下記のようなケースを見かけたら、特に意識して観察する必要がある
    • 例1: 自動テストを導入する上で、導入を推進する側が説明/説得コストを大量に払おうとしている
    • 例2: フロントエンドエンジニアとテックリードのどちらが決めても良い内容を、毎回テックリードが決めて、周りに説明している。
    • 例3: プロダクトオーナーとカスタマーサクセスのどちらが決めても良い内容を、毎回プロダクトオーナーが決めて、周りに説明している。
    • 例4: Scrumについて知らないが、導入する必要がないと考えており、学習する気もない
    • 例5: マイクロサービスについて知らないが、導入する必要がないと考えており、学習する気もない
      • ※ 別にマイクロサービスを推奨する訳ではないが、自分で学び、経験してもいないのに、判断を下すのは時期尚早である
  • それは説明・説得する側に回る人間のストレスを減らすためであり
  • 説明・説得される側の人間の、学習機会の損失や主体性の欠如を避けるためでもある

期待値の観点から

  • 誰が払うべき説明コストなのかを明確にする
  • 何が理由で、誰が、何を、どこまで説明する必要があるのか明確にする
  • とくに「どこまで」の部分が難しいが、ここをやりきることが大切だと感じてる

蛇足

やってみせ、言って聞かせて、させてみせ、ほめてやらねば、人は動かじ。
話し合い、耳を傾け、承認し、任せてやらねば、人は育たず。
やっている、姿を感謝で見守って、信頼せねば、人は実らず。
山本五十六
  • やってみせ、言って聞かせて が知識のINPUT
  • させてみせ が経験のINPUT
  • ほめてやらねば心理的抵抗の除外に結びつく

ドメイン乗っ取りを防ぐためにAWSで .com ドメインを使っている

TL;DR

  • JPドメインの管理をしている株式会社日本レジストリサービスには、下記の規則がある
    • ざっくり言うと、「誰かがあなたのドメインを欲しいと言って、あなたが10日以内に返事をしなければ、ほしいと言った人にあげちゃうよ」という規約である
// https://jprs.jp/doc/rule/toritsugi-rule-wideusejp.html
2 当社が、指定事業者に対して登録者の意思確認等を依頼した場合、指定事
業者がその依頼のときから10日以内に登録者がその意思を有しない旨の回答を
しない場合には、指定事業者において登録者の意思確認等を行い、登録者がそ
の意思を有する旨の回答を得たものとみなす。

用語の確認: レジストリ/レジストラ

// https://jprs.jp/doc/rule/toritsugi-rule-wideusejp.html
2 当社が、指定事業者に対して登録者の意思確認等を依頼した場合、指定事
業者がその依頼のときから10日以内に登録者がその意思を有しない旨の回答を
しない場合には、指定事業者において登録者の意思確認等を行い、登録者がそ
の意思を有する旨の回答を得たものとみなす。
  • つまり、「誰かがあなたのドメインを欲しいと言って、あなたが10日以内に返事をしなければ、ほしいと言った人にあげちゃうよ」という規約である

ドメインに関連する事件

ラブライブドメイン乗っ取り事件とドメイン移管ロック

  • 2019年4月5日、上述した仕組みを悪用した事件が起きた。
  • ラブライブというアニメのドメインがイタズラで乗っ取られて、上記のように書き換えられてしまった事件である
    • 詳細はこちら
    • きっと誰も 10日以内に返事をしなかったんだろう
  • この事件により、 ドメイン移管ロック という言葉が一躍有名になった

お名前.com 不正アクセス事件

  • .jpドメインドメイン移管ロックに 対応している お名前.com だが、2020年6月に重大なインシデントを起こした
  • coincheckとbitbankの2社が、お名前.com 側の管理ツールにおける不具合によってドメインの乗っ取りを受けてしまった
    • どのような不具合だったか詳細は不明
    • 2018年に お名前.com が提供を始めた 「ドメインプロテクション」という機能で対処できたかも不明
  • 全体的なまとめはこちら
  • Coincheck側の説明はこちら
  • 以上の問題により、一概にドメイン移管ロックに対応しているから問題なし、とも言えない

ドメイン乗っ取りの何が危険なのか?

  • ここまで長々とドメイン乗っ取りの事件について話してきた
  • ドメイン乗っ取りの何が危険なのか。ということであるが、ラブライブのときのようにサービスにイタズラされるのであれば怖くない
  • 一番怖いのは、フィッシングに使われてしまうことだ
    • 例えば、企業が利用しているドメインが奪われた場合、そのドメインを利用してお客様にメールを送られてしまうと機密情報が容易に取れてしまう可能性がある
  • 他にも、乗っ取ったドメインで、完全に同じ見た目のサービスを作ってしまえば、パスワードを盗むことも容易にできる
  • ということで、ドメインというものは守らなければならない資産であると言える
  • 故にドメインの捨て方は強く注意しなければならない
    • 企業が過去利用していたドメインが破棄されるのを見計らってそのドメインを購入し、フィッシングに利用する事件がよくある

個人的な決定内容

  • 以上の経緯より、自分は AWS.com ドメインを購入することにしている
  • そして、購入したドメインでは、ドメイン移管ロックを掛けている
  • これは、AWSが安全だと判断したから取った方針である

AWSにおける踏み台(Bastion)サーバーの作り方

モチベーション

Bastionサーバーは、会社のサーバーの入り口であり、アクセス管理は厳重に行う必要があります。Bastion(要塞)という名前の通り、各社強力なセキュリティの対策を行っていました。最も一般的なものだと、個人ごとにアカウントとSSHアクセス用のKey Pairを発行したり、LDAPを利用した全社共通のBastion認証基盤の用意などがあります。しかし、その設定コストはそれなりにあって、インフラエンジニアの負担となることがありました。 これらの問題に対処するため、AWSは 2018年にSSM(Session Manager) という機能をリリースしました。本記事では、SSMを用いた Bastion サーバーの構築について説明をします。

前提知識

従来のBastionサーバー管理方法

f:id:selmertsx:20210811105923p:plain

従来、Bastionサーバーは上の図に示す形で運用されてきました。Public SubnetにEC2 instanceを配置し、そこから Private Subnetの中にあるEC2 Instanceや、RDS、Redshiftにアクセスする方式です。2016年時点のAWS公式ブログにおいてユーザーごとのkey pairを発行する方法が説明されているため、2016年時点においてはこの方法が主流だったと言って良いでしょう。

しかし、この方法は冒頭にて説明したように、安全な運用を継続するには膨大なコストが掛かるという問題がありました。

Session Manager

f:id:selmertsx:20210811110004p:plain

2018年9月11日、Session Managerという機能が公式よりアナウンスされました。この機能は、SSHではなく aws-cli を用いてBastionへアクセスする方法を提供します。これにより、Bastionサーバの管理は大幅にセキュアになります。

  • aws-cliAWSのIAMを用いてアクセス制御をすることができます。
    • Terraform等を用いて、アクセス管理のIaC化が可能
    • IAMのPolicyを用いた柔軟なアクセスコントロールや、MFAの強制が可能
    • SSHの鍵管理のコストが不要になる
    • IDaaSによる ID Federation を利用すれば、リスクベース認証などIDaaSの保有している、よりセキュアな方法で認証できる
  • EC2 instanceを Private Subnetに配置することが可能
    • 従来のBastionサーバーはPublic Subnetにあったため、攻撃されうる可能性があった
    • IP制限を掛けることで一定の対処は可能だが、同時に利便性も損なわれていた
  • 操作履歴はCloud Watch Logsに自動的に残される

という訳で、2021年現在において、新たにBastionサーバーを建てる必要があれば SSMの利用を想定した構築をすべしと言えるでしょう。

SSMベースのBastionサーバーの作り方

SSM ベースでBastionサーバーを建てる場合、必要なリソースは前の図に示したとおりです。 ざっくり書き出すと、下記のリソースになります。

  • VPC
  • Public Subnet
  • NAT Gateway ( Public Subnet 内に配置 )
  • Private Subnet
  • EC2 Instance (SSM Agentをinstall済みのもの)

踏み台を立てようとしているので、VPCやPublic/Private Subnetはすでにある前提で説明します。 Terraform を利用している場合、terraform-aws-ec2-bastionというモジュールを利用するのが一番楽でしょう。 下記のような設定をすればすぐに動きます。

module "bastion" {
  source            = "hazelops/ec2-bastion/aws"
  version           = "~> 2.0"
  aws_profile       = "xxx"
  env               = local.env
  ec2_key_pair_name = local.ec2_key_pair_name
  vpc_id            = module.vpc.id
  private_subnets   = module.bastion_private_subnet.ids
}

aws-cdk を使っている場合、公式が提供しているモジュールがあるためそちらを利用すると良いでしょう。

動作確認とPort Forwardingを利用したRDSへのアクセス

まず、踏み台が正常に動作していることを、下記のコマンドで確認します。

$ aws ssm start-session --target ${EC2のID} --region ap-northeast-1 --profile ${任意のprofile設定}
# MFAの確認
Enter MFA code for arn:aws:iam::xxxx:mfa/${ユーザーアカウント名}:

Starting session with SessionId: botocore-session-xxxx
# EC2 instanceにアクセスしていることを確認
$ pwd
/var/snap/amazon-ssm-agent/2996
$ hostname
ip-xxx

次に、よく利用するBastion経由でのRDS接続方法について記載します。まず、.ssh/configに下記のような設定を記載します。ProxyCommandの中では StartSSHSessionというドキュメントを読み込んでいます。これは、ssh tonnelを構築する際に必要な設定です。

Host xxx-bastion
  HostName ${EC2 internal IP}.ap-northeast-1.compute.internal
  User ubuntu
  Port 22
  ProxyCommand aws ssm start-session --target ${EC2 ID} --document-name AWS-StartSSHSession --region ap-northeast-1 --parameters "portNumber=22" --profile ${aws profile}
  IdentityFile ~/.ssh/${bastionのkey pair}

その後、1つめのconsoleで下記コマンドを実施します。これによって、Bastionを経由してRDSに接続するためのトンネルが構築されます。

ssh -N -L 15432:${RDSのURL}:5432  xxx-bastion

先程のconsoleを閉じないまま、別のconsoleで下記のようなコマンドを実行してください。すると、RDSに接続することができるようになります。

$ psql -h localhost -p 15432 -U {username} -d ${database_name}
Password for user {username}:
psql (13.0, server 11.9)
SSL connection (protocol: TLSv1.2, cipher: xxx, bits: 256, compression: off)
Type "help" for help.
database_name=>

将来の話

CloudShell

2020年12月18日、AWS CloudShellという機能がリリースされました。この機能はAWSのマネジメントコンソールから、Shellを実行できる機能です。そのリリース記事の中に下記の文面が存在しました。

ネットワークアクセス – セッションはインターネットへのアウトバウンド接続を確率できますが、インバウンド接続はどの種類でも許可しません。現在、セッションはプライベート VPC サブネット内のリソースに接続できませんが、近日中に接続できるようになる予定です。

つまり、CloudShellで実質 Bastionと同等の作業ができるようになります。そのため、この記事を読んでいる方がBastionサーバーを建てることになったら、まずはCloudShellの最新のアップデートを確認することをオススメします。 ただし、CloudShellでどの程度証跡が残すことができるかをよく検討して、導入の可否判断をする必要があります。

EC2 instance Connect

現在、手元のPCからBastionを経由してRDSにアクセスするには、どうしても鍵の設定が必要になります。そのため、どうしてもkey pairの共有が必要となってしまいます。認証自体にはAWS IAMを利用しているので形骸化された手順ではあるものの、手間といえば手間でしょう。EC2 instance connectを利用すれば、必要に応じて一時的に公開鍵をuploadすることができるため、この手間を削減することができそうです。

現在、踏み台に触る人数が少ないときはさほど問題はないですが、関係者が増えたタイミングではEC2 instance connectの導入を検討すると良いでしょう。

TypeScriptのnode-fetchをテストの際にmockする

モチベーション

  • TypeScript で node-fetch のライブラリを使ってテストするのに少し手間取った
  • 他にもハマる人がいるかも知れないので、ここに記事として残しておく

テストしたい内容

  • Soracom Arcを利用して unified endpointにテストデータを送信したい
  • テストデータの送信では node-fetch というライブラリを利用する
  • node-fetch を利用して所望のパラメータが送信されていることを確認したい

結論: 実装内容

実装は下記の通り。 重要な部分は ts-jest のtest-helpersを利用しているところ。 ts-jestが必要な理由は後述する

// src/publisher.ts
import fetch from "node-fetch";
export const UNIFIED_ENDPOINT_URL = "http://unified.soracom.io";

export const main = async () => {
  const body = { /* なんらかのデータ*/ };
  await fetch(UNIFIED_ENDPOINT_URL, {
    method: "POST",
    body: JSON.stringify(body),
  });
};

(async () => await main())();
// tests/publisher.test.ts
import fetch, { Response } from "node-fetch";

// SEE: https://kulshekhar.github.io/ts-jest/docs/guides/test-helpers
import { mocked } from "ts-jest/utils";
import { main, UNIFIED_ENDPOINT_URL } from "../src/publisher";
jest.mock("node-fetch");

describe("Publisher", () => {
  test("main", async () => {
    mocked(fetch).mockReturnValue(
      Promise.resolve(new Response("ok", { status: 200 }))
    );
    const expectResponse = { /* 何らかのデータ */ };
    await main();
    expect(fetch).toBeCalledWith(UNIFIED_ENDPOINT_URL, {
      body: JSON.stringify(expectResponse),
      method: "POST",
    });
  });
});

Jest公式ドキュメントの手法

Jest公式では node-fetch のようなライブラリのテスト方法は下記のように指示されている。 だが、これは TypeScript ではコンパイルエラーとなりテストができない。 そのため、ts-jestを導入して対処する必要があった。

jest.mock('node-fetch');

import fetch, {Response} from 'node-fetch';
import {createUser} from './createUser';

test('createUser calls fetch with the right args and returns the user id', async () => {
  fetch.mockReturnValue(Promise.resolve(new Response('4')));

  const userId = await createUser();

  expect(fetch).toHaveBeenCalledTimes(1);
  expect(fetch).toHaveBeenCalledWith('http://website.com/users', {
    method: 'POST',
  });
  expect(userId).toBe('4');
});

f:id:selmertsx:20210811105327p:plain

モブプログラミングの進め方

この文章の目的

システム開発における共通認識の作成、または共同学習を目的としてペア/モブプログラミングが有効とされています。 モブプロをより良いものにするためのプラクティスについて、モブプログラミング ベストプラクティスという書籍を元に説明します。

モブプログラミングとは何か?

モブプログラミングでは、3人以上の人間で一つのプログラミングを作成します。 必要なものは全員が集まって開発できるスペース、一つの大きなディスプレイ、ホワイトボード、一つ以上のPCです。 モブプログラミングについて雰囲気を掴むには、実際業務している下記の動画を見るのが良いでしょう。

https://www.youtube.com/watch?v=Ev7uus12HRY

ペア/モブプログラミングの効能

モブプログラミングを初めて目にする人の多くは、とても非効率的な仕事であると感じます。 しかしながら、このプラクティスは多くの企業に受け入れられており、プロダクションで利用されるすべてのコードをペア/モブプログラミングで実施している企業は少なくありません。 例えば、言わずと知れた Microsoft や、ジョイ・インクの著者であるMenlo Innovations CEOのRich Sheridan氏の会社ではペア/モブプログラミングで開発を行っています。 日本においてもヤフー株式会社、Newspicksを開発している株式会社ユーザーベース、楽天株式会社など、多くの企業が実践しています。

一般的に、モブプログラミングは下記の項目を目的として実施されます。

  • プログラミングの品質向上
    • 内的品質(拡張性や可読性などプログラミングの書き方に関する品質)と
    • 外的品質(パフォーマンスや耐障害性、バグの多寡に関する品質)
    • 上記2つの品質に対して、双方の質の向上を目的とする
  • 開発におけるフロー効率の向上
  • 属人的なタスクの減少
  • チームメンバーの教育と、それによるチーム全体の開発効率の中長期的な向上

上記目標に関して、ヤフー株式会社は明らかに成果が得られていると報告しています。 「LeanとDevOpsの科学」によると、特に外的品質の向上を目的としたチーム外の組織による変更承認プロセスは、開発速度に対して明らかな負の相関を持っており、品質の向上には寄与していないと説明されています。 そのような障害を減らすために、承認プロセスよりもチーム内でペアプログラミングなどによる確認作業を行うことを推奨しています。 そして、日本CTO協会が作成したアセスメントツールである「DX Criteria」でもバリューストリームの最適化のためにペア/モブプログラミングを推奨しています。

モブプログラミングの進め方

環境を整える

一緒に開発する環境を整えていきましょう。個人的には VSCodeのLive Shareを利用するのが一番簡単だと思います。 VSCodeを開いて、ctr + Shift + p して install extentions を選択。検索用のtext boxにてLive Shareと入力します。 そして下図の extentionをインストールします。

f:id:selmertsx:20210811104227p:plain

そして、Editorの下にある下記 「Live Share」ボタンを押せば完了です。

f:id:selmertsx:20210811104243p:plain

すると Live Shareを実施するためのクリップボードにコピーされるので、それをSlackで共有しましょう。 このURLをクリックした人は、招待した人の VS Codeの環境に入ってコーディングすることが可能になります。

メンバーを揃えて役割分担を行う

モブプログラミング ベストプラクティスによると、モブプログラミングを行うメンバーは、プロジェクトの背景や利用する技術を理解していることが望ましいとされています。 そのため、同じチームか技術的に共通項のある人と一緒に開発をするのが良いでしょう。 モブプログラミングには2つの役割が存在します。1つめはタイピスト、直接コーディングをする人です。 2つめの役割はモブ、タイピストに指示を出す人です。難しい課題を解く際は、専門の進行役を設定することもあります。

タイピストの役割

  • タイピストは自分で考えてコーディングしてはいけない
  • モブの指示を理解し、指示された内容でもって実装を行わなければならない
  • 指示された内容よりも、より良い方法が思い浮かんだとしても、それを実装してはならない
  • モブの指示を信頼し、自分が通常試さない方法でもやってみること

モブの役割

  • タイピストに支持を出す
  • 目の前の問題に集中する
  • 問題解決のために、これからやること、これまで分かったことをホワイトボードに整理する
  • 他のモブの意見をよく聞き、分からない部分があれば質問する
  • 必要になりそうなドキュメントを探し、適切なタイミングで提示する
  • 書き上げたプログラムの中で改善可能なものがあれば、適切な方法で伝える

タイムテーブル

準備 (15分くらい)

  • モブプログラミングで解決する問題の概要と大まかな手順について話し合う (10分)
  • タイピストの順番を決める

モビングインターバル

  • 10分インターバルでタイピストを交換し、プログラミングを続ける
  • 定めた目標を達成するまで続ける

振り返り (20分)

  • 当日実施したモブプログラミングに対して振り返りを行う
  • 振り返りにおいては、事実の確認をした後に、KPTをする

教育目的のモブプログラミングの進め方

少数のベテランと多数のビギナーの組み合わせでモブプログラミングを行います。 まずベテランがコードを書きながらビギナーに説明をします。 それによって、ビギナーはベテランのスキルを盗むことができます。

次に、ビギナーがコーディングを行い、ベテランは指示をします。 ベテランがサクサク書いてるとビギナーは分かったつもりになってしまい安く、実際にコーディングすることで本当の理解度を確認、向上させることが目的です。 また、ベテランはビギナーに説明しながらコードを書かせることによって、理解して貰える説明方法を考えることによって深く技術を理解します。

これは技術的な領域だけでなく、業務のドメイン的な知識においても同様に効果があると言われています。

参考資料

とても参考になった

参考になった

個人的に好き

Terraform / aws-cdk を比較してみた(個人の所感です)

プライベートにて、経験の浅いチームから aws-cdk と terraformどちらが良いのですか?と聞かれたのでまとめてみました。間違いがあればご指摘いただければ幸いです。

前提

  • 利用する人間はIaCの初心者である
  • 当然 CloudFormation等に関する基本的な知識がないものとする

結論

  • 初学者にとっては、IaCのツールとしてはTerraformを採用するのが良さそう
  • その理由として最も大きなものは、「学習コストが少ない」こと
  • 「学習コストが少ない」についてブレークダウンすると下記のようになる
    • NewRelic や Sentry、その他SaaSとの連携もIaC化をする場合、Terraformなら1つで対応できる
    • aws-cdk は CloudFormation、aws-cdk、TypeScript、AWSの各種リソース等理解しなければ十分に活用できない
      • 欲を言えば、プログラミングの設計スキルも必要となる
    • TerraformはTerraformとAWSの各種リソースに対する理解の2点のみで良い
      • Terraform自体のキャッチアップを1とするなら、TypeScriptは4~5くらいの規模感
    • aws-cdk は原則として、インフラリソースの手作業による更新が許容されない
      • そのため、consoleを動かして挙動確認しながら理解を深めることができない

背景

2020年現在、AWSのインフラリソースを定義するメジャーな方法は3つあります。AWS CloudFormation、aws-cdk、Terraformです。まずはそれらの立ち位置について簡単に説明していきましょう。

AWS CloudFormation

AWS CloudFormationAWSが公式で提供しているIaCのためのツールです。公式で提供しているということもあり、ほぼすべてのAWSインフラリソースをこれで定義することができます。一般的によく利用されるようなインフラ設定については、サンプルコードがAWS公式によって提供されています。そのため利用者は、大抵のシステムを公式が提供してくれるテンプレートに少し手を加えるだけで構築することができます。また CloudFormationを拡張し、Serverless Application開発を容易にした SAM(Serverless Application Model)という機能も存在します。SAM Localを利用すればコードを手元で動作確認しながら、サーバレスアプリケーションの開発ができます。

良い事の多い CloudFormationですが、実際にコードを見てみましょう。下記にRailsアプリケーションを作る際のCloudFormationの一部を示します。これをサクサク読み書きできるようになるには、相当な修練が必要であることは想像に難くないでしょう。

// https://s3.us-west-2.amazonaws.com/cloudformation-templates-us-west-2/Rails_Single_Instance.template
"Properties": {
  "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                      { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
  "InstanceType"   : { "Ref" : "InstanceType" },
  "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
  "KeyName"        : { "Ref" : "KeyName" },
  "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
    "#!/bin/bash -xe\n",
    "yum update -y aws-cfn-bootstrap\n",
    "/opt/aws/bin/cfn-init -v ",
    "         --stack ", { "Ref" : "AWS::StackId" },
    "         --resource WebServer ",
    "         --configsets full_install ",
    "         --region ", { "Ref" : "AWS::Region" }, "\n",

    "/opt/aws/bin/cfn-signal -e $? ",
    "         --stack ", { "Ref" : "AWS::StackId" },
    "         --resource WebServer ",
    "         --region ", { "Ref" : "AWS::Region" }, "\n"
    ]]}}        
 },
 "CreationPolicy" : {
  "ResourceSignal" : {
    "Timeout" : "PT30M"
  }
}

SAMを用いて小さいServerless Applicationを作るのであれば良いですが、ある程度の規模のシステムになると Cloud Formationについて深い理解をしていなければ、すべてを管理することはとてもむずかしいと思えます。ここで詳細は述べませんが、CloudFomartion自体はIaCの原理原則を守り、リソース間の依存関係の確実な管理をしてくれて、安全な更新が可能な良いものです。ですが、少し専門家向け過ぎるツールであると言えます。

aws-cdk

CloudFormationの辛さについては上述しました。AWSもその問題点を理解しており、CloudFormation Designer などのツールを公開し、GUI上の設定によってCloudFormationが自動生成されるような仕組みを用意していました。しかし、結局の所生成されるのは Cloud Formationです。Cloud Formation自体が読み難いという根本的な問題は解決していません。

その課題に対処するために作られたのが aws-cdk です。端的に言うと、VueやReactからHTMLを生成するように、TypeScriptで作ったプログラムからCloudFormationを生成できるツールです。下記の図のようにcdk がコンパイラとなってCloud Formationを生成します。

f:id:selmertsx:20210507095951p:plain

aws-cdk の登場によって、AWSはCloudFormationを assembly language というポジションにしました。これから先、主にユーザーが利用するのは aws-cdkであり、専門性高いエンジニアがツールを構築したり、cdkのOSSにコミットしたり、デバッグ等で利用するときに Cloud Formationの理解が必要になる。というのが AWS の考えだと思います。

実際に利用している aws-cdk のプログラムを下記に示します。TypeScriptに関する知識があることは前提になりますが、CloudFormationよりも格段に読みやすくなったのではないでしょうか。TypeScriptであるため IDEの補完機能による恩恵も受けやすく、明らかに間違った設定をすればコンパイルすることもできなくなります。

export class ApiGatewayStack extends Stack {
  public readonly api: Resource;
  public readonly authorizer: TokenAuthorizer;

  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const stage = tryGetStage(this.node);
    const cmsRestApi = new RestApi(this, `${stage}-rest-api`, this.getCmsRestApiProps(stage));
    this.authorizer = createAuthorizer(this, stage, cmsRestApi);
    this.api = RestApi.root.addResource("api");
  }

  private getRestApiProps(stage: string): RestApiProps {
    return {
      restApiName: `${stage}-rest-api`,
      deployOptions: {
        loggingLevel: MethodLoggingLevel.INFO,
        dataTraceEnabled: true
      },
      minimumCompressionSize: 1024,
      endpointConfiguration: {
        types: [EndpointType.REGIONAL]
      }
    };
  }
}

さらに、aws-cdk から CloudFormationを生成することができるため、CloudFormationで利用可能な各種ツールをそのまま利用できます。例えば生成したCloudFormation を CloudFormation Designerに取り入れれば、awsのインフラ図を生成することもできます。また、SAM Localと連携してローカル環境で動作させることもできます。最近ではAWS公式から serverless pattern というリポジトリが作られました。頻出する数多くのインフラパターンを詰めたコードのサンプル集です。これを参考にすれば、新しいシステムを作る際に、インフラのベストプラクティスを踏襲して安全なシステムを作りつつも、工数を大幅に削減することができます。

さて、ここまで良いところばかりを述べてきましたが、aws-cdkにもデメリットは数多くあります。まず最も大きなものとしては、「学習コストの大きさ」 です。少なくとも現状では、 CloudFormation、aws-cdk、TypeScriptの3点の知識が必要になります。さらに長期間に渡ってメンテナンス可能なIaCを実現しようとすれば、オブジェクト指向の理解・実践も必要となります。何より大きい課題は、「公式による力強い明確なベストプラクティスやコーディング規約の不在」 です。aws-cdkを取り入れるチームは、自ら設計指針を考え、適切なコーディング規約を作り、チーム内で合意する必要があります。そうしなければ、これまでの数多くのソフトウェアと同じように、負債を多く抱えたシステムになってしまうでしょう。Cloud Formationは、Stack間依存の取り扱いを非常に厳密に行っており、その厳密さがシステムに安定性を与える反面、理解せずに利用すると返却の難しい負債を作り出してしまいます。

これまでの内容をまとめると、「aws-cdk をチームで効果的に利用し続けるためには、CloudFormationやTypeScript、IaCの原則について一定以上の理解をしたエンジニアが集まり、0から設計指針やコーディング規約を作る能力・余地がある」というのがaws-cdkを採用する際の条件になります。

個人的には、大きな開発組織のSREや、フルスタックエンジニア個人による利用では力を発揮するツールという印象を持ちました。

Terraform

Terraform とは Hashcorp社により提供される IaCのツールです。最も大きな特徴としては特定のベンダーに閉じずに、様々なサービスの設定をこれでIaC化できることです。AWSGCP、Azureはもちろん、Auth0NewRelicなど、その他多種多様なSaaSに対応しています。さらに、Go言語に詳しければ自ら作ることもできます。

Terraformの記述方法は次のようになります。Terraformは設定をDSLで記述するため、aws-cdk ほど自由度はありませんが、CloudFormationよりはだいぶ楽に記述することができます。

resource "aws_iam_role_policy" "iam_policy_for_iot" {
  name = replace("${var.system_name_prefix}_iot_rule_to_kinesis_firehose_policy", "-", "_")
  role = aws_iam_role.iot_to_firehose.id

  policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : [
            "firehose:PutRecord",
            "firehose:PutRecordBatch"
          ],
          "Resource" : var.iot_data_firehose_arn
        }
      ]
    }
  )
}

また、Terraformは設計における数多くのベストプラクティスを公式で提供しています。そのため、公式のドキュメントをしっかり読み込めば、開発における大部分のユースケースには対応可能になっています。

ツールの比較

aws-cdkの背景からお話したように、これから先 CloudFormation を素のまま利用しないことを想定していると思われます。そのため、CloudFormationについては比較対象から除外します。よって、以降では aws-cdk と terraformについての比較を行っていきます。

比較表と結論

Terraform aws-cdk
学習容易性 ×
製造容易性
インフラの修正容易性 ×
コードの修正容易性

Terraformとaws-cdkの比較を行った表が上記になります。結果をざっくり一言で言えば、「分かってる人が使うならcdkの方が良いけど、そこまで分かってる人多分市場を探してもそうそういないよね」という言葉になります。以降、比較軸の内容について詳細に記述していきます。

学習容易性 Terraform◎ aws-cdk ×

学習容易性としては、Terraformを優勢としています。理由としては下記の通りです。

  • aws-cdkが必要とする知識は下記の通り
    • 土台としている CloudFormation に関する知識
      • 遠い将来はいらなくなる可能性が高いとはいえ、現状では必須
    • aws-cdk のフレームワーク自体の知識
    • TypeScriptの言語に関する知識
    • オブジェクト指向を用いた設計方法に関する知識
  • Terraform が必要とする知識は Terraformの使い方についてのみ
  • aws-cdkは公式でベストプラクティスを提供していないため自ら考え、構築しなければならない
  • Terraformは公式がベストプラクティスを提供しているため、それを守ればそれなりのものが作れる
  • 良い書籍が出ており、これを読んでおけばある程度の成果物の品質を担保しやすい
  • NewRelic、Sentry等のSaaSもIaC化を検討しており、結局は Terraformを学習しなければならない

製造容易性 Terraform △ aws-cdk ◎

  • aws-cdkの terraform に対する優位性は下記の通り
    • TypeScriptで記述するため型推論による恩恵を受けられる
      • 必要な設定はIDEが教えてくれるし、間違った設定は実行前にコンパイルエラーになる
    • オブジェクト指向によるコードの整理が可能である

手動によるインフラ修正容易性 Terraform ◎ aws-cdk ×

この項目はAWS Console上から手動で行った変更を IaCツールで取り込む際の難しさを示すものです。

コードの修正容易性

  • terraformではmoduleレベルでのリファクタリングの難易度が非常に高い
    • 一つ一つのリソースを terraform state mv していく必要がある
  • aws-cdkは同一の成果物(CloudFormation)が生成されれば良いので、コード自体は柔軟に変更が可能
    • スタック自体を移動するのは難しい

結論

以上、Terraformとaws-cdkの比較を行いました。 経験の浅いチームがプロダクションで利用する状況においては、学習資料が整っているという観点から terraformを使うのが良いのではないでしょうか。 aws-cdkは非常に便利な道具であるものの、深く理解して慎重に使わなければ返済の難しい負債を作り出します。 なので、趣味アプリ等でなれてから本番運用に持っていくと良いのではないかなと思います。

課題を整理・分類し、対処できるように解像度を高めていく

モチベーション

  • 日々、未加工の生肉のような課題っぽいものが飛んでくる
  • 課題っぽいものの中で、今の状態で実施すべき打ち手を把握したい
  • 課題をうまく整理・仕分けし、上記を実施していきたい

課題の図

そもそも課題とは何でしょうか。本書は課題を理想と現状のギャップと捉えます。 裏を返せば、理想がなければ課題もありません。つまり課題がない、という現象が起こってしまう原因の一つは「理想がない」からです。 ~馬田隆明 未来を実装する P.143~

f:id:selmertsx:20210328001046p:plain

  • 馬場隆明さんの「未来を実装する」という書籍では課題を上記のように定義している
  • 僕はこれに加えてもう一つ「知識」が重要な要素だと思っている
  • 理想があっても、知識がなければ課題に形を与えられない
    • 知識は、文献から得られるものだけでなく、自分たちのチームが実験、検証によって得た体験的な知識も含める
  • 形が与えられない課題は、適切な認識がされずに解決されない
    • 漠然とした不安をチームに与えるだけである
  • 様々な知識をもとに、課題を解決可能な状態に落とし込むための順番を整理したい
  • 理想 / 現状 / 課題の図は、個人、チーム、組織と、スケールは違えども大枠や対応方針はだいたい一緒である

課題の種類整理

既知の既知がある。我々が知っているものがあり、そのことを我々が知っていることだ。 既知の未知がある。我々が知らないものがあり、そのことを我々が知っていることだ。 それから、未知の未知がある。我々が知らないものがあり、そのことについても我々が知らないことだ。 ~ アリステアクロール Lean Analytics P.13~

f:id:selmertsx:20210328002628p:plain

  • 理想 / 目標 があれば課題が生まれる
  • 僕は、課題を上記のフォーマットで整理する
  • 既知の既知は、実行あるのみである
  • 既知の未知は、調査 / 学習 / 検証をして、既知の既知にする
  • 未知の未知は、課題分析 / 調査 / 仮説検証を行い、既知の未知にする
  • 未知の既知は、標準化 / マニュアル化を行い、既知の既知にする
  • 今見えている課題は、上記のどこに分類される課題なのかを整理する
  • 課題を対処する上で最も重要なのは、いかに迅速に「既知の既知」まで課題を落とし込むか

課題整理 (詳細)

why what how 状況例 実施内容
why what how が分かったので、あとはやるだけ 実装 / 実行
開発効率を向上させるために、デプロイ頻度を増加させたい。要素技術は分かったが、我々のプロダクトにマッチするか分からない 検証
開発効率を向上させるために、デプロイ頻度を増加させたい。そのための要素技術が分からない 技術調査 / 技術書の読書会 / 技術的な研修
開発効率を向上させるために、デプロイ頻度を増加させれば良さそうだが確証がない 課題仮説の検証
開発効率を向上させるために、何をすれば良いか分からない 調査 / 課題分析 / 仮説立案
  • 課題の状況と、それに対する打ち手を整理したものを上の表に示す
  • 上の図においては、「開発効率を向上させる」というミッションを持った開発チームを例にした
    • チームに落とすのであれば、もう2〜3段階解像度を上げたほうが良い
    • 例えば 「LeanとDevOpsの科学」にあるハイパフォーマーを目指すなど
  • ここで大切なのは、 課題に対する打ち手は、一足飛びにしないこと
  • 何をすれば良いかも分からないのに、技術調査をしても意味がない
    • 意味があったとしても非効率
    • 目をつぶってバットを振るようなもの
  • 検証もしていないのに、本実装に入るのはリスクが高い
  • チームで課題に向き合うときは、今見えている課題はどこのフェーズにあるのかを整理することが重要である
  • そして フェーズごとに適切なゴール設計と対処 が必要である
  • チームにて責任を持つ人の仕事は、下記の通り
    • 着手可能な課題を提供すること
      • そもそもWHYが固まっていないのに、実行部隊にタスクを投げない
    • 課題の詳細を適切にチームに伝えること
    • 課題の終了条件をチームに伝えること
    • チームが一足飛びに課題を解決しようとしたら、会話して気づいて貰うこと
    • 知識の不足により課題を適切に把握できていない場合は、教育の機会を設けること

最後に

  • 課題の解像度を高めるために、取れるアクションの回数には限りがある
    • なんでもかんでも 「やってみなきゃ分からない 」 は許されない
    • 「作ってみてユーザーに当てる」は制限回数が存在するビジネスがある
      • 未完成のものを当てすぎると顧客との信頼関係を失いうる
      • 開発チームの練度、実績、顧客との信頼関係、プロジェクトの期間、競合の状況などにより実際に作れる回数は自ずと決まってくる
        • 庵野監督も、実績がなければあれだけ延期することはできない
      • 自分たちの中で判断する分には、どれだけ雑でも良いだろうし、何度でもやれば良い
  • その限られた回数をどのように使うか、真剣に向き合うことが重要