Kubernetes上の異なるPodにFastAPIサーバを立てて通信させてみた
今回は、Kubernetes上で異なるFastAPIサーバを立てて、片方のFastAPIサーバにアクセスすると別のFastAPIサーバにアクセスされる環境を作ってみました。まだマイクロサービスを構築した経験が少ないので、まずはシンプルな構成から始めてみます。
今回のシステム構成
今回は以下のような構成でシステムを構築します。
- Google CloudのGKE上でシステムを構成
- DockerイメージはArtifact Registryに保存
- FastAPIサーバは二種類建てる
- パブリックアクセスをするためのサーバ
- パブリックサーバからアクセスされるが、クラスタ内に閉じているプライベートサーバ
- ロードバランサーサービスを使ってパブリックサーバへアクセスする
- パブリックサーバからプライベートサーバへのアクセスはClusterIPを利用

それでは実装していく
FastAPIサーバの用意
まずはパブリックサーバのコードとDockerfileを載せます。
import os
import requests
from fastapi import FastAPI
app = FastAPI()
PRIVATE_SERVER_ENDPOINT = os.environ["PRIVATE_SERVER_ENDPOINT"]
PRIVATE_SERVER_PORT = os.environ["PRIVATE_SERVER_PORT"]
@app.get("/hello")
def hello():
print(f"http://{PRIVATE_SERVER_ENDPOINT}:{PRIVATE_SERVER_PORT}/hello")
private_server_response = requests.get(
f"http://{PRIVATE_SERVER_ENDPOINT}:{PRIVATE_SERVER_PORT}/hello"
)
return {
"message_from_public_server": "Hello from public server",
"message_from_private_server": private_server_response.json()["message"],
}
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
COPY . /app
EXPOSE 80
RUN uv sync
CMD ["uv", "run", "uvicorn", "public_server:app", "--host", "0.0.0.0", "--port", "80"]
パブリックサーバでは/helloに対してGETメソッドを投げると、パブリックサーバで作成したメッセージとプライベートサーバから取得したメッセージを返します。なお、プライベートサーバのエンドポイントとポートはConfigMap経由で環境変数として登録します。Dockerfile_publicではpublic_server.pyを起動してサーバを建てます。
次はプライベートサーバの実装を載せます。
import requests
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello():
return {"message": "Hello from private server."}
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
COPY . /app
EXPOSE 80
RUN uv sync
CMD ["uv", "run", "uvicorn", "private_server:app", "--host", "0.0.0.0", "--port", "80"]
プライベートサーバの実装も基本的にはパブリックサーバと同じです。/helloに対してGETすることでプライベートサーバで生成した情報を受け渡すことができます。
Kubernetesリソースの定義
まずはConfigMapの定義からです。ConfigMapで定義した情報をパブリックサーバのPodに対して環境変数として指示します。PRIVATE_SERVER_ENDPOINTにはプライベートサーバに対して用意するClusterIPの名前を、PRIVATE_SERVER_PORTは80で指定しております。
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
data:
PRIVATE_SERVER_ENDPOINT: "fastapi-private-server-cluster-ip"
PRIVATE_SERVER_PORT: "80"
それでは次にPodの定義をします。順番にパブリックサーバ、プライベートサーバの定義となります。今回は両者ともレプリカ数は2つ、ポートは80番を利用します。またパブリックサーバでは先ほど適宜したConfigMapを環境変数として挿入するために、spec.template.spec.containers.envFromに指定しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-public-server-deployment
spec:
replicas: 2
selector:
matchLabels:
app: fastapi-public-server
template:
metadata:
labels:
app: fastapi-public-server
spec:
containers:
- name: fastapi-public-server
image: asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-public-server:latest
imagePullPolicy: Always
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: env-config
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-private-server-deployment
spec:
replicas: 2
selector:
matchLabels:
app: fastapi-private-server
template:
metadata:
labels:
app: fastapi-private-server
spec:
containers:
- name: fastapi-private-server
image: asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-private-server:latest
imagePullPolicy: Always
ports:
- containerPort: 80
次にサービスの定義です。順番にClusterIP、LoadBalancerとなります。ポートは全て80番で統一しております。また、ClusterIPの名前はConfigMapで設定したPRIVATE_SERVER_ENDPOINTと一致させています。
apiVersion: v1
kind: Service
metadata:
name: fastapi-private-server-cluster-ip
spec:
type: ClusterIP
ports:
- name: cluster-port
port: 80
targetPort: 80
selector:
app: fastapi-private-server
apiVersion: v1
kind: Service
metadata:
name: fastapi-public-server-load-balancer
spec:
type: LoadBalancer
selector:
app: fastapi-public-server
ports:
- protocol: TCP
port: 80
targetPort: 80
最後に、今回はkustomizeを利用して一つのリソースファイルに集約しました。
resources:
- configmap.yaml
- public_server.yaml
- private_server.yaml
- private_cluster_ip.yaml
- loadbalancer.yaml
kubectl kustomizee ./ > config.yaml
なお、kustomizeについてはこちらで解説しているのでぜひ参照ください。
Google Cloudの設定
今回必要なのはArtifact RegistryとGKEになります。
まずは以下のコマンドでArtifact Registryを作成しました。
gcloud artifacts repositories create fastapi-repo --repository-format=docker --location=asia-northeast1
次に、以下のコマンドでクラスタを作成しました。
gcloud container clusters create fastapi-cluster --zone us-central1-a --num-nodes 2 --machine-type n1-standard-4
基本的な設定は以下と同じですね。
Dockerイメージの作成
今回は二つのDockerイメージを必要とするので、以下のようにして対応しました。
# ビルド
docker build -t fastapi-public-server -f Dockerfile_public . --platform linux/amd64
docker build -t fastapi-private-server -f Dockerfile_private . --platform linux/amd64
# タグづけ
docker tag fastapi-public-server asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-public-server:latest
docker tag fastapi-private-server asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-private-server:latest
# プッシュ
docker asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-public-server:latest
docker asia-northeast1-docker.pkg.dev/<PROJECT-ID>/fastapi-repo/fastapi-private-server:latest
GKEへのデプロイ
いよいよ定義したリソースをデプロイしてみます。kustomizeを使って一つのファイルに定義を集約したので、以下のようにしてデプロイしました。
kubectl appy -f config.yaml
少し待つとデプロイが完了します。kdashを利用して確認すると以下のようになりました。Podがそれぞれ2つずつ作成されており、ClusterIP、LoadBalancerによるExternalIPの払い出しも確認できます。

また、Swaggerから実行すると以下のようにプライベートサーバからのメッセージを取得した上でパブリックサーバから返答がきていることを確認できました。

最後に、検証が終わったら環境を閉じます。以下のコマンドで環境をクローズします。
kubectl delete -f config.yaml
gcloud container clusters delete fastapi-cluster --zone us-central1-a
まとめ
今回は二つのFastAPIサーバを建てて相互に連携させてレスポンスを返す構成を作ってみました。だいぶKubernetesには慣れてきたので今回の範囲は割とすんなり対応できて成長を感じていますw。次回は今回作った環境のObservabilityを導入して、どのようにサービスが動いているか可視化してみたいと思っています。
Discussion