Save 37% off PRO during our Black Friday Sale! »

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

332f89cc697355902a817506b6995f2b?s=47 y_taka_23
April 16, 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

332f89cc697355902a817506b6995f2b?s=128

y_taka_23

April 16, 2021
Tweet

Transcript

  1. #DevOpsDaysTokyo #DevOpsDaysTokyo Infrastructure as Code の 静的テスト戦略 チェシャ猫 (@y_taka_23) DevOpsDays

    Tokyo 2021 (16th Apr. 2021)
  2. #DevOpsDaysTokyo #DevOpsDaysTokyo 「Infrastructure as Code 辛い」

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

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

    を実現する上で考えるべきこと • IaC をテストする上での戦略とツール
  5. #DevOpsDaysTokyo #DevOpsDaysTokyo IaC における「静的」テスト Why Predictability Matters in IaC?

  6. #DevOpsDaysTokyo #DevOpsDaysTokyo Infrastructure as Code って何だっけ?

  7. #DevOpsDaysTokyo Infrastructure as Code って何だっけ? • ソフトウェア開発のプラクティスをインフラの オートメーションに活かすアプローチ ◦ Git

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

    • インフラ定義ツール (CFn, Terraform) ◦ サーバやストレージの構成・設定管理 • サーバ構成ツール (Ansible, Chef) ◦ サーバ自身の中身の設定 • インフラサービス (CloudWatch など) ◦ インフラやアプリの管理支援
  9. #DevOpsDaysTokyo Mutable Immutable Local Global

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

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

  12. #DevOpsDaysTokyo Mutable Immutable Local Global CloudFormation Terraform Ansible Chef Kubernetes

    Docker
  13. #DevOpsDaysTokyo Mutable Immutable Local Global CloudFormation Terraform Ansible Chef Kubernetes

    Docker 今回注目したいのは Global + Mutable
  14. #DevOpsDaysTokyo #DevOpsDaysTokyo なぜ Global + Mutable は辛いのか?

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

  16. #DevOpsDaysTokyo インフラの現状がカオス 何かが壊れそうで心配 • 失敗経験 • IaC への不信 • 組織の力関係

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

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

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

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

    への不信 • 組織の力関係 • 不均一な構成 • システム疲労 • ノウハウ散逸 • Global: 局所的改善の困難さ • Mutable: 歪みの蓄積
  21. #DevOpsDaysTokyo #DevOpsDaysTokyo 「何が起こるかわからない」を減らせばよい

  22. #DevOpsDaysTokyo #CODT2020 予測可能性 Predictability

  23. #DevOpsDaysTokyo #DevOpsDaysTokyo アプリ開発と IaC を比較すると

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

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

    内部設計
  26. #DevOpsDaysTokyo インフラの V 字モデル? アプリ仕様 IaC 実装 E2E テスト Serverspec

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

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

    など 単体テストの不在 環境一揃い 個別リソース デプロイの壁
  29. #DevOpsDaysTokyo #DevOpsDaysTokyo 「デプロイの壁」の手前でテストできれば IaC もアプリ開発により近づける!

  30. #DevOpsDaysTokyo Section 1 のまとめ • IaC = アプリ開発プラクティスのインフラへの応用 ◦ 今回は

    Global + Mutable の領域にフォーカス • 予測可能性をいかに担保するか? ◦ 実行時に「何が起こるかわからない」という恐怖の克服 • アプリに寄せたテスト戦略 ◦ 静的(= デプロイ前)テストで「何が起こるか」を見切る
  31. #DevOpsDaysTokyo #DevOpsDaysTokyo AWS における予測可能性 How to Manage IaC Predictability on

    AWS?
  32. #DevOpsDaysTokyo 予測可能性の 3 要素 • 再現性 (Reproducibility) ◦ 同じ操作を誰でも、いつでも繰り返すことができる •

    純粋性 (Purity) ◦ 実行前の状態によらず、結果が常に同じになる • モジュール性 (Modularity) ◦ 再利用可能な部品が記述しやすい仕組みを備える
  33. #DevOpsDaysTokyo #DevOpsDaysTokyo 純粋性? 冪等性じゃなくて?

  34. #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)
  35. #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)
  36. #DevOpsDaysTokyo #DevOpsDaysTokyo より具体的に、AWS で考えると

  37. #DevOpsDaysTokyo IaC on AWS の 4 ステップ マネジメント コンソール AWS

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

    CLI CloudFormation CDK (Cloud Dev. Kit) 予測可能性
  39. #DevOpsDaysTokyo マネジメントコンソール • 人間が手作業でリソースを作成 ◦ ナイーブだが直感的で融通も利く • 予測可能性は最も低い ◦ 再現性:なし

    ◦ 純粋性:なし ◦ モジュール性:なし
  40. #DevOpsDaysTokyo AWS CLI • シェルスクリプトなどと組み合わせて自動化 ◦ 機能面では扱える API が最も多い •

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

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

    を生成 ◦ TypeScript / Python / Java / .NET / Go ライブラリ ◦ IDE が使える、型があるので YAML より書くのが楽 • 現状で予測可能性は最も良好 ◦ 再現性:あり ◦ 純粋性:一応あり(実質 CloudFormation と同等) ◦ モジュール性:あり (再利用・配布可能な Construct)
  43. #DevOpsDaysTokyo SQS: MyQueue Subscribe SNS: MyTopic MyStack https://cdkworkshop.com/20-typescript/20-create-project/300-structure.html

  44. #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
  45. #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(親要素)
  46. #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)); } }
  47. #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 ...
  48. #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 が内包
  49. #DevOpsDaysTokyo cdk diff cdk deploy

  50. #DevOpsDaysTokyo #DevOpsDaysTokyo 再利用可能な Construct の自作も可能

  51. #DevOpsDaysTokyo API Count Hits

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

  53. #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 } }); } }
  54. #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 を継承したクラスを作成
  55. #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
  56. #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
  57. #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 } }); } } 環境変数経由で参照
  58. #DevOpsDaysTokyo 各管理手法の予測可能性 再現性 純粋性 モジュール性 マネコン × × × AWS

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

    CLI ◯ × × CloudFormation ◯ △ △ CDK ◯ △ ◯
  60. #DevOpsDaysTokyo #DevOpsDaysTokyo 結局、CDK でも「そこそこ」なの?

  61. #DevOpsDaysTokyo CDK とテスト • Snapshot Test ◦ 生成される YAML が前回と同じか(CDK

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

  63. #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(), }); } }
  64. #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 に相当
  65. #DevOpsDaysTokyo

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

  67. #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), }); } }
  68. #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 に影響を与える変更
  69. #DevOpsDaysTokyo

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

  71. #DevOpsDaysTokyo #DevOpsDaysTokyo Fine-Grained Assertion

  72. #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' })); });
  73. #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 生成結果に関する アサーション
  74. #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)
  75. #DevOpsDaysTokyo

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

  77. #DevOpsDaysTokyo #DevOpsDaysTokyo Validation Test

  78. #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) }); ... }
  79. #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) を確認して範囲外なら例外
  80. #DevOpsDaysTokyo test('DLQ retention should be < 14', () => {

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

  82. #DevOpsDaysTokyo 通常の例外のテストと同様

  83. #DevOpsDaysTokyo Section 2 のまとめ • デプロイ時の予測可能性のために必要な要素 ◦ 再現性 / 純粋性

    / モジュール性 • 段階的に予測可能性を獲得 ◦ コンソール < CLI < CloudFormation < CDK • CDK には静的テスト機構が備わっている ◦ Snapshot / Fine-grained Assertion / Validation
  84. #DevOpsDaysTokyo #DevOpsDaysTokyo よし、自作の Construct デプロイしよう

  85. #DevOpsDaysTokyo Custom Construct API Count Hits

  86. #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 } }); } }
  87. #DevOpsDaysTokyo \エラー!/

  88. #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); } } 権限つけ忘れた!
  89. #DevOpsDaysTokyo #DevOpsDaysTokyo 「何」を「どう」テストすべきなのか?

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

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

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

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

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

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

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

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

    YAML 2. YAML の生成 3. リソースの作成 • 1 + 2 = A かつ A + 3 = B なら 1 + 2 + 3 = B • つまり残りは右側の予測可能性が問題
  98. #DevOpsDaysTokyo #DevOpsDaysTokyo 要するに YAML の「振る舞い」をテストしたい

  99. #DevOpsDaysTokyo リソースに期待する振る舞い • ポリシー的な性質 ◦ リソース・アプリをまたいで制約が掛かっているか ◦ 例:0.0.0.0/0 禁止、コスト集約用のタグ必須 •

    意味論的な性質 ◦ 複数のリソースがうまく「噛み合った」状態で動作するか ◦ 例:ネットワーク疎通が可能か、権限が足りているか
  100. #DevOpsDaysTokyo #DevOpsDaysTokyo どんなツールが使えそう?

  101. #DevOpsDaysTokyo CloudFormation のテストツール • cfn-nag ◦ CloudFormation 用、分野はセキュリティ系 • CloudFormation

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

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

    ◦ Lambda Function 版も提供されている • メリット・デメリット ◦ 定義済みルール:サンプルのみ(既存の YAML から生成機能あり) ◦ 配布・再利用性:低い(ルールファイル直接指定のみ) ◦ 拡張性:あまり高くない(個別の属性をチェックするのみ) https://github.com/aws-cloudformation/cloudformation-guard
  104. #DevOpsDaysTokyo #DevOpsDaysTokyo 複数のリソース間の関係はどうテストする? 自動生成されたリソース名の参照はどう扱う?

  105. #DevOpsDaysTokyo Conftest • Open Policy Agent (OPA) の派生 ◦ Kubernetes

    との連携 (Gatekeeper) が人気 • メリット・デメリット ◦ 定義済みルール:なし ◦ 配布・再利用性:高い(OCI = Docker レジストリで配布可能) ◦ 拡張性:高い(Prolog の一種 Rego を使用) https://github.com/open-policy-agent/conftest
  106. #DevOpsDaysTokyo #DevOpsDaysTokyo Ruby や Rust はともかく Rego って何?

  107. #DevOpsDaysTokyo Rego 言語ことはじめ is_xxxxx { condition1 condition2 } Rego の記述は

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

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

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

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

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

    代入ではない(単一化) 配列 functions の中から全体を成り立たせるような添字 _ が存在すれば それを任意に取って f とする。 Rego の記述は 「xxxxxx とは yyyyyy であることである」 という定義のあつまり
  113. #DevOpsDaysTokyo #DevOpsDaysTokyo 実際にどうすれば権限がテストできるか?

  114. #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); } } 権限の不足を発見したい
  115. #DevOpsDaysTokyo deny[msg] { ... violations = [ [f, t] |

    f := functions[_]; t := tables[_]; needs_permission(f, t); not has_permission(f, t); ] count(violations) > 0 ... } warn[msg] { ... }
  116. #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 は警告
  117. #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 ならばエラー
  118. #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" }
  119. #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 を適切に選んで 以下を満たせるか?
  120. #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 を適切に選んで 条件全体を満たせるか?
  121. #DevOpsDaysTokyo #DevOpsDaysTokyo 慣れるまで結果を確認しつつ練習したい

  122. #DevOpsDaysTokyo https://play.openpolicyagent.org/

  123. #DevOpsDaysTokyo 定義済みルール 配布・再利用 拡張性 cfn-nag ◯ × × cfn-guard △

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

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

    から YAML への変換のテスト • リソースに期待する振る舞い ◦ ポリシー的な性質 / 意味論的な性質 • 生成された YAML をテストするコツ ◦ Conftest で具体的な名前に依存せずテストできる
  126. #DevOpsDaysTokyo #DevOpsDaysTokyo 本日のまとめ Wrap Up!

  127. #DevOpsDaysTokyo 本日のまとめ • IaC における静的テストの必要性 ◦ デプロイ前に予測可能性を確保したい • AWS のリソース管理と

    CDK ◦ 再現性 / 純粋性 / モジュール性 • YAML に対するテスト戦略とツール ◦ cfn-nag or cfn-guard(ポリシ強制)/ Conftest(機能要件)
  128. #DevOpsDaysTokyo #DevOpsDaysTokyo Make Your IaC Predictable! Presented by チェシャ猫 (@y_taka_23)