Slide 1

Slide 1 text

#CODT2020 #CODT2020 Infrastructure as Code の 静的テスト戦略 チェシャ猫 (@y_taka_23) Cloud Operator Days Tokyo 2020 (29th July, 2020)

Slide 2

Slide 2 text

#CODT2020 #CODT2020 「Infrastructure as Code 辛い」

Slide 3

Slide 3 text

#CODT2020 #CODT2020 「デプロイしてみたら上手く動かない」

Slide 4

Slide 4 text

#CODT2020 本日のアジェンダ ● なぜ IaC に静的テストが必要なのか ● AWS 上で IaC を実現する上で考えるべきこと ● IaC をテストする上での戦略とツール

Slide 5

Slide 5 text

#CODT2020 #CODT2020 IaC における「静的」テスト Why Predictability Matters in IaC?

Slide 6

Slide 6 text

#CODT2020 #CODT2020 Infrastructure as Code って何だっけ?

Slide 7

Slide 7 text

#CODT2020 Infrastructure as Code って何だっけ? ● ソフトウェア開発のプラクティスをインフラの オートメーションに活かすアプローチ ○ Git によるバージョン管理 ○ Pull Request によるレビュー ○ 継続的なテスト ○ etc... 『Infrastructure as Codeクラウドにおける サーバ管理の原則とプラクティス』 https://www.oreilly.co.jp/books/9784873117966/

Slide 8

Slide 8 text

#CODT2020 Infrastructure as Code って何だっけ? ● ダイナミックインフラプラットフォーム (AWS) ○ サーバやストレージの提供 ● インフラ定義ツール (CFn, Terraform) ○ サーバやストレージの構成・設定管理 ● サーバ構成ツール (Ansible, Chef) ○ サーバ自身の細部の設定 ● インフラサービス (CloudWatch など) ○ インフラやアプリの管理支援

Slide 9

Slide 9 text

#CODT2020 Mutable Immutable Local Global

Slide 10

Slide 10 text

#CODT2020 Local Global 影響範囲が個々のリソースで完結する 影響範囲がリソースをまたいで全体に及ぶ

Slide 11

Slide 11 text

#CODT2020 Mutable Immutable 既存のリソースに 重ねがけして更新 一度更地にして ゼロから再構築

Slide 12

Slide 12 text

#CODT2020 Mutable Immutable Local Global CloudFormation Terraform Ansible Chef Kubernetes Docker

Slide 13

Slide 13 text

#CODT2020 Mutable Immutable Local Global CloudFormation Terraform Ansible Chef Kubernetes Docker 今回注目したいのは Global + Mutable

Slide 14

Slide 14 text

#CODT2020 #CODT2020 なぜ Global + Mutable は辛いのか?

Slide 15

Slide 15 text

#CODT2020 インフラの現状がカオス ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 16

Slide 16 text

#CODT2020 インフラの現状がカオス 何かが壊れそうで心配 ● 失敗経験 ● IaC への不信 ● 組織の力関係 ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 17

Slide 17 text

#CODT2020 インフラの現状がカオス 何かが壊れそうで心配 例外的な作業 ● 失敗経験 ● IaC への不信 ● 組織の力関係 ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 18

Slide 18 text

#CODT2020 インフラの現状がカオス 何かが壊れそうで心配 例外的な作業 ● 失敗経験 ● IaC への不信 ● 組織の力関係 ● 不均一な構成 ● システム疲労 ● ノウハウ散逸 ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 19

Slide 19 text

#CODT2020 インフラの現状がカオス 何かが壊れそうで心配 例外的な作業 塩漬けインフラ負のサイクル (オートメーション恐怖症) ● 失敗経験 ● IaC への不信 ● 組織の力関係 ● 不均一な構成 ● システム疲労 ● ノウハウ散逸 ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 20

Slide 20 text

#CODT2020 インフラの現状がカオス 何かが壊れそうで心配 例外的な作業 塩漬けインフラ負のサイクル (オートメーション恐怖症) ● 失敗経験 ● IaC への不信 ● 組織の力関係 ● 不均一な構成 ● システム疲労 ● ノウハウ散逸 ● Global: 局所的改善の困難さ ● Mutable: 歪みの蓄積

Slide 21

Slide 21 text

#CODT2020 #CODT2020 「何が起こるかわからない」を減らせばよい

Slide 22

Slide 22 text

#CODT2020 #CODT2020 予測可能性 Predictability

Slide 23

Slide 23 text

#CODT2020 #CODT2020 アプリ開発と IaC を比較すると

Slide 24

Slide 24 text

#CODT2020 開発の V 字モデル 要件定義 コーディング 受け入れテスト 結合テスト 単体テスト 外部設計 内部設計

Slide 25

Slide 25 text

#CODT2020 開発の V 字モデル 要件定義 コーディング 受け入れテスト 結合テスト 単体テスト 外部設計 内部設計

Slide 26

Slide 26 text

#CODT2020 インフラの V 字モデル? アプリ仕様 IaC 実装 E2E テスト Serverspec など 環境一揃い 個別リソース

Slide 27

Slide 27 text

#CODT2020 インフラの V 字モデル? アプリ仕様 IaC 実装 E2E テスト Serverspec など 単体テストの不在 環境一揃い 個別リソース

Slide 28

Slide 28 text

#CODT2020 インフラの V 字モデル? アプリ仕様 IaC 実装 E2E テスト Serverspec など 単体テストの不在 環境一揃い 個別リソース デプロイの壁

Slide 29

Slide 29 text

#CODT2020 #CODT2020 「デプロイの壁」の手前でテストできれば IaC もアプリ開発により近づける!

Slide 30

Slide 30 text

#CODT2020 Section 1 のまとめ ● IaC = アプリ開発プラクティスのインフラへの応用 ○ 今回は Global + Mutable の領域にフォーカス ● 予測可能性をいかに担保するか? ○ 実行時に「何が起こるかわからない」という恐怖の克服 ● アプリに寄せたテスト戦略 ○ 静的(= デプロイ前)テストで「何が起こるか」を見切る

Slide 31

Slide 31 text

#CODT2020 #CODT2020 AWS における予測可能性 How to Manage IaC Predictability on AWS?

Slide 32

Slide 32 text

#CODT2020 予測可能性の 3 要素 ● 再現性 (Reproducibility) ○ 同じ操作を誰でも、いつでも繰り返すことができる ● 純粋性 (Purity) ○ 実行前の状態によらず、結果が常に同じになる ● モジュール性 (Modularity) ○ 再利用可能な部品が記述しやすい仕組みを備える

Slide 33

Slide 33 text

#CODT2020 #CODT2020 純粋性? 冪等性じゃなくて?

Slide 34

Slide 34 text

#CODT2020 冪等性 vs 純粋性 ● デプロイはパラメータ x と事前状態 e の関数 ○ 返り値は変更後の状態:e’ = f (x, e) ● 冪等性:複数回実行しても結果が一定 ○ 任意のパラメータ x と事前状態 e に対して f (x, f (x, e)) = f (x, e) ● 純粋性:実行前の状態によらず結果が一定 ○ 任意のパラメータ x と事前状態 e1, e2 に対して f (x, e1) = f (x, e2)

Slide 35

Slide 35 text

#CODT2020 冪等性 vs 純粋性 ● デプロイはパラメータ x と事前状態 e の関数 ○ 返り値は変更後の状態:e’ = f (x, e) ● 冪等性:複数回実行しても結果が一定(純粋なら冪等) ○ 任意のパラメータ x と事前状態 e に対して f (x, f (x, e)) = f (x, e) ● 純粋性:実行前の状態によらず結果が一定 ○ 任意のパラメータ x と事前状態 e1, e2 に対して f (x, e1) = f (x, e2)

Slide 36

Slide 36 text

#CODT2020 #CODT2020 より具体的に、AWS で考えると

Slide 37

Slide 37 text

#CODT2020 IaC on AWS の 4 ステップ マネジメント コンソール AWS CLI CloudFormation CDK (Cloud Dev. Kit)

Slide 38

Slide 38 text

#CODT2020 IaC on AWS の 4 ステップ マネジメント コンソール AWS CLI CloudFormation CDK (Cloud Dev. Kit) 予測可能性

Slide 39

Slide 39 text

#CODT2020 マネジメントコンソール ● 人間が手作業でリソースを作成 ○ ナイーブだが直感的で融通も利く ● 予測可能性は最も低い ○ 再現性:なし ○ 純粋性:なし ○ モジュール性:なし

Slide 40

Slide 40 text

#CODT2020 AWS CLI ● シェルスクリプトなどと組み合わせて自動化 ○ 機能面では扱える API が最も多い ● 予測可能性はあまり高くない ○ 再現性:あり(繰り返し実行可) ○ 純粋性:なし ○ モジュール性:ほとんどなし

Slide 41

Slide 41 text

#CODT2020 CloudFormation ● YAML による宣言的な定義 ○ 必要な操作ではなく望まれる状態を記述 ● 予測可能性はだいぶ改善した ○ 再現性:あり ○ 純粋性:一応あり(宣言的記述、衝突しない名前の生成) ○ モジュール性:かなり乏しい

Slide 42

Slide 42 text

#CODT2020 Cloud Development Kit (CDK) ● プログラムで CloudFormation 用 YAML を生成 ○ TypeScript / Python / Java / .NET ライブラリ ○ IDE が使える、型があるので YAML より書くのが楽 ● 現状で予測可能性は最も良好 ○ 再現性:あり ○ 純粋性:一応あり(実質 CloudFormation と同等) ○ モジュール性:あり (再利用・配布可能な Construct)

Slide 43

Slide 43 text

#CODT2020 SQS: MyQueue Subscribe SNS: MyTopic MyStack https://cdkworkshop.com/20-typescript/20-create-project/300-structure.html

Slide 44

Slide 44 text

#CODT2020 export class MyStack extend cdk.Stack { constructor(...) { super(...); const queue = new sqs.Queue(this, 'MyQueue', { visibilityTimeout = cdk.Duration.Seconds(300) }); const topic = new sns.Topic(this, 'MyTopic'); topic.addSubscription(new subs.SqsSubscription(queue)); } } SQS: MyQueue Subscribe SNS: MyTopic MyStack

Slide 45

Slide 45 text

#CODT2020 export class MyStack extend cdk.Stack { constructor(...) { super(...); const queue = new sqs.Queue(this, 'MyQueue', { visibilityTimeout = cdk.Duration.Seconds(300) }); const topic = new sns.Topic(this, 'MyTopic'); topic.addSubscription(new subs.SqsSubscription(queue)); } } SQS: MyQueue Subscribe SNS: MyTopic MyStack scope(親要素)

Slide 46

Slide 46 text

#CODT2020 cdk synth aws cloudformation deploy export class MyStack extend cdk.Stack { constructor(...) { super(...); const queue = new sqs.Queue(this, 'MyQueue', { visibilityTimeout = cdk.Duration.Seconds(300) }); const topic = new sns.Topic(this, 'MyTopic'); topic.addSubscription(new subs.SqsSubscription(queue)); } }

Slide 47

Slide 47 text

#CODT2020 cdk synth aws cloudformation deploy Resources: MyQueueXXXXXX: Type: AWS::SQS::Queue Properties: ... MyQueuePolicyXXXXXX: Type: AWS::SQS::QueuePolicy ... MyTopicXXXXXX: Type: AWS::SNS::Topic ... MyQueueMyStackMyTopicXXXXXX: Type: AWS::SNS::Subscription ...

Slide 48

Slide 48 text

#CODT2020 cdk synth aws cloudformation deploy Resources: MyQueueXXXXXX: Type: AWS::SQS::Queue Properties: ... MyQueuePolicyXXXXXX: Type: AWS::SQS::QueuePolicy ... MyTopicXXXXXX: Type: AWS::SNS::Topic ... MyQueueMyStackMyTopicXXXXXX: Type: AWS::SNS::Subscription ... CDK では明示していない = Construct が内包

Slide 49

Slide 49 text

#CODT2020 cdk diff cdk deploy

Slide 50

Slide 50 text

#CODT2020 #CODT2020 再利用可能な Construct の自作も可能

Slide 51

Slide 51 text

#CODT2020 API Count Hits

Slide 52

Slide 52 text

#CODT2020 Custom Construct API Count Hits https://cdkworkshop.com/20-typescript/40-hit-counter.html

Slide 53

Slide 53 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor() { const table = new dynamodb.Table(this, 'Hits', { partitionKey: { ... } }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } }

Slide 54

Slide 54 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor() { const table = new dynamodb.Table(this, 'Hits', { partitionKey: { ... } }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } } Construct を継承したクラスを作成

Slide 55

Slide 55 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor() { const table = new dynamodb.Table(this, 'Hits', { partitionKey: { ... } }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } } DynamoDB の Construct

Slide 56

Slide 56 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor() { const table = new dynamodb.Table(this, 'Hits', { partitionKey: { ... } }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } } Lambda の Construct

Slide 57

Slide 57 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor() { const table = new dynamodb.Table(this, 'Hits', { partitionKey: { ... } }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } } 環境変数経由で参照

Slide 58

Slide 58 text

#CODT2020 各管理手法の予測可能性 再現性 純粋性 モジュール性 マネコン × × × AWS CLI ◯ × × CloudFormation ◯ △ △ CDK ◯ △ ◯

Slide 59

Slide 59 text

#CODT2020 各管理手法の予測可能性 再現性 純粋性 モジュール性 マネコン × × × AWS CLI ◯ × × CloudFormation ◯ △ △ CDK ◯ △ ◯

Slide 60

Slide 60 text

#CODT2020 #CODT2020 結局、CDK でも「そこそこ」なの?

Slide 61

Slide 61 text

#CODT2020 CDK とテスト ● Snapshot Test ○ 生成される YAML が前回と同じか(CDK のアップデートなど) ● Fine-grained Assertion ○ 生成された YAML に目的のリソースが存在しているか ● Validation Test(実体は単なる例外送出のテスト) ○ 不正なパラメータを渡したときにエラーが発生するか

Slide 62

Slide 62 text

#CODT2020 #CODT2020 Snapshot Test

Slide 63

Slide 63 text

#CODT2020 export class DeadLetterQueue extends cdk.Queue { public readonly alarm cloudwatch.IAlarm; constructor(scope, id, props = {}) { super(scope, id); this.alarm = new cloudwatch.Alarm(this, 'Alarm', { alarmDescription: 'messages in the DLQ', evaluationPeriods: 1, threshold: 1, metric: this.metricApproximateNumberOfMessagesVisible(), }); } }

Slide 64

Slide 64 text

#CODT2020 import { SynthUtils } from '@aws-cdk/assert'; test('DLQ preserves the snapshot', () => { const stack = new Stack(); new dlq.DeadLetterQueue(stack, 'DLQ’); expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); }); cdk synth に相当

Slide 65

Slide 65 text

#CODT2020

Slide 66

Slide 66 text

#CODT2020 その時点での YAML が スナップショットとして 保存される

Slide 67

Slide 67 text

#CODT2020 export class DeadLetterQueue extends cdk.Queue { public readonly alarm cloudwatch.IAlarm; constructor(scope, id, props = {}) { super(scope, id); this.alarm = new cloudwatch.Alarm(this, 'Alarm', { alarmDescription: 'messages in the DLQ', evaluationPeriods: 1, threshold: 1, metric: this.metricApproximateNumberOfMessagesVisible(), period: cdk.Duration.minutes(1), }); } }

Slide 68

Slide 68 text

#CODT2020 export class DeadLetterQueue extends cdk.Queue { public readonly alarm cloudwatch.IAlarm; constructor(scope, id, props = {}) { super(scope, id); this.alarm = new cloudwatch.Alarm(this, 'Alarm', { alarmDescription: 'messages in the DLQ', evaluationPeriods: 1, threshold: 1, metric: this.metricApproximateNumberOfMessagesVisible(), period: cdk.Duration.minutes(1), }); } } 生成される YAML に影響を与える変更

Slide 69

Slide 69 text

#CODT2020

Slide 70

Slide 70 text

#CODT2020 - "Period": 300, + "Period": 60, 差分を検知してテスト失敗

Slide 71

Slide 71 text

#CODT2020 #CODT2020 Fine-Grained Assertion

Slide 72

Slide 72 text

#CODT2020 import { expect as expectCDK, SynthUtils, haveResource } from '@aws-cdk/assert'; test('DLQ has the message alarm', () => { const stack = new Stack(); new dlq.DeadLetterQueue(stack, 'DLQ’); expectCDK(stack).to(haveResource('AWS::CloudWatch::Alarm', { Namespace: 'AWS/Lambda' })); });

Slide 73

Slide 73 text

#CODT2020 import { expect as expectCDK, SynthUtils, haveResource } from '@aws-cdk/assert'; test('DLQ has the message alarm', () => { const stack = new Stack(); new dlq.DeadLetterQueue(stack, 'DLQ’); expectCDK(stack).to(haveResource('AWS::CloudWatch::Alarm', { Namespace: 'AWS/Lambda' })); }); CDK が提供する YAML 生成結果に関する アサーション

Slide 74

Slide 74 text

#CODT2020 import { expect as expectCDK, SynthUtils, haveResource } from '@aws-cdk/assert'; test('DLQ has the message alarm', () => { const stack = new Stack(); new dlq.DeadLetterQueue(stack, 'DLQ’); expectCDK(stack).to(haveResource('AWS::CloudWatch::Alarm', { Namespace: 'AWS/Lambda' })); }); わざと間違えてみた (AWS/SQS)

Slide 75

Slide 75 text

#CODT2020

Slide 76

Slide 76 text

#CODT2020 他に多数の属性があっても 実際に記述した Namespace のみを見る = Fine-Grained

Slide 77

Slide 77 text

#CODT2020 #CODT2020 Validation Test

Slide 78

Slide 78 text

#CODT2020 export class DeadLetterQueue extends cdk.Queue { public readonly alarm cloudwatch.IAlarm; constructor(scope, id, props = {}) { if (props.retention != undefined && props.retention > 14) { throw new Error('retention should be <= 14'); } super(scope, id, { retentionPeriod: cdk.Duration.days(props.retention || 14) }); ... }

Slide 79

Slide 79 text

#CODT2020 export class DeadLetterQueue extends cdk.Queue { public readonly alarm cloudwatch.IAlarm; constructor(scope, id, props = {}) { if (props.retention != undefined && props.retention > 14) { throw new Error('retention should be <= 14'); } super(scope, id, { retentionPeriod: cdk.Duration.days(props.retention || 14) }); ... } 引数 (props) を確認して範囲外なら例外

Slide 80

Slide 80 text

#CODT2020 test('DLQ retention should be < 14', () => { const stack = new Stack(); expect(() => { new dlq.DeadLetterQueue(stack, 'DLQ', { retention: 14 }); }).toThrowError(); }); 範囲内(例外は飛ばない)

Slide 81

Slide 81 text

#CODT2020

Slide 82

Slide 82 text

#CODT2020 通常の例外のテストと同様

Slide 83

Slide 83 text

#CODT2020 Section 2 のまとめ ● デプロイ時の予測可能性のために必要な要素 ○ 再現性 / 純粋性 / モジュール性 ● 段階的に予測可能性を獲得 ○ コンソール < CLI < CloudFormation < CDK ● CDK には静的テスト機構が備わっている ○ Snapshot / Fine-grained Assertion / Validation

Slide 84

Slide 84 text

#CODT2020 #CODT2020 よし、自作の Construct デプロイしよう

Slide 85

Slide 85 text

#CODT2020 Custom Construct API Count Hits

Slide 86

Slide 86 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor(...) { const table = new dynamodb.Table(this, 'Hits', { ... }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); } }

Slide 87

Slide 87 text

#CODT2020 \エラー!/

Slide 88

Slide 88 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor(...) { const table = new dynamodb.Table(this, 'Hits', { ... }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); table.grantReadWriteData(this.handler); } } 権限つけ忘れた!

Slide 89

Slide 89 text

#CODT2020 #CODT2020 「何」を「どう」テストすべきなのか?

Slide 90

Slide 90 text

#CODT2020 #CODT2020 テスト戦略とツール Strategies & Tactics for IaC Predictability

Slide 91

Slide 91 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 2. YAML の生成 3. リソースの作成

Slide 92

Slide 92 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 A. 期待する YAML 2. YAML の生成 3. リソースの作成

Slide 93

Slide 93 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 A. 期待する YAML 2. YAML の生成 ● CDK が提供する予測可能性:1 + 2 = A 3. リソースの作成

Slide 94

Slide 94 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 B. 期待する振る舞い A. 期待する YAML 2. YAML の生成 3. リソースの作成 ● CDK が提供する予測可能性:1 + 2 = A

Slide 95

Slide 95 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 B. 期待する振る舞い A. 期待する YAML 2. YAML の生成 3. リソースの作成 ● CDK が提供する予測可能性:1 + 2 = A ● 本当に欲しい予測可能性:1 + 2 + 3 = B

Slide 96

Slide 96 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 B. 期待する振る舞い A. 期待する YAML 2. YAML の生成 3. リソースの作成 ● 1 + 2 = A かつ A + 3 = B なら 1 + 2 + 3 = B

Slide 97

Slide 97 text

#CODT2020 CloudFormation CDK 作成されるリソース 1. 実装 B. 期待する振る舞い A. 期待する YAML 2. YAML の生成 3. リソースの作成 ● 1 + 2 = A かつ A + 3 = B なら 1 + 2 + 3 = B ● つまり残りは右側の予測可能性が問題

Slide 98

Slide 98 text

#CODT2020 #CODT2020 要するに YAML の「振る舞い」をテストしたい

Slide 99

Slide 99 text

#CODT2020 リソースに期待する振る舞い ● ポリシー的な性質 ○ リソース・アプリをまたいで制約が掛かっているか ○ 例:0.0.0.0/0 禁止、コスト集約用のタグ必須 ● 意味論的な性質 ○ 複数のリソースがうまく「噛み合った」状態で動作するか ○ 例:ネットワーク疎通が可能か、権限が足りているか

Slide 100

Slide 100 text

#CODT2020 #CODT2020 どんなツールが使えそう?

Slide 101

Slide 101 text

#CODT2020 CloudFormation のテストツール ● cfn-nag ○ CloudFormation 用、分野はセキュリティ系 ● CloudFormation Guard (cfn-guard) ○ CloudFormation 用、分野は限定せず汎用 ● Conftest ○ 一般の YAML 用、分野は限定せず汎用

Slide 102

Slide 102 text

#CODT2020 cfn-nag ● セキュリティ系の定番ツール ○ ベストプラクティスがあらかじめ定義されている ● メリット・デメリット ○ 定義済みルール:デフォルトで豊富(必要なら抑制も可能) ○ 配布・再利用性:低い(一応 S3 Bucket 経由で共有可能) ○ 拡張性:低い(Ruby でロジックを陽に記述する必要あり) https://github.com/stelligent/cfn_nag

Slide 103

Slide 103 text

#CODT2020 CloudFormation Guard (cfn-guard) ● 新登場の汎用チェックツール (2020/6/17 -) ○ 開発者プレビューなので Rust のソースからビルド ● メリット・デメリット ○ 定義済みルール:なし(既存の YAML からルール生成が可能) ○ 配布・再利用性:低い(ルールファイル直接指定のみ) ○ 拡張性:あまり高くない(個別の属性をチェックする DSL) https://github.com/aws-cloudformation/cloudformation-guard

Slide 104

Slide 104 text

#CODT2020 #CODT2020 複数のリソース間の関係はどうテストする? 自動生成されたリソース名の参照はどう扱う?

Slide 105

Slide 105 text

#CODT2020 Conftest ● Open Policy Agent (OPA) の派生 ○ Kubernetes との連携 (Gatekeeper) が人気 ● メリット・デメリット ○ 定義済みルール:なし ○ 配布・再利用性:高い(OCI = Docker レジストリで配布可能) ○ 拡張性:高い(Prolog の一種 Rego を使用) https://github.com/open-policy-agent/conftest

Slide 106

Slide 106 text

#CODT2020 #CODT2020 Ruby や Rust はともかく Rego って何?

Slide 107

Slide 107 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { condition1 condition2 } Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 108

Slide 108 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { condition1 condition2 } 定義:「xxxxx であるとは」 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 109

Slide 109 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { condition1 condition2 } 内容「Conidition 1 かつ Condition-2 かつ…が成り立つことである」 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 110

Slide 110 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { condition1 } is_xxxxx { condition2 } 内容「Conidition 1 または Condition-2 が成り立つことである」 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 111

Slide 111 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { is_yyyyy } is_yyyyy { is_zzzzz } 定義の参照「xxxxx であるとは yyyyy であることで、その yyyyy であるとは…」 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 112

Slide 112 text

#CODT2020 Rego 言語ことはじめ is_xxxxx { f = functions[_] is_good_func(f) } 代入ではない(単一化) 配列 functions の中から全体を成り立たせるような添字 _ が存在すれば それを任意に取って f とする。 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり

Slide 113

Slide 113 text

#CODT2020 #CODT2020 実際にどうすれば権限がテストできるか?

Slide 114

Slide 114 text

#CODT2020 export class Counter extends cdk.Construct { public readonly handler: lambda.Functions; constructor(...) { const table = new dynamodb.Table(this, 'Hits', { ... }); this.handler = new lambda.Handler(this, 'Handler', { ... environment: { ... HITS_TABLE_NAME: table.tableName } }); table.grantReadWriteData(this.handler); } } 権限の不足を発見したい

Slide 115

Slide 115 text

#CODT2020 deny[msg] { ... violations = [ [f, t] | f := functions[_]; t := tables[_]; needs_permission(f, t); not has_permission(f, t); ] count(violations) > 0 ... } warn[msg] { ... }

Slide 116

Slide 116 text

#CODT2020 deny[msg] { ... violations = [ [f, t] | f := functions[_]; t := tables[_]; needs_permission(f, t); not has_permission(f, t); ] count(violations) > 0 ... } warn[msg] { ... } deny でエラー、warn は警告

Slide 117

Slide 117 text

#CODT2020 deny[msg] { ... violations = [ [f, t] | f := functions[_]; t := tables[_]; needs_permission(f, t); not has_permission(f, t); ] count(violations) > 0 ... } warn[msg] { ... } 権限が必要だが持っていない Function と Table の組 > 0 ならばエラー

Slide 118

Slide 118 text

#CODT2020 allows([_, policy], [table_name, _]) { policy.Type = "AWS::IAM::Policy" statements := policy.Properties.PolicyDocument.Statement[_] statements.Effect = "Allow" statements.Resource[_]["Fn::GetAtt"][0] = table_name statements.Action[_] = "dynamodb.PutItem" statements.Action[_] = "dynamodb.UpdateItem" }

Slide 119

Slide 119 text

#CODT2020 allows([_, policy], [table_name, _]) { policy.Type = "AWS::IAM::Policy" statements := policy.Properties.PolicyDocument.Statement[_] statements.Effect = "Allow" statements.Resource[_]["Fn::GetAtt"][0] = table_name statements.Action[_] = "dynamodb.PutItem" statements.Action[_] = "dynamodb.UpdateItem" } Statement を適切に選んで 以下を満たせるか?

Slide 120

Slide 120 text

#CODT2020 allows([_, policy], [table_name, _]) { policy.Type = "AWS::IAM::Policy" statements := policy.Properties.PolicyDocument.Statement[_] statements.Effect = "Allow" statements.Resource[_]["Fn::GetAtt"][0] = table_name statements.Action[_] = "dynamodb.PutItem" statements.Action[_] = "dynamodb.UpdateItem" } Action を適切に選んで 条件全体を満たせるか?

Slide 121

Slide 121 text

#CODT2020 #CODT2020 慣れるまで結果を確認しつつ練習したい

Slide 122

Slide 122 text

#CODT2020 https://play.openpolicyagent.org/

Slide 123

Slide 123 text

#CODT2020 定義済みルール 配布・再利用 拡張性 cfn-nag ◯ × × cfn-guard × × △ Conftest × ◯ ◯ 各ツールの使いどころ

Slide 124

Slide 124 text

#CODT2020 定義済みルール 配布・再利用 拡張性 cfn-nag ◯ × × cfn-guard × × △ Conftest × ◯ ◯ 各ツールの使いどころ Cfn-nag の定義済みルールを最大限使いつつ、複雑な記述は Conftest でテスト

Slide 125

Slide 125 text

#CODT2020 Section 3 のまとめ ● 何をテストしているのか意識する ○ CDK のテストあくまでも CDK から YAML への変換のテスト ● リソースに期待する振る舞い ○ ポリシー的な性質 / 意味論的な性質 ● 生成された YAML をテストするコツ ○ Conftest で具体的な名前に依存せずテストできる

Slide 126

Slide 126 text

#CODT2020 #CODT2020 本日のまとめ Wrap Up!

Slide 127

Slide 127 text

#CODT2020 本日のまとめ ● IaC における静的テストの必要性 ○ デプロイ前に予測可能性を確保したい ● AWS のリソース管理と CDK ○ 再現性 / 純粋性 / モジュール性 ● YAML に対するテスト戦略とツール ○ cfn-nag(セキュリティ)/ Conftest(正しく動く条件)

Slide 128

Slide 128 text

#CODT2020 #CODT2020 Make Your IaC Predictable! Presented by チェシャ猫 (@y_taka_23)