🔖

Cloud Run マルチコンテナの構築

2023/12/14に公開

プレビュー版の頃に少し使っており、GAされたので投稿することにしました。
Cloud Runでサービスを作りながら説明します。
マルチコンテナの記法のみ知りたい方は構成ファイルの作成・デプロイから見てください。

・ドキュメント
https://cloud.google.com/blog/ja/products/serverless/cloud-run-now-supports-multi-container-deployments
https://cloud.google.com/run/docs/deploying?hl=ja#multicontainer-yaml

やること

APIにテキストファイルをPOSTするとテキストが音声に変換されるサービスを作成します。
単一のCloud RunインスタンスにREST APIと音声変換(VOICEVOX)のコンテナを構築します。

VOICEVOXについて

VOICEVOXとは無料で使えるテキスト読み上げソフトウェアです。
CUIやGUIソフトウェア、dockerイメージなど幅広いサービスから利用できます。
本記事ではdocker hubのイメージを利用します。

公式サイト
git hub
docker hub

事前準備

API有効化

以下のAPIを有効化します。
既にしている場合はスキップ。

Artifact Registry API
Cloud Resource Manager API
Cloud Run Admin API

cmd
# API 有効化
gcloud services enable artifactregistry.googleapis.com
gcloud services enable cloudresourcemanager.googleapis.com
gcloud services enable run.googleapis.com

サービスアカウントの作成

Cloud Runはサービスアカウントを設定しなければ、
デフォルトの Compute Engine サービス アカウント が使用されます。
サービスアカウントを設定する場合は以下のロールを設定してください。
Cloud Runのサービスアカウントについて

Artifact Registry 読み取り(roles/artifactregistry.reader)
Cloud Run デベロッパー(roles/run.developer)

GARリポジトリの作成

イメージの配置先を作成します。
REST API用のみで大丈夫です。
REPOSITORY_NAME, REGION, DESCRIPTIONは適宜編集してください。

# リポジトリ作成
gcloud artifacts repositories create REPOSITORY_NAME \
    --repository-format=docker \
    --location=REGION \
    --description="DESCRIPTION" \
    --async

ソースコードの作成

Dockerfile/
├ src/
│ └ access_voicevox.sh
├ cloudrun.yaml
├ Dockerfile
├ usr/
└ main.py

REST API

main.py
import uvicorn
from fastapi import FastAPI, File, UploadFile ,APIRouter
from fastapi.responses import FileResponse
from pathlib import Path
from datetime import datetime

import os
import logging
import subprocess
import shutil

app = FastAPI()


def process_cmd(cmd) :
	result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	if result.returncode == 0 :
		return result
	elif result.returncode == 1 :
		logging.warning(result)
		return result
	else :
		logging.warning(result)
		return result


def post_voicevox(src_path,filename,response_file):
    result=process_cmd(f"cd {src_path} ; sh access_voicevox.sh {filename} {response_file}")
    return result

@app.post("/")
def res_chat(file: UploadFile | None = None):
    if not file:
        return {"message": "No file sent"}

    else:
        current = Path()
        src_path= current / "src"
        # 受信ファイル配置
        filename=file.filename
        fileobj = file.file
        upload_dir = open(os.path.join(src_path, filename),'wb+')
        shutil.copyfileobj(fileobj, upload_dir)
        upload_dir.close()

        # Voicevox実行
        response_file="audio.wav"
        result = post_voicevox(src_path,filename,response_file)
        if not result.returncode == 0:
              return result

        response = FileResponse(
                path=src_path/response_file,
                filename=f"{response_file}"
                    )
        return response

speaker_idは声を担当するキャラクターのIDなので変更可能です。

speaker_id

https://unixo-lab.com/unity/voicevox.html#:~:text=て下さい。-,スピーカーID一覧,-VOICEVOX Ver. 0.14.5
VOICEVOX Ver. 0.14.5

ID 名前 スタイル
0 四国めたん あまあま
1 ずんだもん あまあま
2 四国めたん ノーマル
3 ずんだもん ノーマル
4 四国めたん セクシー
5 ずんだもん セクシー
6 四国めたん ツンツン
7 ずんだもん ツンツン
8 春日部つむぎ ノーマル
9 波音リツ ノーマル
10 雨晴はう ノーマル
11 玄野武宏 ノーマル
12 白上虎太郎 ふつう
13 青山龍星 ノーマル
14 冥鳴ひまり ノーマル
15 九州そら あまあま
16 九州そら ノーマル
17 九州そら セクシー
18 九州そら ツンツン
19 九州そら ささやき
20 もち子さん ノーマル
21 剣崎雌雄 ノーマル
22 ずんだもん ささやき
23 WhiteCUL ノーマル
24 WhiteCUL たのしい
25 WhiteCUL かなしい
26 WhiteCUL びえーん
27 後鬼 人間ver.
28 後鬼 ぬいぐるみver.
29 No.7 ノーマル
30 No.7 アナウンス
31 No.7 読み聞かせ
32 白上虎太郎 わーい
33 白上虎太郎 びくびく
34 白上虎太郎 おこ
35 白上虎太郎 びえーん
36 四国めたん ささやき
37 四国めたん ヒソヒソ
38 ずんだもん ヒソヒソ
39 玄野武宏 喜び
40 玄野武宏 ツンギレ
41 玄野武宏 悲しみ
42 ちび式じい ノーマル
43 櫻歌ミコ ノーマル
44 櫻歌ミコ 第二形態
45 櫻歌ミコ ロリ
46 小夜/SAYO ノーマル
47 ナースロボ_タイプT ノーマル
48 ナースロボ_タイプT 楽々
49 ナースロボ_タイプT 恐怖
50 ナースロボ_タイプT 内緒話
51 †聖騎士 紅桜† ノーマル
52 雀松朱司 ノーマル
53 麒ヶ島宗麟 ノーマル

voicevoxへの接続はlocalhost:50021にしています。
ポートは後述の構成ファイルで設定します。

create_wav.sh
#! /bin/sh
filename=$1
response_file=$2

speaker_id=8 

curl -s \
    -X POST \
    "localhost:50021/audio_query?speaker=${speaker_id}"\
    --get --data-urlencode text@${filename} \
    > query.json

curl -s \
    -H "Content-Type: application/json" \
    -X POST \
    -d @query.json \
    "localhost:50021/synthesis?speaker=${speaker_id}" \
    > ${response_file}

Dockerfile

Dockerfile
FROM python:3.10-alpine

WORKDIR /mnt/

COPY requeirment.txt /mnt/

RUN apk add curl
RUN pip install \
    -U pip \
    -r requeirment.txt

COPY . /mnt/

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
requeirment.txt
uvicorn
fastapi[all]

構成ファイルの作成・デプロイ

https://cloud.google.com/run/docs/reference/yaml/v1
https://cloud.google.com/run/docs/deploying?hl=ja#multicontainer-yaml
を参考に構成ファイルの作成とデプロイをします。

SERVICE_NAME, SERVICE_ACCOUNT, IMAGE_NAME, TAGは適宜編集してください。

cloudrun.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
  name: SERVICE_NAME
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/execution-environment: gen1
    spec:
      serviceAccountName: SERVICE_ACCOUNT
      containers:
      - image: IMAGE_NAME:TAG
        name: main
        ports:
          - containerPort: 80
      - image: voicevox/voicevox_engine:cpu-ubuntu20.04-latest
        name: voicevox
        startupProbe:
          tcpSocket:
            port: 50021
          timeoutSeconds: 120
          periodSeconds: 240
        resources:
          limits:
            cpu : "2"
            memory: 2Gi

cloudrun.yamlが存在するディレクトリ内から以下のコマンドでデプロイします。

cmd
#Deploy Cloud Run
gcloud run services replace cloudrun.yaml --region asia-northeast1

Cloud Runのコンソールからに移動しサービスが作成されているのを確認。

2つのコンテナで構成されていれば成功です。

テスト

セキュリティから認証設定を変更、サービスのURLをコピーします。

以下の音声に変換するテキストを用意します。
※文言が長いとVOICEVOXコンテナが落ちる場合があります

text.txt
テストです

以下のコマンドでREST APIにPOSTします。
https://URLはコピーしたURLに変更してください。

cmd
curl -X 'POST' \
  'https://URL' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@text.txt;type=text/plain' > result.wav

出力した音声ファイル(result.wav)が再生できれば成功です。
以上で完了です。

問題点

・GARイメージの更新・反映について
gcloud run services replaceはyamlに変更が無ければ再デプロイされません。
そのためGARイメージを更新してもCloud Runのイメージ参照先が更新されずサービスに反映されませんでした。
原因はCloud Runのイメージ参照方法がタグではなくダイジェストを参照しているためです。
私はymalを一部編集して再デプロイしています。
(フラグなどで強制的に再デプロイできるかも)

Discussion