GCP Cloud RunからOCI Always Freeに移行してサービス費用を$0にした話
GCP無料トライアルの期限($300クレジット)が迫ってきた。
使っていたのはCloud Run 2サービス。FX Bot関連のAPI(Python/FastAPI)と市場分析ダッシュボード(Streamlit)だ。トライアル終了後はそのまま課金が発生するので、期限前に手を打つことにした。
選んだ移行先は OCI Always Free。VM.Standard.E2.1.Microが1テナントにつき2台まで永久無料で使える。すでに1台目はFX自動売買Bot用に使っており、2台目が空いていた。今回はこれを活用する。
移行前の構成
GCP (sns-bot-ueno プロジェクト)
├── Cloud Run: sns-bot (Python/FastAPI) ← us-central1
│ └── Cloud Scheduler 13ジョブが呼び出し
GCP (stock-finance-ueno プロジェクト)
└── Cloud Run: finance-dashboard (Streamlit) ← asia-northeast1
Cloud Runは便利だが、トライアル終了後は「使った分だけ課金」になる。稼働率が低いサービスなら余計コストがかかることもある。
移行後の構成
OCI Osaka (138.2.x.x) — Always Free
├── Docker: sns-bot (8081番ポート)
├── Docker: finance-dashboard (8082番ポート)
└── nginx: パスベース振り分け
├── /post/, /note/, /report/, /pdca/ → sns-bot
└── / → finance-dashboard
月額コスト: $0(Always Free永久無料)
Step 1: OCI 2台目インスタンスを作成する
1台目のインスタンス(autotrade用)と同じテナントに、2台目を追加で作る。
OCI CLIが使えるならコマンド一発だ。事前に以下の情報を控えておく。
- テナントのOCID(コンソール → プロファイル → テナンシ)
- ホームリージョン(例: ap-osaka-1)
- 作成済みVCNのサブネットOCID
- SSH公開鍵ファイルのパス
oci compute instance launch \
--compartment-id <テナントOCID> \
--availability-domain <AD名> \
--shape VM.Standard.E2.1.Micro \
--display-name sns-finance-bot \
--image-id <Ubuntu 22.04のOCID> \
--subnet-id <サブネットOCID> \
--ssh-authorized-keys-file ~/.ssh/id_ed25519.pub \
--assign-public-ip true
数分後にパブリックIPが払い出される。
初期セットアップ
ssh ubuntu@<新インスタンスIP>
# Docker インストール
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
# docker-compose v1 インストール(Ubuntu 22.04標準)
sudo apt-get install -y docker-compose
# nginx インストール
sudo apt-get install -y nginx
# 再ログイン(dockerグループ反映)
exit
Step 2: サービスアカウントキーを準備する
Cloud RunはGCPのIAMで認証していたが、OCIからはサービスアカウントキー(JSONファイル)で認証する。
GCPコンソール → IAM → サービスアカウントから、専用SAを作成して最小権限を付与する。
sns-bot用:
roles/datastore.user(Firestore読み書き)
finance-dashboard用:
roles/storage.objectViewer(GCSバケット参照)
作成したキーファイルをOCIインスタンスに配置する。
# ローカルで作業
scp sa-sns.json ubuntu@<IP>:/tmp/
scp sa-finance.json ubuntu@<IP>:/tmp/
# OCI上で配置
ssh ubuntu@<IP>
sudo mkdir -p /opt/sns-bot /opt/finance
sudo mv /tmp/sa-sns.json /opt/sns-bot/sa.json
sudo mv /tmp/sa-finance.json /opt/finance/sa.json
sudo chmod 600 /opt/sns-bot/sa.json /opt/finance/sa.json
Step 3: .envファイルを作成する
Cloud RunではSecret Managerや環境変数を使っていた設定値を、.envファイルに移行する。
GCP Secret Managerに保存していた値は以下で取得できる。
gcloud secrets versions access latest --secret=<シークレット名> --project=<プロジェクトID>
取得した値を/opt/sns-bot/.envに書き出す。Firestore接続先、APIキー類などをまとめて記述する。
Step 4: Docker Composeで2サービスを起動する
/opt/sns-finance/docker-compose.ymlを作成する。
version: '3.8'
services:
sns-bot:
image: sns-bot:latest
container_name: sns-bot
restart: unless-stopped
env_file: /opt/sns-bot/.env
volumes:
- /opt/sns-bot/sa.json:/opt/sns-bot/sa.json:ro
ports:
- '8081:8080'
healthcheck:
test:
[
'CMD',
'python3',
'-c',
"import urllib.request; urllib.request.urlopen('http://localhost:8080/health')",
]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
finance-dashboard:
image: finance-dashboard:latest
container_name: finance-dashboard
restart: unless-stopped
env_file: /opt/finance/.env
volumes:
- /opt/finance/sa.json:/opt/finance/sa.json:ro
ports:
- '8082:8080'
healthcheck:
test:
[
'CMD',
'python3',
'-c',
"import urllib.request; urllib.request.urlopen('http://localhost:8080/health')",
]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
コンテナイメージはローカルでビルドしてOCIに転送する(またはDockerfileをOCI上に転送してビルド)。
# ローカルでビルド → tarで転送
docker build -t sns-bot:latest ./sns/
docker save sns-bot:latest | ssh ubuntu@<IP> "docker load"
# OCI上で起動
ssh ubuntu@<IP>
cd /opt/sns-finance
docker-compose up -d
docker-compose ps # State が Up (healthy) になればOK
Step 5: nginxでリバースプロキシを設定する
Cloud Runではパスごとに別サービスを呼び出す構成だったが、OCIではnginxで振り分ける。
/etc/nginx/sites-available/sns-financeを作成する。
upstream sns_bot {
server 127.0.0.1:8081;
}
upstream finance_dash {
server 127.0.0.1:8082;
}
server {
listen 80;
server_name _;
# sns-bot エンドポイント(Cloud Schedulerから呼ばれる)
location /post/ { proxy_pass http://sns_bot; proxy_read_timeout 120s; }
location /note/ { proxy_pass http://sns_bot; proxy_read_timeout 120s; }
location /health { proxy_pass http://sns_bot; }
location /analytics/ { proxy_pass http://sns_bot; proxy_read_timeout 60s; }
location /report/ { proxy_pass http://sns_bot; proxy_read_timeout 120s; }
location /pdca/ { proxy_pass http://sns_bot; proxy_read_timeout 120s; }
location /smoke { proxy_pass http://sns_bot; proxy_read_timeout 60s; }
# finance-dashboard(ブラウザからアクセス)
location / {
proxy_pass http://finance_dash;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
}
}
有効化して再起動する。
sudo ln -s /etc/nginx/sites-available/sns-finance /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
ハマりどころ3選
1. iptablesのデフォルトルール問題
OCI Ubuntu インスタンスはセキュリティリストで80番を開けても、OS内のiptablesがREJECT-allになっているため外部からアクセスできない。
# 確認
sudo iptables -L INPUT --line-numbers
# → 末尾に REJECT all -- anywhere anywhere reject-with icmp-host-prohibited がある
デフォルトのACCEPTルールの直後に追加する(末尾ではなく番号指定で挿入)。
sudo iptables -I INPUT 4 -p tcp --dport 80 -j ACCEPT
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save
2. Cloud SchedulerはHTTPSを強制する(認証ありの場合)
Cloud SchedulerでOIDC認証付きのジョブを更新してhttp://のURLに変えようとすると、以下のエラーが出る。
HttpRequest.url must start with 'https://' for request with HttpRequest.authorization_header
OCIのHTTPエンドポイントに向ける場合は、認証ヘッダーを削除してHTTPを使う。
gcloud scheduler jobs update http <ジョブ名> \
--location=<リージョン> \
--project=<プロジェクト> \
--uri="http://<OCI_IP>/post/auto" \
--clear-auth-token
13個のジョブを一括で更新するなら、スクリプトにまとめてしまうと楽だ。
3. 1GBのRAMにスワップが必須
VM.Standard.E2.1.MicroはRAM 1GBしかない。2つのコンテナを同居させると起動時のメモリスパイクでOOMになりやすい。
スワップを必ず設定する。
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
2GBあれば、今回の構成(FastAPI + Streamlit)で余裕がある。実際の稼働メモリはsns-botが約64MB、finance-dashboardが約100MBで、合計164MB程度だった。
Step 6: Cloud Schedulerの向き先を変更する
既存のジョブのURIをOCIのIPに変更する。Cloud Runへの向き先をまとめてOCIに切り替えれば移行完了だ。
# 確認コマンド(全ジョブのURI一覧)
gcloud scheduler jobs list --location=<リージョン> --project=<プロジェクト>
# 更新(1ジョブずつ)
gcloud scheduler jobs update http x-bot-morning \
--location=asia-northeast1 \
--project=sns-bot-ueno \
--uri="http://<OCI_IP>/post/auto" \
--clear-auth-token
更新後、テスト実行で疎通確認する。
gcloud scheduler jobs run smoke-test \
--location=asia-northeast1 \
--project=sns-bot-ueno
/healthエンドポイントが{"status":"ok"}を返せば移行成功だ。
移行後のコスト比較
| 項目 | 移行前(Cloud Run) | 移行後(OCI) |
|---|---|---|
| コンピュート | GCPクレジット消費 | $0(Always Free) |
| ネットワーク | GCPクレジット消費 | $0(Always Free) |
| Firestore | 無料枠(50K reads/日) | 無料枠(変わらず) |
| GCS | 無料枠(5GB) | 無料枠(変わらず) |
| Cloud Scheduler | 無料枠(3ジョブ/月無料、超過は$0.10/ジョブ) | 変わらず |
| 月額合計 | クレジット終了後に課金発生 | $0 |
GCPのFirestoreやGCS、Cloud Schedulerは無料枠内なので継続利用できる。Cloud Runだけをなくすことで、コストをゼロに抑えつつGCPのマネージドサービスのメリットも活かせる構成になった。
関連記事
まとめ
GCP → OCI の移行で得られたポイントをまとめると:
- コスト: Cloud Run課金 → $0(OCI Always Free)
- 作業時間: 1日(ハマりどころ込み)
- ダウンタイム: Cloud Schedulerの向き先変更は即時切り替え可能、実質ゼロ
- 運用負荷: Docker Composeとsystemdで自動起動を管理、Cloud Runと遜色ない
「月に数百円の節約」と思うかもしれないが、GCP無料トライアル終了後に何もしなければ課金が始まる。今回の移行で永久に$0のインフラを手に入れた。
副業でサービスを動かしているエンジニアにとって、OCI Always Freeは最強の選択肢の一つだ。GCPの便利なマネージドサービス(Firestore、GCS、Cloud Scheduler)はそのまま使いながら、コンピュートリソースだけをOCIに移す「ハイブリッド構成」も十分現実的な選択肢になる。