はじめに とある事情で 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 ディストリビューションと同等のものになります。
...