TECH
BLOG

MediaPackage 用の CloudFront ディストリビューションを AWS SDK で作成する

2020
12

はじめに

とある事情で MediaPackage のエンドポイント用の CloudFront ディストリビューションを AWS SDK で作成する機会がありました。その際得た知見をソースコードを交えながら備忘録として記事に残しておきます。

本記事内容で紹介しているソースコードは Gist にも同じ内容でアップしてあります。

ちなみに MediaLive + MediaPackage + CloudFront の構成でインフラ構築したい場合は、CloudFormation が MediaPackage にも対応したので CloudFormation の利用を推奨します。

本記事内容はあくまでも何らかの事情で、後から CloudFront ディストリビューションを MediaPackage エンドポイントに紐づけたいケース等で参考になると思われます。

実装内容

作成したソースコードの内容は下記になります。
最下部の createDistributionForMediaPackage が本記事タイトルに該当する関数です。

CloudFrontClientForMediaPackage.ts

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();

おわりに

ニッチな内容なので、本記事内容を今後利用するかどうかは分かりませんが、一応得た知見を記事として残しておきました。同様のことを行う必要が出てきた方の参考になれれば幸いです。

参考リンク

RELATED PROJECT

No items found.