

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# CloudFront를 사용하여 비디오 스트리밍
<a name="tutorial-stream-video-with-cloudfront"></a>

미디어 워크플로는 일반적으로 비디오 온디맨드(VOD) 파일, HTTP 라이브 스트리밍(HLS) 패키지, 이미지 및 그래픽과 같은 완성된 콘텐츠를 편집기, 생산자 및 자동화 시스템이 NFS 또는 SMB를 사용하여에 쓰는 FSx for ONTAP 볼륨에 저장합니다.

FSx for ONTAP 볼륨에 연결된 Amazon S3 액세스 포인트를 사용하여 CloudFront는 볼륨에서 직접 콘텐츠를 제공할 수 있습니다. 편집기와 프로덕션 시스템은 항상 있는 방식으로 NFS 또는 SMB를 통해 볼륨에 게시하고, CloudFront는 액세스 포인트를 통해 콘텐츠를 가져오며, 최종 사용자는 가장 가까운 CloudFront 엣지 로케이션에서 콘텐츠를 수신합니다.

이 자습서에서는 샘플 비디오를 HLS 적응 비트레이트 패키지로 인코딩하고, 출력을 FSx for ONTAP 볼륨에 연결된 액세스 포인트에 업로드하고, 오리진 액세스 제어로 CloudFront 배포를 구성하여 최종 사용자가 CloudFront를 우회하여 볼륨에 직접 도달할 수 없도록 하고, 스트림이 처음부터 끝까지 재생되는지 확인합니다.

**참고**  
이 자습서를 완료하는 데 약 **40\~60분이** 걸립니다. AWS 서비스 사용한 에는 생성한 리소스에 대한 요금이 발생합니다. **정리** 섹션을 포함하여 모든 단계를 즉시 완료하면 미국 동부(버지니아 북부)에서 예상 비용이 **1 USD** 미만입니다 AWS 리전. 이 견적에는 FSx for ONTAP 볼륨 자체에 대한 지속적인 요금은 포함되지 않습니다.

## 패턴 작동 방식
<a name="tutorial-cf-how-it-works"></a>

요청 흐름은 다음과 같습니다.
+ 최종 사용자의 플레이어(브라우저, 모바일 앱, 스마트 TV)가 CloudFront 도메인에서 HLS 마스터 재생 목록을 요청합니다.
+ CloudFront는 엣지 캐시를 확인합니다. CloudFront는 오리진 액세스 제어(OAC)와 함께 서명 버전 4(SigV4)를 사용하여 요청에 서명하고 액세스 포인트의 Amazon S3 엔드포인트로 전달합니다.
+ 액세스 포인트는 CloudFront 서비스 보안 주체가 배포로 범위가 지정되도록 허용하는 액세스 정책에 대해 요청을 승인하고 FSx for ONTAP 볼륨에서 요청된 객체를 반환합니다.
+ CloudFront는 엣지에서 응답을 캐싱하여 최종 사용자에게 반환합니다.

HLS 패키지는 서로 다른 캐시 정책의 이점을 활용하는 두 가지 유형의 파일을 혼합합니다.
+ **재생 목록**(`.m3u8`)은 스트림을 구성하는 세그먼트를 설명합니다. 업데이트된 재생 목록을 빠르게 게시할 수 있도록 짧은 `Cache-Control` TTL을 사용합니다.
+ **세그먼트**(`.ts`)에는 인코딩된 비디오와 오디오가 포함됩니다. 일단 작성되면 세그먼트의 콘텐츠는 변경되지 않으므로 변경 불가능한 긴 `Cache-Control` TTL을 사용합니다.

## 사전 조건
<a name="tutorial-cf-prerequisites"></a>
+ Amazon S3 액세스 포인트가 연결된 FSx for ONTAP 볼륨입니다. CloudFront가 액세스할 수 있도록 액세스 포인트에 **인터넷** 네트워크 오리진이 있어야 합니다. 지침은 [액세스 포인트 생성](fsxn-creating-access-points.md) 섹션을 참조하세요.
+ AWS CLI 버전 2는 CloudFront 배포, 오리진 액세스 제어 및 액세스 포인트 정책을 생성할 수 있는 자격 증명으로 설치 및 구성됩니다.
+ 샘플 비디오를 HLS로 인코딩하기 위해 로컬에 설치된 [FFmpeg](https://ffmpeg.org/)입니다.
+ 소스 비디오 파일입니다. 이 자습서에서는 Creative Commons 아래에 릴리스된 52초 1080p 클립인 Blender Foundation의 [Sintel 트레일러](https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4)를 사용합니다.

## 1단계: 소스 비디오를 HLS 패키지로 인코딩
<a name="tutorial-cf-encode"></a>

FFmpeg를 사용하여 사실적인 OTT(over-the-top) 비트레이트로 360p, 720p 및 1080p에서 3가지 변형 HLS 패키지를 생성합니다. 결과 패키지에는 변형별 재생 목록을 참조하는 마스터 재생 목록이 포함되며, 각 재생 목록은 4초 전송 스트림 세그먼트를 나열합니다.

1. 소스 비디오를 다운로드합니다.

   ```
   $ mkdir -p ~/media && cd ~/media
   curl -sSL -o sintel-1080p.mp4 \
       https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4
   ```

1. 세 가지 적응 비트레이트 변형을 사용하여 비디오를 HLS로 인코딩합니다.

   ```
   $ mkdir hls && cd hls
   ffmpeg -i ../sintel-1080p.mp4 \
       -filter_complex "[0:v]split=3[v1][v2][v3]; \
           [v1]scale=w=640:h=360[v1out]; \
           [v2]scale=w=1280:h=720[v2out]; \
           [v3]scale=w=1920:h=1080[v3out]" \
       -map "[v1out]" -c:v:0 libx264 -b:v:0 800k  -maxrate:v:0 856k  -bufsize:v:0 1200k \
       -map "[v2out]" -c:v:1 libx264 -b:v:1 3000k -maxrate:v:1 3200k -bufsize:v:1 4500k \
       -map "[v3out]" -c:v:2 libx264 -b:v:2 5500k -maxrate:v:2 5900k -bufsize:v:2 8250k \
       -preset veryfast -g 48 -keyint_min 48 -sc_threshold 0 \
       -map a:0 -map a:0 -map a:0 -c:a aac -b:a:0 96k -b:a:1 128k -b:a:2 128k \
       -f hls -hls_time 4 -hls_playlist_type vod -hls_flags independent_segments \
       -hls_segment_filename "stream_%v/seg_%03d.ts" \
       -master_pl_name master.m3u8 \
       -var_stream_map "v:0,a:0,name:360p v:1,a:1,name:720p v:2,a:2,name:1080p" \
       "stream_%v/playlist.m3u8"
   ```

   명령은 마스터 재생 목록 1개, 변형 재생 목록 3개, 각 변형에 대한 전송 스트림 세그먼트가 있는 디렉터리 트리를 생성합니다.

   ```
   hls/
   ├── master.m3u8
   ├── stream_360p/
   │   ├── playlist.m3u8
   │   ├── seg_000.ts
   │   └── ...
   ├── stream_720p/
   │   ├── playlist.m3u8
   │   ├── seg_000.ts
   │   └── ...
   └── stream_1080p/
       ├── playlist.m3u8
       ├── seg_000.ts
       └── ...
   ```

## 2단계: 액세스 포인트에 HLS 패키지 업로드
<a name="tutorial-cf-upload"></a>

패키지를 두 번 업로드합니다. 즉, TTL이 짧은 재생 목록의 경우 한 번, 변경 불가능한 TTL이 길고 변경 불가능한 세그먼트의 경우 한 번 업로드합니다. 올바른 설정은 중요합니다. 대부분의 플레이어`Content-Type`는 `.m3u8` 및 `application/vnd.apple.mpegurl`에 대해 `video/mp2t`가 필요합니다`.ts`.

{{access-point-alias}}를 액세스 포인트 별칭으로 바꿉니다.

```
$ # Playlists: short TTL, m3u8 content type
aws s3 cp ~/media/hls/ "s3://{{access-point-alias}}/content/sintel/" \
    --recursive --exclude "*" --include "*.m3u8" \
    --content-type "application/vnd.apple.mpegurl" \
    --cache-control "max-age=60"

# Segments: long immutable TTL, ts content type
aws s3 cp ~/media/hls/ "s3://{{access-point-alias}}/content/sintel/" \
    --recursive --exclude "*" --include "*.ts" \
    --content-type "video/mp2t" \
    --cache-control "max-age=31536000,immutable"
```

두 파일이 모두 예상 콘텐츠 유형 및 캐시 헤더와 함께 업로드되었는지 확인합니다.

```
$ aws s3api head-object --bucket {{access-point-alias}} \
    --key content/sintel/master.m3u8 \
    --query '{ContentType:ContentType,CacheControl:CacheControl}'
```

## 3단계: 오리진 액세스 제어 생성
<a name="tutorial-cf-oac"></a>

오리진 액세스 제어(OAC)를 사용하면 CloudFront만 객체를 가져올 수 있도록 CloudFront가 액세스 포인트에 대한 요청에 서명할 수 있습니다. OAC가 없으면 최종 사용자는 액세스 포인트 엔드포인트에서 직접 객체를 요청하여 CloudFront를 우회할 수 있습니다.

```
$ aws cloudfront create-origin-access-control \
    --origin-access-control-config \
    'Name=fsxn-media-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3'
```

응답의 `Id`에 주의하세요. 다음 단계에서 해당 항목을 사용합니다.

## 4단계: CloudFront 배포 생성
<a name="tutorial-cf-distribution"></a>

액세스 포인트 별칭을 오리진 도메인으로 사용하여 CloudFront 배포를 생성합니다. 2단계에서 설정한 `Cache-Control` 헤더를 준수하는 `CachingOptimized` 관리형 캐시 정책을 사용합니다.

1. 다음 구성을 라는 파일에 저장하여 자리 표시자를 `dist.json`바꿉니다.

   ```
   {
       "CallerReference": "fsxn-media-1",
       "Comment": "FSx for ONTAP media delivery",
       "Enabled": true,
       "DefaultRootObject": "",
       "Origins": {
           "Quantity": 1,
           "Items": [{
               "Id": "fsxn-ap",
               "DomainName": "{{access-point-alias}}.s3.{{region}}.amazonaws.com",
               "S3OriginConfig": {"OriginAccessIdentity": ""},
               "OriginAccessControlId": "{{oac-id}}",
               "ConnectionAttempts": 3,
               "ConnectionTimeout": 10
           }]
       },
       "DefaultCacheBehavior": {
           "TargetOriginId": "fsxn-ap",
           "ViewerProtocolPolicy": "redirect-to-https",
           "AllowedMethods": {
               "Quantity": 2, "Items": ["GET", "HEAD"],
               "CachedMethods": {"Quantity": 2, "Items": ["GET", "HEAD"]}
           },
           "Compress": true,
           "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
       },
       "PriceClass": "PriceClass_100",
       "ViewerCertificate": {"CloudFrontDefaultCertificate": true}
   }
   ```
**참고**  
`PriceClass_100`는 북미 및 유럽에서만 CloudFront 엣지 로케이션을 사용하므로이 자습서에서는 비용을 낮게 유지합니다. 글로벌 엣지 적용 범위의 경우 값을 로 변경합니다`PriceClass_All`. 자세한 내용은 [ CloudFront 배포의 가격 등급 선택을 참조하세요](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PriceClass.html).

1. 배포를 생성합니다.

   ```
   $ aws cloudfront create-distribution --distribution-config file://dist.json \
       --query 'Distribution.{Id:Id,DomainName:DomainName,ARN:ARN}'
   ```

   응답에 배포 ID, ARN 및 도메인 이름을 기록해 둡니다. 배포하는 데 약 5분이 걸립니다. 배포하는 동안 5단계로 계속 진행할 수 있습니다.

## 5단계: CloudFront를 허용하는 액세스 포인트 정책 연결
<a name="tutorial-cf-ap-policy"></a>

액세스 포인트 정책은 CloudFront 서비스 보안 주체에게 `AWS:SourceArn` 조건을 사용하여 특정 배포로 범위가 지정된 객체를 읽을 수 있는 권한을 부여합니다.

1. 다음 정책을 라는 파일에 저장하여 자리 표시자를 `ap-policy.json`바꿉니다.

   ```
   {
       "Version": "2012-10-17", 		 	 	 
       "Statement": [{
           "Sid": "AllowCloudFrontServicePrincipal",
           "Effect": "Allow",
           "Principal": {"Service": "cloudfront.amazonaws.com"},
           "Action": "s3:GetObject",
           "Resource": "arn:aws:s3:{{region}}:{{account-id}}:accesspoint/{{access-point-name}}/object/*",
           "Condition": {
               "StringEquals": {
                   "AWS:SourceArn": "arn:aws:cloudfront::{{account-id}}:distribution/{{distribution-id}}"
               }
           }
       }]
   }
   ```

1. 정책을 액세스 포인트에 연결합니다.

   ```
   $ aws s3control put-access-point-policy \
       --account-id {{account-id}} \
       --name {{access-point-name}} \
       --policy file://ap-policy.json
   ```

## 6단계: 재생 확인
<a name="tutorial-cf-verify"></a>

배포가 `Deployed` 상태에 도달할 때까지 기다립니다.

```
$ aws cloudfront get-distribution --id {{distribution-id}} \
    --query 'Distribution.Status'
```

CloudFront를 통해 마스터 재생 목록을 가져옵니다.

```
$ curl -sS "https://{{distribution-domain}}/content/sintel/master.m3u8"
```

응답에는 세 가지 변형이 나열되어야 합니다.

```
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=1031744,RESOLUTION=640x360,CODECS="avc1.64001e,mp4a.40.2"
stream_360p/playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=3497301,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
stream_720p/playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=6311285,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
stream_1080p/playlist.m3u8
```

응답 헤더에서 올바른 콘텐츠 유형, 캐시 제어 및 캐시 상태를 확인합니다.

```
$ curl -sSI "https://{{distribution-domain}}/content/sintel/stream_1080p/seg_000.ts"
```

응답이 성공하면 `content-type: video/mp2t`, `cache-control: max-age=31536000,immutable`및 응답이 엣지에서 왔는지 오리진에서 왔는지 여부를 나타내는 `x-cache` 헤더가 표시됩니다.

마지막으로 스트림을 FFmpeg로 끝까지 재생하여 모든 세그먼트가 올바르게 가져오고 디코딩되는지 확인합니다.

```
$ ffprobe -v error \
    -show_entries stream=codec_name,width,height \
    -show_entries format=duration \
    "https://{{distribution-domain}}/content/sintel/master.m3u8"
```

Safari 또는 VLC에서 마스터 재생 목록 URL을 열거나 hls.js와 같은 JavaScript 플레이어를 사용하여 웹 페이지에 포함할 수도 있습니다. [https://github.com/video-dev/hls.js](https://github.com/video-dev/hls.js) 

## 패턴 확장
<a name="tutorial-cf-extending"></a>
+ **HTTPS와 함께 사용자 지정 도메인을 사용합니다.** 도메인에 대한 ACM 인증서를 요청하고 배포에 연결한 다음 CloudFront 도메인을 가리키는 CNAME 레코드를 추가합니다. 지침은 [ CloudFront에서 사용자 지정 URLs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-procedures.html).
+ **서명된 URLs 또는 서명된 쿠키로 프리미엄 콘텐츠를 보호합니다.** 권한이 필요한 콘텐츠(구독 서비스, 조기 액세스 미리 보기, 지오펜스 콘텐츠)의 경우 CloudFront 서명된 URLs 또는 서명된 쿠키를 사용합니다. [서명된 URLs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html).
+ **새 콘텐츠를 게시할 때 캐시를 무효화합니다.** 재생 목록을 교체하거나 새 HLS 패키지를 업로드할 때 `aws cloudfront create-invalidation`를 사용하여 CloudFront 엣지에서 이전 버전을 제거합니다. TTLs이 긴 변경 불가능한 세그먼트의 경우 세그먼트 파일 이름은 패키지별로 고유하기 때문에 일반적으로 무효화가 필요하지 않습니다.
+ **브라우저 기반 플레이어에 대해 CORS를 활성화합니다.** 다른 도메인의 브라우저 기반 HLS 플레이어가 스트림을 로드하는 경우 CloudFront 응답 `Access-Control-Allow-Origin` 헤더 정책을 사용하여 응답에 헤더를 추가합니다.
+ **최종 사용자 요청을 로깅합니다.** CloudFront 표준 로깅 또는 실시간 로그를 활성화하여 분석, 결제 또는 남용 탐지에 대한 최종 사용자 요청을 캡처합니다.

## 문제 해결
<a name="tutorial-cf-troubleshooting"></a>

403 CloudFront에서 금지됨  
액세스 포인트 정책이 누락되었거나, CloudFront 서비스 보안 주체가 포함되어 있지 않거나, `AWS:SourceArn` 조건이 잘못된 배포 ARN을 참조합니다. 를 사용하여 정책을 `aws s3control get-access-point-policy` 확인하고 배포 ARN이 `aws cloudfront create-distribution` 응답의 정책과 일치하는지 확인합니다.

플레이어가 마스터 재생 목록을 로드하지만 재생하지 못함  
세그먼트 파일에 `Content-Type: video/mp2t` 및 재생 목록에이 있는지 확인합니다`Content-Type: application/vnd.apple.mpegurl`. 일부 플레이어는 일반 콘텐츠 유형의 세그먼트를 거부합니다. 올바른 `--content-type` 플래그로 다시 업로드합니다.

새 재생 목록은 최종 사용자에게 도달하는 데 시간이 걸립니다.  
CloudFront는 `Cache-Control` 헤더가 설정한 TTL의 재생 목록을 캐시합니다. 더 짧은 TTL이 필요한 경우 더 작은 `max-age` 값으로 재생 목록을 다시 업로드하거나 무효화를 생성합니다. 세그먼트의 내용은 변경되지 않으므로 세그먼트에이 문제가 없습니다.

`x-cache: Miss from cloudfront` 모든 요청 시  
리전의 최종 사용자가 파일을 처음 요청할 때는 정상입니다. CloudFront는 누락 시 오리진에서 가져오고 TTL에 대한 응답을 캐싱합니다. 해당 엣지 로케이션에서 동일한 파일에 대한 후속 요청은를 반환합니다`Hit from cloudfront`.

액세스 포인트에 대한 직접 액세스가 거부됨  
이는 예상된 동작입니다. OAC에는 CloudFront의 SigV4-signed 요청이 필요하며 액세스 포인트 정책은 CloudFront 서비스 보안 주체에 대한 액세스를 제한합니다. 최종 사용자는 배포 도메인을 통해서만 콘텐츠에 도달할 수 있습니다.

## 정리
<a name="tutorial-cf-clean-up"></a>

배포를 비활성화하고 삭제한 다음 나머지 리소스를 삭제합니다. 배포를 삭제하려면 먼저 비활성화해야 합니다.이 작업은 몇 분 정도 걸립니다.

를 비활성화하려면의 두 가지 값`--if-match`, `get-distribution-config`즉 `ETag`의와의 내부 `DistributionConfig` 객체가 필요합니다`--distribution-config`(전체 응답에는 수락하지 `update-distribution` 않는 ETag도 포함됨).

```
$ # Capture the current ETag and the DistributionConfig body
GET_ETAG=$(aws cloudfront get-distribution-config --id {{distribution-id}} \
    --query 'ETag' --output text)
aws cloudfront get-distribution-config --id {{distribution-id}} \
    --query 'DistributionConfig' --output json \
    | jq '.Enabled = false' > dist-updated.json

# Disable the distribution. The response returns a new ETag.
UPDATE_ETAG=$(aws cloudfront update-distribution --id {{distribution-id}} \
    --if-match "$GET_ETAG" --distribution-config file://dist-updated.json \
    --query 'ETag' --output text)

# Wait for Status to reach Deployed before deleting.
aws cloudfront get-distribution --id {{distribution-id}} \
    --query 'Distribution.Status'

# Delete the distribution using the ETag from the update call.
aws cloudfront delete-distribution --id {{distribution-id}} \
    --if-match "$UPDATE_ETAG"

# Fetch the OAC ETag, then delete the OAC.
OAC_ETAG=$(aws cloudfront get-origin-access-control --id {{oac-id}} \
    --query 'ETag' --output text)
aws cloudfront delete-origin-access-control --id {{oac-id}} \
    --if-match "$OAC_ETAG"
aws s3control delete-access-point-policy \
    --account-id {{account-id}} --name {{access-point-name}}
aws s3 rm "s3://{{access-point-alias}}/content/sintel/" --recursive
```