Informationen zu diesem Codelab
1. Einführung
Übersicht
In diesem Codelab erfahren Sie, wie Sie Bilder mit Cloud Storage, Firestore und Cloud Run hochladen und bereitstellen. Außerdem erfahren Sie, wie Sie die Clientbibliotheken von Google für die Authentifizierung verwenden, um Gemini aufzurufen.
Aufgaben in diesem Lab
- FastAPI-Anwendung in Cloud Run bereitstellen
- Clientbibliotheken von Google für die Authentifizierung verwenden
- Dateien mit einem Cloud Run-Dienst in Cloud Storage hochladen
- Daten in Firestore lesen und schreiben
- Bilder aus Cloud Storage in einem Cloud Run-Dienst abrufen und anzeigen
2. Einrichtung und Anforderungen
Legen Sie Umgebungsvariablen fest, die in diesem Codelab verwendet werden.
PROJECT_ID=dogfood-gcf-saraford
REGION=us-central1
GCS_BUCKET_NAME=dogfood-gcf-saraford-codelab-wietse-2
SERVICE_NAME=fastapi-storage-firestore
SERVICE_ACCOUNT=fastapi-storage-firestore-sa
SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
APIs aktivieren
gcloud services enable run.googleapis.com \
storage.googleapis.com \
firestore.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
Cloud Storage-Bucket zum Speichern der Bilder erstellen
gsutil mb -p dogfood-gcf-saraford -l us-central1 gs://$GCS_BUCKET_NAME
Erlauben Sie den öffentlichen Zugriff auf den Bucket, in dem Sie Bilder auf die Website hochladen und anzeigen können:
gsutil iam ch allUsers:objectViewer gs://$GCS_BUCKET_NAME
Erstellen Sie ein Dienstkonto mit dem folgenden Befehl:
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name="SA for CR $SERVICE_ACCOUNT"
Gewähren Sie dem Dienstkonto Zugriff auf Firestore und den GCS-Bucket.
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
--role="roles/datastore.user"
gsutil iam ch serviceAccount:$SERVICE_ACCOUNT_ADDRESS:roles/storage.objectAdmin gs://$GCS_BUCKET_NAME
3. Firestore-Datenbank erstellen
Führen Sie den folgenden Befehl aus, um eine Firestore-Datenbank zu erstellen:
gcloud firestore databases create --location=nam5
4. Anwendung erstellen
Erstellen Sie ein Verzeichnis für Ihren Code.
mkdir codelab-cr-fastapi-firestore-gcs
cd codelab-cr-fastapi-firestore-gcs
Erstellen Sie zuerst die HTML-Vorlagen, indem Sie ein Verzeichnis „Vorlagen“ anlegen.
mkdir templates
cd templates
Erstellen Sie eine neue Datei mit dem Namen index.html
und folgendem Inhalt:
<!DOCTYPE html>
<html>
<head>
<title>Cloud Run Image Upload Demo</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.upload-form { margin-bottom: 20px; padding: 15px; border: 1px solid #ccc; border-radius: 5px; background-color: #f9f9f9; }
.image-list { margin-top: 30px; }
.image-item { border-bottom: 1px solid #eee; padding: 10px 0; }
.image-item img { max-width: 100px; max-height: 100px; vertical-align: middle; margin-right: 10px;}
.error { color: red; font-weight: bold; margin-top: 10px;}
</style>
</head>
<body>
<h1>Upload an Image</h1>
<p>Files will be uploaded to GCS bucket: <strong>{{ bucket_name }}</strong> and metadata stored in Firestore.</p>
<div class="upload-form">
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" accept="image/*" required>
<button type="submit">Upload Image</button>
</form>
{% if error_message %}
<p class="error">{{ error_message }}</p>
{% endif %}
</div>
<div class="image-list">
<h2>Recently Uploaded Images:</h2>
{% if images %}
{% for image in images %}
<div class="image-item">
<a href="{{ image.gcs_url }}" target="_blank">
<img src="{{ image.gcs_url }}" alt="{{ image.filename }}" title="Click to view full size">
</a>
<span>{{ image.filename }}</span>
<small>(Uploaded: {{ image.uploaded_at.strftime('%Y-%m-%d %H:%M:%S') if image.uploaded_at else 'N/A' }})</small><br/>
<small><a href="{{ image.gcs_url }}" target="_blank">{{ image.gcs_url }}</a></small>
</div>
{% endfor %}
{% else %}
<p>No images uploaded yet or unable to retrieve list.</p>
{% endif %}
</div>
</body>
</html>
Erstellen Sie nun Ihren Python-Code und andere Dateien im Stammverzeichnis.
cd ..
Erstellen Sie eine .gcloudignore
-Datei mit folgendem Inhalt:
__pycache__
Erstellen Sie eine Datei mit dem Namen main.py
und mit folgendem Inhalt:
import os
import datetime
from fastapi import FastAPI, File, UploadFile, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from google.cloud import storage, firestore
# --- Configuration ---
# Get bucket name and firestore collection from Cloud Run env vars
GCS_BUCKET_NAME = os.environ.get("GCS_BUCKET_NAME", "YOUR_BUCKET_NAME_DEFAULT")
FIRESTORE_COLLECTION = os.environ.get("FIRESTORE_COLLECTION", "YOUR_FIRESTORE_DEFAULT")
# --- Initialize Google Client Libraries ---
# These client libraries will use the Application Default Credentials
# for your service account within the Cloud Run environment
storage_client = storage.Client()
firestore_client = firestore.Client()
# --- FastAPI App ---
app = FastAPI()
templates = Jinja2Templates(directory="templates")
# --- Routes ---
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
"""Serves the main upload form."""
# Query Firestore for existing images to display
images = []
try:
docs = firestore_client.collection(FIRESTORE_COLLECTION).order_by(
"uploaded_at", direction=firestore.Query.DESCENDING
).limit(10).stream() # Get latest 10 images
for doc in docs:
images.append(doc.to_dict())
except Exception as e:
print(f"Warning: Could not fetch images from Firestore: {e}")
# Continue without displaying images if Firestore query fails
return templates.TemplateResponse("index.html", {
"request": request,
"bucket_name": GCS_BUCKET_NAME,
"images": images # Pass images to the template
})
@app.post("/upload")
async def handle_upload(request: Request, file: UploadFile = File(...)):
"""Handles file upload, saves to GCS, and records in Firestore."""
if not file:
return {"message": "No upload file sent"}
elif not GCS_BUCKET_NAME or GCS_BUCKET_NAME == "YOUR_BUCKET_NAME_DEFAULT":
return {"message": "GCS Bucket Name not configured."}, 500 # Internal Server Error
try:
# 1. Upload to GCS
# note: to keep the demo code short, there are no file verifications
# for an actual real-world production app, you will want to add checks
gcs_url = upload_to_gcs(file, GCS_BUCKET_NAME)
# 2. Save metadata to Firestore
save_metadata_to_firestore(file.filename, gcs_url, FIRESTORE_COLLECTION)
# Redirect back to the main page after successful upload
return RedirectResponse(url="/", status_code=303) # Redirect using See Other
except Exception as e:
print(f"Upload failed: {e}")
return templates.TemplateResponse("index.html", {
"request": request,
"bucket_name": GCS_BUCKET_NAME,
"error_message": f"Upload failed: {e}",
"images": [] # Pass empty list on error or re-query
}, status_code=500)
# --- Helper Functions ---
def upload_to_gcs(uploadedFile: UploadFile, bucket_name: str) -> str:
"""Uploads a file to Google Cloud Storage and returns the public URL."""
try:
bucket = storage_client.bucket(bucket_name)
# Create a unique blob name (e.g., timestamp + original filename)
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d%H%M%S")
blob_name = f"{timestamp}_{uploadedFile.filename}"
blob = bucket.blob(blob_name)
# Upload the file
# Reset file pointer just in case
uploadedFile.file.seek(0)
blob.upload_from_file(uploadedFile.file, content_type=uploadedFile.content_type)
print(f"File {uploadedFile.filename} uploaded to gs://{bucket_name}/{blob_name}")
return blob.public_url # Return the public URL
except Exception as e:
print(f"Error uploading to GCS: {e}")
raise # Re-raise the exception for FastAPI to handle
def save_metadata_to_firestore(filename: str, gcs_url: str, collection_name: str):
"""Saves image metadata to Firestore."""
try:
doc_ref = firestore_client.collection(collection_name).document()
doc_ref.set({
'filename': filename,
'gcs_url': gcs_url,
'uploaded_at': firestore.SERVER_TIMESTAMP # Use server timestamp
})
print(f"Metadata saved to Firestore collection {collection_name}")
except Exception as e:
print(f"Error saving metadata to Firestore: {e}")
# Consider raising the exception or handling it appropriately
raise # Re-raise the exception
Erstellen Sie eine Dockerfile
-Datei mit folgendem Inhalt:
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
# Install poetry
RUN pip install poetry
RUN poetry self add poetry-plugin-export
# Copy poetry files
COPY pyproject.toml poetry.lock* ./
# Copy application code
COPY . .
# Export dependencies to requirements.txt
RUN poetry export -f requirements.txt --output requirements.txt
# Final stage
FROM python:3.12-slim
WORKDIR /app
# Copy files from builder
COPY --from=builder /app/ .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Compile bytecode to improve startup latency
# -q: Quiet mode
# -b: Write legacy bytecode files (.pyc) alongside source
# -f: Force rebuild even if timestamps are up-to-date
RUN python -m compileall -q -b -f .
# Expose port
EXPOSE 8080
# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
und eine pyproject.toml
mit folgendem Inhalt erstellt:
[tool.poetry]
name = "cloud-run-fastapi-demo"
version = "0.1.0"
description = "Demo FastAPI app for Cloud Run showing GCS upload and Firestore integration."
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.110.0"
uvicorn = {extras = ["standard"], version = "^0.29.0"} # Includes python-multipart
google-cloud-storage = "^2.16.0"
google-cloud-firestore = "^2.16.0"
jinja2 = "^3.1.3"
python-multipart = "^0.0.20"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
5. In Cloud Run bereitstellen
Unten finden Sie den Befehl für die Bereitstellung in Cloud Run. Ihr Code wird komprimiert und an Cloud Build gesendet, wo das Image mithilfe des Dockerfiles erstellt wird.
Da es sich um eine quellebasierte Bereitstellung in Cloud Run handelt, wird in der Cloud Console für den Dienst der Tab „Quelle“ mit Ihrem Code angezeigt.
gcloud run deploy $SERVICE_NAME \
--source . \
--allow-unauthenticated \
--service-account=$SERVICE_ACCOUNT_ADDRESS \
--set-env-vars=GCS_BUCKET_NAME=$GCS_BUCKET_NAME \
--set-env-vars=FIRESTORE_COLLECTION=$FIRESTORE_COLLECTION
6. Dienst testen
Öffnen Sie die Dienst-URL in Ihrem Webbrowser und laden Sie ein Bild hoch. Sie wird in der Liste angezeigt.
7. Berechtigungen für den öffentlichen Cloud Storage-Bucket ändern
Wie bereits erwähnt, wird in diesem Codelab ein öffentlicher GCS-Bucket verwendet. Wir empfehlen, den Bucket entweder zu löschen oder den Zugriff von „allUsers“ auf den Bucket zu entfernen. Führen Sie dazu den folgenden Befehl aus:
gsutil iam ch -d allUsers:objectViewer gs://$GCS_BUCKET_NAME
Mit dem folgenden Befehl können Sie prüfen, ob der Zugriff für alle Nutzer entfernt wurde:
gsutil iam get gs://$GCS_BUCKET_NAME
8. Glückwunsch
Herzlichen Glückwunsch zum Abschluss des Codelabs.
Behandelte Themen
- FastAPI-Anwendung in Cloud Run bereitstellen
- Clientbibliotheken von Google für die Authentifizierung verwenden
- Dateien mit einem Cloud Run-Dienst in Cloud Storage hochladen
- Daten in Firestore lesen und schreiben
- Bilder aus Cloud Storage in einem Cloud Run-Dienst abrufen und anzeigen
9. Bereinigen
Wenn Sie den Cloud Run-Dienst löschen möchten, rufen Sie die Cloud Console unter https://console.cloud.google.com/run auf und löschen Sie den Dienst.
Führen Sie die folgenden Befehle aus, um den Cloud Storage-Bucket zu löschen:
echo "Deleting objects in gs://$GCS_BUCKET_NAME..."
gsutil rm -r gs://$GCS_BUCKET_NAME/*
echo "Deleting bucket gs://$GCS_BUCKET_NAME..."
gsutil rb gs://$GCS_BUCKET_NAME
Wenn Sie das gesamte Projekt löschen möchten, rufen Sie https://console.cloud.google.com/cloud-resource-manager auf, wählen Sie das in Schritt 2 erstellte Projekt aus und klicken Sie auf „Löschen“. Wenn Sie das Projekt löschen, müssen Sie die Projekte in Ihrem Cloud SDK ändern. Sie können eine Liste aller verfügbaren Projekte aufrufen, indem Sie gcloud projects list
ausführen.