Cloud Storage, Firestore ve Cloud Run'u kullanarak resim yükleme ve yayınlama

Cloud Storage, Firestore ve Cloud Run'u kullanarak resim yükleme ve yayınlama

Bu codelab hakkında

subjectSon güncelleme Nis 4, 2025
account_circleBir Google çalışanı tarafından yazılmıştır

1. Giriş

Bu codelab'de Cloud Storage, Firestore ve Cloud Run'u kullanarak resimleri nasıl yükleyeceğinizi ve yayınlayacağınızı öğreneceksiniz. Ayrıca, Gemini'ye çağrı yapmak için kimlik doğrulama amacıyla Google'ın istemci kitaplıklarını nasıl kullanacağınızı da öğreneceksiniz.

  • FastAPI uygulamasını Cloud Run'a dağıtma
  • Kimlik doğrulaması için Google'ın istemci kitaplıklarını kullanma
  • Cloud Run hizmeti kullanarak Cloud Storage'a dosya yükleme
  • Firestore'da veri okuma ve yazma
  • Cloud Run hizmetinde Cloud Storage'dan resimleri alma ve görüntüleme

2. Kurulum ve Gereksinimler

Bu codelab boyunca kullanılacak ortam değişkenlerini ayarlayın.

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

API'leri etkinleştir

gcloud services enable run.googleapis.com \
                       storage.googleapis.com \
                       firestore.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com

Resimlerin depolanacağı bir Cloud Storage paketi oluşturun

gsutil mb -p dogfood-gcf-saraford -l us-central1 gs://$GCS_BUCKET_NAME

Web sitesine resim yükleyip görüntüleyebileceğiniz pakete herkese açık erişime izin verin:

gsutil iam ch allUsers:objectViewer gs://$GCS_BUCKET_NAME

Aşağıdaki komutu çalıştırarak bir hizmet hesabı oluşturun:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
   
--display-name="SA for CR $SERVICE_ACCOUNT"

Ayrıca SA'ya Firestore ve GCS paketine erişim izni verin.

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 veritabanını oluşturma

Firestore veritabanı oluşturmak için aşağıdaki komutu çalıştırın

gcloud firestore databases create --location=nam5

4. Uygulamayı oluşturma

Kodunuz için bir dizin oluşturun.

mkdir codelab-cr-fastapi-firestore-gcs
cd codelab
-cr-fastapi-firestore-gcs

Öncelikle bir şablon dizini oluşturarak html şablonlarını oluşturursunuz.

mkdir templates
cd templates

Aşağıdaki içeriği içeren index.html adlı yeni bir dosya oluşturun:

<!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>

Şimdi kök dizininde Python kodunuzu ve diğer dosyalarınızı oluşturun.

cd ..

Aşağıdaki içeriği içeren bir .gcloudignore dosyası oluşturun:

__pycache__

Aşağıdaki içeriğe sahip main.py adlı bir dosya oluşturun:

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

Aşağıdaki içeriği içeren bir Dockerfile oluşturun:

# 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"]

ve aşağıdakipyproject.toml

[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. Cloud Run&#39;a dağıt

Cloud Run'a dağıtım yapma komutu aşağıda verilmiştir. Kodunuz sıkıştırılır ve görüntüyü oluşturmak için Dockerfile'ı kullanan Cloud Build'e gönderilir.

Bu, Cloud Run'a kaynak tabanlı bir dağıtım olduğundan, hizmetin Cloud Console'daki sayfasında kodunuzu içeren bir Kaynak sekmesi görürsünüz.

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. Hizmetinizi test etme

Hizmet URL'sini web tarayıcınızda açın ve bir resim yükleyin. Bu, listede gösterilir.

7. Herkese açık Cloud Storage paketindeki izinleri değiştirme

Daha önce de belirtildiği gibi, bu codelab'de herkese açık bir GCS paketi kullanılır. Paketi silmeniz veya aşağıdaki komutu çalıştırarak allUsers erişimini paketten kaldırmanız önerilir:

gsutil iam ch -d allUsers:objectViewer gs://$GCS_BUCKET_NAME

Aşağıdaki komutu çalıştırarak allUsers erişiminin kaldırıldığını onaylayabilirsiniz:

gsutil iam get gs://$GCS_BUCKET_NAME

8. Tebrikler

Codelab'i tamamladığınız için tebrikler.

İşlediğimiz konular

  • FastAPI uygulamasını Cloud Run'a dağıtma
  • Kimlik doğrulaması için Google'ın istemci kitaplıklarını kullanma
  • Cloud Run hizmeti kullanarak Cloud Storage'a dosya yükleme
  • Firestore'da veri okuma ve yazma
  • Cloud Run hizmetinde Cloud Storage'dan resimleri alma ve görüntüleme

9. Temizleme

Cloud Run hizmetini silmek için https://console.cloud.google.com/run adresindeki Cloud Run Cloud Console'a gidip hizmeti silin.

Cloud Storage paketini silmek için aşağıdaki komutları çalıştırabilirsiniz:

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

Projenin tamamını silmeyi seçerseniz https://console.cloud.google.com/cloud-resource-manager adresine gidip 2. adımda oluşturduğunuz projeyi seçin ve Sil'i tıklayın. Projeyi silerseniz Cloud SDK'nızdaki projeleri değiştirmeniz gerekir. gcloud projects list komutunu çalıştırarak mevcut tüm projelerin listesini görüntüleyebilirsiniz.