G-gen の出口です。当記事では、Google Kubernetes Engine(GKE)にデプロイした Web アプリケーション Ingress でインターネット公開する方法、またそのアプリに Cloud Armor ポリシーを設定して、アクセス元 IP アドレスを制限する方法を解説します。
概要
検証の概要
当記事で検証した構成は、以下のとおりです。
この構成では、GKE 上で動作するアプリケーションが、Firestore に保存されたユーザー情報を、入力されたユーザ ID に基づいて表示します。GKE クラスタに対しては、 Ingressリソースとして外部アプリケーションロードバランサを作成します。そのロードバランサに対して、Cloud Armor を用いて IP アドレスによるアクセス制限を適用します。
Google Kubernetes Engine とは
Google Kubernetes Engine(以下、GKE)は、Google Cloud(旧称 GCP)が提供する Kubernetes のマネージドサービスです。
以下の記事で GKE について解説していますので、ご参照ください。
Ingress とは
Ingress は、Kubernetes クラスタ外部からクラスタ内部への HTTP(S) アクセスを管理する Kubernetes の API オブジェクトです。この Ingress と Service を組み合わせることで、Service の背後にある Pod にデプロイされているアプリケーションへ外部からアクセスできるようになります。
- 参考 : Ingress | Kubernetes
GKE には、組み込みの GKE Ingress コントローラが用意されています。GKE で Ingress リソースを作成すると、このコントローラによってアプリケーションロードバランサが GKE クラスタ外部に自動的に構成されます。
Cloud Armor とは
Cloud Armor は、Google Cloud が提供するフルマネージドの WAF(Web Application Firewall)サービスです。Cloud Armor のセキュリティポリシーを作成してアプリケーションロードバランサに関連付けることで、アクセス制限をかけることができます。
以下の記事で Cloud Armor について解説していますので、ご参照ください。
Firestore の準備
データベースの作成
Firestore で (default)
データベースを作成します。
(default)
データベースは Firestore のデフォルトとなるデータベースで、Firestore クライアントライブラリや Google Cloud CLI から接続するときにデータベース ID を指定しない場合、自動的に (default)
データベースに接続されます。
gcloud firestore databases create \--database="(default)" \--location=asia-northeast1 \--type=firestore-native
データの準備
作成した (default)
データベースに、コレクション ID が users
で、age
, id
, name
という3つのフィールドを持っているドキュメントを追加します。
アプリケーションの準備
ソースコードの作成
GKE 上で動作するアプリケーションのソースコードを作成します。
ディレクトリ構成は以下の通りです。
. ├── main.py ├── requirements.txt ├── Dockerfile └── templates └── index.html
main.py
ソースコードは以下のとおりです。
PROJECT_ID
には、ご自身のプロジェクト ID を設定してください。
from flask import Flask, render_template, requestfrom google.cloud import firestorePROJECT_ID = "プロジェクト ID に置き換えてください"app = Flask(__name__)# Firestore インスタンス初期化db = firestore.Client(project=PROJECT_ID)COLLECTION_NAME = "users" # Firestore コレクション名ID_FIELD_NAME = "id" # Firestore IDフィールド名@app.route("/", methods=["GET", "POST"])def index():data = Nonemessage = Noneif request.method == "POST":try:user_id = request.form.get("user_id")if not user_id:message = "IDを入力してください。"return render_template('index.html', data=data, message=message)doc_ref = db.collection(COLLECTION_NAME).where(ID_FIELD_NAME, "==", user_id).get()if doc_ref:data = doc_ref[0].to_dict()else:message = "指定されたIDのデータは見つかりませんでした。"except Exception as e:message = f"エラーが発生しました:{str(e)}"return render_template("index.html", data=data, message=message)if __name__ == "__main__":app.run(host="0.0.0.0", port=8080, debug=True)
templates/index.html
ユーザ ID を入力するフォームが搭載されていて、そのユーザ ID を Firestore で検索して受け取った結果を表示する HTML ファイルを作成します。
<!DOCTYPE html><html><head><title>Firestore Viewer</title></head><body><h1>Firestore Data Viewer</h1><form method="POST"><label for="user_id">IDを入力:</label><input type="text" id="user_id" name="user_id"><button type="submit">検索</button></form><br>{% if data %}<h2>データ:</h2><p>名前: {{ data.name }}</p><p>年齢: {{ data.age }}</p>{% elif message %}<p>{{ message }}</p>{% endif %}</body></html>
requirements.txt
使用するライブラリを、以下の通りに定義します。
Flask==3.1.0 google-cloud-firestore==2.19.0
Dockerfile
GKE へデプロイするために Docker イメージを用意する必要があるため、Dockerfile を作成します。
FROM python:3.12-slim WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8080 CMD [ "python", "./main.py" ]
Artifact Registry の作成
Docker イメージを保存するための Artifact Registry 標準リポジトリを作成します。
以下のコマンドで、firestore-app
という名前のリポジトリを作成します。
gcloud artifacts repositories create firestore-app \--repository-format=docker \--location=asia-northeast1
Artifact Registry にアップロード
Cloud Build を利用して Docker イメージをビルドし、作成した Artifact Registry にプッシュします。
プロジェクト ID に置き換えてください の部分を、ご自身のプロジェクト ID に置き換えて、Dockerfile を作成したディレクトリで以下のコマンドを実行します。
PROJECT="プロジェクト ID に置き換えてください"gcloud builds submit --tag asia-northeast1-docker.pkg.dev/${PROJECT}/firestore-app/firestore-app
GKE クラスタの設定
GKE クラスタの作成
以下のコマンドで、firestore-app
という名前の Autopilot モードの GKE クラスタを作成します。
Autopilot モードではノード、スケーリング、セキュリティといったクラスタ構成が Google Cloud によって管理されます。GKE のベスト プラクティスと推奨事項を遵守されたクラスタ構成が提供されます。
gcloud container clusters create-auto firestore-app \--location=asia-northeast1 \--project=${PROJECT}
サービスアカウントの設定
今回の構成では、GKE クラスタ上で実行されるワークロードから Firestore へのアクセスが必要になります。
GKE Autopilot モード では Workload Identity Federation が常に有効化されており、GKE の ServiceAccount リソースを IAM プリンシパルとして直接関連付けることができます。この機能を利用して、Firestore へのアクセス権を付与します。
Workload Identity Federation についてや、その他の認証方法については以下の記事で解説しておりますので、ご参照ください。
ServiceAccount リソースの作成
以下のコマンドで、Kubernetes ServiceAccout リソース firestore-app
を作成します。
kubectl create serviceaccount firestore-app
権限の付与
作成した ServiceAccout を参照する IAM ポリシーを作成します。
以下のコマンドで、作成した ServiceAccout に対して Cloud Datastore 閲覧者 (roles/datastore.viewer
) ロールを付与します。
プロジェクト番号に置き換えてください の部分を、ご自身のプロジェクト番号に置き換えてください。
PROJECT_NUMBER="プロジェクト番号に置き換えてください"gcloud projects add-iam-policy-binding projects/${PROJECT} \--role=roles/datastore.viewer \--member=principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT}.svc.id.goog/subject/ns/default/sa/firestore-app
GKE クラスタにアプリケーションをデプロイ
以前の手順で Artifact Registry にアップロードしたコンテナイメージを使用して、アプリケーションを GKE クラスタに登録します。
以下のマニフェストファイル deployment.yaml
を GKE クラスタに適用し、Deployment リソースを作成します。
# deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:name: firestore-app-deploymentnamespace: defaultlabels:app: firestore-appspec:replicas: 3selector:matchLabels:app: firestore-apptemplate:metadata:labels:app: firestore-appspec:serviceAccountName: firestore-app # 作成した ServiceAccout の名前containers:- name: firestore-appimage: asia-northeast1-docker.pkg.dev/(プロジェクトID)/firestore-app/firestore-app:latestports:- containerPort: 8080resources:requests:cpu: 50mmemory: 52Mi
続いて、アプリケーションを公開するための Service を GKE クラスタに登録します。
以下のマニフェストファイル service.yaml
を GKE クラスタに適用し、Service リソースを作成します。
# service.yamlapiVersion: v1kind: Servicemetadata:name: firestore-app-servicenamespace: defaultannotations:cloud.google.com/neg: '{"ingress": true}'spec:selector:app: firestore-appports:- port: 8080protocol: TCPtargetPort: 8080type: NodePort
Ingress を設定してアプリケーションを公開
最初に Cloud Armor のセキュリティポリシーを構成せずに、アクセス元の制限を設定してしない状態で Ingress リソースを作成してみます。
以下のマニフェストファイル ingress.yaml
を GKE クラスタに適用します。
# ingress.yamlapiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: firestore-app-ingressnamespace: defaultannotations:kubernetes.io/ingress.class: "gce"spec:defaultBackend:service:name: firestore-app-serviceport:number: 8080
今回の検証では、Ingress によって作成されるアプリケーションロードバランサは HTTP によってアクセスされます。
HTTPS を使用する場合は、SSL/TLS 証明書を Ingress と関連付ける必要があります。詳細は、以下の記事をご参照ください。
Ingress によって作成されたアプリケーションロードバランサの外部 IP アドレスを確認し、アクセスすると Firestore Viewer
というタイトルのアプリケーションの画面が表示されます。
Firestore に追加したユーザ ID を入力すると、そのユーザ ID に対応するユーザ情報が表示されることが確認できます。
Cloud Armor による アクセス制限を適用
Cloud Armor のセキュリティポリシーを設定
アクセス元の IP アドレスを制限する Cloud Armor のセキュリティポリシーを作成します。
まず以下のコマンドで、firestore-app-policy
という名前のセキュリティポリシーを作成します。
gcloud compute security-policies create firestore-app-policy
次に、作成したセキュリティポリシーのデフォルトのルール (優先度 2,147,483,647) を更新し、すべてのアクセス元からのアクセスを制限します。
gcloud compute security-policies rules update 2147483647 \--security-policy=firestore-app-policy \--action="deny-403"
特定の IP アドレス範囲からのアクセスを許可するルールを作成します。
ルールの優先度はデフォルトのルールよりも高く設定します。以下の例では、優先度 1000 で設定しています。
gcloud compute security-policies rules create 1000 \--security-policy=firestore-app-policy \--src-ip-ranges="<許可する IP アドレス範囲>" \--action="allow"
BackendConfig を作成
Ingress と Cloud Armor のセキュリティポリシーを関連付けるために、GKE のカスタムリソースである BackendConfig リソースを作成します。
以下のマニフェストファイル backend.yaml
を GKE クラスタに適用します。
apiVersion: cloud.google.com/v1kind: BackendConfigmetadata:name: firestore-app-backendconfigspec:securityPolicy:name: firestore-app-policy # 作成したセキュリティポリシーの名前
Service と BackendConfig の関連付け
BackendConfig は Ingress リソースに対してではなく、 Service リソースの Service ポートに関連付けられます。GKE Ingress コントローラはアプリケーションロードバランサを作成する際に、この関連付けられた BackendConfig の設定を利用します。
マニフェストファイル service.yaml
を以下のように更新して、 GKE クラスタに適用します。
# service.yamlapiVersion: v1kind: Servicemetadata:name: firestore-app-servicenamespace: defaultannotations:cloud.google.com/neg: '{"ingress": true}'cloud.google.com/backend-config: '{"ports": {"8080":"firestore-app-backendconfig"}}' # 追記spec:selector:app: firestore-appports:- port: 8080protocol: TCPtargetPort: 8080type: NodePort
動作確認
まず、Cloud Armor のセキュリティポリシーで許可していない IP アドレス範囲からアクセスしてみます。
Ingress によって作成されたアプリケーションロードバランサの外部 IP アドレスにアクセスすると、以下のようにアクセスが拒否されることが確認できます。
次に、ポリシーで許可された IP アドレス範囲からアクセスしてみます。
すると、以下のように Firestore Viewer
アプリケーションの画面が表示されることが確認できます。