Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Infrastructure as Code の静的テスト戦略 #DevOpsDaysTokyo / DevOpsDays Tokyo 2021

Infrastructure as Code の静的テスト戦略 #DevOpsDaysTokyo / DevOpsDays Tokyo 2021

DevOpsDays Tokyo 2021 で使用したスライドです。

Infrastructure as Code を導入してみたはいいけれど、デプロイしてみたらなぜか上手く動かない。そんな経験はありませんか? 本セッションでは、実際の環境を構築する「前」に、IaC のコード自体に対してテストを行う手法について解説します。

ご存知の通り Infrastructure as Code (IaC) は、インフラをコードで定義することを通し、アプリケーション開発のベストプラクティスをインフラ領域にも輸入しようとする方法論です。IaC の考え方は近年急速に普及し、開発フローの一部として種々の IaC ツールを利用することは半ば常識のような状態にあります。

しかし同時に、IaC は銀の弾丸ではありません。特に組織的な導入を考えようとすると、得てして「なぜか上手くいかない」「余計に運用が辛くなってしまった」という声を聞くこともよくあります。

なぜこんな事態に陥ってしまうのでしょうか? 本セッションの前半ではこの問題を言語化するために、「予測可能性」という考え方を導入します。IaC のデプロイ結果の予測しづらさがなぜ運用の辛さに繋がるのか、その構造を図解した上で、予測可能性を担保するための原理・原則を解説します。

では、具体的にその予測可能性を担保するにはどうしたらよいのでしょう? セッションの後半では AWS を取り上げ、IaC の実践の中でいかにインフラの予測可能性を担保するのか、方法論や具体的なツールの適用について解説します。

IaC 特有の辛さに疲れてしまったあなたが、もう一度 IaC に光明を見い出すためのセッションです。

イベント概要:https://confengine.com/conferences/devopsdays-tokyo-2021/proposal/15207/infrastructure-as-code
ブログ記事:https://ccvanishing.hateblo.jp/entry/2021/04/17/091533

y_taka_23

April 16, 2021
Tweet

More Decks by y_taka_23

Other Decks in Technology

Transcript

  1. #DevOpsDaysTokyo 本日のアジェンダ • なぜ IaC に静的テストが必要なのか • AWS 上で IaC

    を実現する上で考えるべきこと • IaC をテストする上での戦略とツール
  2. #DevOpsDaysTokyo Infrastructure as Code って何だっけ? • ソフトウェア開発のプラクティスをインフラの オートメーションに活かすアプローチ ◦ Git

    によるバージョン管理 ◦ Pull Request によるレビュー ◦ 継続的なテスト ◦ etc... 『Infrastructure as Codeクラウドにおける サーバ管理の原則とプラクティス』 https://www.oreilly.co.jp/books/9784873117966/
  3. #DevOpsDaysTokyo Infrastructure as Code って何だっけ? • ダイナミックインフラプラットフォーム (AWS) ◦ サーバやストレージの提供

    • インフラ定義ツール (CFn, Terraform) ◦ サーバやストレージの構成・設定管理 • サーバ構成ツール (Ansible, Chef) ◦ サーバ自身の中身の設定 • インフラサービス (CloudWatch など) ◦ インフラやアプリの管理支援
  4. #DevOpsDaysTokyo インフラの現状がカオス 何かが壊れそうで心配 例外的な作業 • 失敗経験 • IaC への不信 •

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

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

    への不信 • 組織の力関係 • 不均一な構成 • システム疲労 • ノウハウ散逸 • Global: 局所的改善の困難さ • Mutable: 歪みの蓄積
  7. #DevOpsDaysTokyo インフラの V 字モデル? アプリ仕様 IaC 実装 E2E テスト Serverspec

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

    など 単体テストの不在 環境一揃い 個別リソース デプロイの壁
  9. #DevOpsDaysTokyo Section 1 のまとめ • IaC = アプリ開発プラクティスのインフラへの応用 ◦ 今回は

    Global + Mutable の領域にフォーカス • 予測可能性をいかに担保するか? ◦ 実行時に「何が起こるかわからない」という恐怖の克服 • アプリに寄せたテスト戦略 ◦ 静的(= デプロイ前)テストで「何が起こるか」を見切る
  10. #DevOpsDaysTokyo 予測可能性の 3 要素 • 再現性 (Reproducibility) ◦ 同じ操作を誰でも、いつでも繰り返すことができる •

    純粋性 (Purity) ◦ 実行前の状態によらず、結果が常に同じになる • モジュール性 (Modularity) ◦ 再利用可能な部品が記述しやすい仕組みを備える
  11. #DevOpsDaysTokyo 冪等性 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)
  12. #DevOpsDaysTokyo 冪等性 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)
  13. #DevOpsDaysTokyo IaC on AWS の 4 ステップ マネジメント コンソール AWS

    CLI CloudFormation CDK (Cloud Dev. Kit) 予測可能性
  14. #DevOpsDaysTokyo AWS CLI • シェルスクリプトなどと組み合わせて自動化 ◦ 機能面では扱える API が最も多い •

    予測可能性はあまり高くない ◦ 再現性:あり(繰り返し実行可) ◦ 純粋性:なし ◦ モジュール性:ほとんどなし
  15. #DevOpsDaysTokyo CloudFormation • YAML による宣言的な定義 ◦ 必要な操作ではなく望まれる状態を記述 • 予測可能性はだいぶ改善した ◦

    再現性:あり ◦ 純粋性:一応あり(宣言的記述、衝突しない名前の生成) ◦ モジュール性:かなり乏しい
  16. #DevOpsDaysTokyo Cloud Development Kit (CDK) • プログラムで CloudFormation 用 YAML

    を生成 ◦ TypeScript / Python / Java / .NET / Go ライブラリ ◦ IDE が使える、型があるので YAML より書くのが楽 • 現状で予測可能性は最も良好 ◦ 再現性:あり ◦ 純粋性:一応あり(実質 CloudFormation と同等) ◦ モジュール性:あり (再利用・配布可能な Construct)
  17. #DevOpsDaysTokyo 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
  18. #DevOpsDaysTokyo 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(親要素)
  19. #DevOpsDaysTokyo 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)); } }
  20. #DevOpsDaysTokyo 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 ...
  21. #DevOpsDaysTokyo 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 が内包
  22. #DevOpsDaysTokyo 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 } }); } }
  23. #DevOpsDaysTokyo 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 を継承したクラスを作成
  24. #DevOpsDaysTokyo 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
  25. #DevOpsDaysTokyo 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
  26. #DevOpsDaysTokyo 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 } }); } } 環境変数経由で参照
  27. #DevOpsDaysTokyo CDK とテスト • Snapshot Test ◦ 生成される YAML が前回と同じか(CDK

    のアップデートなど) • Fine-grained Assertion ◦ 生成された YAML に目的のリソースが存在しているか • Validation Test(実体は単なる例外送出のテスト) ◦ 不正なパラメータを渡したときにエラーが発生するか
  28. #DevOpsDaysTokyo 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(), }); } }
  29. #DevOpsDaysTokyo 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 に相当
  30. #DevOpsDaysTokyo 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), }); } }
  31. #DevOpsDaysTokyo 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 に影響を与える変更
  32. #DevOpsDaysTokyo 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' })); });
  33. #DevOpsDaysTokyo 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 生成結果に関する アサーション
  34. #DevOpsDaysTokyo 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)
  35. #DevOpsDaysTokyo 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) }); ... }
  36. #DevOpsDaysTokyo 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) を確認して範囲外なら例外
  37. #DevOpsDaysTokyo test('DLQ retention should be < 14', () => {

    const stack = new Stack(); expect(() => { new dlq.DeadLetterQueue(stack, 'DLQ', { retention: 14 }); }).toThrowError(); }); 範囲内(例外は飛ばない)
  38. #DevOpsDaysTokyo Section 2 のまとめ • デプロイ時の予測可能性のために必要な要素 ◦ 再現性 / 純粋性

    / モジュール性 • 段階的に予測可能性を獲得 ◦ コンソール < CLI < CloudFormation < CDK • CDK には静的テスト機構が備わっている ◦ Snapshot / Fine-grained Assertion / Validation
  39. #DevOpsDaysTokyo 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 } }); } }
  40. #DevOpsDaysTokyo 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); } } 権限つけ忘れた!
  41. #DevOpsDaysTokyo CloudFormation CDK 作成されるリソース 1. 実装 A. 期待する YAML 2.

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

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

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

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

    YAML 2. YAML の生成 3. リソースの作成 • 1 + 2 = A かつ A + 3 = B なら 1 + 2 + 3 = B • つまり残りは右側の予測可能性が問題
  46. #DevOpsDaysTokyo リソースに期待する振る舞い • ポリシー的な性質 ◦ リソース・アプリをまたいで制約が掛かっているか ◦ 例:0.0.0.0/0 禁止、コスト集約用のタグ必須 •

    意味論的な性質 ◦ 複数のリソースがうまく「噛み合った」状態で動作するか ◦ 例:ネットワーク疎通が可能か、権限が足りているか
  47. #DevOpsDaysTokyo CloudFormation のテストツール • cfn-nag ◦ CloudFormation 用、分野はセキュリティ系 • CloudFormation

    Guard (cfn-guard) ◦ CloudFormation 用、分野は限定せず汎用 • Conftest ◦ 一般の YAML 用、分野は限定せず汎用
  48. #DevOpsDaysTokyo cfn-nag • セキュリティ系の定番ツール ◦ ベストプラクティスがあらかじめ定義されている • メリット・デメリット ◦ 定義済みルール:デフォルトで豊富(必要なら抑制も可能)

    ◦ 配布・再利用性:低い(一応 S3 Bucket 経由で共有可能) ◦ 拡張性:低い(Ruby でロジックを陽に記述する必要あり) https://github.com/stelligent/cfn_nag
  49. #DevOpsDaysTokyo CloudFormation Guard (cfn-guard) • Rust 製の AWS 公式チェックツール (2020/10-)

    ◦ Lambda Function 版も提供されている • メリット・デメリット ◦ 定義済みルール:サンプルのみ(既存の YAML から生成機能あり) ◦ 配布・再利用性:低い(ルールファイル直接指定のみ) ◦ 拡張性:あまり高くない(個別の属性をチェックするのみ) https://github.com/aws-cloudformation/cloudformation-guard
  50. #DevOpsDaysTokyo Conftest • Open Policy Agent (OPA) の派生 ◦ Kubernetes

    との連携 (Gatekeeper) が人気 • メリット・デメリット ◦ 定義済みルール:なし ◦ 配布・再利用性:高い(OCI = Docker レジストリで配布可能) ◦ 拡張性:高い(Prolog の一種 Rego を使用) https://github.com/open-policy-agent/conftest
  51. #DevOpsDaysTokyo Rego 言語ことはじめ is_xxxxx { condition1 condition2 } Rego の記述は

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

    Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり
  53. #DevOpsDaysTokyo Rego 言語ことはじめ is_xxxxx { condition1 condition2 } 内容「Conidition 1

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

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

    } 定義の参照「xxxxx であるとは yyyyy であることで、その yyyyy であるとは…」 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり
  56. #DevOpsDaysTokyo Rego 言語ことはじめ is_xxxxx { f = functions[_] is_good_func(f) }

    代入ではない(単一化) 配列 functions の中から全体を成り立たせるような添字 _ が存在すれば それを任意に取って f とする。 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり
  57. #DevOpsDaysTokyo 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); } } 権限の不足を発見したい
  58. #DevOpsDaysTokyo deny[msg] { ... violations = [ [f, t] |

    f := functions[_]; t := tables[_]; needs_permission(f, t); not has_permission(f, t); ] count(violations) > 0 ... } warn[msg] { ... }
  59. #DevOpsDaysTokyo 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 は警告
  60. #DevOpsDaysTokyo 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 ならばエラー
  61. #DevOpsDaysTokyo 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" }
  62. #DevOpsDaysTokyo 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 を適切に選んで 以下を満たせるか?
  63. #DevOpsDaysTokyo 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 を適切に選んで 条件全体を満たせるか?
  64. #DevOpsDaysTokyo 定義済みルール 配布・再利用 拡張性 cfn-nag ◯ × × cfn-guard △

    × △ Conftest × ◯ ◯ 各ツールの使いどころ デフォルトで済むなら cfn-nag / 追加するなら cfn-guard 複数のリソースの関係は Conftest で記述
  65. #DevOpsDaysTokyo Section 3 のまとめ • 何をテストしているのか意識する ◦ CDK のテストあくまでも CDK

    から YAML への変換のテスト • リソースに期待する振る舞い ◦ ポリシー的な性質 / 意味論的な性質 • 生成された YAML をテストするコツ ◦ Conftest で具体的な名前に依存せずテストできる
  66. #DevOpsDaysTokyo 本日のまとめ • IaC における静的テストの必要性 ◦ デプロイ前に予測可能性を確保したい • AWS のリソース管理と

    CDK ◦ 再現性 / 純粋性 / モジュール性 • YAML に対するテスト戦略とツール ◦ cfn-nag or cfn-guard(ポリシ強制)/ Conftest(機能要件)