Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation

Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation

Często, a właściwie prawie zawsze, jeżeli tworzymy buckety S3 za pomocą CloudFormation, to przy usuwaniu stacka mamy problem. AWS nie usunie bucketa, jeżeli są w nim jakieś pliki. A przewaznie są, bo po coś go w końcu tworzyliśmy. Dziś pokażę, jak skasować pliki w S3 przy usuwaniu stacka Cloudformation.

Jeżeli ktoś nie napotkał jeszcze tego problemu, to szybko go zobrazujemy. Utworzymy bucket za pomocą poniższego template:

AWSTemplateFormatVersion: 2010-09-0920
Description: ---
Resources: 
  Bucket:
    Type: 'AWS::S3::Bucket'
    Properties: 
      LifecycleConfiguration:
        Rules:
          - Id: expiration
            Status: Enabled
            ExpirationInDays: 3

I jeżeli po utworzeniu stacka

Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation - stack

spróbujemy go usunąć, spotka nas rozczarowanie.

Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation - bucket

Przy ponownej próbie usunięcia stacka CloudFormation poinformuje nas, że ma problem z usunięciem bucketa i możemy skasować stack, ale tylko jeżeli zostawimy nasz bucket.

Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation - delete bucket

Ale jest na to sposób. I to wbrew pozorom dość prosty. A to co pokażę w tym artykule i udostępnię na GitHub-ie  możecie użyć w dowolnym template, który będzie tworzył buckety S3.

Jak skasować pliki w S3 przy usuwaniu stacka Cloudformation

Pomoże nam Custom Resource w postaci funkcji Lambda, która w momencie usuwania stacka Cloud Formation, przed usunięciem bucketa, skasuje z niego wszystkie pliki. To co musimy dodatkowo utworzyć w naszym template to:

  • rola z uprawnianiami dla funkcji – Type: AWS::IAM::Role – CleanupBucketOnDeleteLambdaRole
  • funkcja Lambda – Type: AWS::Lambda::Function – CleanupBucketOnDeleteLambda
  • custom resource – Type: Custom::CleanupBucket – CleanupBucketOnDelete

No to po kolei.

Rola dla funkcji Lambda

Nasza funkcja musi mieć możliwość skasowania plików z S3. Ale pliki mogą mieć wersje, plików może być wiele i będą potrzebne paginatory do ich obsługi. Czyli potrzebne są uprawnienia do S3. Dodatkowo dobrze jest mieć logi z działania funkcji, dodamy więc też uprawnienia do logowania.

Policies:
- PolicyName: !Join [ -, [!Ref 'AWS::StackName', 'CleanupBucketOnDeleteLambdaPolicy'] ]
  PolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
      Action:
      - logs:*
      - s3:*
      Resource: '*'
    - Effect: Deny
      Action:
      - s3:DeleteBucket
      Resource: '*'

Uprawnienia w roli są oczywiście „trochę” za duże i na produkcji proponuję je ograniczyć do tych naprawdę niezbędnych. Wystarczy przejrzeć kod i wyłuskać metody, których funkcja używa. Chciałem jednak ten artykuł uprościć. Dodałem tylko zakaz usuwania samych bucketów,

- Effect: Deny 
  Action: 
  - s3:DeleteBucket 
  Resource: '*'

gdyż jeżeli przez przypadek skasujemy sam bucket w funkcji, to usuwanie stacka także się nie powiedzie.

Funkcja Lambda

Założeniem było to, żeby zmieścić się z kodem w ramach samego template Cloudformation i nie używać dodatkowych plików i bibliotek w samej funkcji. Powinno to uprościć wdrożenia. Użyłem także funkcji send z modułu cfn-response , która zwraca odpowiedni obiekt.

Handler funkcji jest bardzo prosty

def lambda_handler(event, context):
    try:
        bucket = event['ResourceProperties']['BucketName']
        if event['RequestType'] == 'Delete':
            empty_bucket(bucket)
            send(event, context, SUCCESS, {})
        else:
            send(event, context, SUCCESS, {})
    except Exception as e:
        logger.error(str(e))
        send(event, context, FAILED, {})

Wydobywamy z eventu nazwę bucketa i sprawdzamy z jakim zdarzeniem mamy do czynienia. Jeżeli jest to Delete to uruchamiamy usuwanie plików, a jeżeli coś innego to po prostu zwracamy powodzenie. W przypadku błędu zwracamy wartość FAILURE.

Pozostałej częsci funkcji nie będę opisywał. Kasuje ona wszystkie obiekty z bucketa. Kod samej funkcji także jest dostępny w repozytorium.

Custom Resource

CleanupBucketOnDelete:
  DependsOn: Bucket
  Type: Custom::CleanupBucket
  Properties:
    ServiceToken: 
     Fn::GetAtt: 
        - "CleanupBucketOnDeleteLambda"
        - "Arn"
    BucketName: !Ref Bucket

Jako zmienna BucketName do funkcji zostanie przekazana nazwa naszego bucketa.

I to właściwie wszystko. Teraz usuwanie stacka, włącznie z bucketem S3,  powinno powieść się bez najmniejszego problemu.

Comments are closed.