Ana içeriğe geç
Kesit Python
İleri · Üretime hazırlık

Bulut temelleri: nesne depolama, IAM, serverless ve maliyet

IaaS/PaaS/SaaS, bölge ve AZ kavramlarını, S3 nesne depolamayı ve boto3'ü, data lake katmanlarını ve tarih bölümlemeyi, IAM politikaları ve en az yetkiyi, imzalı URL'yi, serverless fonksiyonları, yönetilen veritabanlarını, maliyet tuzaklarını ve sahte bulutla test yazmayı öğrenirsin.

Uygulamada aç, 22 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden bulut?

Kanat Havayolları'nın veri ekibi her gece uçuş, rezervasyon ve gecikme dosyalarını işliyor. İlk yıl tek bir sunucu yetti. Sonra veri büyüdü, sunucunun diski doldu, bir gece disk bozuldu ve iki günlük veri kayboldu; yeni sunucu almak haftalar sürdü. Bulut (cloud) bu sorunları kiralık, ölçeklenebilir ve yönetilen hizmetlerle çözer: depolamayı terabayt terabayt değil gigabayt-ay olarak ödersin, sunucuyu dakikalar içinde açıp kapatırsın, yedekleme ve donanım arızası sağlayıcının sorunudur.

Bu derste bir veri ya da backend mühendisinin bulutta ilk gün ihtiyaç duyduğu temelleri öğreneceksin: hizmet modelleri, bölgeler, nesne depolama (S3) ve boto3, data lake klasör düzeni, IAM ve en az yetki ilkesi, imzalı URL, serverless fonksiyonlar, yönetilen veritabanları ve maliyet. Uygulamayı Docker ile paketlemeyi docker-deploy dersinde gördün; bu ders o paketin nereye ve nasıl gideceğini anlatır.

Kendi bilgisayarında deneboto3 gerçek bir AWS hesabı ve ağ erişimi ister; bu sayfadaki motorda çalışmaz. Gri kutulardaki kodları kendi bilgisayarında dene: pip install boto3 moto. Gerçek hesapta denerken ücretsiz katman (free tier) sınırlarını ve bütçe alarmını ayarlamayı unutma. Yeşil kutular ve alıştırmalar boto3'ün arayüzünü taklit eden bellek içi bir SahteS3 sınıfı ve saf Python ile çalışır.

Hizmet modelleri, bölgeler ve sağlayıcılar

Bölge (region) coğrafi bir veri merkezi grubudur (ör. Frankfurt); kullanılabilirlik alanı (availability zone, AZ) bölge içindeki birbirinden bağımsız elektrik ve ağa sahip veri merkezidir. Kritik sistemler en az iki AZ'ye yayılır; bir AZ çökerse diğeri çalışmaya devam eder. Bölge seçimi gecikme (kullanıcıya yakınlık), fiyat ve yasal veri yerleşimi kurallarına göre yapılır.

HizmetAWSGoogle CloudAzure
Nesne depolamaS3Cloud Storage (GCS)Blob Storage
Sanal makineEC2Compute EngineVirtual Machines
Serverless fonksiyonLambdaCloud Run functionsAzure Functions
Konteyner çalıştırmaECS / FargateCloud RunContainer Apps
Yönetilen PostgreSQLRDS / AuroraCloud SQLAzure Database for PostgreSQL
Veri ambarıRedshiftBigQuerySynapse / Fabric
Kimlik ve yetkiIAMCloud IAMEntra ID + RBAC

Nesne depolama: bucket, key, prefix

S3 bir dosya sistemi değildir; anahtar-değer deposudur. Bucket (kova) küresel olarak benzersiz adlı bir kaptır; içindeki her nesne (object) bir anahtar (key) ile saklanır: ham/kanat/ucuslar/yil=2024/ay=09/gun=01/part-000.csv. Konsolda "klasör" gibi görünen şey anahtarın /'a kadarki önekidir (prefix); gerçek klasör yoktur. Nesneler değiştirilmez, üstüne yazılır; "dosyanın sonuna ekle" yoktur.

import boto3

s3 = boto3.client("s3", region_name="eu-central-1")      # kimlik bilgisi ortamdan/rolden gelir
s3.upload_file("ucuslar.csv", "kanat-veri", "ham/kanat/ucuslar/yil=2024/ay=09/gun=01/part-000.csv")

nesne = s3.get_object(Bucket="kanat-veri", Key="ham/kanat/ucuslar/yil=2024/ay=09/gun=01/part-000.csv")
metin = nesne["Body"].read().decode("utf-8")

# Listeleme en fazla 1000 anahtar döndürür: sayfalayıcı (paginator) kullan
sayfalayici = s3.get_paginator("list_objects_v2")
for sayfa in sayfalayici.paginate(Bucket="kanat-veri", Prefix="ham/kanat/ucuslar/yil=2024/ay=09/"):
    for nesne in sayfa.get("Contents", []):
        print(nesne["Key"], nesne["Size"])

Aynı arayüzü bellek içinde taklit eden bir sınıfla tarayıcıda deneyelim. Alıştırmalarda bu SahteS3 sınıfını kullanacaksın; metot ve parametre adları boto3 ile aynıdır, böylece kodun gerçek istemciye neredeyse değişmeden taşınır.

class NoSuchKey(KeyError):
    pass


class SahteS3:
    """boto3 S3 istemcisinin bellek içi, sadeleştirilmiş taklidi."""

    def __init__(self):
        self.kovalar = {}

    def create_bucket(self, Bucket):
        self.kovalar.setdefault(Bucket, {})

    def put_object(self, Bucket, Key, Body):
        if isinstance(Body, str):
            Body = Body.encode("utf-8")
        self.kovalar[Bucket][Key] = Body
        return {"ETag": str(hash(Body))}

    def get_object(self, Bucket, Key):
        try:
            return {"Body": self.kovalar[Bucket][Key], "ContentLength": len(self.kovalar[Bucket][Key])}
        except KeyError:
            raise NoSuchKey(Key) from None

    def delete_object(self, Bucket, Key):
        self.kovalar[Bucket].pop(Key, None)

    def list_objects_v2(self, Bucket, Prefix="", MaxKeys=1000, ContinuationToken=None):
        anahtarlar = sorted(k for k in self.kovalar[Bucket] if k.startswith(Prefix))
        if ContinuationToken:
            anahtarlar = [k for k in anahtarlar if k > ContinuationToken]
        sayfa = anahtarlar[:MaxKeys]
        yanit = {"KeyCount": len(sayfa), "IsTruncated": len(anahtarlar) > MaxKeys}
        if sayfa:                                   # gerçek S3 gibi: boş sonuçta Contents yok
            yanit["Contents"] = [{"Key": k, "Size": len(self.kovalar[Bucket][k])} for k in sayfa]
        if yanit["IsTruncated"]:
            yanit["NextContinuationToken"] = sayfa[-1]
        return yanit


s3 = SahteS3()
s3.create_bucket(Bucket="kanat-veri")
for gun in ["01", "02", "03"]:
    s3.put_object(Bucket="kanat-veri", Key=f"ham/kanat/ucuslar/yil=2024/ay=09/gun={gun}/part-000.csv",
                  Body="flight_id,status\n1,landed\n")
s3.put_object(Bucket="kanat-veri", Key="ham/kanat/gecikmeler/yil=2024/ay=09/gun=01/part-000.csv", Body="x")

yanit = s3.list_objects_v2(Bucket="kanat-veri", Prefix="ham/kanat/ucuslar/", MaxKeys=2)
print([n["Key"].split("/")[-2] for n in yanit["Contents"]], yanit["IsTruncated"])
print(s3.get_object(Bucket="kanat-veri", Key="ham/kanat/ucuslar/yil=2024/ay=09/gun=01/part-000.csv")["Body"].decode())

Data lake yerleşimi ve bölümleme

Data lake (veri gölü), farklı kaynaklardan gelen ham ve işlenmiş verinin nesne depolamada düzenli bir yol şemasıyla tutulmasıdır. Yaygın katman düzeni (madalyon mimarisi olarak da bilinir):

KatmanİçerikKural
ham/ (bronze)Kaynaktan geldiği gibiAsla değiştirilmez; yeniden işleme kaynağı
temiz/ (silver)Tür dönüşümü, tekilleştirme, şema doğrulamaGenellikle Parquet
mart/ (gold)İş için özetlenmiş tablolarPanolar ve analistler okur

Her katmanda veri tarihe göre bölümlenir (partitioning). Hive biçimi (yil=2024/ay=09/gun=01) sorgu motorlarının (Athena, BigQuery, Spark, DuckDB) yalnızca ilgili klasörleri okumasını sağlar (partition pruning): "Eylül verisi" sorgusu diğer ayları hiç açmaz. Ay ve gün iki haneli yazılmalıdır, yoksa metin sıralaması bozulur (ay=10 < ay=9).

from datetime import date

def lake_yolu(katman, kaynak, tablo, gun, parca=0):
    if katman not in {"ham", "temiz", "mart"}:
        raise ValueError(f"bilinmeyen katman: {katman}")
    return f"{katman}/{kaynak}/{tablo}/yil={gun.year}/ay={gun.month:02d}/gun={gun.day:02d}/part-{parca:03d}.csv"

print(lake_yolu("ham", "kanat", "ucuslar", date(2024, 9, 1)))
print(sorted(["ay=9", "ay=10", "ay=11"]), sorted(["ay=09", "ay=10", "ay=11"]))

IAM ve en az yetki

IAM (Identity and Access Management) "kim, hangi kaynakta, hangi işlemi yapabilir?" sorusunu yönetir. Yetki JSON politikalarıyla verilir. Değerlendirme kuralı basittir: varsayılan olarak her şey yasaktır; bir Allow izin verir; açık bir Deny her zaman kazanır.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "HamKatmaniOku",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::kanat-veri/ham/kanat/*"
    },
    {
      "Sid": "MartKatmaninaYaz",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::kanat-veri/mart/*"
    }
  ]
}

En az yetki ilkesi (least privilege): her kimlik yalnızca işini yapmak için gereken eylemlere, yalnızca gereken kaynaklarda sahip olmalı. "Action": "s3:*" ve "Resource": "*" görmek kod incelemesinde (code review) kırmızı bayraktır.

Erişim anahtarı (access key)Rol (role)
Nedir?Kalıcı kullanıcı adı/parola çiftiGeçici kimlik bilgisi veren kimlik
SüreSilinene kadar geçerliDakikalar/saatler sonra kendiliğinden düşer
RiskKoda, git'e, loga sızabilirSızsa bile kısa ömürlü
KullanımMümkünse hiç; zorunluysa kasada (secrets manager)EC2, Lambda, konteyner, CI (OIDC) için standart

boto3 kimlik bilgisini sırayla ortam değişkenlerinden, ~/.aws profilinden ve çalıştığı makinenin rolünden arar. Koda asla anahtar yazma; yazdıysan anahtarı hemen iptal et (git geçmişinden silmek yetmez).

İmzalı URL (presigned URL)

Bir müşteriye özel bir faturayı indirtmek için kovayı herkese açmak yerine süreli, imzalı bir bağlantı üretirsin: s3.generate_presigned_url("get_object", Params={...}, ExpiresIn=900). URL; nesne anahtarı, son kullanma zamanı ve gizli anahtarla üretilmiş bir HMAC imzası içerir. Sunucu imzayı yeniden hesaplar, eşleşmiyorsa ya da süre geçtiyse reddeder. Mantığı standart kütüphaneyle:

import hashlib
import hmac

GIZLI = b"sunucuda-saklanan-gizli-anahtar"

def imzala(anahtar, bitis):
    mesaj = f"{anahtar}|{bitis}".encode()
    return hmac.new(GIZLI, mesaj, hashlib.sha256).hexdigest()[:16]

bitis = 1_735_700_000 + 900          # simdi + 15 dakika (epoch saniye)
imza = imzala("fatura/2024/12/F-1001.pdf", bitis)
print(f"https://kanat-veri.s3.amazonaws.com/fatura/2024/12/F-1001.pdf?expires={bitis}&sig={imza}")
print(hmac.compare_digest(imza, imzala("fatura/2024/12/F-1001.pdf", bitis)),
      hmac.compare_digest(imza, imzala("fatura/2024/12/F-1002.pdf", bitis)))

Serverless fonksiyon ve olay tetikleyici

Serverless (sunucusuz) fonksiyonda yalnız bir fonksiyon yazarsın; sağlayıcı onu bir olay geldiğinde çalıştırır, milisaniye başına ücret alır, boşta maliyet sıfırdır. Tipik veri kullanımı: ham/'a yeni dosya düşünce S3 bir olay üretir, Lambda dosyayı okuyup doğrular ve temiz/'e yazar. Sınırları: çalışma süresi sınırı (AWS Lambda'da 15 dakika), soğuk başlangıç (cold start) gecikmesi, sınırlı bellek/disk. Uzun işler için konteyner (Fargate, Cloud Run) ya da toplu iş (batch) hizmetleri seçilir.

import json, urllib.parse, boto3

s3 = boto3.client("s3")

def handler(event, context):
    for kayit in event["Records"]:
        kova = kayit["s3"]["bucket"]["name"]
        anahtar = urllib.parse.unquote_plus(kayit["s3"]["object"]["key"])   # olaydaki anahtar URL kodludur!
        govde = s3.get_object(Bucket=kova, Key=anahtar)["Body"].read()
        # ... doğrula, dönüştür ...
        s3.put_object(Bucket=kova, Key=anahtar.replace("ham/", "temiz/", 1), Body=govde)
    return {"statusCode": 200, "body": json.dumps({"islenen": len(event["Records"])})}

Yönetilen veritabanı ve ambar

Kendi sunucunda PostgreSQL kurmak yerine yönetilen hizmet (RDS, Cloud SQL) yedekleme, yama, çoğaltma ve AZ'ler arası yedekliliği üstlenir; sen şema ve sorguyla ilgilenirsin (postgresql-python dersi). Analitik için BigQuery (sunucusuz, taranan bayt başına ücret), Snowflake (depolama ve hesaplama ayrı ölçeklenir) ve Redshift gibi ambarlar kullanılır; dbt modelleri (dbt-elt) bu ambarlarda çalışır. Ortak tasarım ilkesi: depolama ve hesaplamanın ayrılması. Veri ucuz nesne depolamada durur, sorgu anında hesaplama kiralanır.

Maliyet tuzakları

Bulut faturası birkaç kalemden oluşur: depolama (GB-ay), istek sayısı (her PUT/GET/LIST), dışarı çıkan trafik (egress: buluttan internete ya da başka bölgeye veri aktarımı) ve hesaplama süresi. En sık sürprizler:

# Temsili birim fiyatlar (gerçek fiyatlar bölgeye ve zamana göre değişir; sağlayıcının fiyat sayfasına bak)
FIYAT = {"depolama_gb_ay": 0.023, "put_1000": 0.005, "get_1000": 0.0004, "cikis_gb": 0.09}

def aylik(gb, put, get, cikis_gb):
    return round(gb * FIYAT["depolama_gb_ay"] + put / 1000 * FIYAT["put_1000"]
                 + get / 1000 * FIYAT["get_1000"] + cikis_gb * FIYAT["cikis_gb"], 2)

# Aynı 500 GB veri: saatte 3600 küçük dosya mı, saatte 1 büyük dosya mı?
print("küçük dosyalar:", aylik(500, 3600 * 24 * 30, 3600 * 24 * 30 * 5, 50), "$")
print("birleştirilmiş:", aylik(500, 24 * 30, 24 * 30 * 5, 50), "$")

Altyapıyı kod olarak yönetmek

Konsolda tıklayarak kurulan altyapı tekrarlanamaz ve denetlenemez. IaC (Infrastructure as Code) araçlarında (Terraform, Pulumi, AWS CDK) kovalar, roller ve politikalar dosyada tanımlanır, kod incelemesinden geçer ve plan ile değişiklik önceden görülür.

# main.tf
resource "aws_s3_bucket" "veri" {
  bucket = "kanat-veri"
  tags   = { ekip = "veri", ortam = "prod" }
}
resource "aws_s3_bucket_lifecycle_configuration" "ham" {
  bucket = aws_s3_bucket.veri.id
  rule {
    id     = "ham-soguk"
    status = "Enabled"
    filter { prefix = "ham/" }
    transition { days = 90  storage_class = "GLACIER" }
  }
}

Test: sahte bulut

Birim testlerinde gerçek buluta gitmek yavaş, pahalı ve kırılgandır. moto kütüphanesi boto3 çağrılarını bellek içinde karşılar; alıştırmalardaki SahteS3 ile aynı fikir. Entegrasyon testleri için LocalStack gibi yerel emülatörler de vardır.

import boto3
from moto import mock_aws

@mock_aws
def test_rapor_mart_katmanina_yazilir():
    s3 = boto3.client("s3", region_name="eu-central-1")
    s3.create_bucket(Bucket="kanat-veri", CreateBucketConfiguration={"LocationConstraint": "eu-central-1"})
    rapor_uret(s3, "kanat-veri", "2024-09-01")              # test edilen fonksiyon
    anahtarlar = [o["Key"] for o in s3.list_objects_v2(Bucket="kanat-veri", Prefix="mart/")["Contents"]]
    assert anahtarlar == ["mart/kanat/gunluk/yil=2024/ay=09/gun=01/rapor.json"]
KVKK ve veri yerleşimiKişisel veriyi yurt dışındaki bir bölgeye aktarmak KVKK kapsamında ek şartlara bağlıdır. Bölge seçimi teknik değil hukuki bir karardır: hangi verinin (kimlik, sağlık, finans) hangi bölgede tutulabileceğini hukuk ve bilgi güvenliği ekipleriyle birlikte belirle, kişisel veriyi mümkünse maskele ya da takma adlandır (pseudonymization) ve şifreleme anahtarlarının yönetimini planla.
Gerçek işteBulut olaylarının çoğu karmaşık saldırılar değil, basit yapılandırma hatalarıdır: herkese açık bırakılmış bir kova, git'e itilmiş bir erişim anahtarı, s3:* yetkili bir uygulama rolü. Kıdemli ekipler üç kuralı otomatikleştirir: hesap düzeyinde "herkese açık erişimi engelle" ayarı, uzun ömürlü anahtar yerine rol ve OIDC, ve her kaynağa sahibini ve maliyet merkezini gösteren etiket. Faturayı her ay ekip bazında incelemek de mühendisliğin parçasıdır (FinOps).
Derinleş

Azure ve BigQuery karşılıkları

Türkiye'deki ilanlarda Azure ve BigQuery sık geçer. Kavramlar aynı, adlar farklıdır. İstemciler ağ ve hesap ister; tarayıcıda çalışmaz.

KavramAWSAzureGoogle Cloud
Nesne depolamaS3Blob StorageCloud Storage (GCS)
Veri ambarıRedshiftSynapse, FabricBigQuery
Veri taşıma ve zamanlamaGlue, Step FunctionsData FactoryDataflow, Composer
Python kütüphanesiboto3azure-storage-blobgoogle-cloud-storage, google-cloud-bigquery
Uzmanlaş

Tablo biçimleri

Data lake'te düz Parquet dosyalarının üstüne Apache Iceberg, Delta Lake ve Hudi gibi açık tablo biçimleri ACID işlem, şema evrimi, zamanda yolculuk (eski sürümü sorgulama) ve satır düzeyinde güncelleme ekler. "Lakehouse" mimarisi bunların üstüne kurulur.

Tutarlılık ve idempotent yazma

S3 bugün yazdığını hemen okuyabildiğin güçlü tutarlılık sunar; ama olay tetikleyicileri en az bir kez (at-least-once) teslim edilir. Aynı olay iki kez gelebilir; Lambda'n aynı girdiyle iki kez çalışınca aynı sonucu üretmeli (deterministik çıktı anahtarı, üstüne yazma).

Ağ ve şifreleme

VPC (sanal özel ağ), alt ağlar ve güvenlik grupları veritabanını internetten ayırır; VPC endpoint ile S3 trafiği internete çıkmadan gider (hem güvenlik hem çıkış maliyeti). Kovalarda varsayılan şifreleme açık olmalı; hassas veride anahtarı kendin yönettiğin KMS anahtarı kullan.

Gözlemlenebilirlik

CloudWatch, Cloud Logging gibi hizmetler logları ve metrikleri toplar; S3 erişim logları ve CloudTrail "kim, ne zaman, neyi okudu?" sorusunu yanıtlar. Denetim izi KVKK açısından da gereklidir.

Alıştırmalara geç (22)