次のような条件でRuby on Railsアプリケーションを運用することにした。
- プラットフォームは Google Cloud Platform (GCP)。
- 負荷状況により自動スケーリング(auto scaling)を行うためマネージドインスタンスグループを利用。
- データベースは Google Cloud SQL 上の PostgreSQL インスタンスを利用。
セットアップの過程で少し困ったのが、PostgreSQL インスタンスに接続できるIPアドレスのリスト(ホワイトリスト)をどう管理するか、という点である。
なぜ困るか。次の3点に集約される。
- マネージドインスタンスグループに属する VM インスタンスと PostgreSQL インスタンスは別のネットワークにある。
- 自動スケーリングによって起動される VM インスタンスの IP アドレスは事前にわからない。
- 自動スケーリングによって起動された VM インスタンスに割り当てられた IP アドレスは、インスタンス停止後に他のインスタンスにより再利用される。
アプリケーションサーバーと同じネットワーク内に PostgreSQL サーバーを自前で立てて動かす場合、そのネットワーク(例えば、192.168.0.0/24
)を pg_hba.conf
に記載してやればいいのだが、その方法は使えない。
VM インスタンスの起動時にその IP アドレスをホワイトリストに登録し、停止時に抹消する必要がある。
この状況を打開する方法はふたつある。ひとつは、VM gcloud
コマンドで VM インスタンスの起動時にその IP アドレスをホワイトリストに登録し、停止時に抹消するという方法、もうひとつは、Cloud SQL Proxyを利用する方法である。
今回は、前者の方法について解説しよう。
前提条件
対象となる GCP プロジェクトに関して、以下の準備作業を済ませる。
- GCP コンソールの[APIとサービス]→[ライブラリ]から「Cloud SQL Admin API」が有効化してある。
- 「Cloud SQL Admin」権限を持つ GCP のサービスアカウントを作成してある。
- サーバー OS は Ubuntu 18.04
- Ruby 2.4 以上がインストール済み
- 上記サービスアカウントの資格情報(JSONデータ)を
/root/gcp_sa_credentials.json
に保存してある。
VM インスタンスの起動・停止時に実行する Ruby スクリプトの作成
/root/bin/
ディレクトリに次のような内容のファイル vm-instance-startup.rb
を作成する。ただし、5 行目の pg-instance-0
の部分は、自分の Google Cloud SQL インスタンスの名前(id)で置き換えること。
#!/usr/bin/env ruby require "json" SQL_INSTANCE_ID = "pg-instalnce-0" GCLOUD_COMMAND = "/snap/bin/gcloud sql instances" KEY_FILE_PATH = "/root/gcp_sa_credentials.json" system("/snap/bin/gcloud auth activate-service-account --key-file=#{KEY_FILE_PATH}") api_url = "http://metadata.google.internal/computeMetadata/v1" + "/instance/network-interfaces/0/access-configs/0/external-ip" external_ip = `/usr/bin/curl -s #{api_url} -H "Metadata-Flavor: Google"` sql_instance_description = `#{GCLOUD_COMMAND} describe #{SQL_INSTANCE_ID} --format=json` description_hash = JSON.parse(sql_instance_description) networks = description_hash["settings"]["ipConfiguration"]["authorizedNetworks"].map do |e| e["value"] end unless networks.include?(external_ip) networks << external_ip joined = networks.join(",") system("#{GCLOUD_COMMAND} patch #{SQL_INSTANCE_ID} --quiet --authorized-networks=#{joined}") end
また、同じディレクトリに次のような内容のファイル vm-instance-shutdown.rb
を作成する。
#!/usr/bin/env ruby require "json" SQL_INSTANCE_ID = "pg-00" GCLOUD_COMMAND = "/snap/bin/gcloud sql instances" KEY_FILE_PATH = "/root/gcp_sa_credentials.json" system("/snap/bin/gcloud auth activate-service-account --key-file=#{KEY_FILE_PATH}") api_url = "http://metadata.google.internal/computeMetadata/v1" + "/instance/network-interfaces/0/access-configs/0/external-ip" external_ip = `/usr/bin/curl -s #{api_url} -H "Metadata-Flavor: Google"` sql_instance_description = `#{GCLOUD_COMMAND} describe #{SQL_INSTANCE_ID} --format=json` description_hash = JSON.parse(sql_instance_description) networks = description_hash["settings"]["ipConfiguration"]["authorizedNetworks"].map do |e| e["value"] end if networks.include?(external_ip) networks.delete(external_ip) joined = networks.join(",") system("#{GCLOUD_COMMAND} patch #{SQL_INSTANCE_ID} --quiet --authorized-networks=#{joined}") end
ふたつの Ruby スクリプトの内容はほぼ同じである。末尾の6行だけが異なっている。
そして、これらの Ruby ファイルの実行可能フラグを立てておく。
$ chmod u+x /root/bin/*.rb
Systemdの設定ファイルを作成
ディレクトリ /etc/systemd/system/
に whitelist-registry.service
という名前のファイルを次のような内容で作成する(名前は何でもよい)。ただし、このファイルには実行可能フラグを立てないこと。
[Unit] Description=Google Cloud SQL Whitelist Registry [Service] Type=oneshot ExecStart=/root/bin/vm-instance-startup.rb ExecStop=/root/bin/vm-instance-shutdown.rb RemainAfterExit=yes [Install] WantedBy=multi-user.target
そして、このサービスを有効化する。
$ sudo systemctl enable whitelist-registry.service
動作確認
サービスの起動を試す。
$ sudo systemctl start whitelist-registry.service
GCP コンソールで Cloud SQL インスタンスの「承認」タブで、この VM インスタンスの IP アドレスが「承認済みネットワーク」に加わっていれば OK である。
サービスの停止を試す。
$ sudo systemctl stop whitelist-registry.service
GCP コンソールで Cloud SQL インスタンスの「承認」タブで、この VM インスタンスの IP アドレスが「承認済みネットワーク」から消えていれば OK である。
最後に VM インスタンス自体を再起動したり、停止したりして、動作確認する。
考察
今回解説した方法の懸念点は、Systemd の停止スクリプトが実行されないまま VM インスタンスが異常終了したときに、その IP アドレスがホワイトリストに残ってしまう、ということである。データベース自体にパスワードが設定されていればそれほど危険ではないものの、気持ち悪さは残る。
Cloud SQL Proxy を使えばこの懸念は解消されるだろうが、正直に言えば、私自身がまだ試せていない。近いうちにレポートを書きたいと思う。