G-gen 又吉です。今回は Cloud Run jobs を用いて、FTP サーバから Cloud Storage にファイル転送する仕組みを作成していきます。
概要
データ分析パイプラインなどで、データソースが FTP サーバ上に存在する時、FTP サーバからクラウド上のデータレイクにデータ転送が必要なケースもあるかと思います。 今回は、先日 (2023 年 4 月) GA した Cloud Run jobs を用いて、FTP サーバから Cloud Storage にファイル転送する仕組みを作成していきたいと思います。
Cloud Run jobs は Cloud Scheduler や Cloud Workflows と連携することでスケジュール実行することも可能ですが、今回は検証のため gcloud コマンドを用いて手動実行していきます。
Cloud Run jobs の詳細については、以下の記事をご参照下さい。
事前準備
開発環境の準備
ディレクトリ構成
開発環境には Cloud Shell を使用します。Cloud Shell を起動したら、以下のディレクトリ構成で各ファイルを作成して下さい。
get_file_from_ftp_server
|-- Dockerfile
|-- main.py
`-- requirements.txt
Dockerfile
# Pythonイメージを取得 FROM python:3.10 # ローカルコードをコンテナイメージにコピー ENV APP_HOME /app WORKDIR $APP_HOME COPY . ./ # 依存関係のインストール RUN pip install --no-cache-dir -r requirements.txt # コンテナの起動時の実行コマンド CMD ["/usr/local/bin/python3", "main.py"]
main.py
main.py には以下を記述して下さい。 使用したライブラリについて、FTP 接続には ftplib を、Cloud Storage バケットへのアップロードには、Python Client for Google Cloud Storage を使用しました。
from ftplib import FTP import os from google.cloud import storage FTP_SERVER_IP = os.environ.get("FTP_SERVER_IP") FTP_USER = os.environ.get("FTP_USER") FTP_PASSWD = os.environ.get("FTP_PASSWD") FILE_NAME = os.environ.get("FILE_NAME") BUCKET_NAME = os.environ.get("BUCKET_NAME") if __name__ == "__main__": try: # FTP サーバへ接続 ftp = FTP(host=FTP_SERVER_IP, user=FTP_USER, passwd=FTP_PASSWD) # パッシブモードを有効 ftp.set_pasv(True) except Exception as e: print("An error occurred connecting to the ftp server:", str(e)) raise else: try: # FTP サーバからファイル取得 filename = FILE_NAME ftp.retrbinary("RETR " + filename, open(filename, "wb").write) ftp.quit() except Exception as e: print("An error occurred when retrieving files from the ftp server:", str(e)) raise else: try: # Cloud Storage へアップロード storage_client = storage.Client() bucket = storage_client.bucket(BUCKET_NAME) blob = bucket.blob(filename) blob.upload_from_filename(filename) except Exception as e: print("An error occurred while uploading a file to Cloud Storage:", str(e)) raise
requirements.txt
google-cloud-storage==2.9.0
使用するリソースの作成
Cloud Shell で get_file_from_ftp_server のディレクトリ階層に移動したら、以下コマンドを順次実行します。
# 環境変数の設定 export PROJECT_ID={プロジェクト ID を入力} export PROJECT_NAME={プロジェクト名を入力} export BILLING_ACCOUNT_ID={請求先アカウント ID を入力} export BUCKET_NAME={取得するファイルの格納先バケット名を入力} export FTP_USER=ftpuser export FTP_SERVER_PW=1234 export FILE_NAME=sample.txt # プロジェクト作成 gcloud projects create ${PROJECT_ID} --name=${PROJECT_NAME} # プロジェクト設定の変更 gcloud config set project ${PROJECT_ID} # 請求先アカウントの紐づけ gcloud beta billing projects link ${PROJECT_ID} \ --billing-account=${BILLING_ACCOUNT_ID} # API の有効化 gcloud services enable compute.googleapis.com \ secretmanager.googleapis.com \ run.googleapis.com \ artifactregistry.googleapis.com \ cloudbuild.googleapis.com # FW の作成 gcloud compute firewall-rules create ftp-rule \ --allow tcp:20-21,tcp:40000-45000 # VM の作成 gcloud compute instances create "ftp-server" \ --zone="us-central1-a" \ --machine-type="e2-micro" \ --image-family="debian-11" \ --image-project="debian-cloud" \ --boot-disk-size="10" \ --boot-disk-type="pd-standard" # バケットの作成 gcloud storage buckets create gs://${BUCKET_NAME} #シークレットの作成 gcloud secrets create my_password --replication-policy="automatic" # シークレットにバージョンの追加 echo -n ${FTP_SERVER_PW} | gcloud secrets versions add my_password --data-file=- # プロジェクト番号を環境変数に追加 export PROJECT_NO=$(gcloud projects list --filter=${PROJECT_ID} --format="value(projectNumber)") # シークレットへ読み取り権限をCompute Engineのデフォルトサービスアカウントに付与 gcloud secrets add-iam-policy-binding my_password \ --member="serviceAccount:${PROJECT_NO}-compute@developer.gserviceaccount.com" \ --role="roles/secretmanager.secretAccessor" # アーティファクトリポジトリの作成 gcloud artifacts repositories create docker-repo-ftp \ --repository-format="docker" \ --location="us-central1" \ --description="Docker repository" # Dockerイメージのビルド gcloud builds submit --region="us-central1" \ --tag="us-central1-docker.pkg.dev/${PROJECT_ID}/docker-repo-ftp/jobs:latest" # VM の外部 IP を環境変数に追加 export FTP_SERVER_IP=$(gcloud compute instances describe ftp-server \ --zone="us-central1-a" \ --format="get(networkInterfaces[0].accessConfigs[0].natIP)") # Cloud Run jobsの作成 gcloud run jobs create ftp-job \ --image="us-central1-docker.pkg.dev/${PROJECT_ID}/docker-repo-ftp/jobs:latest" \ --region="us-central1" \ --set-env-vars="FTP_SERVER_IP=${FTP_SERVER_IP}" \ --set-env-vars="FTP_USER=${FTP_USER}" \ --set-env-vars="FILE_NAME=${FILE_NAME}" \ --set-env-vars="BUCKET_NAME=${BUCKET_NAME}" \ --set-secrets="FTP_PASSWD=my_password:latest"
FTP サーバの初期設定
先程作成した ftp-server VM に SSH でログインし、FTP サーバの初期設定を行っていきます。
gcloud compute ssh ftp-server --zone="us-central1-a" --project=${PROJECT_ID}
ターミナルに ユーザー名@ホスト名:~$
が表示されたらログイン成功です。
はじめに、OS のバージョンを確認します。
# OS のバージョン確認 matayuuu@ftp-server:~$ lsb_release No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 11 (bullseye) Release: 11 Codename: bullseye
次に、以下コマンドを実行します。
# パッケージのアップデート sudo apt-get update # FTPサーバーをインストール sudo apt-get install vsftpd # VM の外部 IP を確認 (後ほど使用するので出力された IP アドレスをメモしておく) curl -s ifconfig.me # vsftpd 設定ファイルを編集 sudo vi /etc/vsftpd.conf
vsftpd 設定ファイルの中身を以下のように加筆修正します。
- 既存の設定を修正
- listen=NO → listen=YES
- listen_ipc6=YES → listen_ipc6=NO
- 新規の設定を追加 (最後の行に追記)
- pasv_enable=YES
- pasv_min_port=40000
- pasv_max_port=45000
- pasv_address=${先程メモしたVMの外部IP}
1. 既存の設定を修正
では、FTP サーバがスタンドアロンモードで動作するよう、また IPv4 アドレスのみを許可するように設定しています。
2. 新規の設定を追加
では、FTP サーバがパッシブモードで動作するよう、また パッシブモード時に使用するポート範囲と FTP 接続を確立する IP アドレスを設定しています。
続けて以下のコマンドを実行します。
# vsftpdを再起動 sudo systemctl restart vsftpd # フォルダの作成 sudo mkdir /home/ftpuser # ファイルを作成 echo "Hello, FTP!" | sudo tee /home/ftpuser/sample.txt # ユーザーの作成(検証のためパスワードは「1234」と簡潔なものとし、その他はデフォルト設定) sudo adduser ftpuser # ユーザーをFTPグループに追加 sudo usermod -aG ftp ftpuser # ftpuserディレクトリの所有者をftpuser、グループをftpに変更 sudo chown ftpuser:ftp /home/ftpuser # ftpuserディレクトリの書き込み権限を全削除 sudo chmod a-w /home/ftpuser # ログアウト exit
動作確認
Cloud Run jobs の実行
以下のコマンドで Cloud Run jobs を手動実行します。
gcloud run jobs execute ftp-job \ --region="us-central1"
Cloud Run jobs のコンソール画面からジョブの実行結果を確認できます。
Cloud Storage に FTP サーバから取得した sample.txt も保存されていることが確認できました。
リソースの削除
以下のコマンドを実行し、検証で作成したプロジェクトを削除します。
gcloud projects delete ${PROJECT_ID}
本番運用に向けての考慮事項
接続方式
IP アドレス
今回は FTP サーバに外部 IP を付与しすべてのソース元 IP アドレスを許可しましたが、本番運用では①内部 IP アドレスのみを許可、もしくは②許可した外部 IP アドレスのみを許可する構成が想定されます。 必要に応じ、サーバレス VPC アクセス や Cloud NAT を用いて Cloud Run jobs から FTP サーバへの通信を制御していく必要があります。
参考 : Static outbound IP address
プロトコル
インターネット経由でファイル転送を行う際は、セキュリティの観点から FTP プロトコルより SFTP プロトコルを採用することが多いです。その際は、SFTP サーバの SSH 認証キーを Secret Manager に保存して管理する等の必要があります。
サービスアカウントの権限
今回は Cloud Run に Compute Engine のデフォルトサービスアカウントをアタッチしましたが、必要最低限のロールを付与したサービスアカウントを作成することが推奨されます。
又吉 佑樹(記事一覧)
クラウドソリューション部
はいさい、沖縄出身のクラウドエンジニア!
セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Champion Innovator (AI/ML)。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリスト。好きな分野は生成 AI。
Follow @matayuuuu