Python Cloud Function'larda Özel pip Bağımlılıkları
Selamlar, bu yazımda Google Cloud Functions üzerinde Python dağıtırken pip tarafında karşımıza çıkan tipik baş ağrılarını masaya yatıracağız. Public PyPI'dan birkaç paket çekip requirements.txt koyduğunuzda iş genelde yürür; ama gerçek hayatta private bir Artifact Registry, GitHub'da duran bir paket, bir de native uzantı isteyen bir kütüphane çıkar ve build aniden patlar. Hadi bu durumlara tek tek bakalım.
Temel iskelet ve sürüm sabitleme
En basit haliyle main.py ile aynı klasöre bir requirements.txt koyuyoruz, gerisini Cloud Build hallediyor. Önemli olan, sürümleri sabitlemek - yoksa bir gün geçici bir minor release ile fonksiyon prod'da ImportError döndürür.
# requirements.txt
functions-framework==3.4.0
google-cloud-storage==2.14.0
google-cloud-bigquery==3.14.0
requests==2.31.0
pandas==2.1.4
Fonksiyon tarafı da klasik bir HTTP handler:
# main.py
import functions_framework
from google.cloud import storage
import pandas as pd
import requests
@functions_framework.http
def islem_yap(request):
# Gelen JSON'u DataFrame'e cevirip isliyoruz
df = pd.DataFrame(request.get_json().get('data', []))
client = storage.Client()
bucket = client.bucket('my-bucket')
response = requests.get('https://api.example.com/config')
return {'rows': len(df), 'status': 'ok'}
Deploy komutu da Gen 2 için şöyle:
gcloud functions deploy islem-yap \
--gen2 \
--runtime=python312 \
--region=europe-west1 \
--source=. \
--entry-point=islem_yap \
--trigger-http \
--memory=512Mi
Bence direkt requirements.txt yazmak yerine pip-tools ile derlemek daha sağlıklı; transitif bağımlılıklar da pinlenmiş olur ve build'ler tekrar üretilebilir hale gelir.
pip install pip-tools
pip-compile requirements.in --output-file=requirements.txt
Private PyPI: Artifact Registry yolu
Şirket içi paketleriniz Artifact Registry'de duruyorsa kaynak klasöre bir pip.conf koymak yetiyor. Cloud Build pip install çağrısında bu dosyayı otomatik okur.
# pip.conf
[global]
extra-index-url = https://europe-west1-python.pkg.dev/<Proj>/python-packages/simple/
Sonra requirements.txt içine private paketinizi sıradan bir bağımlılık gibi yazıyorsunuz. Tek yapmanız gereken, Cloud Build service account'una roles/artifactregistry.reader vermek:
PROJECT_NUMBER=$(gcloud projects describe <Proj> --format='value(projectNumber)')
gcloud artifacts repositories add-iam-policy-binding python-packages \
--location=europe-west1 \
--member="serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
--role='roles/artifactregistry.reader'
Harici bir private PyPI varsa (Gemfury, devpi, kendi kurduğunuz bir mirror) build-time env var'ı işe yarar:
gcloud functions deploy islem-yap \
--gen2 \
--runtime=python312 \
--source=. \
--entry-point=islem_yap \
--trigger-http \
--set-build-env-vars="PIP_EXTRA_INDEX_URL=https://user:token@pypi.<Co>.com/simple/"
Token'ı requirements.txt içine yazıp commit etmek yerine bunu tercih edin. Hayatınız kolaylaşır, secret de repository'de gezmez.
GitHub'dan ve yerelden paket çekmek
Public bir GitHub deposundan paket çekmek git+https://... notasyonuyla doğrudan mümkün:
functions-framework==3.4.0
git+https://github.com/<Org>/<Pkg>.git@v1.2.3#egg=<Pkg>
Private repo ise bir personal access token ile build-time env var üzerinden gider. Aynı mantık: token'ı dosyaya gömmeyin, Cloud Build ortamına enjekte edin.
Yerel paketleriniz varsa onları kaynak klasörüne dahil etmek yeterli; Python Cloud Functions tüm --source dizinini path'e ekliyor:
islem-yap/
main.py
requirements.txt
lib/
__init__.py
yardimcilar.py
utils/
__init__.py
bicim.py
from lib.yardimcilar import kayit_isle
from utils.bicim import yanit_uret
Native uzantılar ve tam kontrol için Dockerfile
numpy, pandas, cryptography gibi native uzantı taşıyan paketlerin çoğu Cloud Build'in default Linux ortamında zaten derlenir. Sorun, system kütüphanesi isteyen daha egzotik paketlerde başlar (libpq, libmagic, libffi gibi). Burada işin temizi Gen 2 fonksiyonu için kendi Docker image'ınızı kullanmak.
FROM python:3.12-slim
RUN apt-get update && apt-get install -y \
libpq-dev \
libmagic1 \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FUNCTION_TARGET=handler
CMD ["functions-framework", "--target=handler", "--port=8080"]
Bu image'ı Artifact Registry'ye basıp fonksiyonu üzerine deploy edebilirsiniz. Şahsi kanaatim, üç-dört apt paketi gerekmeye başladığı an bu yola geçmek hem build sürelerini hem de hata ayıklamayı çok kolaylaştırır.
Sık karşılaşılan tuzaklar
- Sürümleri pinlememek: Bir gün
pandasminor sürümü artar, fonksiyon cold start'taImportErrorile çakılır. Heppip-compilekullanın. pip.conf'u commit etmemek: Yerelde çalışıyor, Cloud Build'de paket bulunamıyor. Dosya kaynak klasörde değilse hiç okunmaz.- Token'ı
requirements.txt'e yazmak: Repo'ya sızar, rotate edene kadar açık kalır. Build env var şart. - Devasa paketleri sürüklemek:
boto360 MB,pandas150 MB taşır. Sadece S3 lazımsamypy-boto3-s3, hafif veri işleme içinpolarsgibi alternatiflere bakın - cold start farkı hissedilir.
Kapanış
Cloud Functions'ta bağımlılık yönetimi yerel virtualenv'e göre biraz daha disiplinli olmayı gerektiriyor; ama desenler bir kez oturduktan sonra private paket de native uzantı da rutin işe dönüşüyor. Basitten karmaşığa doğru ilerleyin, Dockerfile'ı en sona saklayın. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
