streampack の動画配信プラットフォームの開発を担当している Tana です。
概要
弊社は先日 Google Cloud™ パートナー認定を取得しました。
既存サービスは AWS をベースに提供してますが、
今後GCP
上で動画配信のニーズも増えてくることも考えられ、
この機会に知見を増やすべく、streampack on GCP
を試みました。
その際のプロセスとトラブルシューティングなどを共有できればと思っております。
リソース部分の構成
リソース | AWS | GCP |
---|---|---|
仮想サーバ(コンテナ) | ECS(EC2) | GKE |
コンテナレジストリ | ECR | GCR |
データベース | RDS(MySQL) | Cloud SQL |
ストレージ | S3 | GCS(Storage) |
CDN | CloudFront | Cloud CDN |
動画変換 | MediaConvert / ElasticTranscoder | FFmpeg on GKE |
ログ | CloudWatch Logs | StackDriver |
GCP には動画変換のマネージドサービスはないので、FFmpeg を使って HLS に変換させます。
アプリケーション部分
アプリケーションにて AWSへの操作は AWS Ruby SDK v3 を使用していたので、
Aws::
というキーワードで影響範囲を洗い出し、動画コアである下記の部分の修正が
必要でした。
- アップロード
- サムネイル
- 動画変換
- 配信
他のオプショナルな機能の一つとしてライブからアーカイブファイル対応にて S3 + SQS + Lambda を使ってもいましたが今回は割愛
アップロード
GCSは AWS S3 と同様に Direct Upload をサポートしておりましたので、
下記のように put signed URL を返してくれるように API を修正しました。
GCP or AWS の環境に応じてそれぞれのURLを返し、アップロード時にセットしてます。
コマンド
$ gsutil signurl -m PUT -d 1d -c video/mp4 ~/credentials.json gs://your-bucket-name/test.mp4
storage = Google::Cloud::Storage.new
bucket = storage.bucket(ENV['GCS_BUCKET'])
sslkey = OpenSSL::PKey::RSA.new("-----BEGIN PRIVATE KEY-----\n...")
put_url = bucket.signed_url(
s3_key,
method: 'PUT',
signing_key: sslkey,
issuer: ENV['GCS_SV_ACCOUNT'],
expires: 1.hour,
content_type: filetype # これ重要
)
signed URLのレスポンスが体感で数秒かかるので、signed URL のリストを取得したり、リクエストが多いサービスでの使用は注意が必要そうです。
サムネイル
CarrierWave, Fog のライブラリは様々な AWS, GCP, Azure など Cloud Provider をサポートしているので、基本設定を変え同じコードでサムネイルの生成・リサイズ・配信ができます。
動画変換
GCPは動画配信のマネージドサービスはないので、worker デーモンプロセスを GKE の pod 上で FFmpeg を使いバックグラウンドで変換させてます。もともとローカル開発で AWS リソース設定していなくても動くことを考慮していたので、フィーチャーフラグにて ElasticTranscoder -> FFmpeg 切り替えれるようにしてます。もちろん GKE の特徴でもあるスケーラビリティなので、Node or Pods 数を増やして並列で処理することも可能です。
Adaptive Bitrate対応や透かし入れたり、プリセット定義や複数パイプラインでの実施を考慮するケースではどうしても自前でやると workflow が大変なので、要件に応じてマネージドサービスを使う方が賢明でしょう。
GCPリソースセットアップ
GCS(Storage)
AWS S3と同じオブジェクトストレージで概念や操作も似ているので s3 経験者であればスムーズかと思います。GCPの便利なのが、 Cloud Shell 上から、簡単に操作できるところです。わざわざ AWS CLI のセットアップや AWS IAM からcredentials を発行しローカルにセットしたり、セットアップは不要です。
GCS の操作は gcloud
ではなく gsutil
になります。
ヘルプ
$ gsutil help
コピーファイル
gsutl cp test.mp4 gs://your-bucket-name/
public対応
$ gsutil -D setacl public-read gs://your-bucket-name/test.mp4
cors 設定は画面上からできないので、下記のコマンドで実施します。
$ gsutil cors set cors.json gs://<your-bucket-name>
----
[
{
"origin": ["https://<your domain>"],
"responseHeader": ["*"],
"method": ["GET", "PUT", "HEAD"],
"maxAgeSeconds": 3600
}
]
----
クレデンシャルの発行
AWS の場合は IAM から発行しますが、GCSの場合は Storage -> Settings から発行できます。
Cloud SQL - データーベース
GKEからデータベース接続する際は下記の方法があります。
- public IP
- private IP
- cloud SQL Proxy
public IP だと変わってしまうことがあるため、セキュアな接続である Proxy を使った方法で実施しました。
サービスアカウント
クレデンシャルの発行
Owner 権限を持ったアカウントで Cloud SQL Client の Role を持ったサービスアカウントを作成します。
credentials を上記のように取得して、後ほど使うのでダウンロードしておきます。
注意: Owner 権限出ないと、Roleの選択画面が出てこないので注意です。
GCR - コンテナレジストリ
ECRと違って、リポジトリの事前作成は不要です。
GCRに push できるように下記のセットアップして、ホスト先を指定して push します。
$ gcloud auth configure-docker
GCRホストが一番近そうな asia.gcr.io
を指定してます。
$ docker build -t asia.gcr.io/<your-project-name>/<image name> .
$ docker push asia.gcr.io/<your-project-name>/<image_name>
Vulnerability scanning(beta)
コンテナ内の脆弱性診断がベータ版ですが、提供されております。
Enable にするだけで、push後に診断してくれます。
下記はアプリケーションの結果です。
https://qiita.com/ytanaka3/items/8c308db2ee58ea63626a
にてブログ書きましたが、コンテナイメージの最適化を行っていたので、大丈夫でした。
nginx だと、、、
見直しが必要そうです。。
ミドルウェア周りのセキュリティ対策を DevOps標準フローとして導入できそうです。
GKE - Google Kubernetes Engine
cloudsql-proxy 設定
先ほどのサービスアカウントで設定しダウンロードした credentials.json を下記のコマンドにて Secrets として登録します。
$ kubectl create secret generic cloudsql-instance-credentials
--from-file=credentials.json=/path/to/credentials.json
Cloud Shell の場合は credentials のアップロードは下記の方法で可能です。
確認は下記のコマンドでも確認できます。
$ kubectl get secrets
ConfigMap 設定
環境変数を deployment or pod ファイルにそれぞれ定義することもできますが、すでに準備した .envファイルを読み込ませて、登録します。
$ kubectl create configmap cms-config --from-env-file=.env
DB_HOST=127.0.0.1
DB_USERNAME=xxxx
DB_PASSWORD=xxxx
DB_NAME=xxxx
GCS_BUCKET=bucket-name
GCS_KEY=xxx
GCS_SV_ACCOUNT=xxx
cloudsql-proxy のケースのホスト名は 127.0.0.1
になりますので注意が必要です。(public IPではないです。)
正しく登録されたか、中身の確認は下記を実行します。
$ kubectl get configmap app-config -o yaml
deployment 設定
$ kubectl create -f app-deployment.yaml
#... 抜粋
# cloudSQL用のイメージ
containers:
# CMSアプリ
- image: asia.gcr.io/<your-project-name>/<repository>
name: app
# configMap で定義した環境変数読み込み
envFrom:
- configMapRef:
name: cms-config
# ... 抜粋
# cloudsql-proxy サイドカー
- image: b.gcr.io/cloudsql-docker/gce-proxy:1.14
name: cloudsql-proxy
command: ["/cloud_sql_proxy",
"-instances=<your-project-name>:<db-region>:<db-name>=tcp:3306",
"-credential_file=/secrets/cloudsql/credentials.json"]
securityContext:
runAsUser: 2
allowPrivilegeEscalation: false
# マウント
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
細かな設定で時間を要したのが、volumes マウントです。
上記のコードには記載してませんが、ECSだとマウントする際は、マウント元の情報はキープされますが、k8s だとマウントしたパスのファイルは削除されます。nginx 上で assets を配信していたのですが、アクセスできなかったので、
$ kubectl exec -it <pod name> -c <container name> sh # <- nginx
にて確認すると、コンテナ内で assets 配下を見てみると空っぽでした。
調べたりテストする限りだと仕様のようなので、lifecycle:postStart にてコピーするように対応しました。
サービス設定
サービス(Ingress)はロードバランサーであり AWS でいう ELB(ALB) に当たります。
$ kubectl create -f app-service.yaml
apiVersion: v1
kind: Service
metadata:
name: app-lb
labels:
app: app
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: app
確認は
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
app-lb LoadBalancer 10.4.15.244 xx.xxx.xx.xxx 80:31485/TCP 18d
払い出された EXTERNAL-IP にアクセスしてサービス確認します。
DB接続がうまくいかない場合は?
StackDriverを確認します。
またはコマンドからもログを確認できます。
$ kubectl get pods # pod名を取得
$ kubectl logs -f <pod name> <container name> # cloudsql-proxy
pods&service の削除
$ kubectl delete -f app-deployment.yaml
$ kubectl delete -f app-lb.yaml
Cloud CDN
今回は静的ファイルのHLS動画ファイルは GCS に置いてますので、Cloud CDNで配信できるようにします。Cloud CDN は Load Balancing(Backend & Frontend)の組み合わせ設定が必要のようです。
バックエンド設定
Storageがオリジンになりますので、backend には Storage を指定します。
フロントエンド設定
設定すると、フロントエンドのIPアドレスが発行されます。
パスルール
パスルールは今回は特にルーティング不要なので、デフォルトのままにしてます。
下記は生成後の画面になります。
とりあえず、IPアドレスが払い出されたので、そのIPと bucket にある public ファイルにアクセスできます。キャッシュのインバリデーションは AWS CloudFront に比べると数分かかりますので、サービスによっては注意が必要です。また、Cloud Interconnect を使えば、Akamai
や Fastly
でも配信できそうなので、マルチCDNで配信するニーズがある場合は最適かもしれません。
https://cloud.google.com/interconnect/docs/how-to/cdn-interconnect
結論
あくまで私がGCSのメリットを実感したことは、
- プロジェクト間のスイッチが簡単(dev -> production, project A -> project B)
- Cloud Shell にて、疎通確認やコマンドにて動作確認が可能
- デベロッパーフレンドリーな管理画面(選択肢が最低限)
- AWS に比べると Role に悩まされることが少ない。
まだ理解できていないリソースやアプリ側で対応できていないところもあり課題が山積みですが、
パブリッククラウドの知識・経験があれば GCPリソースのセットアップはスムーズに進めることができました。
アプリ側も改善し、個々のリソースの最適化を行い、スムーズにGCP上で構築&サービス提供できるように進めればと思います。