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
spróbujemy go usunąć, spotka nas rozczarowanie.
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.
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.