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

Infrastructure as Code の静的テスト戦略 #CODT2020 / Cloud Operator Days Tokyo 2020

Infrastructure as Code の静的テスト戦略 #CODT2020 / Cloud Operator Days Tokyo 2020

Cloud Operator Days Tokyo 2020 で使用したスライドです。

インフラをコードとして管理することで環境構築の再現性を担保する IaC の手法は、ここ数年で急激に普及しました。しかし、そのコードに対するテストは簡単なチェックに留まっていることも多く、実際に環境を構築してみたら上手く動かない、という状況がしばしば起こります。そこで本セッションでは、実際の環境を構築する「前」に、IaC のコード自体に対してテストを行う手法について解説します。

実際にインフラをコードで管理していて、早い段階で設定ミスを検出して無駄なコストを削減したい方におすすめのセッションです。

イベント概要:https://cloudopsdays.com/
ブログ記事:https://ccvanishing.hateblo.jp/entry/2020/07/30/173935

y_taka_23

July 29, 2020
Tweet

More Decks by y_taka_23

Other Decks in Technology

Transcript

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

    View full-size slide

  2. #CODT2020
    #CODT2020
    「Infrastructure as Code 辛い」

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  9. #CODT2020
    Mutable Immutable
    Local
    Global

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. #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)

    View full-size slide

  35. #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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  44. #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

    View full-size slide

  45. #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(親要素)

    View full-size slide

  46. #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));
    }
    }

    View full-size slide

  47. #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 ...

    View full-size slide

  48. #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 が内包

    View full-size slide

  49. #CODT2020
    cdk diff
    cdk deploy

    View full-size slide

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

    View full-size slide

  51. #CODT2020
    API
    Count Hits

    View full-size slide

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

    View full-size slide

  53. #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
    }
    });
    }
    }

    View full-size slide

  54. #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 を継承したクラスを作成

    View full-size slide

  55. #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

    View full-size slide

  56. #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

    View full-size slide

  57. #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
    }
    });
    }
    }
    環境変数経由で参照

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  62. #CODT2020
    #CODT2020
    Snapshot Test

    View full-size slide

  63. #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(),
    });
    }
    }

    View full-size slide

  64. #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 に相当

    View full-size slide

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

    View full-size slide

  66. #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),
    });
    }
    }

    View full-size slide

  67. #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 に影響を与える変更

    View full-size slide

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

    View full-size slide

  69. #CODT2020
    #CODT2020
    Fine-Grained Assertion

    View full-size slide

  70. #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'
    }));
    });

    View full-size slide

  71. #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 生成結果に関する
    アサーション

    View full-size slide

  72. #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)

    View full-size slide

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

    View full-size slide

  74. #CODT2020
    #CODT2020
    Validation Test

    View full-size slide

  75. #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)
    });
    ...
    }

    View full-size slide

  76. #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) を確認して範囲外なら例外

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  81. #CODT2020
    Custom
    Construct
    API
    Count Hits

    View full-size slide

  82. #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
    }
    });
    }
    }

    View full-size slide

  83. #CODT2020
    \エラー!/

    View full-size slide

  84. #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);
    }
    }
    権限つけ忘れた!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  110. #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);
    }
    }
    権限の不足を発見したい

    View full-size slide

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

    View full-size slide

  112. #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 は警告

    View full-size slide

  113. #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 ならばエラー

    View full-size slide

  114. #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"
    }

    View full-size slide

  115. #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 を適切に選んで
    以下を満たせるか?

    View full-size slide

  116. #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 を適切に選んで
    条件全体を満たせるか?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide