Cloud Functions第2世代を使ってみよう！

# はじめに

こんにちは、京セラコミュニケーションシステム　西田（@kccs_hiromi-nishida）です。
[前回の記事](https://qiita.com/kccs_hiromi-nishida/items/0a696d99b30ca864fe47 "ExcelをBigQueryに連携する処理をシームレスに実現してみた")でCloud Functionsの第1世代を使ってExcelをCSVに変換する処理を作成しました。

今回は、この第1世代で作成した処理を第2世代で作り直してみたいと思います。
第1世代のCloud Functionsを第2世代もリリースされたことだし使ってみたいな・・と思っている方や、第2世代ってまだ使ったことないな・・という方！

第1世代と第2世代の比較などもあわせて紹介しますので、ぜひ記事をご覧いただき作成にチャレンジしてみてください。

:::note warn
本記事は2023年5月ごろに作成しております。よって、引用している文章などはこの時点での最新となります。ご了承ください。
:::

## この記事の対象者

- Cloud Functions第2世代を使ってみたい方
- Cloud Functions第1世代と第2世代の作成方法の違いに興味がある方


## 第1世代と第2世代の違い

公式ドキュメントの比較表を確認してみましょう。

https://cloud.google.com/functions/docs/concepts/version-comparison?hl=ja#comparison-table

簡単にまとめると・・
* リクエストのタイムアウト時間がHTTPトリガーの場合、最大60分に延長
* CPU／メモリが第1世代の2倍に増加
* 同時実行数が最大1000件まで増加
* トラフィック分割のサポート
* EventarcのサポートやCloudEventsのサポートランタイム拡大により対応イベントソースが充実

とくにタイムアウト時間がHTTPトリガーの場合、60分に延長されたのは大きなポイントかなと思います。
実行時間が伸びたことにより、Cloud Functionsを使う場面が増えるのではないでしょうか。
（Pub/Sub経由のちょっと長めのバッチ処理もCloud Functions上でできそうですよね）

また、トラフィック分割のサポートにより、ABテストやカナリアリリースができるようになったのも、構築・運用される立場の方からみた場合、ポイントの1つかなと思います。

https://cloud.google.com/functions/docs/configuring/traffic-splitting?hl=ja

### 料金に関して

第1世代と第2世代の課金方法ですが、第2世代はCloud Runの料金が適用されるなど少し違いがあるので取り上げたいと思います。

#### 第1世代の料金

第1世代の料金は、公式ドキュメントにも記載があるように以下の要素で料金が決まります。
※下記以外にもイメージの保存料金がかかりますが、わずかな金額なのでここでは割愛します

<dl>
  <dt>呼び出し回数</dt>
  <dd>関数を実行した回数に応じた課金</dd>
  <dt>コンピューティング時間</dt>
  <dd>関数の呼び出しから完了までにかかった時間に応じた課金</dd>
  <dt>データ通信</dt>
  <dd>関数から外に転送されるデータのサイズによる課金</dd>
</dl>

https://cloud.google.com/functions/pricing?hl=ja

簡単にですが表にまとめてみました。
※コンピューティング時間は一般的なコストを記載しています
<table>
	<tbody>
        <thead>
            <tr>
                <th>課金方法</th> <th>料金</th> <th>無料枠</th>
            </tr>
        </thead>
		<tr>
			<td>呼び出し回数</td>
			<td>$0.4/100万回</td>
            <td>最初の200万回</td>
		</tr>
		<tr>
			<td rowspan="2">コンピューティング時間<br>※Tier1の場合</td>
			<td>$0.0000025/GB秒</td>
			<td>400,000 GB 秒</td>
		</tr>
		<tr>
			<td>$0.0000100/GHz秒</td>
			<td>200,000 GHz 秒</td>
		</tr>
		<tr>
			<td>データ通信</td>
			<td>$0.12/GB(送信・下り)</td>
            <td>5GBのデータ送信</td>
		</tr>
	</tbody>
</table>

料金に関しては、Google Cloud Pricing Calculatorで試算することができます。

https://cloud.google.com/products/calculator?hl=ja

このページから、CLOUD FUNCTIONSのアイコンを選択し、入力していくだけで試算が可能です。試しに以下条件で試算してみました。

* Location： Tokyo(asia-northeast1)
* Type： Memory 256MB CPU 400MHz
* Execution time per function(関数の実行時間)： 3000ms
* Invocations per month(関数の呼び出し回数/月)： 300万回

この条件だと、$39.03となりました。
<img width="300" alt="Google Cloud Pricing Calculator" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/bb428edd-1952-3c15-25e6-788b02a906c7.png">
<font color="DarkGray">_Google Cloud Pricing Calculator_</font>

#### 第2世代の料金

第2世代はプラットフォームにCloud Runを使用しているので、Cloud Runの料金が適用されます。
ですので、Cloud Runの課金方法を確認する必要があります。
※CPU、メモリコンピューティング時間はTier1の場合の料金

| 課金方法                   | 料金                 | 無料枠         |
|---------------------------|---------------------|---------------|
| CPUコンピューティング時間   | $0.00002400/vCPU秒   | 180,000/vCPU秒 |
| メモリコンピューティング時間 | $0.00000250/GiB 秒   | 360,000/GiB秒  |
| リクエスト回数              | $0.40/100万リクエスト | 200万リクエスト |

同じようにGoogle Cloud Pricing Calculatorで試算してみましょう。

* Location： Tokyo(asia-northeast1)
* Cpu：1
* Memory：256MB
* Execution time per request(実行時間)： 3000ms
* Requests per Month (呼び出し回数/月)： 300万回

この条件だと、$6.88となりました。
<img width="300" alt="Google Cloud Pricing Calculator" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/04d846a8-a8e0-8b5e-90e9-a45a58b5cdd3.png">
<font color="DarkGray">_Google Cloud Pricing Calculator_</font>

### 余談

Cloud Functions第2世代はプラットフォームがCloud Runなので、第1世代にくらべると性能は上がっていると言えます。
となると、Cloud RunとCloud Functionsどっち使えばいいの？となりませんか？
そんな方向け（？）に公式ブログに記事があったので紹介しておきます。

https://cloud.google.com/blog/ja/products/serverless/cloud-run-vs-cloud-functions-for-serverless

## さっそく作ってみよう

[前回の記事](https://qiita.com/kccs_hiromi-nishida/items/0a696d99b30ca864fe47 "ExcelをBigQueryに連携する処理をシームレスに実現してみた")と同様に、Cloud Storageへのファイルアップロードをトリガーに実行されるCloud Functionsを第2世代で作り直してみます。

:::note warn
使用画像について
特別な記載のない限り、画像はGoogle Cloud - Cloud Functions画面をキャプチャしたものとなります
:::

Cloud Functionsから[ファンクションを作成]を選択します。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/099c97f5-f8c9-6935-2393-a1e6bc94b280.png">

### Eventarcトリガーの作成

Cloud Functions第2世代では、Eventarcトリガーを使用することでサポートされているイベントタイプで関数をトリガーすることができます。

画像のように基本情報を設定してみてください。
入力し終わったら、[EVENTARCトリガーを追加]を選択します。

<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/ad1efee3-7af5-91fd-4186-3582f7ec0537.png">

以下のように設定をし、[トリガーを保存]を選択してください。
* トリガーのタイプ：Googleのソース
* イベントプロバイダ：Cloud Storage
* イベント：google.cloud.storage.object.v1.finalized
* バケット：Excelファイルをアップロードするバケットを選択

<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/56e0e607-1884-56cc-aede-9aad9e9ab61a.png">

※画像の赤枠のようにロール付与の警告が表示されている場合は、［付与］ボタンを選択することでロールを付与することができます。表示されている場合は付与してください。

Eventarcトリガーが追加されました。
[次へ]を選択し、コードの作成を行いましょう。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/a94fa76c-3c15-719b-9bc0-48517c771300.png">

### コードの作成

前回記事のコードをそのまま使います。（一部分修正する箇所はありますが）
ランタイム（Python 3.11）を選択し、必要なライブラリを定義します。
requirements.txtを選択し、以下を貼りつけてください。

```txt:requirements.txt
openpyxl==3.*
google-cloud-storage
```
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/3af5a64f-0fe6-e20a-2316-49246bcc2264.png">

次に処理をmain.pyに記述します。
以下のコードを貼り付け、エントリポイントを［conv_csv_2nd］に変更し、デプロイを選択してください。
※長いので折りたたんでいます。クリックすると展開します。

<details><summary>プログラムコード</summary><div>

```python:main.py
import functions_framework
import io
import openpyxl
import re
import csv
from google.cloud import storage as storage

@functions_framework.cloud_event
def conv_csv_2nd(cloud_event):
    
    data = cloud_event.data

    # トリガーとなったバケットを取得
    BUCKET_NAME = data['bucket']
    # CSVファイルを出力するバケット
    TARGET_BUCKET_NAME = "csv_output"
    # 対象のシート名
    TARGET_SHEET_NAME = "データ連携"
    
    OUT_NAME = 'merge.csv'
    client = storage.Client()

    # バケットの取得
    bucket = client.get_bucket(BUCKET_NAME)
    # バケット内のオブジェクト一覧を取得
    blobs = client.list_blobs(BUCKET_NAME)

    # 取得対象行（ヘッダーは不要なので2行目から）
    start_row = 2
    pattern = r".*\.(xlsx|xls)$"

    si = io.StringIO()
    writer = csv.writer(si, quoting=csv.QUOTE_ALL)

    for blob in blobs:
    # .xlsx,.xlsファイルだけを処理対象とする
        if re.match(pattern, blob.name, re.IGNORECASE):
            buffer = io.BytesIO()
            blob.download_to_file(buffer)
            wb = openpyxl.load_workbook(buffer)
            
            if TARGET_SHEET_NAME in wb.sheetnames:
                targetSheet = wb[TARGET_SHEET_NAME]
                
                # 対象シートの最大行数・最大列数を取得
                max_column = targetSheet.max_column
                max_row = targetSheet.max_row
                
                # 開始行～最大行までループしながら1～最大列数分の値を取得して書き込み
                for r in range(start_row, max_row+1):
                    row_data = []
                    for c in range(1, max_column+1):
                        row_data.append(targetSheet.cell(row=r, column=c).value)
                    writer.writerow(row_data)
            wb.close()

    # CSVアップロード対象のバケットにCSVファイルをアップロード
    bucket = client.get_bucket(TARGET_BUCKET_NAME)
    blob = bucket.blob(OUT_NAME)
    blob.upload_from_string(data=si.getvalue(), content_type='text/csv')
    client.close()
    si.close()
    return "OK"

```
</div></details>

<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/9265cbab-c23a-7aa0-5574-0df8a15f90dd.png">

デプロイが成功すると、緑色のチェックマークがつきます。

<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/b8776a7a-192f-d922-2629-242e437f1e20.png">

#### ソースコードの違い

第1世代と第2世代で、作成したコードにどのような違いがあるか紹介しておきます。

* import文の追加
以下の1文が追加となります。
CloudEvent 関数を使うときに必須のライブラリなので、おまじないのように入れておきます。

```python:main.py
import functions_framework
```

* 関数の引数の変更
第2世代は引数がcloud_eventになります。
```diff_python:main.py
+ def conv_csv_2nd(cloud_event):
- def conv_csv(event, context):
```

* eventからのデータ取得方法 
たとえば、eventからバケット名を取得する場合は以下のような記述になります。
※関数の引数であるcloud_event / eventから取得
```diff_python:main.py
# 第2世代
+ data = cloud_event.data
+ BUCKET_NAME = data['bucket']

# 第1世代
- BUCKET_NAME = event['bucket']
```

### 動作確認

動作確認は、以下の記事と手順が同じなので、ここでは割愛します。
以下の記事を見てください。

https://qiita.com/kccs_hiromi-nishida/items/0a696d99b30ca864fe47#%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86

### 実行時にエラーが出た場合

おそらく、ログにこのエラーが出ていると思います。
<img width="500" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/143f4171-1df0-cb60-b6a3-4125a361836e.png">
このエラーは認証のエラーの為、サービスアカウントに必要な権限が付与されているかを確認し、付与されていなければ付与してください。

**1.　サービスアカウントを確認する**

作成したCloud Functionsを開き、［詳細］を選択すると、使用しているサービスアカウントが表示されていますので、このサービスアカウントに対して権限を付与します。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/533bce8e-a540-3d56-80a8-77327cad0d38.png">

**2.　権限の付与**

［IAMと管理］→［IAM］を選択します。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/a56e4fa6-e3ff-f0a7-1730-89a8d7c1ae5d.png">
1で確認したサービスアカウントを探します。
認証エラーが出る場合は、この4つの権限が付与されているか確認してください。
付与されていないと思うので、足りない権限を付与します。右側の鉛筆のようなアイコンを選択してください。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/0c97900e-07ee-f5f2-97f0-ab4a8d6af68d.png">
画像のようにロールを追加し、保存を選択すれば完了です。
<img width="400" alt="Cloud Functions" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2752159/7f4cc9cf-cabb-8bf9-dfd5-13c1a7be7d09.png">

## 最後に

今回は第1世代と第2世代の違いや料金、具体的な関数の作成方法をご紹介しました。
まだまだ第1世代の関数が多いのが現状かなと思いますが、メリットも多いので第2世代を積極的に使っていきたいですね！（いつか第1世代がサポート終了になるかもしれないですし）