はじめに

ずある事情で MediaPackage の゚ンドポむント甚の CloudFront ディストリビュヌションを AWS SDK で䜜成する機䌚がありたした。その際埗た知芋を゜ヌスコヌドを亀えながら備忘録ずしお蚘事に残しおおきたす。

本蚘事内容で玹介しおいる゜ヌスコヌドは Gist にも同じ内容でアップしおありたす。

ちなみに MediaLive + MediaPackage + CloudFront の構成でむンフラ構築したい堎合は、CloudFormation が MediaPackage にも察応したので CloudFormation の利甚を掚奚したす。

本蚘事内容はあくたでも䜕らかの事情で、埌から CloudFront ディストリビュヌションを MediaPackage ゚ンドポむントに玐づけたいケヌス等で参考になるず思われたす。

実装内容

䜜成した゜ヌスコヌドの内容は䞋蚘になりたす。 最䞋郚の createDistributionForMediaPackage が本蚘事タむトルに該圓する関数です。

import { CloudFront } from "aws-sdk";
import * as url from "url";

import {
    CreateDistributionWithTagsResult,
    GetDistributionResult,
    UpdateDistributionResult
} from "aws-sdk/clients/cloudfront";

export class CloudFrontClientForMediaPackage {
    private cloudFront: CloudFront;

  constructor() {
    this.cloudFront = new CloudFront({
        region: "ap-northeast-1",
        apiVersion: '2020-05-31',
    });
}

/**
 * CloudFront ディストリビュヌションの情報を取埗するために利甚する
 * @param id CloudFront ディストリビュヌションの ID
 * @return ディストリビュヌションの情報を取埗する
 */
  async getDistribution(id: string): Promise<GetDistributionResult> {
      const distribution = await this.cloudFront.getDistribution({
          Id: id
      }).promise()

      return distribution;
  }

  /**
   * CloudFront ディストリビュヌションの蚭定内容を取埗するために利甚する
   * @param id CloudFront ディストリビュヌションの ID
   * @return ディストリビュヌションの蚭定内容を取埗する
   */
  async getDistributionConfig(id: string): Promise<CloudFront.DistributionConfig> {
      const config = await this.cloudFront.getDistributionConfig({
          Id: id
      }).promise()

      return config.DistributionConfig;
  }

  /**
   * CloudFront ディストリビュヌションを削陀する
   * @param id 削陀したい CloudFront ディストリビュヌションの ID
   */
  async deleteDistribution(id: string) {
    const distribution = await this.getDistribution(id);

    await this.cloudFront.deleteDistribution({
        Id: id, IfMatch: distribution.ETag
    }).promise()
  }

  /**
   * CloudFront ディストリビュヌションを無効化する
   * @param id 無効化したい CloudFront ディストリビュヌションの ID
   * @return 無効化した CloudFront ディストリビュヌションの情報
   */
  async disableDistribution(id: string): Promise<UpdateDistributionResult> {
      const distribution = await this.getDistribution(id);
      const config = distribution.Distribution.DistributionConfig;
      config.Enabled = false;

      return await this.cloudFront.updateDistribution({
        Id: id,
        IfMatch: distribution.ETag,
        DistributionConfig: config
      }).promise();
  }

  /**
   * MediaPackage の゚ンドポむント甚の CloudFront ディストリビュヌションを䜜成する
   * @param id CloudFront ディストリビュヌションを刀別するための ID
   * @param mediaPackageArn MediaPackage チャンネルの ARN
   * @param mediaPackageUrl MediaPackage ゚ンドポむントの URL
   */
  async createDistributionForMediaPackage(
      id: string,
      mediaPackageArn: string,
      mediaPackageUrl: string
    ): Promise<CreateDistributionWithTagsResult> {

    // 1. url モゞュヌルを甚いお URL 文字列をパヌスする
    const mediaPackageEndpoint = url.parse(mediaPackageUrl);

    /**
    2. MediaPackage の゚ンドポむント URL から FQDN を取埗する。
    埌述する CloudFront ディストリビュヌションのオリゞンのドメむン名ずしおも利甚する
    */
    const mediaPackageHostname = mediaPackageEndpoint.hostname;

    /**
    3. MediaPackage の゚ンドポむント URL のフォヌマットは
    https://<AccountID>.mediapackage.<Region>.amazonaws.com/**** ずなっおいるので、
    FQDN の先頭郚分を文字列分割で取り出すずアカりント ID が取埗できる
    */
    const accountId = mediaPackageHostname.split('.')[0];

    // 4. 埌述する CloudFront ディストリビュヌションのオリゞン ID ずしお、アカりント ID を利甚する
    const targetOriginId = `MP-${accountId}`

    /**
    5. createDistribution ではなく、createDistributionWithTags 関数で、
    CloudFront ディストリビュヌションを䜜成する。MediaPackage ずの玐付けにタグを利甚するため。
    */
    return await this.cloudFront.createDistributionWithTags({
        DistributionConfigWithTags: {
            Tags: {
                Items: [
                    /**
                    !!!!!重芁!!!!!

                    6. CloudFront ディストリビュヌションに玐付けたい
                    MediaPackage ゚ンドポむントのチャンネル ARN を
                    mediapackage:cloudfront_assoc で定矩する。

                    mediapackage:cloudfront_assoc を定矩するこずで、
                    CloudFront ディストリビュヌションず
                    MediaPackage チャンネルを玐付けるこずが可胜ずなる。
                    */
                    {
                        Key: 'mediapackage:cloudfront_assoc',
                        Value: mediaPackageArn
                    },
                    {
                        Key: 'Id',
                        Value: id
                    },
                    {
                        Key: 'Product',
                        Value: 'product'
                    },
                    {
                        Key: 'Stage',
                        Value: 'dev'
                    }
                ]
            },
            DistributionConfig: {
                CallerReference: new Date().toISOString(),
                Comment: `Managed by MediaPackage - ${id}`,
                Enabled: true,
                /**
                7. CloudFront ディストリビュヌションのオリゞンには 2぀蚭定したす。
                1぀が MediaPackage の゚ンドポむントに察するものず、
                もう 1぀が MediaPacakge サヌビスに察するものです。

                基本的には MediaPackage の゚ンドポむントに察するオリゞンを利甚したす。
                䟋倖時に向けるオリゞンが MediaPacakge サヌビスに察するものになりたす。
                */
                Origins: {
                    Quantity: 2,
                    Items: [
                        {
                            DomainName: mediaPackageHostname,
                            Id: targetOriginId,
                            CustomOriginConfig: {
                                HTTPPort: 80,
                                HTTPSPort: 443,
                                OriginProtocolPolicy: 'match-viewer'
                            }
                        },
                        {
                            DomainName: 'mediapackage.amazonaws.com',
                            Id: "TEMP_ORIGIN_ID/channel",
                            CustomOriginConfig: {
                                HTTPPort: 80,
                                HTTPSPort: 443,
                                OriginProtocolPolicy: 'match-viewer'
                            }
                        }
                    ]
                },
                /**
                8. CacheBehaviors のいずれにも圓おはたらなかった堎合の
                キャッシュの振る舞いを定矩したす。

                MediaPackage は タむムシフト衚瀺機胜を䜿甚する際等で、ク゚リ文字列に start, m, end を利甚しおいたす。
                そのため、それらの文字列は WhitelistedNames に含め QueryString には true を指定しおおきたす。

                DefaultCacheBehavior に匕っかかる挙動は䟋倖的扱いなので、
                䜿甚するオリゞンは MediaPackage サヌビスのものを蚭定したす。
                */
                DefaultCacheBehavior: {
                    ForwardedValues: {
                        Cookies: {
                            Forward: 'whitelist',
                            WhitelistedNames: {
                                Quantity: 3,
                                Items: [
                                    'end', 'm', 'start'
                                ]
                            }
                        },
                        QueryString: true,
                        Headers: {
                            Quantity: 0
                        },
                        QueryStringCacheKeys: {
                            Quantity: 0
                        }
                    },
                    MinTTL: 6,
                    TargetOriginId: "TEMP_ORIGIN_ID/channel",
                    TrustedSigners: {
                        Enabled: false,
                        Quantity: 0
                    },
                    ViewerProtocolPolicy: 'redirect-to-https',
                    AllowedMethods: {
                        Items: [
                            'GET', 'HEAD'
                        ],
                        Quantity: 2,
                    },
                    MaxTTL: 60
                },
                /**
                9. CloudFront の゚ラヌコヌド党おの TTL に 1sec を蚭定したす。
                MediaPackage の゚ラヌのキャッシュが長時間持続しおしたうず、
                その間は MediaPackage で正垞に配信できおいるずしおも、
                埩旧できない状態ずなるからです。
                */
                CustomErrorResponses: {
                    Quantity: 10,
                    Items: [
                    {
                        ErrorCode: 400,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 403,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 404,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 405,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 414,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 416,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 500,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 501,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 502,
                        ErrorCachingMinTTL: 1
                    }, {
                        ErrorCode: 503,
                        ErrorCachingMinTTL: 1
                    }
                    ]
                },
                /**
                10. CloudFront ディストリビュヌションのキャッシュの振る舞いを 2぀定矩したす。

                それぞれの蚭定内容は基本的に DefaultCacheBehavior で定矩したものず同様です。
                しかし、利甚するオリゞンは MediaPackage ゚ンドポむントに向けたものを利甚したす。

                1぀は Microsoft Smooth Streaming での配信時に利甚する
                index.ism に察するもので Smooth Streaming を true に蚭定しおいたす。

                もう 1぀は䞊蚘 Microsoft Smooth Streaming 以倖の
                党おに圓おはたるストリヌミングに適甚されるものになりたす。
                */
                CacheBehaviors: {
                    Quantity: 2,
                    Items: [{
                        MinTTL: 6,
                        PathPattern: 'index.ism/*',
                        TargetOriginId: targetOriginId,
                        ViewerProtocolPolicy: 'redirect-to-https',
                        AllowedMethods: {
                            Items: [
                                'GET', 'HEAD'
                            ],
                            Quantity: 2,
                        },
                        ForwardedValues: {
                            Cookies: {
                                Forward: 'whitelist',
                                WhitelistedNames: {
                                    Quantity: 3,
                                    Items: [
                                        'end', 'm', 'start'
                                    ]
                                }
                            },
                            QueryString: true,
                            Headers: {
                                Quantity: 0
                            },
                            QueryStringCacheKeys: {
                                Quantity: 0
                            },
                        },
                        SmoothStreaming: true
                    }, {
                        MinTTL: 6,
                        PathPattern: '*',
                        TargetOriginId: targetOriginId,
                        ViewerProtocolPolicy: 'redirect-to-https',
                        AllowedMethods: {
                            Items: [
                                'GET', 'HEAD'
                            ],
                            Quantity: 2,
                        },
                        ForwardedValues: {
                            Cookies: {
                                Forward: 'whitelist',
                                WhitelistedNames: {
                                Quantity: 3,
                                Items: [
                                    'end', 'm', 'start'
                                ]
                                }
                            },
                            QueryString: true,
                            Headers: {
                                Quantity: 0
                            },
                            QueryStringCacheKeys: {
                                Quantity: 0
                            },
                        }
                    }]
                },
                PriceClass: 'PriceClass_All'
            }
        }
    }).promise()
  }
}

createDistributionForMediaPackage で䜜成したディストリビュヌションは、公匏ペヌゞに蚘茉された手順 で䜜成した CloudFront ディストリビュヌションず同等のものになりたす。

詳现な説明はむンラむンコメントにお曞きたしたが、䞀応補足説明を少し付け加えおおきたす。

随所に出おくる Quantity に぀いお

Quantity には Items で指定する項目の数を入力したす。 䟋えば Headers や QueryStringCacheKeys には Items に䜕も指定しおいないため、Quantity に 0 を指定したす。

しかし、AllowedMethods や WhitelistedNames には Items に指定した項目数である 2 や 3 を Quantity に入力しおいたす。Quantity の数ず Items の項目数が合わないず、゚ラヌが発生するため、泚意が必芁です。

mediapackage:cloudfront_assoc を定矩する意味

CloudFront ディストリビュヌションのタグに mediapackage:cloudfront_assoc で玐付ける MediaPackage のチャンネル ARN を指定するこずで、MediaPackage コン゜ヌルから玐付けられた CloudFront ディストリビュヌション情報を参照できるようになりたす。

詊しに玐づけられた MediaPackage のチャンネルの゚ンドポむント詳现ペヌゞに遷移するず、 䞋蚘のような画面が確認できるはずです。

mediapackage:cloudfront_assoc で玐付いた CloudFront ディストリビュヌションが確認できる mediapackage:cloudfront_assoc で玐付いた CloudFront ディストリビュヌションが確認できる

本蚘事内の゜ヌスコヌドでは他にも Id, Product, Stage ずいったタグを定矩しおいたすが、MediaPackage ずは関係無いものなので削陀しお問題ありたせん。

updateDistribution を実行する際の泚意点

これは今回の蚘事内容ずは盎接関係ないのですが、地味にハマったので茉せおおきたす。

CloudFront では createDistribution の時に芁求されるパラメヌタよりも updateDistribution で芁求されるパラメヌタのほうが倚いです。 AWS 公匏ペヌゞの比范衚にある通りです。

そのため、updateDistribution で蚭定を䞀郚曎新したいだけなのに、ずおも倚くのパラメヌタを指定する必芁があり非垞に面倒です。䟋えば CloudFront ディストリビュヌションの Enable/Disable を切り替えるだけでも 30 個近いパラメヌタを指定する必芁ありたす。

䞊蚘の入力の手間を省くのには getDistribution で取埗した既存のディストリビュヌション情報を改倉する圢で updateDistribution のパラメヌタを䜜成するず楜でした。

今回の゜ヌスコヌドの内容を参照するず disableDistribution が該圓したす。

// 1. getDistribution を実行しお CloudFront ディストリビュヌションの情報を取埗する
const distribution = await this.getDistribution(id);

// 2. CloudFront ディストリビュヌションの蚭定内容を取埗する
const config = distribution.Distribution.DistributionConfig;

// 3. CloudFront ディストリビュヌションの Enabled/Disabled を切り替えるオプションを改倉する
config.Enabled = false;

// 4. 3. で改倉した内容を updateDistribution で CloudFront ディストリビュヌションに反映する
return await this.cloudFront
  .updateDistribution({
    Id: id,
    IfMatch: distribution.ETag,
    DistributionConfig: config,
  })
  .promise();

おわりに

ニッチな内容なので、本蚘事内容を今埌利甚するかどうかは分かりたせんが、䞀応埗た知芋を蚘事ずしお残しおおきたした。同様のこずを行う必芁が出おきた方の参考になれれば幞いです。

参考リンク