Formas de Replicar Datos con S3
Published: 2023-12-30 13:31:04

Cuando se trata de replicación de datos entre Buckets S3, nos encontramos comunmente con 2 escenarios:

- Los Buckets (Source & Target) se encuentran en la misma cuenta.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gqgn7o2yl3zmg3nhpgg.png)



- Los Buckets (Source & Target) se encuentran en cuenas diferentes.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0re8wi6ptzsa1qnmedp.png)





Es importante resaltar que existen multiples metodos para replicar datos entre buckets, sin embargo es fundamental entender el caso de uso que se esté abordando en ese momento. En esta publicación nos enfocaremos en casos donde estrictamente se replicará objetos entre S3 Buckets, por lo cual podriamos optar por utilizar la funcionalidad más standard ofrecida por el propio servicio de S3. Estas implementaciones las realizaremos utilizando **CloudFormation**.

Si bien ambos escenarios son muy similares, hay que considerar que difieren sensiblemente en la manera de que se deben implementar. Para el caso de que **los Buckets se encuentran en la misma cuenta**, no debe considerarse la incorporación de una **Bucket Policy **en el Target Bucket, sin embargo debe considerarse la activación del **Versionado**, ya que es condición necesaria para que funcione la replicación y además sirve para poder restaurar los objetos a su versión anterior.

Para desplegar la **Solución #1 (Buckets en la misma Cuenta)**, deberemos crear los siguientes archivos:


1. Archivo de Deploy (deploy.sh)

```
###
STACK_NAME="s3-replication-same-account-template"
TEMPLATE_FILE_NAME="s3-replication-same-account-template"
###

PROFILE="default"
ARTIFACTORY_BUCKET="**Nombre de un Random Bucket, para subir el tamplate**"

#1) Create Package
aws cloudformation package --template ./$TEMPLATE_FILE_NAME.yaml \
                           --s3-bucket $ARTIFACTORY_BUCKET \
                           --output json > $TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                           --profile $PROFILE

#2.1) Create Stack (From Package)
    aws cloudformation create-stack --stack-name $STACK_NAME \
                                    --parameters file://./parameters.json \
                                    --template-body file://./$TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                                    --profile $PROFILE \
                                    --region us-east-1 \
                                    --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM
```



2. Archivo de Parametros (parameters.json)

```
[
    {
        "ParameterKey": "pSampleType",
        "ParameterValue": "same-account-replication"
    }
]
```

3. Archivo de Cloudformation (s3-replication-same-account-
template.yaml)

```
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: "Same Account - S3 Replication"

Parameters:
  pSampleType:
    Description: S3 Replication Sample Type
    Type: String 
 

Resources:

  #####################################################
  #################### S3 BUCKET ######################
  #####################################################
  rSourceBucket:
    #DependsOn: rReplicationRole  
    Type: AWS::S3::Bucket
    Properties:       
      BucketName: !Sub "${pSampleType}-source-bucket-${AWS::AccountId}"            
      VersioningConfiguration: 
        Status: Enabled
      ReplicationConfiguration:
        Role: !GetAtt rReplicationRole.Arn
        Rules:
          - Id: !Sub "${pSampleType}-sample"
            Status: Enabled
            Prefix: datalake
            Destination:
              Bucket: !GetAtt rDestinationBucket.Arn
              StorageClass: STANDARD  
      Tags: 
        - Key: "S3-BucketName"        
          Value: !Sub "${pSampleType}-source-bucket-${AWS::AccountId}"
        - Key: "CostCenter"
          Value: "00000"  
  
  rDestinationBucket:  
    Type: AWS::S3::Bucket
    Properties:       
      BucketName: !Sub "${pSampleType}-destination-bucket-${AWS::AccountId}"            
      VersioningConfiguration: 
        Status: Enabled      
      Tags: 
        - Key: "S3-BucketName"        
          Value: !Sub "${pSampleType}-destination-bucket-${AWS::AccountId}"
        - Key: "CostCenter"
          Value: "00000"  



  #####################################################
  ##################### IAM ROLE ######################
  #####################################################

  rReplicationRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${pSampleType}-role"      
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: "Allow"
            Principal:
              Service:
                - "s3.amazonaws.com"
      Policies:
        - PolicyName: S3ReplicationPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "s3:GetObjectVersionForReplication"
                  - "s3:GetObjectVersionAcl"
                  - "s3:GetObjectVersionTagging"
                Resource: !Sub "arn:aws:s3:::${pSampleType}-source-bucket-${AWS::AccountId}/*"
              - Effect: Allow
                Action:
                  - "s3:ListBucket"
                  - "s3:GetReplicationConfiguration"
                Resource: !Sub "arn:aws:s3:::${pSampleType}-source-bucket-${AWS::AccountId}"
              - Effect: Allow
                Action:
                  - "s3:ReplicateObject"
                  - "s3:ReplicateDelete"
                  - "s3:ReplicateTags"
                Resource: !Sub "arn:aws:s3:::${pSampleType}-destination-bucket-${AWS::AccountId}/*"
     


```


Para desplegar la solución simplemente se debe ejecutar el archivo de deploy de la siguiente manera:

```
bash deploy.sh
```


Continuando con el despliegue de la **Solución #2 (Buckets en distintas cuentas)**, deberemos crear los siguientes archivos:


1. Archivo de Deploy Source (deploy_source.sh)

```
###
STACK_NAME="s3-replication-different-account-source-template"
TEMPLATE_FILE_NAME="s3-replication-different-account-source-template"
###

PROFILE="default"
ARTIFACTORY_BUCKET="**Nombre de un Random Bucket, para subir el tamplate**"

#1) Create Package
aws cloudformation package --template ./$TEMPLATE_FILE_NAME.yaml \
                           --s3-bucket $ARTIFACTORY_BUCKET \
                           --output json > $TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                           --profile $PROFILE

#2.1) Create Stack (From Package)
    aws cloudformation create-stack --stack-name $STACK_NAME \
                                    --parameters file://./parameters-source.json \
                                    --template-body file://./$TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                                    --profile $PROFILE \
                                    --region us-east-1 \
                                    --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM
```

2. Archivo de Deploy Target (deploy_destination.sh)

```
###
STACK_NAME="s3-replication-different-account-destination-template"
TEMPLATE_FILE_NAME="s3-replication-different-account-destination-template"
###

PROFILE="default"
ARTIFACTORY_BUCKET="**Nombre de un Random Bucket, para subir el tamplate**"

#1) Create Package
aws cloudformation package --template ./$TEMPLATE_FILE_NAME.yaml \
                           --s3-bucket $ARTIFACTORY_BUCKET \
                           --output json > $TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                           --profile $PROFILE

#2.1) Create Stack (From Package)
    aws cloudformation create-stack --stack-name $STACK_NAME \
                                    --parameters file://./parameters-destination.json \
                                    --template-body file://./$TEMPLATE_FILE_NAME-packaged-$ENV.yaml \
                                    --profile $PROFILE \
                                    --region us-east-1 \
                                    --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM
```

3. Archivo de Parametros Source (parameters-source.json)

```
[
    {
        "ParameterKey": "pSampleType",
        "ParameterValue": "different-account-replication"
    },
    {
        "ParameterKey": "pDestinationBucketName",
        "ParameterValue": "different-account-replication-destination-bucket-aws-account-id"
    }
]
```

4. Archivo de Parametros Source (parameters-destination.json)

```
[
    {
        "ParameterKey": "pSampleType",
        "ParameterValue": "different-account-replication"
    },
    {
        "ParameterKey": "pReplicationRoleArn",
        "ParameterValue": "arn:aws:iam::aws-account-id:role/different-account-replication-role"
    }
]
```

5. Archivo de Cloudformation (s3-replication-different-account-source-template.yaml)

```
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: "Same Account - S3 Replication"

Parameters:
  pSampleType:
    Description: S3 Replication Sample Type
    Type: String 
  pDestinationBucketName:
    Description: S3 Destination Bucket
    Type: String 

Resources:

  #####################################################
  #################### S3 BUCKET ######################
  #####################################################
  rSourceBucket:
    #DependsOn: rReplicationRole  
    Type: AWS::S3::Bucket
    Properties:       
      BucketName: !Sub "${pSampleType}-source-bucket-${AWS::AccountId}"            
      VersioningConfiguration: 
        Status: Enabled
      ReplicationConfiguration:
        Role: !GetAtt rReplicationRole.Arn
        Rules:
          - Id: !Sub "${pSampleType}-sample"
            Status: Enabled
            Prefix: datalake
            Destination:
              Bucket: !Sub "arn:aws:s3:::${pDestinationBucketName}"
              StorageClass: STANDARD  
      Tags: 
        - Key: "S3-BucketName"        
          Value: !Sub "${pSampleType}-source-bucket-${AWS::AccountId}"
        - Key: "CostCenter"
          Value: "00000"  

  #####################################################
  ##################### IAM ROLE ######################
  #####################################################

  rReplicationRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${pSampleType}-role"      
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: "Allow"
            Principal:
              Service:
                - "s3.amazonaws.com"
      Policies:
        - PolicyName: S3ReplicationPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - "s3:GetObjectVersionForReplication"
                  - "s3:GetObjectVersionAcl"
                  - "s3:GetObjectVersionTagging"
                Resource: !Sub "arn:aws:s3:::${pSampleType}-source-bucket-${AWS::AccountId}/*"
              - Effect: Allow
                Action:
                  - "s3:ListBucket"
                  - "s3:GetReplicationConfiguration"
                Resource: !Sub "arn:aws:s3:::${pSampleType}-source-bucket-${AWS::AccountId}"
              - Effect: Allow
                Action:
                  - "s3:ReplicateObject"
                  - "s3:ReplicateDelete"
                  - "s3:ReplicateTags"
                Resource: !Sub "arn:aws:s3:::${pDestinationBucketName}/*"
     




```

6. Archivo de Cloudformation (s3-replication-different-account-destination-template.yaml)

```
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: "Same Account - S3 Replication"

Parameters:
  pSampleType:
    Description: S3 Replication Sample Type
    Type: String

  pReplicationRoleArn:
    Description: Role in the source account for the replication
    Type: String
 

Resources:

  #####################################################
  #################### S3 BUCKET ######################
  #####################################################
   
  rDestinationBucket:  
    Type: AWS::S3::Bucket
    Properties:       
      BucketName: !Sub "${pSampleType}-destination-bucket-${AWS::AccountId}"            
      VersioningConfiguration: 
        Status: Enabled      
      Tags: 
        - Key: "S3-BucketName"        
          Value: !Sub "${pSampleType}-destination-bucket-${AWS::AccountId}"
        - Key: "CostCenter"
          Value: "00000"  



  # #####################################################
  # ################## BUCKET POLICY ####################
  # #####################################################

  rDestinationBucketsPolicy:
    Type: AWS::S3::BucketPolicy
    Properties: 
      Bucket: !Sub "${pSampleType}-destination-bucket-policy-${AWS::AccountId}" 
      PolicyDocument:
        Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal:
                AWS: !Ref pReplicationRoleArn
              Action: 
                - "s3:ReplicateObject"
                - "s3:ReplicateDelete"
              Resource: !Sub "${rDestinationBucket.Arn}/*"
            - Effect: "Allow"
              Principal: 
                AWS: !Ref pReplicationRoleArn
              Action:
                - "s3:List*"
                - "s3:GetBucketVersioning"
                - "s3:PutBucketVersioning"
              Resource: !GetAtt rDestinationBucket.Arn



```

Para desplegar la solución simplemente se debe ejecutar el archivo de deploy pero en las 2 cuentas de AWS (Source & Target), de la misma manera que en el escenerio #1. 


Para Finalizar, se debe considerar que la replicación entre objetos puede demorar algunos segundos o varios minutos, ya que va a depender de AWS, sin embargo AWS cuenta con **RTC**, que básicamente es un SLA, donde asegura que en el lapso de 15 minutos (Máximo) serán replicados los objetos desde el Source al Target. Es clave tener en consideración esta feature cuando se tratan de escenarios de casos de uso críticos donde contar con los objetos a tiempo es necesario. 

Muchas gracias!
Cristian R. Carballo 
https://www.linkedin.com/in/cristianrcarballo/