GoogleCloudの使い捨てインスタンスで文字コードを変換する


本記事は[GCP(Google Cloud Platform) Advent Calendar 2022 17日目](https://qiita.com/advent-calendar/2022/gcp)の記事です。
（※参加枠が空いていたのでもう１本書いてみました）

:::note warn
この記事では、（gcloudコマンドでインスタンス生成時に制限時間をつけるオプションについて）ベータ版の機能を使用しています。
:::

## 概要

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/801b5561-c7a7-312d-86c9-e6da6a9a3f55.png" width=60%>

Shift-JISのCSVファイルをBigQueryにアップロードする場合、UTF-8に変換する必要があります。容量が数GB程度ならローカルで変換しても良いですし、自動化したいならCloudFunctionsを使っても良いと思います。
しかし、数10GBのCSVファイルが大量に存在した場合はCloudFunctionsのメモリでは足りなくなってきます。

そこで今回は、数10GBのCSVファイルが大量に存在した場合でも、使い捨てのVMインスタンスを使って並列に文字コード変換処理できるような方法をまとめます。

#### 今回紹介するもの
  * Google Strage上に保存されたShift-JISのCSVファイルをUTF-8に変換して保存するコマンドライン。
    * 並列で実行できるように、１つのファイルについ１つのVMインスタンスを立てる。
    * VMインスタンスには制限時間を設け、時間が超過したら自動的に削除されるようにする。
    * ログ出力やエラー時のリトライなどは行わない。（複雑になりそうなので）

## 手順

  * まず、VMインスタンスを生成するコマンドを取得します。
  * その後に、コマンドを少し修正し起動時に文字コード変換処理を行うようにします。

#### VMインスタンス生成コマンドの取得

[VMインスタンス](https://console.cloud.google.com/compute/instances)ページに移動後、「＋インスタンスを作成」をクリックしてインスタンスの作成時の設定画面に入ります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/5cc3b49a-94c8-2435-78e3-0b188b389f0f.png" width=50%>

■名前、リージョンを設定
適当に名前とリージョンを設定します。リージョンはストレージと合わせおくと少し送受信が早くなると思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/614970a1-7bce-d248-277b-aec426b2dce7.png" width=50%>

■マシンの構成
デフォルトのままで良いです。早く処理したければ、もっと良いマシンを選んでも良いと思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/746149ef-fa92-b6d9-d04f-93a4ced37924.png" width=50%>

■ブートディスク
ブートディスクのサイズはデフォルト10GBなので、50GBとしました。10GBのファイルを変換したかったので、少し増やしました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/b96f0f0b-7039-e1e4-cdcf-9d4e599232cc.png" width=50%>

■IDとAPIへのアクセス
アクセススコープはAPIごとにアクセス権を設定を選択します。今回はCloud Storageから変換元ファイルのダウンロードと、変換後ファイルのアップロードを行うため、ストレージは「読み取り/書き込み」に変更します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/c5cdd876-3f3b-8973-95d0-933054438cef.png" width=50%>

■可用性ポリシー
「詳細オプション＞管理」を選択し以下の通り設定します。
  * VMプロビジョニングモデル：スポット
  * 「VMの制限時間を設定する」にチェックを入れる
  * 制限時間：0.25
  * VMの終了時：削除

スポットを選択することで料金が安くなります。また、制限時間を設定して、時間経過後自動的に削除するようにしておくことで、インスタンスの消し忘れによる過剰課金を防げます。（処理完了後にインスタンスを消去するコマンドを自動的に実行させても良いのですが、処理途中でエラーが発生した場合にも確実に消去させることが結構めんどくさいので、個人的にですが制限時間のおかげで気軽に使い捨てインスタンスを作れるようになったと思います。）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/137e916b-5722-7079-10c8-711792a812e0.png" width=50%>

■同等のコマンドライン
画面最下部の「同等のコマンドライン」をクリックしてgcloudでのコマンドを取得する。
これにて、VMインスタンス生成用のコマンドを取得しました。（ただ、このままでは動かないです。22年12月現在）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/01a25d34-b20d-1dbe-63ae-da75c9771188.png" width=50%>


#### コマンドの修正

上記で取得したコマンドに以下2つの項目を修正します。

  * `gcloud`の後に`beta`をつける
    *  22年12月時点において、インスタンス生成における制限時間オプション(`max-run-duration`)はベータ版でしか提供されていないようです。
  * スタートアップスクリプトをつける
    * `--metadata=startup-script`に、ダウンロード・文字コード変換・アップロードのスクリプトを記入します。
    * `<src_uri>`,`<dst_uri>`にそれぞれ変換元、変換先のgsutil URIを設定しまず。
    * 文字コード変換には`iconv`を使用します。
    
```bash
gcloud beta compute instances create <インスタンス名> \
  --project=<project> \
  --zone=asia-northeast1-b  \
  --machine-type=e2-medium  \
  --network-interface=network-tier=PREMIUM,subnet=default  \
  --no-restart-on-failure  \
  --maintenance-policy=TERMINATE  \
  --provisioning-model=SPOT  \
  --instance-termination-action=DELETE \
  --max-run-duration=90s  \
  --service-account=<service-account> \
  --scopes=https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/trace.append,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/devstorage.read_write  \
  --create-disk=auto-delete=yes,boot=yes,device-name=instance-1,image=projects/debian-cloud/global/images/debian-11-bullseye-v20221206,mode=rw,size=50,type=projects/<project>/zones/asia-northeast1-b/diskTypes/pd-balanced  \
  --no-shielded-secure-boot  \
  --shielded-vtpm  \
  --shielded-integrity-monitoring  \
  --reservation-affinity=any \
  --metadata=startup-script='#! /bin/bash
    gsutil cp <src_uri> tmp_cp932.csv
    iconv -f sjis -t utf-8 tmp_cp932.csv > tmp_utf8.csv
    gsutil cp tmp_utf8.csv <dst_uri>
  '
```

## 検証

#### 検証用ファイルの作成
pythonでShift-JISのCSVファイルを生成してテストします。以下のスクリプトで生成されたcsvファイルは11.1GBあります。作成できたら、Cloud Storage上の適当な場所に保存します。
``` python
import numpy as np
import pandas as pd
n = 300_000_000
pd.DataFrame({
    'id':np.arange(n),
    '郵便番号':'100-0001',
    '住所':'東京都千代田区千代田'
}).to_csv('address_cp932.csv', encoding='cp932', index=None)
```

#### 実行
コマンドを実行します。
```bash
gcloud beta compute instances create <vmname> \
  --zone=asia-northeast1-b --machine-type=e2-medium  \
  --maintenance-policy=TERMINATE --provisioning-model=SPOT  \
  --instance-termination-action=DELETE --max-run-duration=900s  \
  <略>
  --metadata=startup-script='#! /bin/bash
    gsutil cp gs://<bucket_name>/address_cp932.csv tmp_cp932.csv
    iconv -f sjis -t utf-8 tmp_cp932.csv > tmp_utf8.csv
    gsutil cp tmp_utf8.csv gs://<bucket_name>/address_utf8.csv
  '
```

ネットワークトラフィックをみると10分程度で完了していることがわかります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/beec550d-9c75-b4c3-3c33-bf4995d50d3c.png" width=100%>

## まとめ

GoogleCloudの使い捨てインスタンスで文字コードを変換する方法を紹介しました。
現在、gcloudで制限時間を設定するオプションはベータ版の機能となっていますが、一刻もはやく大量のCSVファイルをBigQueryに入れる必要があったため、同じ課題を抱えている人はいるのではないかと思い共有しました。

ちなみに、大量にVMインスタンスを生成用とした所、[外部IPアドレスの割当上限](https://cloud.google.com/compute/resource-usage#external_ip_addresses)でエラーとなりました。私の環境では、ひとつのリージョンにつき64件が上限だったようです。そのため、上限を超えるインスタンスを作成したかったので、リージョンを変えて対処しました。（上限を変更してもらうようにリクエストを出しても良かったと思います。）