DEV Community

Cover image for Amazon S3 — Sécurité, performances et infrastructure as code (Partie 2)
sylvainbruas for AWS Community Builders

Posted on Originally published at sylvain.bruas.fr

Amazon S3 — Sécurité, performances et infrastructure as code (Partie 2)

Amazon S3 — Sécurité, performances et infrastructure as code

Dans la première partie de cet article, nous avons exploré Amazon Simple Storage Service (S3) en profondeur : sa présentation, ses cas d'usage légitimes et les anti-patterns à éviter. Dans cette seconde partie, nous passons à la pratique : sécurité, optimisation des coûts, performances, architecture événementielle et un exemple complet d'infrastructure as code avec Terraform.


Bonnes pratiques de sécurité

Block Public Access — activé par défaut depuis 2023

Depuis avril 2023, tous les nouveaux buckets ont Block Public Access activé par défaut. C'est la première ligne de défense :

{
  "BlockPublicAcls": true,
  "IgnorePublicAcls": true,
  "BlockPublicPolicy": true,
  "RestrictPublicBuckets": true
}
Enter fullscreen mode Exit fullscreen mode

Règle d'or : Ne désactivez JAMAIS Block Public Access sur un bucket de production. Si vous avez besoin de servir du contenu public, utilisez Amazon CloudFront avec un Origin Access Control (OAC).

Chiffrement

Depuis janvier 2023, tous les nouveaux objets sont chiffrés par défaut avec SSE-S3 (AES-256). Pour plus de contrôle :

Option Contrôle de la clé Audit Coût supplémentaire
SSE-S3 AWS gère tout Limité Gratuit
SSE-KMS Vous contrôlez la clé AWS Key Management Service (KMS) CloudTrail ~0,03 \$/10k requêtes
SSE-C Vous fournissez la clé à chaque requête Total Gratuit (hors gestion)
CSE Chiffrement côté client avant upload Total Gratuit

Recommandation : SSE-KMS avec une clé CMK dédiée par bucket sensible. Cela permet l'audit via AWS CloudTrail et la séparation des responsabilités.

Bucket Policy — Principe du moindre privilège

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::mon-bucket-website/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXXXXXXXXX"
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Versioning et Object Lock

  • Versioning : activez-le toujours sur les buckets critiques. Permet de récupérer un objet supprimé ou écrasé.
  • Object Lock : mode Compliance pour les données réglementaires (WORM — Write Once Read Many). Impossible de supprimer, même pour le root account.
  • MFA Delete : exige une authentification MFA pour supprimer des versions. Protection contre les compromissions de credentials.

Optimisation des coûts

Lifecycle Policies

Les lifecycle policies automatisent la transition entre classes de stockage et la suppression :

{
  "Rules": [
    {
      "ID": "ArchiveLogs",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "logs/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER_IR"
        },
        {
          "Days": 365,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 2555
      },
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 30
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

S3 Intelligent-Tiering

Pour les données dont le pattern d'accès est imprévisible, Intelligent-Tiering déplace automatiquement les objets entre les tiers :

  • Frequent Access : accédé dans les 30 derniers jours
  • Infrequent Access : pas accédé depuis 30 jours (-40% vs Standard)
  • Archive Instant Access : pas accédé depuis 90 jours (-68% vs Standard)
  • Archive Access : pas accédé depuis 90-180 jours (optionnel)
  • Deep Archive Access : pas accédé depuis 180-730 jours (optionnel)

Pas de frais de retrieval — uniquement un coût de monitoring de 0,0025 \$/1000 objets/mois.

S3 Storage Lens

Dashboard gratuit qui analyse votre utilisation S3 à travers tous vos buckets et comptes (via Organizations) :

  • Identification des buckets sans lifecycle policy
  • Détection des objets qui pourraient être dans une classe moins chère
  • Métriques de requêtes pour identifier les hot spots
  • Recommandations de coût automatiques

Performances et optimisation

Partitionnement par préfixe

S3 supporte 5 500 GET/s et 3 500 PUT/s par préfixe. Si vous avez besoin de plus, distribuez vos objets sur plusieurs préfixes :

❌ Mauvais :  s3://bucket/data/file-000001.json → file-999999.json

✅ Bon :      s3://bucket/data/a1/file-000001.json
              s3://bucket/data/b2/file-000002.json
              s3://bucket/data/c3/file-000003.json
Enter fullscreen mode Exit fullscreen mode

Depuis 2018, S3 gère automatiquement le partitionnement pour les nouveaux buckets, mais un bon design de clés reste pertinent pour les charges extrêmes.

Multipart Upload

Pour les fichiers > 100 Mo, utilisez toujours le multipart upload :

  • Upload en parallèle de parties de 5 Mo à 5 Go
  • Reprise en cas d'échec (une seule partie à ré-uploader)
  • Throughput agrégé supérieur à un single PUT
  • Obligatoire au-delà de 5 Go (limite du single PUT)

S3 Transfer Acceleration

Active un endpoint CloudFront pour accélérer les uploads depuis des localisations éloignées de la région du bucket :

Sans acceleration : Client (Tokyo) → Internet → S3 (eu-west-1)  ~200ms
Avec acceleration : Client (Tokyo) → Edge (Tokyo) → Backbone AWS → S3 (eu-west-1)  ~80ms
Enter fullscreen mode Exit fullscreen mode

Coût supplémentaire : 0,04 \$/Go (et 0,08 \$/Go pour les transferts accélérés entre continents).

Byte-Range Fetches

Au lieu de télécharger un fichier entier de 10 Go, demandez uniquement les bytes nécessaires :

GET /big-file.parquet
Range: bytes=0-1048575
Enter fullscreen mode Exit fullscreen mode

Athena et Spark utilisent cette technique pour ne lire que les colonnes nécessaires dans les fichiers Parquet.


Event-driven architecture avec S3

S3 peut déclencher des actions automatiques quand un objet est créé, modifié ou supprimé :

S3 Event Notifications

Destinations supportées :

Exemple d'architecture événementielle

Upload image (PUT) → S3 Event → EventBridge
                                    ├→ Lambda (generate thumbnail)
                                    ├→ Lambda (extract EXIF metadata)
                                    ├→ SQS (virus scan queue)
                                    └→ SNS (notify admin)
Enter fullscreen mode Exit fullscreen mode

Exemple pratique : Infrastructure S3 complète avec Terraform

Nous allons créer une infrastructure complète pour un cas réel : un bucket de data lake avec lifecycle policies, chiffrement KMS, réplication cross-region, et notifications EventBridge.

Structure du projet

s3-datalake/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── s3-bucket/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── environments/
    ├── dev.tfvars
    └── prod.tfvars
Enter fullscreen mode Exit fullscreen mode

Module S3 réutilisable (modules/s3-bucket/main.tf)

# modules/s3-bucket/main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# --- KMS Key pour le chiffrement ---
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}
data "aws_region" "current" {}

resource "aws_kms_key" "s3" {
  description             = "KMS key for S3 bucket ${var.bucket_name}"
  deletion_window_in_days = 30
  enable_key_rotation     = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EnableIAMPolicies"
        Effect = "Allow"
        Principal = {
          AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "AllowS3ServiceFeatures"
        Effect = "Allow"
        Principal = {
          Service = "s3.amazonaws.com"
        }
        Action   = ["kms:Decrypt", "kms:GenerateDataKey"]
        Resource = "*"
        Condition = {
          StringEquals = {
            "aws:SourceAccount" = data.aws_caller_identity.current.account_id
            "kms:ViaService"    = "s3.${data.aws_region.current.name}.amazonaws.com"
          }
        }
      }
    ]
  })

  tags = var.tags
}

resource "aws_kms_alias" "s3" {
  name          = "alias/${var.bucket_name}"
  target_key_id = aws_kms_key.s3.key_id
}

# --- Bucket principal ---

resource "aws_s3_bucket" "main" {
  bucket = var.bucket_name

  tags = merge(var.tags, {
    Name        = var.bucket_name
    Environment = var.environment
  })
}

# --- Block Public Access ---

resource "aws_s3_bucket_public_access_block" "main" {
  bucket = aws_s3_bucket.main.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# --- Versioning ---

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id

  versioning_configuration {
    status = "Enabled"
  }
}

# --- Chiffrement par défaut (SSE-KMS) ---

resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
  bucket = aws_s3_bucket.main.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_policy" "main" {
  bucket = aws_s3_bucket.main.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "DenyInsecureTransport"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource  = [aws_s3_bucket.main.arn, "${aws_s3_bucket.main.arn}/*"]
        Condition = {
          Bool = { "aws:SecureTransport" = "false" }
        }
      },
      {
        Sid       = "DenyWrongEncryption"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:PutObject"
        Resource  = "${aws_s3_bucket.main.arn}/*"
        Condition = {
          StringNotEquals = { "s3:x-amz-server-side-encryption" = "aws:kms" }
          Null            = { "s3:x-amz-server-side-encryption" = "false" }
        }
      }
    ]
  })

  depends_on = [aws_s3_bucket_public_access_block.main]
}

# --- Lifecycle Rules ---

resource "aws_s3_bucket_lifecycle_configuration" "main" {
  bucket = aws_s3_bucket.main.id

  # Transition des données raw vers IA après 30 jours
  rule {
    id     = "transition-raw-data"
    status = "Enabled"

    filter {
      prefix = "raw/"
    }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }

    transition {
      days          = 90
      storage_class = "GLACIER_IR"
    }

    transition {
      days          = 365
      storage_class = "DEEP_ARCHIVE"
    }

    expiration {
      days = var.raw_data_retention_days
    }
  }

  # Nettoyage des versions obsolètes
  rule {
    id     = "cleanup-noncurrent-versions"
    status = "Enabled"

    filter {}

    noncurrent_version_transition {
      noncurrent_days = 30
      storage_class   = "STANDARD_IA"
    }

    noncurrent_version_expiration {
      noncurrent_days = 90
    }
  }

  # Abandon des multipart uploads incomplets
  rule {
    id     = "abort-incomplete-multipart"
    status = "Enabled"

    filter {}

    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }

  depends_on = [aws_s3_bucket_versioning.main]
}



# --- EventBridge Notifications ---

resource "aws_s3_bucket_notification" "main" {
  bucket      = aws_s3_bucket.main.id
  eventbridge = true
}
# --- Replication Configuration (Cross-Region) ---

resource "aws_s3_bucket_replication_configuration" "main" {
  count = var.enable_replication ? 1 : 0

  bucket = aws_s3_bucket.main.id
  role   = aws_iam_role.replication[0].arn

  rule {
    id     = "replicate-all"
    status = "Enabled"

    filter {}

    destination {
      bucket        = var.replication_destination_bucket_arn
      storage_class = "STANDARD_IA"

      encryption_configuration {
        replica_kms_key_id = var.replication_kms_key_arn
      }
    }

    source_selection_criteria {
      sse_kms_encrypted_objects {
        status = "Enabled"
      }
    }

    delete_marker_replication {
      status = "Enabled"
    }
  }

  depends_on = [aws_s3_bucket_versioning.main]
}

# --- IAM Role pour la réplication ---

resource "aws_iam_role" "replication" {
  count = var.enable_replication ? 1 : 0

  name = "${var.bucket_name}-replication-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Action    = "sts:AssumeRole"
        Principal = { Service = "s3.amazonaws.com" }
      }
    ]
  })

  tags = var.tags
}

resource "aws_iam_role_policy" "replication" {
  count = var.enable_replication ? 1 : 0

  name = "${var.bucket_name}-replication-policy"
  role = aws_iam_role.replication[0].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:GetReplicationConfiguration", "s3:ListBucket"]
        Resource = aws_s3_bucket.main.arn
      },
      {
        Effect = "Allow"
        Action = [
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectVersionTagging"
        ]
        Resource = "${aws_s3_bucket.main.arn}/*"
      },
      {
        Effect   = "Allow"
        Action   = ["s3:ReplicateObject", "s3:ReplicateDelete", "s3:ReplicateTags"]
        Resource = "${var.replication_destination_bucket_arn}/*"
      },
      {
        Effect   = "Allow"
        Action   = "kms:Decrypt"
        Resource = aws_kms_key.s3.arn
      },
      {
        Effect   = "Allow"
        Action   = "kms:Encrypt"
        Resource = var.replication_kms_key_arn
      }
    ]
  })
}

output "bucket_id" {
  value = aws_s3_bucket.main.id
}

output "bucket_name" {
  value = aws_s3_bucket.main.bucket
}

output "bucket_arn" {
  value = aws_s3_bucket.main.arn
}

output "kms_key_arn" {
  value = aws_kms_key.s3.arn
}
Enter fullscreen mode Exit fullscreen mode

Variables du module (modules/s3-bucket/variables.tf)

# modules/s3-bucket/variables.tf
variable "bucket_name" {
  description = "Nom du bucket S3"
  type        = string

  validation {
    condition     = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.bucket_name))
    error_message = "Minuscules, chiffres et tirets uniquement, 3 à 63 caractères."
  }
}

variable "environment" {
  description = "Environnement (dev, staging, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "L'environnement doit être dev, staging ou prod."
  }
}

variable "raw_data_retention_days" {
  description = "Rétention des données sous raw/ avant suppression"
  type        = number
  default     = 2555

  validation {
    condition     = var.raw_data_retention_days > 365
    error_message = "La rétention doit dépasser 365 jours (dernière transition)."
  }
}

variable "enable_replication" {
  description = "Activer la réplication cross-region depuis ce bucket"
  type        = bool
  default     = false
}

variable "replication_destination_bucket_arn" {
  description = "ARN du bucket de destination (région différente)"
  type        = string
  default     = ""
}

variable "replication_kms_key_arn" {
  description = "ARN de la clé KMS du bucket de destination"
  type        = string
  default     = ""
}

variable "tags" {
  description = "Tags additionnels"
  type        = map(string)
  default     = {}
}
Enter fullscreen mode Exit fullscreen mode

Fichier principal (main.tf)

# main.tf
terraform {
  required_version = ">= 1.9"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.80"
    }
  }
}

provider "aws" {
  region = var.region

  default_tags {
    tags = {
      Project   = "DataLake"
      ManagedBy = "Terraform"
      Owner     = "data-team"
    }
  }
}

provider "aws" {
  alias  = "replica"
  region = var.replica_region

  default_tags {
    tags = {
      Project   = "DataLake"
      ManagedBy = "Terraform"
      Owner     = "data-team"
    }
  }
}

variable "region" {
  type    = string
  default = "eu-west-1"
}

variable "replica_region" {
  type    = string
  default = "eu-central-1"
}

variable "project_name" {
  description = "Préfixe des ressources"
  type        = string
}

variable "environment" {
  type    = string
  default = "dev"
}

variable "data_retention_days" {
  type    = number
  default = 2555
}

module "datalake_replica" {
  source = "./modules/s3-bucket"

  providers = {
    aws = aws.replica
  }

  bucket_name             = "${var.project_name}-${var.environment}-datalake-replica"
  environment             = var.environment
  raw_data_retention_days = var.data_retention_days

  tags = {
    Purpose = "Data Lake replica"
  }
}

module "datalake_bucket" {
  source = "./modules/s3-bucket"

  bucket_name             = "${var.project_name}-${var.environment}-datalake"
  environment             = var.environment
  raw_data_retention_days = var.data_retention_days

  enable_replication                 = true
  replication_destination_bucket_arn = module.datalake_replica.bucket_arn
  replication_kms_key_arn            = module.datalake_replica.kms_key_arn

  tags = {
    Purpose    = "Data Lake"
    DataClass  = "Confidential"
    CostCenter = "data-engineering"
  }
}

resource "aws_cloudwatch_event_rule" "s3_object_created" {
  name        = "${var.project_name}-s3-object-created"
  description = "Déclenché quand un objet est créé sous raw/ dans le data lake"

  event_pattern = jsonencode({
    source        = ["aws.s3"]
    "detail-type" = ["Object Created"]
    detail = {
      bucket = {
        name = [module.datalake_bucket.bucket_name]
      }
      object = {
        key = [{ prefix = "raw/" }]
      }
    }
  })
}

resource "aws_sqs_queue" "raw_object_events" {
  name                      = "${var.project_name}-raw-object-events"
  message_retention_seconds = 345600
  sqs_managed_sse_enabled   = true
}

resource "aws_cloudwatch_event_target" "raw_object_events" {
  rule      = aws_cloudwatch_event_rule.s3_object_created.name
  target_id = "RawObjectEvents"
  arn       = aws_sqs_queue.raw_object_events.arn
}

resource "aws_sqs_queue_policy" "raw_object_events" {
  queue_url = aws_sqs_queue.raw_object_events.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = { Service = "events.amazonaws.com" }
        Action    = "sqs:SendMessage"
        Resource  = aws_sqs_queue.raw_object_events.arn
        Condition = {
          ArnEquals = { "aws:SourceArn" = aws_cloudwatch_event_rule.s3_object_created.arn }
        }
      }
    ]
  })
}

output "datalake_bucket_name" {
  value = module.datalake_bucket.bucket_name
}

output "datalake_kms_key_arn" {
  value = module.datalake_bucket.kms_key_arn
}

output "replica_bucket_name" {
  value = module.datalake_replica.bucket_name
}

output "raw_object_events_queue_url" {
  value = aws_sqs_queue.raw_object_events.id
}

Enter fullscreen mode Exit fullscreen mode

Monitoring et alertes

Métriques Amazon CloudWatch essentielles pour S3

# monitoring.tf

resource "aws_sns_topic" "alarms" {
  name = "${var.project_name}-s3-alarms"
}

resource "aws_s3_bucket_metric" "datalake_entire_bucket" {
  bucket = module.datalake_bucket.bucket_id
  name   = "EntireBucket"
}

resource "aws_cloudwatch_metric_alarm" "s3_4xx_errors" {
  alarm_name          = "${var.project_name}-s3-4xx-errors"
  alarm_description   = "Trop d'erreurs 4xx sur le bucket S3"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  threshold           = 100

  namespace   = "AWS/S3"
  metric_name = "4xxErrors"
  period      = 300
  statistic   = "Sum"

  dimensions = {
    BucketName = module.datalake_bucket.bucket_name
    FilterId   = aws_s3_bucket_metric.datalake_entire_bucket.name
  }

  treat_missing_data = "notBreaching"

  alarm_actions = [aws_sns_topic.alarms.arn]
  ok_actions    = [aws_sns_topic.alarms.arn]
}

resource "aws_cloudwatch_metric_alarm" "s3_bucket_size" {
  alarm_name          = "${var.project_name}-s3-bucket-size"
  alarm_description   = "Le volume en classe Standard dépasse 1 To"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  threshold           = 1099511627776

  namespace   = "AWS/S3"
  metric_name = "BucketSizeBytes"
  period      = 86400
  statistic   = "Maximum"

  dimensions = {
    BucketName  = module.datalake_bucket.bucket_name
    StorageType = "StandardStorage"
  }

  treat_missing_data = "missing"

  alarm_actions = [aws_sns_topic.alarms.arn]
}
Enter fullscreen mode Exit fullscreen mode

Checklist de production

Avant de mettre un bucket S3 en production, vérifiez systématiquement ces points :

Sécurité

  • [ ] Block Public Access activé
  • [ ] Chiffrement SSE-KMS avec clé dédiée
  • [ ] Bucket policy avec moindre privilège
  • [ ] Versioning activé sur les données critiques
  • [ ] MFA Delete activé sur les buckets sensibles
  • [ ] Access logging activé vers un bucket dédié
  • [ ] Pas de wildcard * dans les Principal des policies

Coûts

  • [ ] Lifecycle policies configurées
  • [ ] Intelligent-Tiering activé si pattern d'accès incertain
  • [ ] Abort incomplete multipart uploads (7 jours max)
  • [ ] Noncurrent version expiration configurée
  • [ ] S3 Storage Lens activé au niveau Organization

Résilience

  • [ ] Réplication cross-region pour les données critiques
  • [ ] Object Lock en mode Compliance si requis réglementairement
  • [ ] Versioning pour permettre le rollback
  • [ ] Backup cross-account via AWS Backup

Performance

  • [ ] Préfixes bien distribués pour les charges > 5 000 req/s
  • [ ] Transfer Acceleration si uploads internationaux
  • [ ] Multipart upload configuré pour fichiers > 100 Mo
  • [ ] CloudFront devant le bucket pour les lectures publiques

Observabilité

  • [ ] Métriques CloudWatch configurées (4xx, 5xx, latence)
  • [ ] Alertes sur la taille du bucket et le nombre d'objets
  • [ ] S3 Storage Lens avec métriques avancées
  • [ ] CloudTrail Data Events activé si audit requis

Tarification — Comprendre sa facture S3

La facturation S3 repose sur 4 axes :

1. Stockage (Go/mois)

  • S3 Standard : 0,023 \$/Go (eu-west-1)
  • S3 Standard-IA : 0,0125 \$/Go
  • S3 Glacier IR : 0,004 \$/Go
  • S3 Glacier Deep Archive : 0,00099 \$/Go

2. Requêtes

  • PUT/COPY/POST/LIST : 0,005 \$/1 000 requêtes (Standard)
  • GET/SELECT : 0,0004 \$/1 000 requêtes (Standard)
  • Lifecycle transitions : 0,01 \$/1 000 transitions

3. Transfert de données

  • Upload (IN) : gratuit
  • Download vers Internet : 0,09 \$/Go (premiers 10 To/mois)
  • Download vers CloudFront : gratuit
  • Download vers même région : gratuit
  • Cross-region replication : 0,02 \$/Go

4. Fonctionnalités optionnelles

  • S3 Inventory : 0,0025 \$/million d'objets
  • S3 Analytics : 0,10 \$/million d'objets analysés
  • Object Lock : pas de surcoût
  • Versioning : vous payez le stockage de chaque version

Astuce FinOps : Le poste le plus souvent sous-estimé est le coût des requêtes LIST et GET sur des millions de petits fichiers. 1 million de GET = 0,40 \$. Si un job Spark fait 100M de GET/jour, ça représente 40 \$/jour soit ~1 200 \$/mois juste en requêtes.


FAQ

S3 est-il adapté pour stocker des fichiers de moins de 128 Ko ?

Oui, mais attention au overhead. Chaque objet a un minimum de facturation de 128 Ko pour les classes IA et Glacier. Pour S3 Standard, il n'y a pas de minimum. Si vous avez des millions de fichiers {'<'} 128 Ko, restez en Standard ou regroupez-les (batching).

Peut-on renommer un objet S3 ?

Non. S3 n'a pas d'opération rename. Pour "renommer", il faut copier l'objet avec la nouvelle clé puis supprimer l'ancien. Pour les gros fichiers, cela implique un multipart copy.

Quelle est la différence entre S3 et EBS ?

S3 est du stockage objet (accès via HTTP/API), EBS est du stockage block (accès via une instance EC2, comme un disque dur). S3 est pour les fichiers, EBS est pour les systèmes de fichiers et bases de données.

Comment sécuriser un bucket qui doit être accessible publiquement ?

Ne rendez jamais le bucket lui-même public. Utilisez CloudFront avec un Origin Access Control (OAC). Le bucket reste privé, CloudFront est le seul à pouvoir y accéder.

S3 Express One Zone vs S3 Standard — quand choisir ?

S3 Express One Zone offre une latence ~10x inférieure (single-digit ms) mais coûte 3x plus cher et ne réplique pas entre AZ. Utilisez-le uniquement pour les workloads qui nécessitent une latence minimale ET où la perte de données est acceptable (cache, données recalculables).


Conclusion

Amazon S3 est bien plus qu'un simple service de stockage. C'est un écosystème complet qui sert de fondation à la majorité des architectures AWS modernes. Sa durabilité exceptionnelle, sa scalabilité infinie et son intégration native avec l'ensemble des services AWS en font un choix incontournable.

Les clés pour bien utiliser S3 :

  1. Pensez en termes de lifecycle — chaque objet a un cycle de vie, automatisez les transitions
  2. Sécurisez par défaut — Block Public Access, KMS, bucket policies restrictives
  3. Optimisez les coûts — Intelligent-Tiering, lifecycle policies, Storage Lens
  4. Architecturez en événementiel — utilisez les notifications S3 + EventBridge pour déclencher des traitements
  5. Monitorez — CloudWatch, Storage Lens, CloudTrail Data Events

Dans le prochain article, nous explorerons AWS Lambda, le service de compute serverless qui s'intègre parfaitement avec S3 pour construire des architectures event-driven.

Top comments (0)