Skip to content
English
On this page

Ejercicio: Prestashop 8 en AWS con EFS y Aurora

Escenario

Implementaremos una tienda Prestashop 8 con:

  • EFS para almacenamiento compartido
  • Aurora MySQL para base de datos
  • Auto Scaling Group para alta disponibilidad
  • CloudFront para CDN
  • WAF para seguridad
  • ElastiCache para sesiones y caché
  • Route 53 para DNS
  • ACM para certificados SSL

Estructura del Proyecto

plaintext
prestashop-aws/
├── infrastructure/
│   ├── terraform/
│   │   ├── modules/
│   │   │   ├── efs/
│   │   │   ├── aurora/
│   │   │   ├── elasticache/
│   │   │   ├── asg/
│   │   │   ├── waf/
│   │   │   └── cdn/
│   │   │
│   │   └── environments/
│   │       ├── dev/
│   │       ├── stg/
│   │       └── prod/
│   │
│   ├── ansible/
│   │   ├── roles/
│   │   │   ├── php/
│   │   │   ├── nginx/
│   │   │   ├── prestashop/
│   │   │   └── monitoring/
│   │   │
│   │   └── playbooks/
│   │       ├── setup.yml
│   │       └── deploy.yml
│   │
│   └── scripts/
│       ├── backup/
│       ├── monitoring/
│       └── maintenance/

├── config/
│   ├── nginx/
│   │   └── prestashop.conf
│   │
│   ├── php/
│   │   └── php.ini
│   │
│   └── prestashop/
│       └── parameters.php

├── docker/
│   ├── php/
│   │   └── Dockerfile
│   │
│   └── nginx/
│       └── Dockerfile

└── scripts/
    ├── deploy.sh
    ├── backup.sh
    └── monitor.sh

Etapas del Ejercicio

Etapa 1: Infraestructura Base

  • VPC y Networking
  • EFS Setup
  • Aurora MySQL Setup
  • ElastiCache Configuration

Etapa 2: Capa de Aplicación

  • Auto Scaling Group
  • Launch Template
  • Application Load Balancer
  • Instancia Base con Prestashop

Etapa 3: CDN y Seguridad

  • CloudFront Distribution
  • WAF Rules
  • ACM Certificates
  • Route 53 Configuration

Etapa 4: Prestashop y Optimización

  • Prestashop Installation
  • Performance Tuning
  • Cache Configuration
  • Media Storage Setup

Etapa 5: Monitoreo y Mantenimiento

  • CloudWatch Dashboards
  • Backup Strategy
  • Maintenance Scripts
  • Alerting Setup

Etapa 6: CI/CD y Pruebas

  • Pipeline Setup
  • Environment Configurations
  • Testing Strategy
  • Deployment Automation

Etapa 1: Infraestructura Base

Objetivos:

  1. Configurar VPC y Networking
  2. Implementar EFS
  3. Configurar Aurora MySQL
  4. Establecer ElastiCache

1.1 VPC y Networking

hcl
# infrastructure/terraform/modules/network/main.tf

variable "environment" {
  type = string
}

variable "vpc_cidr" {
  type = string
}

locals {
  azs = ["${data.aws_region.current.name}a", "${data.aws_region.current.name}b", "${data.aws_region.current.name}c"]
}

resource "aws_vpc" "prestashop" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "prestashop-vpc-${var.environment}"
    Environment = var.environment
  }
}

# Subnets públicas para ALB
resource "aws_subnet" "public" {
  count             = 3
  vpc_id            = aws_vpc.prestashop.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 4, count.index)
  availability_zone = local.azs[count.index]

  tags = {
    Name        = "prestashop-public-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

# Subnets privadas para aplicación
resource "aws_subnet" "private_app" {
  count             = 3
  vpc_id            = aws_vpc.prestashop.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 4, count.index + 3)
  availability_zone = local.azs[count.index]

  tags = {
    Name        = "prestashop-private-app-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

# Subnets privadas para base de datos
resource "aws_subnet" "private_db" {
  count             = 3
  vpc_id            = aws_vpc.prestashop.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 4, count.index + 6)
  availability_zone = local.azs[count.index]

  tags = {
    Name        = "prestashop-private-db-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.prestashop.id

  tags = {
    Name        = "prestashop-igw-${var.environment}"
    Environment = var.environment
  }
}

# NAT Gateways
resource "aws_eip" "nat" {
  count = 3
  vpc   = true

  tags = {
    Name        = "prestashop-nat-eip-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_nat_gateway" "main" {
  count         = 3
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name        = "prestashop-nat-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

# Route Tables
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.prestashop.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name        = "prestashop-public-rt-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_route_table" "private" {
  count  = 3
  vpc_id = aws_vpc.prestashop.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[count.index].id
  }

  tags = {
    Name        = "prestashop-private-rt-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

1.2 Configuración EFS

hcl
# infrastructure/terraform/modules/efs/main.tf

resource "aws_efs_file_system" "prestashop" {
  creation_token = "prestashop-${var.environment}"
  encrypted      = true

  lifecycle_policy {
    transition_to_ia = "AFTER_30_DAYS"
  }

  performance_mode = "generalPurpose"
  throughput_mode  = "bursting"

  tags = {
    Name        = "prestashop-efs-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_efs_mount_target" "prestashop" {
  count           = length(var.private_subnet_ids)
  file_system_id  = aws_efs_file_system.prestashop.id
  subnet_id       = var.private_subnet_ids[count.index]
  security_groups = [aws_security_group.efs.id]
}

resource "aws_security_group" "efs" {
  name        = "prestashop-efs-${var.environment}"
  description = "Security group for PrestaShop EFS"
  vpc_id      = var.vpc_id

  ingress {
    description     = "NFS from EC2"
    from_port       = 2049
    to_port         = 2049
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "prestashop-efs-sg-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_efs_backup_policy" "policy" {
  file_system_id = aws_efs_file_system.prestashop.id

  backup_policy {
    status = "ENABLED"
  }
}

1.3 Aurora MySQL

hcl
# infrastructure/terraform/modules/aurora/main.tf

resource "aws_rds_cluster" "prestashop" {
  cluster_identifier     = "prestashop-${var.environment}"
  engine                = "aurora-mysql"
  engine_version        = "8.0.mysql_aurora.3.03.0"
  database_name         = "prestashop"
  master_username       = var.db_username
  master_password       = var.db_password
  skip_final_snapshot   = var.environment != "prod"
  
  vpc_security_group_ids = [aws_security_group.aurora.id]
  db_subnet_group_name   = aws_db_subnet_group.aurora.name
  
  backup_retention_period = var.environment == "prod" ? 30 : 7
  preferred_backup_window = "03:00-04:00"
  
  enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]
  
  serverlessv2_scaling_configuration {
    min_capacity = var.environment == "prod" ? 1.0 : 0.5
    max_capacity = var.environment == "prod" ? 8.0 : 2.0
  }

  tags = {
    Name        = "prestashop-aurora-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_rds_cluster_instance" "prestashop" {
  count               = var.environment == "prod" ? 2 : 1
  identifier          = "prestashop-${var.environment}-${count.index + 1}"
  cluster_identifier  = aws_rds_cluster.prestashop.id
  instance_class     = "db.serverless"
  engine             = aws_rds_cluster.prestashop.engine
  engine_version     = aws_rds_cluster.prestashop.engine_version

  performance_insights_enabled = true
  monitoring_interval         = 60
  
  tags = {
    Name        = "prestashop-aurora-instance-${count.index + 1}-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_security_group" "aurora" {
  name        = "prestashop-aurora-${var.environment}"
  description = "Security group for PrestaShop Aurora cluster"
  vpc_id      = var.vpc_id

  ingress {
    description     = "MySQL from EC2"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  tags = {
    Name        = "prestashop-aurora-sg-${var.environment}"
    Environment = var.environment
  }
}

1.4 ElastiCache

hcl
# infrastructure/terraform/modules/elasticache/main.tf

resource "aws_elasticache_cluster" "prestashop" {
  cluster_id           = "prestashop-${var.environment}"
  engine              = "redis"
  node_type           = var.environment == "prod" ? "cache.t3.medium" : "cache.t3.micro"
  num_cache_nodes     = 1
  parameter_group_family = "redis6.x"
  port                = 6379
  
  subnet_group_name    = aws_elasticache_subnet_group.prestashop.name
  security_group_ids   = [aws_security_group.elasticache.id]
  
  maintenance_window   = "mon:01:00-mon:02:00"
  snapshot_window     = "02:00-03:00"
  snapshot_retention_period = var.environment == "prod" ? 7 : 1

  automatic_failover_enabled = var.environment == "prod"
  
  tags = {
    Name        = "prestashop-redis-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_elasticache_subnet_group" "prestashop" {
  name       = "prestashop-cache-subnet-${var.environment}"
  subnet_ids = var.private_subnet_ids
}

resource "aws_security_group" "elasticache" {
  name        = "prestashop-elasticache-${var.environment}"
  description = "Security group for PrestaShop ElastiCache"
  vpc_id      = var.vpc_id

  ingress {
    description     = "Redis from EC2"
    from_port       = 6379
    to_port         = 6379
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  tags = {
    Name        = "prestashop-elasticache-sg-${var.environment}"
    Environment = var.environment
  }
}

Verificación de la Etapa 1

Checklist:

  • [ ] VPC creada y configurada
  • [ ] Subnets públicas y privadas creadas
  • [ ] EFS montado y accesible
  • [ ] Aurora MySQL configurado
  • [ ] ElastiCache funcionando
  • [ ] Security Groups configurados
  • [ ] Permisos IAM establecidos

Pruebas Recomendadas:

  1. Verificar conectividad entre subnets
  2. Probar montaje de EFS
  3. Validar conexión a Aurora
  4. Comprobar acceso a ElastiCache

Troubleshooting Común:

  1. Problemas de VPC:

    • Verificar tablas de ruteo
    • Comprobar NAT Gateways
    • Validar CIDR blocks
  2. Errores de EFS:

    • Revisar mount targets
    • Verificar security groups
    • Comprobar permisos
  3. Problemas de Aurora:

    • Verificar credenciales
    • Revisar security groups
    • Comprobar subnets
  4. Errores de ElastiCache:

    • Verificar subnet group
    • Revisar security groups
    • Comprobar parámetros

Etapa 2: Capa de Aplicación

Objetivos:

  1. Configurar Launch Template
  2. Implementar Auto Scaling Group
  3. Configurar Application Load Balancer
  4. Preparar instancia base de Prestashop

2.1 Launch Template

hcl
# infrastructure/terraform/modules/asg/launch_template.tf

resource "aws_launch_template" "prestashop" {
  name_prefix   = "prestashop-template-${var.environment}"
  image_id      = var.ami_id
  instance_type = var.environment == "prod" ? "t3.medium" : "t3.small"

  network_interfaces {
    associate_public_ip_address = false
    security_groups            = [aws_security_group.app.id]
  }

  iam_instance_profile {
    name = aws_iam_instance_profile.prestashop.name
  }

  user_data = base64encode(templatefile("${path.module}/scripts/user_data.sh", {
    environment     = var.environment
    efs_id         = var.efs_id
    region         = data.aws_region.current.name
    db_host        = var.db_endpoint
    db_name        = var.db_name
    db_user        = var.db_username
    db_password    = var.db_password
    redis_endpoint = var.redis_endpoint
  }))

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
      volume_size           = 20
      volume_type          = "gp3"
      delete_on_termination = true
      encrypted            = true
    }
  }

  monitoring {
    enabled = true
  }

  metadata_options {
    http_endpoint          = "enabled"
    http_tokens           = "required"
    instance_metadata_tags = "enabled"
  }

  tag_specifications {
    resource_type = "instance"
    tags = {
      Name        = "prestashop-instance-${var.environment}"
      Environment = var.environment
    }
  }

  lifecycle {
    create_before_destroy = true
  }
}

# User data script
# scripts/user_data.sh
#!/bin/bash
yum update -y
yum install -y amazon-efs-utils nginx php-fpm

# Montar EFS
mkdir -p /var/www/prestashop
echo "${efs_id}:/ /var/www/prestashop efs _netdev,tls,iam 0 0" >> /etc/fstab
mount -a

# Configurar PHP
sed -i 's/memory_limit = 128M/memory_limit = 256M/' /etc/php.ini
sed -i 's/max_execution_time = 30/max_execution_time = 300/' /etc/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 64M/' /etc/php.ini

# Configurar Nginx
cat > /etc/nginx/conf.d/prestashop.conf << 'EOL'
server {
    listen 80;
    server_name _;
    root /var/www/prestashop;
    
    client_max_body_size 64m;
    
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
EOL

# Iniciar servicios
systemctl enable nginx php-fpm
systemctl start nginx php-fpm

# Configurar CloudWatch agent
yum install -y amazon-cloudwatch-agent
cat > /opt/aws/amazon-cloudwatch-agent/bin/config.json << 'EOL'
{
  "metrics": {
    "append_dimensions": {
      "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
      "InstanceId": "${aws:InstanceId}"
    },
    "metrics_collected": {
      "disk": {
        "measurement": ["used_percent"],
        "resources": ["/"],
        "metrics_collection_interval": 60
      },
      "mem": {
        "measurement": ["mem_used_percent"],
        "metrics_collection_interval": 60
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/nginx/access.log",
            "log_group_name": "/prestashop/${environment}/nginx/access",
            "log_stream_name": "{instance_id}"
          },
          {
            "file_path": "/var/log/nginx/error.log",
            "log_group_name": "/prestashop/${environment}/nginx/error",
            "log_stream_name": "{instance_id}"
          },
          {
            "file_path": "/var/log/php-fpm/error.log",
            "log_group_name": "/prestashop/${environment}/php-fpm/error",
            "log_stream_name": "{instance_id}"
          }
        ]
      }
    }
  }
}
EOL

systemctl enable amazon-cloudwatch-agent
systemctl start amazon-cloudwatch-agent

2.2 Auto Scaling Group

hcl
# infrastructure/terraform/modules/asg/asg.tf

resource "aws_autoscaling_group" "prestashop" {
  name                = "prestashop-asg-${var.environment}"
  desired_capacity    = var.environment == "prod" ? 2 : 1
  max_size           = var.environment == "prod" ? 4 : 2
  min_size           = var.environment == "prod" ? 2 : 1
  target_group_arns  = [aws_lb_target_group.prestashop.arn]
  vpc_zone_identifier = var.private_subnet_ids
  health_check_type  = "ELB"
  health_check_grace_period = 300

  launch_template {
    id      = aws_launch_template.prestashop.id
    version = "$Latest"
  }

  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }

  dynamic "tag" {
    for_each = {
      Name        = "prestashop-instance-${var.environment}"
      Environment = var.environment
    }
    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_policy" "cpu" {
  name                   = "prestashop-cpu-policy-${var.environment}"
  autoscaling_group_name = aws_autoscaling_group.prestashop.name
  policy_type           = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 70.0
  }
}

resource "aws_autoscaling_policy" "requests" {
  name                   = "prestashop-requests-policy-${var.environment}"
  autoscaling_group_name = aws_autoscaling_group.prestashop.name
  policy_type           = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label        = "${aws_lb.prestashop.arn_suffix}/${aws_lb_target_group.prestashop.arn_suffix}"
    }
    target_value = 1000.0
  }
}

2.3 Application Load Balancer

hcl
# infrastructure/terraform/modules/alb/main.tf

resource "aws_lb" "prestashop" {
  name               = "prestashop-alb-${var.environment}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets           = var.public_subnet_ids

  enable_deletion_protection = var.environment == "prod"

  access_logs {
    bucket  = aws_s3_bucket.alb_logs.id
    prefix  = "alb-logs"
    enabled = true
  }

  tags = {
    Name        = "prestashop-alb-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.prestashop.arn
  port             = "80"
  protocol         = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.prestashop.arn
  port             = "443"
  protocol         = "HTTPS"
  ssl_policy       = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn  = var.certificate_arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.prestashop.arn
  }
}

resource "aws_lb_target_group" "prestashop" {
  name     = "prestashop-tg-${var.environment}"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id

  health_check {
    enabled             = true
    healthy_threshold   = 2
    interval            = 30
    matcher            = "200"
    path               = "/healthcheck.php"
    port               = "traffic-port"
    timeout            = 5
    unhealthy_threshold = 2
  }

  stickiness {
    type            = "app_cookie"
    cookie_name     = "PrestaShopSession"
    cookie_duration = 86400
  }

  tags = {
    Name        = "prestashop-tg-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_security_group" "alb" {
  name        = "prestashop-alb-${var.environment}"
  description = "Security group for PrestaShop ALB"
  vpc_id      = var.vpc_id

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "prestashop-alb-sg-${var.environment}"
    Environment = var.environment
  }
}

Verificación de la Etapa 2

Checklist:

  • [ ] Launch Template configurado
  • [ ] Auto Scaling Group funcionando
  • [ ] Load Balancer configurado
  • [ ] Health checks activos
  • [ ] Seguridad configurada
  • [ ] Logs habilitados
  • [ ] Monitoreo configurado

Pruebas Recomendadas:

  1. Validar escalado automático
  2. Probar health checks
  3. Verificar distribución de carga
  4. Comprobar logs

Troubleshooting Común:

  1. Problemas de Launch Template:

    • Verificar user data
    • Revisar permisos IAM
    • Comprobar security groups
  2. Errores de Auto Scaling:

    • Verificar políticas de escalado
    • Revisar límites de capacidad
    • Comprobar health checks
  3. Problemas de Load Balancer:

    • Verificar target groups
    • Revisar security groups
    • Comprobar SSL/TLS
  4. Errores de Instancia:

    • Verificar logs de arranque
    • Revisar montaje EFS
    • Comprobar configuración PHP/Nginx

Etapa 3: CDN y Seguridad

Objetivos:

  1. Configurar CloudFront Distribution
  2. Implementar WAF y reglas de seguridad
  3. Gestionar certificados SSL con ACM
  4. Configurar Route 53

3.1 CloudFront Distribution

hcl
# infrastructure/terraform/modules/cdn/main.tf

resource "aws_cloudfront_distribution" "prestashop" {
  enabled             = true
  is_ipv6_enabled    = true
  comment            = "PrestaShop CDN - ${var.environment}"
  price_class        = var.environment == "prod" ? "PriceClass_All" : "PriceClass_100"
  wait_for_deployment = false

  origin {
    domain_name = var.alb_domain_name
    origin_id   = "ALB"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }

    custom_header {
      name  = "X-Custom-Header"
      value = var.origin_custom_header
    }
  }

  origin {
    domain_name = var.s3_bucket_domain_name
    origin_id   = "S3-Media"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.media.cloudfront_access_identity_path
    }
  }

  # Cache behavior for static assets
  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "ALB"

    forwarded_values {
      query_string = true
      cookies {
        forward = "whitelist"
        whitelisted_names = ["PrestaShop-*"]
      }
      headers = ["Host", "Authorization"]
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
    compress               = true

    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.security_headers.arn
    }
  }

  # Cache behavior for media
  ordered_cache_behavior {
    path_pattern     = "/img/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-Media"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
    compress               = true
  }

  # Cache behavior for admin
  ordered_cache_behavior {
    path_pattern     = "/admin*"
    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "ALB"

    forwarded_values {
      query_string = true
      cookies {
        forward = "all"
      }
      headers = ["*"]
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 0
    max_ttl                = 0
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    acm_certificate_arn      = var.certificate_arn
    minimum_protocol_version = "TLSv1.2_2021"
    ssl_support_method       = "sni-only"
  }

  web_acl_id = var.waf_web_acl_id

  tags = {
    Name        = "prestashop-cdn-${var.environment}"
    Environment = var.environment
  }
}

# CloudFront Function para Security Headers
resource "aws_cloudfront_function" "security_headers" {
  name    = "security-headers-${var.environment}"
  runtime = "cloudfront-js-1.0"
  code    = <<-EOT
function handler(event) {
    var response = event.response;
    var headers = response.headers;

    headers['strict-transport-security'] = { value: 'max-age=31536000; includeSubDomains; preload'};
    headers['x-content-type-options'] = { value: 'nosniff'};
    headers['x-frame-options'] = { value: 'DENY'};
    headers['x-xss-protection'] = { value: '1; mode=block'};
    headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin'};
    headers['content-security-policy'] = {
        value: "default-src 'self' *.prestashop.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googleapis.com *.gstatic.com; style-src 'self' 'unsafe-inline' *.googleapis.com; img-src 'self' data: *.googleapis.com; font-src 'self' *.gstatic.com"
    };

    return response;
}
EOT
}

3.2 WAF Configuration

hcl
# infrastructure/terraform/modules/waf/main.tf

resource "aws_wafv2_web_acl" "prestashop" {
  name        = "prestashop-waf-${var.environment}"
  description = "WAF rules for PrestaShop"
  scope       = "CLOUDFRONT"

  default_action {
    allow {}
  }

  # SQL Injection Protection
  rule {
    name     = "SQLiProtection"
    priority = 1

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesSQLiRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name               = "SQLiProtectionMetric"
      sampled_requests_enabled  = true
    }
  }

  # Common Attack Protection
  rule {
    name     = "CommonAttackProtection"
    priority = 2

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name               = "CommonAttackProtectionMetric"
      sampled_requests_enabled  = true
    }
  }

  # Rate Limiting
  rule {
    name     = "RateLimit"
    priority = 3

    action {
      block {}
    }

    statement {
      rate_based_statement {
        limit              = var.environment == "prod" ? 2000 : 1000
        aggregate_key_type = "IP"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name               = "RateLimitMetric"
      sampled_requests_enabled  = true
    }
  }

  # Admin Path Protection
  rule {
    name     = "AdminProtection"
    priority = 4

    action {
      block {}
    }

    statement {
      and_statement {
        statements = [
          {
            byte_match_statement {
              search_string = "/admin"
              field_to_match {
                uri_path {}
              }
              text_transformation {
                priority = 1
                type     = "LOWERCASE"
              }
              positional_constraint = "STARTS_WITH"
            }
          },
          {
            not_statement {
              statement {
                ip_set_reference_statement {
                  arn = aws_wafv2_ip_set.admin_allowed_ips.arn
                }
              }
            }
          }
        ]
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name               = "AdminProtectionMetric"
      sampled_requests_enabled  = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name               = "PrestaShopWAFMetric"
    sampled_requests_enabled  = true
  }

  tags = {
    Name        = "prestashop-waf-${var.environment}"
    Environment = var.environment
  }
}

# IP Set para acceso admin
resource "aws_wafv2_ip_set" "admin_allowed_ips" {
  name               = "prestashop-admin-ips-${var.environment}"
  description        = "Allowed IPs for admin access"
  scope              = "CLOUDFRONT"
  ip_address_version = "IPV4"
  addresses          = var.admin_allowed_ips

  tags = {
    Name        = "prestashop-admin-ips-${var.environment}"
    Environment = var.environment
  }
}

3.3 Route 53 y ACM

hcl
# infrastructure/terraform/modules/dns/main.tf

resource "aws_acm_certificate" "prestashop" {
  provider = aws.us-east-1  # Certificado debe estar en us-east-1 para CloudFront
  domain_name       = var.domain_name
  validation_method = "DNS"
  subject_alternative_names = [
    "*.${var.domain_name}",
    "*.${var.environment}.${var.domain_name}"
  ]

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name        = "prestashop-cert-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_route53_zone" "primary" {
  count = var.create_zone ? 1 : 0
  name  = var.domain_name

  tags = {
    Name        = "prestashop-zone-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_route53_record" "validation" {
  for_each = {
    for dvo in aws_acm_certificate.prestashop.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = var.zone_id
  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
  ttl     = 60
}

resource "aws_route53_record" "www" {
  zone_id = var.zone_id
  name    = "www.${var.environment}.${var.domain_name}"
  type    = "A"

  alias {
    name                   = var.cloudfront_domain_name
    zone_id               = var.cloudfront_hosted_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "apex" {
  zone_id = var.zone_id
  name    = var.environment == "prod" ? var.domain_name : "${var.environment}.${var.domain_name}"
  type    = "A"

  alias {
    name                   = var.cloudfront_domain_name
    zone_id               = var.cloudfront_hosted_zone_id
    evaluate_target_health = false
  }
}

# Health Check
resource "aws_route53_health_check" "prestashop" {
  fqdn              = var.alb_domain_name
  port              = 443
  type              = "HTTPS"
  resource_path     = "/healthcheck.php"
  failure_threshold = "3"
  request_interval  = "30"

  tags = {
    Name        = "prestashop-health-${var.environment}"
    Environment = var.environment
  }
}

Verificación de la Etapa 3

Checklist:

  • [ ] CloudFront configurado y funcionando
  • [ ] WAF con reglas activas
  • [ ] Certificados SSL generados
  • [ ] DNS configurado
  • [ ] Health checks activos
  • [ ] Seguridad aplicada
  • [ ] Caché optimizado

Pruebas Recomendadas:

  1. Validar distribución de contenido
  2. Probar reglas WAF
  3. Verificar SSL/TLS
  4. Comprobar DNS

Troubleshooting Común:

  1. Problemas de CloudFront:

    • Verificar orígenes
    • Revisar comportamientos de caché
    • Comprobar invalidaciones
  2. Errores de WAF:

    • Verificar reglas
    • Revisar logs
    • Comprobar rate limits
  3. Problemas de SSL:

    • Verificar validación de certificados
    • Revisar configuración SNI
    • Comprobar renovación
  4. Errores de DNS:

    • Verificar propagación
    • Revisar registros
    • Comprobar health checks

Etapa 4: Prestashop y Optimización

Objetivos:

  1. Optimizar instalación Prestashop
  2. Configurar caching y media storage
  3. Optimizar rendimiento
  4. Implementar seguridad adicional

4.1 Prestashop Deployment y Configuración

php
// config/parameters.php
<?php
return [
    'parameters' => [
        'database_host' => getenv('PS_DB_HOST'),
        'database_port' => getenv('PS_DB_PORT'),
        'database_name' => getenv('PS_DB_NAME'),
        'database_user' => getenv('PS_DB_USER'),
        'database_password' => getenv('PS_DB_PASSWD'),
        'database_prefix' => 'ps_',
        'database_engine' => 'InnoDB',
        
        // Cache Configuration
        'cache_enable' => true,
        'cache_system' => 'CacheRedis',
        'redis_host' => getenv('PS_REDIS_HOST'),
        'redis_port' => getenv('PS_REDIS_PORT'),
        'redis_auth' => null,
        'redis_db' => 0,
        
        // Media Configuration
        'ps_media_server_1' => getenv('PS_MEDIA_SERVER'),
        'ps_media_server_2' => '',
        'ps_media_server_3' => '',
        'media_server_1' => true,
        'media_server_2' => false,
        'media_server_3' => false,
        
        // Performance
        'smarty_cache' => true,
        'smarty_caching_type' => 'redis',
        'smarty_clear_cache' => 'everytime',
        'smarty_compilation_check' => false,
        'smarty_force_compile' => false,
        
        // Security
        'cookie_key' => getenv('PS_COOKIE_KEY'),
        'cookie_iv' => getenv('PS_COOKIE_IV'),
        'ps_creation_date' => date('Y-m-d'),
        'ps_ssl_enabled' => true,
        'ps_ssl_enabled_everywhere' => true,
        
        // Debug (environment specific)
        '_PS_MODE_DEV_' => getenv('PS_MODE_DEV') === 'true',
        '_PS_DEBUG_SQL_' => false,
        '_PS_DEBUG_PROFILING_' => false,
    ]
];

4.2 Optimización de Caché

php
// classes/CacheRedis.php
<?php
class CacheRedis extends Cache {
    private $redis;
    private $keys = [];
    private $prefix;

    public function __construct() {
        $this->connect();
        
        // Set prefix based on shop ID for multi-shop setups
        $this->prefix = _PS_VERSION_ . '-' . (defined('_PS_SHOP_ID_') ? _PS_SHOP_ID_ : 1) . '-';
    }

    private function connect() {
        $this->redis = new Redis();
        
        try {
            $this->redis->connect(
                _PS_REDIS_HOST_,
                _PS_REDIS_PORT_,
                2.0
            );
            
            if (_PS_REDIS_AUTH_) {
                $this->redis->auth(_PS_REDIS_AUTH_);
            }
            
            $this->redis->select(_PS_REDIS_DB_);
            
            // Set compression for large values
            $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_LZ4);
            
            // Set serializer
            $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'Redis connection failed: ' . $e->getMessage(),
                3
            );
            return false;
        }
        
        return true;
    }

    public function _set($key, $value, $ttl = 0) {
        $key = $this->prefix . $key;
        $this->keys[] = $key;
        
        if ($ttl < 0) {
            $ttl = 0;
        }
        
        try {
            if ($ttl) {
                return $this->redis->setex($key, $ttl, $value);
            }
            
            return $this->redis->set($key, $value);
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'Redis set failed: ' . $e->getMessage(),
                3
            );
            return false;
        }
    }

    public function _get($key) {
        try {
            return $this->redis->get($this->prefix . $key);
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'Redis get failed: ' . $e->getMessage(),
                3
            );
            return false;
        }
    }

    public function _exists($key) {
        try {
            return (bool)$this->redis->exists($this->prefix . $key);
        } catch (Exception $e) {
            return false;
        }
    }

    public function _delete($key) {
        try {
            return $this->redis->del($this->prefix . $key);
        } catch (Exception $e) {
            return false;
        }
    }

    public function _deleteMulti(array $keys) {
        try {
            $prefixed_keys = array_map(
                function($key) {
                    return $this->prefix . $key;
                },
                $keys
            );
            
            return $this->redis->del($prefixed_keys);
        } catch (Exception $e) {
            return false;
        }
    }

    public function flush() {
        try {
            // Only flush keys with our prefix
            $iterator = null;
            $pattern = $this->prefix . '*';
            
            while ($keys = $this->redis->scan($iterator, $pattern)) {
                foreach ($keys as $key) {
                    $this->redis->del($key);
                }
            }
            
            return true;
        } catch (Exception $e) {
            return false;
        }
    }
}

4.3 Media Storage Integration

php
// classes/storage/PrestaShopAWSStorage.php
<?php
class PrestaShopAWSStorage {
    private $s3;
    private $bucket;
    private $cdn_domain;

    public function __construct() {
        $this->initializeS3();
        $this->bucket = getenv('PS_S3_BUCKET');
        $this->cdn_domain = getenv('PS_CDN_DOMAIN');
    }

    private function initializeS3() {
        try {
            $this->s3 = new Aws\S3\S3Client([
                'version' => 'latest',
                'region'  => getenv('PS_S3_REGION'),
                'credentials' => [
                    'key'    => getenv('PS_S3_ACCESS_KEY'),
                    'secret' => getenv('PS_S3_SECRET_KEY'),
                ]
            ]);
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'S3 initialization failed: ' . $e->getMessage(),
                3
            );
        }
    }

    public function upload($source, $destination) {
        try {
            $result = $this->s3->putObject([
                'Bucket' => $this->bucket,
                'Key'    => $destination,
                'Body'   => fopen($source, 'rb'),
                'ACL'    => 'public-read',
                'CacheControl' => 'max-age=31536000',
                'ContentType' => mime_content_type($source),
            ]);

            return $result['ObjectURL'];
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                'S3 upload failed: ' . $e->getMessage(),
                3
            );
            return false;
        }
    }

    public function delete($path) {
        try {
            $this->s3->deleteObject([
                'Bucket' => $this->bucket,
                'Key'    => $path,
            ]);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    public function getUrl($path) {
        if ($this->cdn_domain) {
            return 'https://' . $this->cdn_domain . '/' . $path;
        }

        try {
            $command = $this->s3->getCommand('GetObject', [
                'Bucket' => $this->bucket,
                'Key'    => $path
            ]);

            $request = $this->s3->createPresignedRequest($command, '+1 hour');
            return (string)$request->getUri();
        } catch (Exception $e) {
            return false;
        }
    }

    public function exists($path) {
        try {
            return $this->s3->doesObjectExist($this->bucket, $path);
        } catch (Exception $e) {
            return false;
        }
    }
}

4.4 Performance Optimization

javascript
// modules/psperformance/config.js
module.exports = {
    // Configuración de compresión de imágenes
    images: {
        quality: {
            jpg: 85,
            png: 85,
            webp: 80
        },
        sizes: {
            thumbnail: { width: 100, height: 100 },
            small: { width: 300, height: 300 },
            medium: { width: 600, height: 600 },
            large: { width: 1200, height: 1200 }
        },
        formats: ['jpg', 'webp']
    },

    // Configuración de caché de páginas
    pageCache: {
        enabled: true,
        ttl: 3600,
        excludedUrls: [
            '/cart',
            '/order',
            '/my-account'
        ],
        varyBy: [
            'currency',
            'language',
            'customer_group'
        ]
    },

    // Configuración de minificación
    minification: {
        css: {
            enabled: true,
            excludeFiles: [
                'custom.css'
            ]
        },
        js: {
            enabled: true,
            excludeFiles: [
                'custom.js'
            ]
        }
    },

    // Configuración de lazy loading
    lazyLoading: {
        enabled: true,
        threshold: 0.1,
        elements: [
            'img',
            'iframe'
        ]
    },

    // Configuración de crítico CSS
    criticalCss: {
        enabled: true,
        routes: [
            'index',
            'category',
            'product'
        ]
    },

    // Configuración de caché del navegador
    browserCache: {
        static: {
            maxAge: 31536000,
            patterns: [
                '*.css',
                '*.js',
                '*.jpg',
                '*.png',
                '*.webp'
            ]
        },
        dynamic: {
            maxAge: 3600,
            patterns: [
                '*.html',
                '*/ajax/*'
            ]
        }
    }
};

Verificación de la Etapa 4

Checklist:

  • [ ] Prestashop configurado correctamente
  • [ ] Caché Redis funcionando
  • [ ] Media Storage configurado
  • [ ] Optimizaciones implementadas
  • [ ] Seguridad reforzada
  • [ ] Performance optimizada
  • [ ] Logs funcionando

Pruebas Recomendadas:

  1. Verificar rendimiento de caché
  2. Probar carga de medios
  3. Validar optimizaciones
  4. Comprobar seguridad

Troubleshooting Común:

  1. Problemas de Caché:

    • Verificar conexión Redis
    • Revisar configuración
    • Comprobar TTLs
  2. Errores de Media:

    • Verificar permisos S3
    • Revisar CDN
    • Comprobar rutas
  3. Problemas de Performance:

    • Revisar configuración
    • Validar compresión
    • Comprobar lazy loading
  4. Errores de Prestashop:

    • Verificar logs
    • Revisar permisos
    • Comprobar módulos

Etapa 5: Monitoreo y Mantenimiento

Objetivos:

  1. Implementar monitoreo completo
  2. Configurar alertas y dashboards
  3. Establecer rutinas de mantenimiento
  4. Configurar backups automáticos

5.1 CloudWatch Configuration

yaml
# infrastructure/terraform/modules/monitoring/main.tf

# Dashboard principal
resource "aws_cloudwatch_dashboard" "prestashop" {
  dashboard_name = "prestashop-${var.environment}"
  
  dashboard_body = jsonencode({
    widgets = [
      {
        type = "metric"
        x    = 0
        y    = 0
        width = 12
        height = 6
        
        properties = {
          metrics = [
            ["AWS/EC2", "CPUUtilization", "AutoScalingGroupName", var.asg_name],
            [".", "MemoryUtilization", ".", "."],
            [".", "DiskSpaceUtilization", ".", "."]
          ]
          period = 300
          stat   = "Average"
          region = var.aws_region
          title  = "EC2 Metrics"
        }
      },
      {
        type = "metric"
        x    = 12
        y    = 0
        width = 12
        height = 6
        
        properties = {
          metrics = [
            ["AWS/ApplicationELB", "RequestCount", "LoadBalancer", var.alb_name],
            [".", "HTTPCode_Target_4XX_Count", ".", "."],
            [".", "HTTPCode_Target_5XX_Count", ".", "."]
          ]
          period = 300
          stat   = "Sum"
          region = var.aws_region
          title  = "ALB Metrics"
        }
      },
      {
        type = "metric"
        x    = 0
        y    = 6
        width = 12
        height = 6
        
        properties = {
          metrics = [
            ["AWS/RDS", "CPUUtilization", "DBClusterIdentifier", var.db_cluster_id],
            [".", "FreeableMemory", ".", "."],
            [".", "DatabaseConnections", ".", "."]
          ]
          period = 300
          stat   = "Average"
          region = var.aws_region
          title  = "RDS Metrics"
        }
      },
      {
        type = "metric"
        x    = 12
        y    = 6
        width = 12
        height = 6
        
        properties = {
          metrics = [
            ["AWS/EFS", "BurstCreditBalance", "FileSystemId", var.efs_id],
            [".", "PermittedThroughput", ".", "."],
            [".", "TotalIOBytes", ".", "."]
          ]
          period = 300
          stat   = "Average"
          region = var.aws_region
          title  = "EFS Metrics"
        }
      }
    ]
  })
}

# Alarmas principales
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
  alarm_name          = "prestashop-high-cpu-${var.environment}"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "300"
  statistic           = "Average"
  threshold           = "80"
  alarm_description   = "Monitor high CPU utilization"
  alarm_actions      = [aws_sns_topic.alerts.arn]
  dimensions = {
    AutoScalingGroupName = var.asg_name
  }
}

resource "aws_cloudwatch_metric_alarm" "error_rate" {
  alarm_name          = "prestashop-error-rate-${var.environment}"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "HTTPCode_Target_5XX_Count"
  namespace           = "AWS/ApplicationELB"
  period              = "300"
  statistic           = "Sum"
  threshold           = "10"
  alarm_description   = "Monitor error rate"
  alarm_actions      = [aws_sns_topic.alerts.arn]
  dimensions = {
    LoadBalancer = var.alb_name
  }
}

# Log groups
resource "aws_cloudwatch_log_group" "prestashop" {
  name              = "/prestashop/${var.environment}"
  retention_in_days = var.environment == "prod" ? 90 : 30
  
  tags = {
    Environment = var.environment
    Application = "prestashop"
  }
}

# Métricas personalizadas
resource "aws_cloudwatch_metric_alarm" "order_processing_time" {
  alarm_name          = "prestashop-order-processing-${var.environment}"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "OrderProcessingTime"
  namespace           = "Prestashop/Orders"
  period              = "300"
  statistic           = "Average"
  threshold           = "30"
  alarm_description   = "Monitor order processing time"
  alarm_actions      = [aws_sns_topic.alerts.arn]
}

5.2 Scripts de Mantenimiento

bash
#!/bin/bash
# scripts/maintenance/daily_tasks.sh

set -e

# Variables
ENVIRONMENT=$1
APP_PATH="/var/www/prestashop"
BACKUP_BUCKET="prestashop-backups-${ENVIRONMENT}"
DATE=$(date +%Y-%m-%d)
LOG_FILE="/var/log/prestashop/maintenance.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

# Backup de base de datos
backup_database() {
    log "Starting database backup..."
    
    mysqldump \
        --single-transaction \
        --routines \
        --triggers \
        --events \
        --skip-lock-tables \
        prestashop > "/tmp/prestashop_${DATE}.sql"
        
    gzip "/tmp/prestashop_${DATE}.sql"
    
    aws s3 cp \
        "/tmp/prestashop_${DATE}.sql.gz" \
        "s3://${BACKUP_BUCKET}/database/${DATE}/"
        
    rm "/tmp/prestashop_${DATE}.sql.gz"
    
    log "Database backup completed"
}

# Limpieza de caché
clean_cache() {
    log "Starting cache cleanup..."
    
    redis-cli FLUSHDB
    php $APP_PATH/bin/console cache:clear
    
    log "Cache cleanup completed"
}

# Optimización de imágenes
optimize_images() {
    log "Starting image optimization..."
    
    find $APP_PATH/img -type f \( -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" \) -mtime -1 | while read img; do
        if [[ "$img" == *.png ]]; then
            pngquant --force --quality=80-95 "$img"
        else
            jpegoptim --max=85 "$img"
        fi
    done
    
    log "Image optimization completed"
}

# Verificación de integridad
check_integrity() {
    log "Starting integrity check..."
    
    php $APP_PATH/bin/console prestashop:module verify-integrity
    
    log "Integrity check completed"
}

# Limpieza de logs
clean_logs() {
    log "Starting log cleanup..."
    
    find /var/log/prestashop -type f -name "*.log" -mtime +30 -delete
    find $APP_PATH/var/logs -type f -name "*.log" -mtime +30 -delete
    
    log "Log cleanup completed"
}

# Rotación de backups
rotate_backups() {
    log "Starting backup rotation..."
    
    # Mantener últimos 30 días para desarrollo/staging
    if [ "$ENVIRONMENT" != "prod" ]; then
        aws s3 ls "s3://${BACKUP_BUCKET}/database/" | \
        sort -r | \
        tail -n +31 | \
        while read -r line; do
            aws s3 rm "s3://${BACKUP_BUCKET}/database/$(echo $line | awk '{print $2}')"
        done
    fi
    
    log "Backup rotation completed"
}

# Ejecución principal
main() {
    log "Starting maintenance tasks for environment: $ENVIRONMENT"
    
    backup_database
    clean_cache
    optimize_images
    check_integrity
    clean_logs
    rotate_backups
    
    log "All maintenance tasks completed successfully"
}

# Ejecutar script
if [[ ! "$ENVIRONMENT" =~ ^(dev|stg|prod)$ ]]; then
    log "ERROR: Invalid environment. Use: dev, stg, or prod"
    exit 1
fi

main

5.3 Custom Metrics Collection

php
// modules/ps_metrics/MetricsCollector.php
<?php
class MetricsCollector {
    private $cloudwatch;
    private $namespace;
    private $environment;

    public function __construct() {
        $this->environment = getenv('PS_ENVIRONMENT');
        $this->namespace = "Prestashop/{$this->environment}";
        
        $this->cloudwatch = new Aws\CloudWatch\CloudWatchClient([
            'version' => 'latest',
            'region'  => getenv('AWS_REGION')
        ]);
    }

    public function publishMetrics($metricData) {
        try {
            $dimensions = [
                [
                    'Name' => 'Environment',
                    'Value' => $this->environment
                ]
            ];

            $metrics = [];
            foreach ($metricData as $metric => $value) {
                $metrics[] = [
                    'MetricName' => $metric,
                    'Value'      => $value,
                    'Unit'       => $this->determineUnit($metric),
                    'Dimensions' => $dimensions,
                    'Timestamp'  => time()
                ];
            }

            $this->cloudwatch->putMetricData([
                'Namespace'  => $this->namespace,
                'MetricData' => $metrics
            ]);

            return true;
        } catch (Exception $e) {
            PrestaShopLogger::addLog(
                "Error publishing metrics: {$e->getMessage()}",
                3
            );
            return false;
        }
    }

    private function determineUnit($metric) {
        $unitMap = [
            'OrderTotal' => 'Count',
            'OrderProcessingTime' => 'Milliseconds',
            'CartAbandonment' => 'Count',
            'PageLoadTime' => 'Milliseconds',
            'ApiLatency' => 'Milliseconds',
            'ActiveUsers' => 'Count',
            'ProductViews' => 'Count',
            'SearchQueries' => 'Count'
        ];

        return isset($unitMap[$metric]) ? $unitMap[$metric] : 'None';
    }

    public function collectPerformanceMetrics() {
        return [
            'PageLoadTime' => $this->measurePageLoadTime(),
            'ApiLatency' => $this->measureApiLatency(),
            'ActiveUsers' => $this->countActiveUsers(),
            'DatabaseQueries' => $this->countDatabaseQueries()
        ];
    }

    public function collectBusinessMetrics() {
        return [
            'OrderTotal' => $this->countTotalOrders(),
            'CartAbandonment' => $this->calculateCartAbandonment(),
            'ProductViews' => $this->countProductViews(),
            'ConversionRate' => $this->calculateConversionRate()
        ];
    }

    private function measurePageLoadTime() {
        $startTime = defined('_PS_PAGE_START_TIME_') ? _PS_PAGE_START_TIME_ : 0;
        return $startTime ? (microtime(true) - $startTime) * 1000 : 0;
    }

    private function countActiveUsers() {
        return Db::getInstance()->getValue('
            SELECT COUNT(DISTINCT id_guest)
            FROM ' . _DB_PREFIX_ . 'connections
            WHERE date_add > DATE_SUB(NOW(), INTERVAL 15 MINUTE)
        ');
    }

    private function calculateCartAbandonment() {
        $sql = '
            SELECT 
                COUNT(DISTINCT c.id_cart) as abandoned,
                COUNT(DISTINCT o.id_order) as converted
            FROM ' . _DB_PREFIX_ . 'cart c
            LEFT JOIN ' . _DB_PREFIX_ . 'orders o 
                ON c.id_cart = o.id_cart
            WHERE c.date_add > DATE_SUB(NOW(), INTERVAL 24 HOUR)
        ';

        $result = Db::getInstance()->getRow($sql);
        return [
            'abandoned' => (int)$result['abandoned'],
            'converted' => (int)$result['converted']
        ];
    }
}

Verificación de la Etapa 5

Checklist:

  • [ ] Monitoreo configurado
  • [ ] Alarmas activas
  • [ ] Scripts de mantenimiento funcionando
  • [ ] Métricas personalizadas recolectándose
  • [ ] Dashboards creados
  • [ ] Backup automatizado
  • [ ] Rotación de logs

Pruebas Recomendadas:

  1. Verificar alertas
  2. Probar backups
  3. Validar métricas
  4. Comprobar mantenimiento

Troubleshooting Común:

  1. Problemas de Monitoreo:

    • Verificar permisos IAM
    • Revisar métricas
    • Comprobar logs
  2. Errores de Backup:

    • Verificar espacio
    • Revisar permisos
    • Comprobar rotación
  3. Problemas de Métricas:

    • Verificar recolección
    • Revisar dashboards
    • Comprobar alarmas
  4. Errores de Mantenimiento:

    • Verificar scripts
    • Revisar schedules
    • Comprobar logs

Etapa 6: CI/CD y Pruebas

Objetivos:

  1. Configurar pipeline CI/CD
  2. Implementar pruebas automatizadas
  3. Establecer despliegue seguro
  4. Gestionar entornos múltiples

6.1 CodePipeline Configuration

yaml
# infrastructure/terraform/modules/cicd/main.tf

resource "aws_codepipeline" "prestashop" {
  name     = "prestashop-pipeline-${var.environment}"
  role_arn = aws_iam_role.codepipeline_role.arn

  artifact_store {
    location = aws_s3_bucket.artifacts.id
    type     = "S3"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName = aws_codecommit_repository.prestashop.repository_name
        BranchName     = var.environment
      }
    }
  }

  stage {
    name = "Build"

    action {
      name            = "Test"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        ProjectName = aws_codebuild_project.test.name
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeploy"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        ApplicationName = aws_codedeploy_app.prestashop.name
        DeploymentGroupName = aws_codedeploy_deployment_group.prestashop.deployment_group_name
      }
    }
  }

  stage {
    name = "Validation"

    action {
      name            = "IntegrationTests"
      category        = "Test"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        ProjectName = aws_codebuild_project.integration.name
      }
    }
  }
}

# Buildspec para pruebas
resource "aws_codebuild_project" "test" {
  name          = "prestashop-tests-${var.environment}"
  description   = "Run PrestaShop tests"
  build_timeout = "30"
  service_role  = aws_iam_role.codebuild_role.arn

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                      = "aws/codebuild/standard:5.0"
    type                       = "LINUX_CONTAINER"
    privileged_mode            = true
    image_pull_credentials_type = "CODEBUILD"

    environment_variable {
      name  = "ENVIRONMENT"
      value = var.environment
    }
  }

  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec.test.yml"
  }

  cache {
    type  = "LOCAL"
    modes = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_SOURCE_CACHE"]
  }
}

# Configuración de despliegue
resource "aws_codedeploy_app" "prestashop" {
  name = "prestashop-${var.environment}"
}

resource "aws_codedeploy_deployment_group" "prestashop" {
  app_name               = aws_codedeploy_app.prestashop.name
  deployment_group_name  = "prestashop-${var.environment}"
  service_role_arn      = aws_iam_role.codedeploy_role.arn

  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }

  blue_green_deployment_config {
    deployment_ready_option {
      action_on_timeout = "CONTINUE_DEPLOYMENT"
    }

    terminate_blue_instances_on_deployment_success {
      action                           = "TERMINATE"
      termination_wait_time_in_minutes = 5
    }
  }

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }

  alarm_configuration {
    alarms = ["prestashop-deployment-alarm"]
  }
}

6.2 Test Suites

php
// tests/TestSuite.php
<?php
namespace Tests;

use PHPUnit\Framework\TestCase;
use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopId;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

abstract class TestSuite extends WebTestCase
{
    protected static $container;
    protected static $kernel;
    protected static $client;
    protected static $entityManager;

    public static function setUpBeforeClass(): void
    {
        parent::setUpBeforeClass();
        self::$kernel = static::createKernel();
        self::$kernel->boot();
        self::$container = self::$kernel->getContainer();
        self::$client = static::createClient();
        self::$entityManager = self::$container->get('doctrine.orm.entity_manager');
    }

    protected function setUp(): void
    {
        parent::setUp();
        self::$entityManager->beginTransaction();
    }

    protected function tearDown(): void
    {
        self::$entityManager->rollback();
        parent::tearDown();
    }

    protected function createProduct($data = [])
    {
        $productRepository = self::$container->get('prestashop.core.api.product.repository');
        
        $defaultData = [
            'name' => 'Test Product',
            'price' => 19.99,
            'reference' => 'TEST-' . uniqid(),
            'active' => true,
            'shopId' => new ShopId(1)
        ];

        $productData = array_merge($defaultData, $data);
        return $productRepository->create($productData);
    }

    protected function createCategory($data = [])
    {
        $categoryRepository = self::$container->get('prestashop.core.api.category.repository');
        
        $defaultData = [
            'name' => 'Test Category',
            'active' => true,
            'shopId' => new ShopId(1)
        ];

        $categoryData = array_merge($defaultData, $data);
        return $categoryRepository->create($categoryData);
    }

    protected function createOrder($customer, $products)
    {
        $orderRepository = self::$container->get('prestashop.core.api.order.repository');
        
        return $orderRepository->create([
            'customerId' => $customer->getId(),
            'products' => $products,
            'paymentMethod' => 'test',
            'status' => 'awaiting'
        ]);
    }
}

// tests/Integration/ProductTest.php
class ProductTest extends TestSuite
{
    public function testProductCreation()
    {
        $product = $this->createProduct([
            'name' => 'Test Product Integration',
            'price' => 29.99
        ]);

        $this->assertNotNull($product->getId());
        $this->assertEquals('Test Product Integration', $product->getName());
        $this->assertEquals(29.99, $product->getPrice());
    }

    public function testProductUpdate()
    {
        $product = $this->createProduct();
        
        $productRepository = self::$container->get('prestashop.core.api.product.repository');
        $productRepository->update($product->getId(), [
            'price' => 39.99
        ]);

        $updatedProduct = $productRepository->find($product->getId());
        $this->assertEquals(39.99, $updatedProduct->getPrice());
    }
}

// tests/Unit/CartCalculationTest.php
class CartCalculationTest extends TestCase
{
    private $cartCalculator;

    protected function setUp(): void
    {
        $this->cartCalculator = new CartCalculator();
    }

    public function testTotalCalculation()
    {
        $items = [
            ['price' => 10.00, 'quantity' => 2, 'tax_rate' => 0.20],
            ['price' => 15.00, 'quantity' => 1, 'tax_rate' => 0.20]
        ];

        $expected = 42.00; // (10 * 2 * 1.2) + (15 * 1 * 1.2)
        $result = $this->cartCalculator->calculateTotal($items);

        $this->assertEquals($expected, $result);
    }
}

6.3 Deployment Scripts

bash
#!/bin/bash
# scripts/deploy/prestashop-deploy.sh

set -e

ENVIRONMENT=$1
DEPLOYMENT_ID=$(date +%Y%m%d%H%M%S)
APP_PATH="/var/www/prestashop"
BACKUP_PATH="/var/www/backups"
MAINTENANCE_FILE="${APP_PATH}/maintenance.enable"

# Funciones de utilidad
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

enable_maintenance() {
    log "Enabling maintenance mode..."
    touch $MAINTENANCE_FILE
}

disable_maintenance() {
    log "Disabling maintenance mode..."
    rm -f $MAINTENANCE_FILE
}

create_backup() {
    log "Creating backup..."
    BACKUP_DIR="${BACKUP_PATH}/${DEPLOYMENT_ID}"
    mkdir -p $BACKUP_DIR
    
    # Backup files
    tar -czf "${BACKUP_DIR}/files.tar.gz" -C $APP_PATH .
    
    # Backup database
    mysqldump prestashop > "${BACKUP_DIR}/database.sql"
}

validate_deployment() {
    log "Validating deployment..."
    
    # Verificar archivos críticos
    for file in "config/parameters.php" "index.php" "classes/db/Db.php"; do
        if [ ! -f "${APP_PATH}/${file}" ]; then
            log "ERROR: Critical file missing: ${file}"
            return 1
        fi
    }
    
    # Verificar permisos
    find $APP_PATH -type f -exec chmod 644 {} \;
    find $APP_PATH -type d -exec chmod 755 {} \;
    chown -R www-data:www-data $APP_PATH
    
    # Verificar conexión a base de datos
    php -r "
        require '${APP_PATH}/config/config.inc.php';
        \$db = Db::getInstance();
        if (!\$db->connect()) {
            exit(1);
        }
    "
    
    if [ $? -ne 0 ]; then
        log "ERROR: Database connection failed"
        return 1
    }
    
    return 0
}

rollback() {
    log "Rolling back deployment..."
    
    BACKUP_DIR="${BACKUP_PATH}/${DEPLOYMENT_ID}"
    
    # Restaurar archivos
    rm -rf $APP_PATH/*
    tar -xzf "${BACKUP_DIR}/files.tar.gz" -C $APP_PATH
    
    # Restaurar base de datos
    mysql prestashop < "${BACKUP_DIR}/database.sql"
    
    # Limpiar caché
    rm -rf $APP_PATH/var/cache/*
    
    log "Rollback completed"
}

deploy() {
    log "Starting deployment for environment: $ENVIRONMENT"
    
    # Crear backup
    create_backup
    
    # Activar modo mantenimiento
    enable_maintenance
    
    # Desplegar nueva versión
    log "Deploying new version..."
    rsync -av --delete /tmp/prestashop-release/ $APP_PATH/
    
    # Actualizar composer
    composer install --no-dev --optimize-autoloader
    
    # Limpiar caché
    rm -rf $APP_PATH/var/cache/*
    php bin/console cache:clear
    
    # Ejecutar migraciones si existen
    if [ -d "${APP_PATH}/upgrades" ]; then
        php bin/console prestashop:upgrade --no-interaction
    fi
    
    # Validar despliegue
    if ! validate_deployment; then
        log "Deployment validation failed, rolling back..."
        rollback
        exit 1
    fi
    
    # Desactivar modo mantenimiento
    disable_maintenance
    
    log "Deployment completed successfully"
}

# Ejecución principal
if [[ ! "$ENVIRONMENT" =~ ^(dev|stg|prod)$ ]]; then
    log "ERROR: Invalid environment. Use: dev, stg, or prod"
    exit 1
fi

# Ejecutar despliegue
deploy

6.4 Monitoreo Post-Despliegue

python
# scripts/monitoring/post_deploy_check.py

import boto3
import requests
import time
from datetime import datetime, timedelta

class PostDeploymentMonitor:
    def __init__(self, environment, region):
        self.environment = environment
        self.cloudwatch = boto3.client('cloudwatch', region_name=region)
        self.sns = boto3.client('sns', region_name=region)
        self.base_url = f"https://{environment}.yourdomain.com"
        
    def run_checks(self):
        """Ejecuta todas las verificaciones post-despliegue"""
        checks = [
            self.check_application_health(),
            self.check_error_rates(),
            self.check_response_times(),
            self.check_database_connections()
        ]
        
        return all(checks)
    
    def check_application_health(self):
        """Verifica el estado general de la aplicación"""
        try:
            response = requests.get(f"{self.base_url}/health-check")
            return response.status_code == 200
        except Exception as e:
            self.alert(f"Health check failed: {str(e)}")
            return False    
    

    def check_response_times(self):
        """Monitorea tiempos de respuesta post-despliegue"""
        end_time = datetime.utcnow()
        start_time = end_time - timedelta(minutes=15)
        
        response = self.cloudwatch.get_metric_statistics(
            Namespace="AWS/ApplicationELB",
            MetricName="TargetResponseTime",
            Dimensions=[
                {
                    'Name': 'LoadBalancer',
                    'Value': f'prestashop-{self.environment}'
                }
            ],
            StartTime=start_time,
            EndTime=end_time,
            Period=300,
            Statistics=['Average']
        )
        
        if response['Datapoints']:
            avg_response_time = sum(point['Average'] for point in response['Datapoints']) / len(response['Datapoints'])
            if avg_response_time > 2.0:  # 2 segundos
                self.alert(f"High response times detected: {avg_response_time:.2f} seconds")
                return False
        return True

    def check_database_connections(self):
        """Verifica conexiones a la base de datos"""
        response = self.cloudwatch.get_metric_statistics(
            Namespace="AWS/RDS",
            MetricName="DatabaseConnections",
            Dimensions=[
                {
                    'Name': 'DBClusterIdentifier',
                    'Value': f'prestashop-{self.environment}'
                }
            ],
            StartTime=datetime.utcnow() - timedelta(minutes=15),
            EndTime=datetime.utcnow(),
            Period=300,
            Statistics=['Average']
        )
        
        if response['Datapoints']:
            avg_connections = response['Datapoints'][0]['Average']
            if avg_connections > 80:  # 80% del máximo
                self.alert(f"High database connection count: {avg_connections}")
                return False
        return True

    def alert(self, message):
        """Envía alertas a través de SNS"""
        try:
            self.sns.publish(
                TopicArn=f"arn:aws:sns:{self.region}:{self.account_id}:prestashop-alerts-{self.environment}",
                Message=message,
                Subject=f"Prestashop Post-Deployment Alert - {self.environment}"
            )
        except Exception as e:
            print(f"Failed to send alert: {str(e)}")

class DeploymentValidator:
    def __init__(self, environment, region):
        self.monitor = PostDeploymentMonitor(environment, region)
        self.codedeploy = boto3.client('codedeploy', region_name=region)
        self.environment = environment

    def validate_deployment(self, deployment_id):
        """Valida un despliegue completo"""
        # Esperar a que el despliegue esté completo
        self.wait_for_deployment(deployment_id)
        
        # Ejecutar verificaciones post-despliegue
        checks_passed = self.monitor.run_checks()
        
        if not checks_passed:
            # Iniciar rollback si las verificaciones fallan
            self.initiate_rollback(deployment_id)
            return False
            
        return True

    def wait_for_deployment(self, deployment_id):
        """Espera a que el despliegue se complete"""
        while True:
            response = self.codedeploy.get_deployment(
                deploymentId=deployment_id
            )
            
            status = response['deploymentInfo']['status']
            if status == 'Succeeded':
                break
            elif status in ['Failed', 'Stopped']:
                raise Exception(f"Deployment failed with status: {status}")
                
            time.sleep(30)

    def initiate_rollback(self, deployment_id):
        """Inicia un rollback del despliegue"""
        try:
            self.codedeploy.stop_deployment(
                deploymentId=deployment_id,
                autoRollbackEnabled=True
            )
            print(f"Initiated rollback for deployment {deployment_id}")
        except Exception as e:
            print(f"Failed to initiate rollback: {str(e)}")

def main():
    import sys
    if len(sys.argv) != 4:
        print("Usage: post_deploy_check.py <environment> <region> <deployment_id>")
        sys.exit(1)
        
    environment = sys.argv[1]
    region = sys.argv[2]
    deployment_id = sys.argv[3]
    
    validator = DeploymentValidator(environment, region)
    
    try:
        success = validator.validate_deployment(deployment_id)
        sys.exit(0 if success else 1)
    except Exception as e:
        print(f"Validation failed: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main()

Verificación Final de la Etapa 6

Checklist de CI/CD:

  • [ ] Pipeline completamente configurado
  • [ ] Tests automatizados funcionando
  • [ ] Despliegue automatizado
  • [ ] Monitoreo post-despliegue activo
  • [ ] Rollback automático configurado
  • [ ] Scripts de validación funcionando
  • [ ] Alertas configuradas

Pruebas Recomendadas:

  1. Ejecutar pipeline completo
  2. Verificar tests en cada ambiente
  3. Probar rollback automático
  4. Validar monitoreo post-despliegue

Troubleshooting Común:

  1. Problemas de Pipeline:

    • Verificar permisos IAM
    • Revisar buildspec
    • Comprobar artefactos
  2. Errores de Tests:

    • Verificar ambiente de pruebas
    • Revisar configuración
    • Comprobar dependencias
  3. Problemas de Despliegue:

    • Verificar scripts
    • Revisar permisos
    • Comprobar rollback
  4. Errores de Monitoreo:

    • Verificar métricas
    • Revisar alertas
    • Comprobar logs

Resumen Final del Proyecto

  1. Infraestructura Base (Etapa 1):

    • VPC y networking
    • EFS para almacenamiento
    • Aurora MySQL
    • ElastiCache
  2. Capa de Aplicación (Etapa 2):

    • Auto Scaling Group
    • Load Balancer
    • Launch Template
  3. Seguridad y CDN (Etapa 3):

    • CloudFront
    • WAF
    • Certificados SSL
    • Route 53
  4. Prestashop y Optimización (Etapa 4):

    • Configuración optimizada
    • Caché
    • Media Storage
    • Performance
  5. Monitoreo (Etapa 5):

    • CloudWatch
    • Logs
    • Alertas
    • Mantenimiento
  6. CI/CD (Etapa 6):

    • Pipeline automático
    • Tests
    • Despliegue seguro
    • Validación post-despliegue

La implementación está lista para soportar una tienda Prestashop escalable, segura y altamente disponible en AWS.