【約100万円コスト削減】Github Actions×AWS Nukeで不要なAWSリソース自動削除システムを作成！

閲覧ありがとうございます。
今回はタイトルにある通り、AWS初心者だった(今もですが)私が昨年度半年ほどかけて行ってきた教育用AWS環境のコスト削減について書き留めておこうと思います。
（かなり書くのが遅くなってしまいました<(_ _)>）

登壇資料↓↓↓

https://www.docswell.com/s/Nacchan0524/5XEQRG-GitHubActionsandAWSNuke-Aoki

# AWS Nukeとは
AWSサービスごとに環境内のリソースを一括することができるオープンソースのツールです。
削除指定したタグの付与の有無により、削除可否を分けることができるため、今回は「Owner」というタグが付与されていないものを不要リソースとみなし、AWS Nukeによる一括削除の対象としました。
AWS NukeのGitHubリポジトリ：https://github.com/ekristen/aws-nuke/blob/main/README.md

# GitHub Actionsとは
GitHubの機能の一つです。リポジトリに保存したコードの実行を自動化することができます(CI/CD)

処理はワークフローファイルにジョブとして定義でき、任意のスケジュールやイベントでトリガーすることができます。
一回きりの実行・繰り返し実行など、カスタマイズが可能です。

# 事前準備
GitHub ActionsとAWS環境を連携させる必要があります。
連携方法については前回の記事をご覧ください。

https://qiita.com/natsumi_a/items/11d22f4812fcbdc5f98d

# 作成した仕組み
GitHub Actions上で、AWS教育環境に対し定期的に不要なリソースを一括削除・Slackに削除リソースを連携するしくみを作成
設定ファイルやワークフローファイル等はGitHub上にも公開しているのでご参考までに見ていただければと思います

https://github.com/Nacchan0524/natsumi-nuke/blob/main/nuke-config.yml

【 システム詳細 】
* リソース作成者を管理するため、必要なリソースには「Owner」タグを付与する。(Ownerタグのないリソースは削除)
* コスト比率を大きく占めるリソースをAWS Nukeでの削除対象(include)に設定
* 毎週木曜の11:00ごろに削除予定のリソースを周知するメッセージをSlackに投稿する(dry-run)
* 毎週木曜の14:00ごろに周知したリソースを実際に一括削除する(--no-dry-run)

## Nuke-Configファイル
AWS Nukeで削除/除外する対象サービスやアカウントを明記するためのファイル

<details><summary>実際のConfigファイル</summary>

```
regions:
  - global
  - us-east-1 # バージニア北部
  - us-east-2 # オハイオ
  - us-west-1 # 北カルフォルニア
  - us-west-2 # オレゴン
  - ap-south-1 # ムンバイ
  - ap-northeast-3 # 大阪
  - ap-northeast-2 # ソウル
  - ap-southeast-1 # シンガポール
  - ap-southeast-2 # シドニー
  - ap-northeast-1 # 東京
  - ca-central-1 # カナダ中部
  - eu-central-1 # フランクフルト
  - eu-west-1 # アイルランド
  - eu-west-2 # ロンドン
  - eu-west-3 # パリ
  - eu-north-1 # ストックホルム
  - sa-east-1 # サンパウロ

blocklist:
  - 999999999999

resource-types:
  includes:
    - EC2Address
    - EC2Host
    - EC2InstanceConnectEndpoint
    - EC2RouteTable
    - EC2VPCEndpoint
    - EC2Instance
    - ELBv2
    - ELBv2TargetGroup
    - ELB
    - FSxFileSystem
    - RDSClusterSnapshot
    - RDSDBCluster
    - RDSEventSubscription
    - RDSInstance
    - RDSOptionGroup
    - RDSProxy
    - RDSSnapshot
    - SimpleDBDomain
    - Route53HealthCheck
    - Route53HostedZone
    - Route53Profile
    - Route53ProfileAssociation
    - Route53ResolverEndpoint
    - Route53ResolverRule
    - Route53TrafficPolicy
    - AWS::ECR::PublicRepository
    - AWS::ECR::Repository
    - EKSCluster
    - EKSFargateProfile
    - EKSNodegroup
    - EMRCluster
    - EMRSecurityConfiguration
    - ECSCapacityProvider
    - ECSCluster
    - ECSClusterInstance
    - EFSFileSystem
    - EFSMountTarget
    - BedrockAgent
    - BedrockCustomModel
    - BedrockDataSource
    - BedrockEvaluationJob
    - BedrockGuardrail
    - BedrockModelCustomizationJob
    - BedrockModelInvocationLoggingConfiguration
    - BedrockPrompt
    - BedrockProvisionedModelThroughput
    - CloudWatchAlarm
    - CloudWatchAnomalyDetector
    - CloudWatchDashboard
    - CloudWatchEventsBuses
    - CloudWatchEventsRule
    - CloudWatchEventsTarget
    - CloudWatchInsightRule
    - CloudWatchLogsDestination
    - CloudWatchLogsLogGroup
    - CloudWatchLogsResourcePolicy
    - CloudWatchRUMApp
    - Route53ResourceRecordSet
    - MemoryDBParameterGroup
    - MemoryDBACL
    - MemoryDBCluster
    - MemoryDBSubnetGroup
    - MemoryDBUser
    - EC2PlacementGroup
    - EC2CustomerGateway
    - EC2LaunchTemplate
    - EC2ClientVpnEndpoint
    - EC2ClientVpnEndpointAttachment
    - EC2NetworkACL
    - EC2SpotFleetRequest
    - EC2TGW
    - EC2TGWAttachment
    - EC2TGWConnectPeer
    - EC2NATGateway
    - FSxBackup
    - EC2Volume
    - EC2Image
    - EC2Snapshot
    - EC2NetworkInterface
    - EC2Subnet
    - RDSDBClusterParameterGroup
    - RDSDBParameterGroup
    - RDSDBSubnetGroup
  
  excludes:
    - AWS::AppFlow::ConnectorProfile # Cloud controlリソースのため
    - AWS::AppFlow::Flow # Cloud controlリソースのため
    - AWS::AppRunner::Service # Cloud controlリソースのため
    - AWS::ApplicationInsights::Application # Cloud controlリソースのため
    - AWS::Backup::Framework # Cloud controlリソースのため
    - AWS::ECR::PublicRepository # Cloud controlリソースのため
    - AWS::ECR::PullThroughCacheRule # Cloud controlリソースのため
    - AWS::ECR::RegistryPolicy # Cloud controlリソースのため
    - AWS::ECR::ReplicationConfiguration # Cloud controlリソースのため
    - AWS::MWAA::Environment # Cloud controlリソースのため
    - AWS::NetworkFirewall::Firewall # Cloud controlリソースのため
    - AWS::NetworkFirewall::FirewallPolicy # Cloud controlリソースのため
    - AWS::NetworkFirewall::RuleGroup # Cloud controlリソースのため
    - AWS::Synthetics::Canary # Cloud controlリソースのため
    - AWS::Timestream::Database # Cloud controlリソースのため
    - AWS::Timestream::ScheduledQuery # Cloud controlリソースのため
    - AWS::Timestream::Table # Cloud controlリソースのため
    - AWS::Transfer::Workflow # Cloud controlリソースのため
    - CloudWatchEventsBuses # タグによるフィルタリングが未対応のため
    - CloudWatchEventsRule # タグによるフィルタリングが未対応のため
    - CloudWatchEventsTarget # タグによるフィルタリングが未対応のため
    - CloudTrailTrail # 社内規約に添わせるため
    - ConfigServiceConfigRule # 社内規約に添わせるため
    - ConfigServiceConfigurationRecorder # 社内規約に添わせるため
    - ConfigServiceDeliveryChannel # 社内規約に添わせるため
    - SecurityHub # 社内規約に添わせるため
    - CloudWatchAlarm # 社内規約に添わせるため
    - CloudWatchDashboard # 社内規約に添わせるため
    - CloudWatchLogsDestination # 社内規約に添わせるため
    - CloudWatchLogsLogGroup # 社内規約に添わせるため
    - CloudWatchLogsResourcePolicy # 社内規約に添わせるため
    - CloudWatchRUMApp # 社内規約に添わせるため
    - SNSEndpoint # 社内規約に添わせるため
    - SNSPlatformApplication # 社内規約に添わせるため
    - SNSSubscription # 社内規約に添わせるため
    - SNSTopic # 社内規約に添わせるため
    - RoboMakerRobotApplication #サービス終了のため
    - RoboMakerSimulationApplication #サービス終了のため
    - RoboMakerSimulationJob #サービス終了のため
    - ElasticTranscoderPipeline #サービス終了のため
    - ElasticTranscoderPreset #サービス終了のため
    - CloudSearchDomain #サービス終了のため
    - OpsWorksApp #サービス終了のため
    - OpsWorksCMBackup #サービス終了のため
    - OpsWorksCMServer #サービス終了のため
    - OpsWorksCMServerState #サービス終了のため
    - OpsWorksInstance #サービス終了のため
    - OpsWorksLayer #サービス終了のため
    - OpsWorksUserProfile #サービス終了のため
    - CodeStarProject #サービス終了のため
    - FMSNotificationChannel #エラー発生するため
    - FMSPolicy #エラー発生するため
    - ACMCertificate #エラー発生するため
    - AWS::ACMPCA::CertificateAuthority #エラー発生するため
    - ACMPCACertificateAuthorityState #エラー発生するため
    - KMSAlias #社内規約に添わせるため
    - KMSKey #社内規約に添わせるため
    - S3Object #消されたくないオブジェクトがあるため
    - IAMOpenIDConnectProvider
    - IAMPolicy
    - IAMRole
    - IAMRolePolicy
    - IAMRolePolicyAttachment
    - IAMSAMLProvider
    - IAMAccountSettingPasswordPolicy
    - IAMGroup
    - IAMGroupPolicy
    - IAMGroupPolicyAttachment
    - IAMInstanceProfile
    - IAMInstanceProfileRole
    - IAMLoginProfile
    - IAMRolesAnywhereCRL
    - IAMRolesAnywhereProfile
    - IAMRolesAnywhereTrustAnchor
    - IAMServerCertificate
    - IAMServiceSpecificCredential
    - IAMSigningCertificate
    - IAMUser
    - IAMUserAccessKey
    - IAMUserGroupAttachment
    - IAMUserHTTPSGitCredential
    - IAMUserMFADevice
    - IAMUserPolicy
    - IAMUserPolicyAttachment
    - IAMUserSSHPublicKey
    - IAMVirtualMFADevice
    - MachineLearningBranchPrediction
    - MachineLearningDataSource
    - MachineLearningEvaluation
    - MachineLearningMLModel
    - Macie
    - RedshiftServerlessNamespace
    - RedshiftServerlessSnapshot
    - RedshiftServerlessWorkgroup
    - GameLiftBuild
    - GameLiftFleet
    - GameLiftMatchmakingConfiguration
    - GameLiftMatchmakingRuleSet
    - GameLiftQueue
    - AWSBackupVaultAccessPolicy
    - BackupVault
    - CloudFormationStack  #依頼
    - SecurityHub
    - RekognitionCollection
    - RekognitionDataset
    - RekognitionProject
    - BackupReportPlan
    - AWS::EC2::VPC  #依頼
    - AWSBackupPlan
    - AWSBackupRecoveryPoint
    - AWSBackupSelection
    - DataPipelinePipeline  #kiitos
    - OSPackage
    - OSCollection
    - OSDomain
    - OSPipeline
    - OSVPCEndpoint
    - EC2DHCPOption
    - EC2VPCPeeringConnection
    - EC2VPNConnection
    - EC2VPNGateway
    - EC2VPNGatewayAttachment
    - EC2EgressOnlyInternetGateway
    - EC2InternetGateway  #エラー原因
    - EC2InternetGatewayAttachment  #エラー原因
    - EC2VPCEndpointConnection
    - EC2VPCEndpointServiceConfiguration
    - EC2DefaultSecurityGroupRule
    - AMGWorkspace
    - AMPWorkspace
    - AWS::ApiGateway::ApiKey
    - AWS::ApiGateway::ClientCertificate
    - APIGatewayDomainName
    - APIGatewayRestAPI
    - AWS::ApiGateway::UsagePlan
    - APIGatewayV2API
    - APIGatewayV2VpcLink
    - APIGatewayVpcLink
    - AWS::AccessAnalyzer::Analyzer
    - AccessAnalyzerArchiveRule
    - AmplifyApp
    - AppConfigApplication
    - AppConfigConfigurationProfile
    - AppConfigDeploymentStrategy
    - AppConfigEnvironment
    - AppConfigHostedConfigurationVersion
    - AppMeshGatewayRoute
    - AppMeshMesh
    - AppMeshRoute
    - AppMeshVirtualGateway
    - AppMeshVirtualNode
    - AppMeshVirtualRouter
    - AppMeshVirtualService
    - AppRegistryApplication
    - AppRunnerConnection
    - AppRunnerService
    - AppStreamDirectoryConfig
    - AppStreamFleet
    - AppStreamFleetState
    - AppStreamImage
    - AppStreamImageBuilder
    - AppStreamImageBuilderWaiter
    - AppStreamStack
    - AppStreamStackFleetAttachment
    - AppSyncGraphqlAPI
    - ApplicationAutoScalingScalableTarget
    - AthenaDataCatalog
    - AthenaNamedQuery
    - AthenaPreparedStatement
    - AthenaWorkGroup
    - AutoScalingGroup
    - AutoScalingLaunchConfiguration
    - AutoScalingLifecycleHook
    - AutoScalingPlansScalingPlan
    - BatchComputeEnvironment
    - BatchComputeEnvironmentState
    - BatchJobQueue
    - BatchJobQueueState
    - BillingCostandUsageReport
    - BudgetsBudget
    - Cloud9Environment
    - CloudDirectoryDirectory
    - CloudDirectorySchema
    - CloudFormationStackSet
    - CloudFormationType
    - CloudFrontCachePolicy
    - CloudFrontDistribution
    - CloudFrontDistributionDeployment
    - CloudFrontFunction
    - CloudFrontKeyGroup
    - CloudFrontOriginAccessControl
    - CloudFrontOriginAccessIdentity
    - CloudFrontOriginRequestPolicy
    - CloudFrontPublicKey
    - CloudFrontResponseHeadersPolicy
    - CloudHSMV2Cluster
    - CloudHSMV2ClusterHSM
    - CodeArtifactDomain
    - CodeArtifactRepository
    - CodeBuildBuild
    - CodeBuildBuildBatch
    - CodeBuildProject
    - CodeBuildReport
    - CodeBuildReportGroup
    - CodeBuildSourceCredential
    - CodeCommitRepository
    - CodeDeployApplication
    - CodeDeployDeploymentConfig
    - CodeDeployDeploymentGroup
    - CodeGuruProfilingGroup
    - CodeGuruReviewerRepositoryAssociation
    - CodePipelineCustomActionType
    - CodePipelinePipeline
    - CodePipelineWebhook
    - CognitoIdentityPool
    - CognitoIdentityProvider
    - CognitoUserPool
    - CognitoUserPoolClient
    - CognitoUserPoolDomain
    - ComprehendDocumentClassifier
    - ComprehendDominantLanguageDetectionJob
    - ComprehendEndpoint
    - ComprehendEntitiesDetectionJob
    - ComprehendEntityRecognizer
    - ComprehendEventsDetectionJob
    - ComprehendKeyPhrasesDetectionJob
    - ComprehendPiiEntitiesDetectionJob
    - ComprehendSentimentDetectionJob
    - ComprehendTargetedSentimentDetectionJob
    - ConfigServiceConfigRule
    - ConfigServiceConfigurationRecorder
    - ConfigServiceConformancePack
    - ConfigServiceDeliveryChannel
    - DAXCluster
    - DAXParameterGroup
    - DAXSubnetGroup
    - DatabaseMigrationServiceCertificate
    - DatabaseMigrationServiceEndpoint
    - DatabaseMigrationServiceEventSubscription
    - DatabaseMigrationServiceReplicationInstance
    - DatabaseMigrationServiceReplicationTask
    - DatabaseMigrationServiceSubnetGroup
    - DeviceFarmProject
    - ESDomain
    - ElasticBeanstalkApplication
    - ElasticBeanstalkEnvironment
    - ElasticacheCacheCluster
    - ElasticacheCacheParameterGroup
    - ElasticacheReplicationGroup
    - ElasticacheSubnetGroup
    - ElasticacheUser
    - ElasticacheUserGroup
    - FirehoseDeliveryStream
    - GlobalAccelerator
    - GlobalAcceleratorEndpointGroup
    - GlobalAcceleratorListener
    - GlueBlueprint
    - GlueClassifier
    - GlueConnection
    - GlueCrawler
    - GlueDataBrewDatasets
    - GlueDataBrewJobs
    - GlueDataBrewProjects
    - GlueDataBrewRecipe
    - GlueDataBrewRulesets
    - GlueDataBrewSchedules
    - GlueDatabase
    - GlueDevEndpoint
    - GlueJob
    - GlueMLTransform
    - GlueSecurityConfiguration
    - GlueSession
    - GlueTrigger
    - GlueWorkflow
    - GuardDutyDetector
    - ImageBuilderComponent
    - ImageBuilderDistributionConfiguration
    - ImageBuilderImage
    - ImageBuilderInfrastructureConfiguration
    - ImageBuilderPipeline
    - ImageBuilderRecipe
    - Inspector2
    - InspectorAssessmentRun
    - InspectorAssessmentTarget
    - InspectorAssessmentTemplate
    - IoTAuthorizer
    - IoTCACertificate
    - IoTCertificate
    - IoTJob
    - IoTOTAUpdate
    - IoTPolicy
    - IoTRoleAlias
    - IoTSiteWiseAccessPolicy
    - IoTSiteWiseAsset
    - IoTSiteWiseAssetModel
    - IoTSiteWiseDashboard
    - IoTSiteWiseGateway
    - IoTSiteWisePortal
    - IoTSiteWiseProject
    - IoTStream
    - IoTThing
    - IoTThingGroup
    - IoTThingType
    - IoTThingTypeState
    - IoTTopicRule
    - IoTTwinMakerComponentType
    - IoTTwinMakerEntity
    - IoTTwinMakerScene
    - IoTTwinMakerSyncJob
    - IoTTwinMakerWorkspace
    - KendraIndex
    - KinesisAnalyticsApplication
    - KinesisStream
    - KinesisVideoProject
    - LambdaEventSourceMapping
    - LambdaFunction
    - LambdaLayer
    - LexBot
    - LexIntent
    - LexModelBuildingServiceBotAlias
    - LexSlotType
    - LightsailDisk
    - LightsailDomain
    - LightsailInstance
    - LightsailKeyPair
    - LightsailLoadBalancer
    - LightsailStaticIP
    - MGNJob
    - MGNSourceServer
    - MQBroker
    - MSKCluster
    - MSKConfiguration
    - ManagedBlockchainMember
    - MediaConvertJobTemplate
    - MediaConvertPreset
    - MediaConvertQueue
    - MediaLiveChannel
    - MediaLiveInput
    - MediaLiveInputSecurityGroup
    - MediaPackageChannel
    - MediaPackageOriginEndpoint
    - MediaStoreContainer
    - MediaStoreDataItems
    - MediaTailorConfiguration
    - NeptuneCluster
    - NeptuneInstance
    - NeptuneSnapshot
    - NetworkManagerConnectPeer
    - NetworkManagerCoreNetwork
    - NetworkManagerGlobalNetwork
    - NetworkManagerNetworkAttachment
    - PinpointApp
    - PinpointPhoneNumber
    - PipesPipe
    - PollyLexicon
    - QLDBLedger
    - QuickSightSubscription
    - QuickSightUser
    - RedshiftCluster
    - RedshiftParameterGroup
    - RedshiftScheduledAction
    - RedshiftSnapshot
    - RedshiftSnapshotSchedule
    - RedshiftSubnetGroup
    - ResourceExplorer2Index
    - ResourceExplorer2View
    - ResourceGroupGroup
    - S3AccessGrantsGrant
    - S3AccessGrantsInstance
    - S3AccessGrantsLocation
    - S3AccessPoint
    - AWS::S3::Bucket
    - S3MultipartUpload
    - SESConfigurationSet
    - SESIdentity
    - SESReceiptFilter
    - SESReceiptRuleSet
    - SESTemplate
    - SFNStateMachine
    - SQSQueue
    - SSMActivation
    - SSMAssociation
    - SSMDocument
    - SSMMaintenanceWindow
    - SSMParameter
    - SSMPatchBaseline
    - SSMQuickSetupConfigurationManager
    - SSMResourceDataSync
    - SageMakerApp
    - SageMakerDomain
    - SageMakerEndpoint
    - SageMakerEndpointConfig
    - SageMakerModel
    - SageMakerNotebookInstance
    - SageMakerNotebookInstanceLifecycleConfig
    - SageMakerNotebookInstanceState
    - SageMakerSpace
    - SageMakerUserProfiles
    - SchedulerSchedule
    - SecretsManagerSecret
    - ServiceCatalogConstraintPortfolioAttachment
    - ServiceCatalogPortfolio
    - ServiceCatalogPortfolioProductAttachment
    - ServiceCatalogPortfolioShareAttachment
    - ServiceCatalogPrincipalPortfolioAttachment
    - ServiceCatalogProduct
    - ServiceCatalogProvisionedProduct
    - ServiceCatalogTagOption
    - ServiceCatalogTagOptionPortfolioAttachment
    - ServiceDiscoveryInstance
    - ServiceDiscoveryNamespace
    - ServiceDiscoveryService
    - SignerSigningJob
    - StorageGatewayFileShare
    - StorageGatewayGateway
    - StorageGatewayTape
    - StorageGatewayVolume
    - TranscribeCallAnalyticsCategory
    - StorageGatewayTape
    - StorageGatewayVolume
    - TranscribeCallAnalyticsCategory
    - TranscribeCallAnalyticsJob
    - TranscribeLanguageModel
    - TranscribeMedicalTranscriptionJob
    - TranscribeMedicalVocabulary
    - TranscribeTranscriptionJob
    - TranscribeVocabulary
    - TranscribeVocabularyFilter
    - TransferServer
    - TransferServerUser
    - TransferWebApp
    - WAFRegionalByteMatchSet
    - WAFRegionalByteMatchSetIP
    - WAFRegionalIPSet
    - WAFRegionalIPSetIP
    - WAFRegionalRateBasedRule
    - WAFRegionalRateBasedRulePredicate
    - WAFRegionalRegexMatchSet
    - WAFRegionalRegexMatchTuple
    - WAFRegionalRegexPatternSet
    - WAFRegionalRegexPatternString
    - WAFRegionalRule
    - WAFRegionalRuleGroup
    - WAFRegionalRulePredicate
    - WAFRegionalWebACL
    - WAFRegionalWebACLRuleAttachment
    - WAFRule
    - WAFWebACL
    - WAFWebACLRuleAttachment
    - WAFv2APIKey
    - WAFv2IPSet
    - WAFv2RegexPatternSet
    - WAFv2RuleGroup
    - WAFv2WebACL
    - WorkSpacesWorkspace
    - XRayGroup
    - XRaySamplingRule
    - CodeStarConnection #サービス終了のため
    - CodeStarNotificationRule #サービス終了のため
    - DirectoryServiceDirectory #エラー原因
    - FSxBackup #消せない
    - DynamoDBTableItem #消せない
    - DynamoDBBackup #消せない
    - EC2Volume #消せない
    - EC2Image #消せない 
    - EC2Snapshot #消せない
    - EC2SecurityGroup #消せない
    - EC2NetworkInterface #消せない
    - EC2Subnet #消せない
    - RDSDBClusterParameterGroup #消せない
    - RDSDBParameterGroup #消せない
    - RDSDBSubnetGroup #消せない
    - BedrockKnowledgeBase #無限ループ
    - ECSService #フィルタリング対象外
    - ECSTask #フィルタリング対象外
    - ECSTaskDefinition #フィルタリング対象外
    - DynamoDBTable #レコード消えちゃう
    - EC2SecurityGroup #トラブル元！
    - EC2KeyPair #無料
    
accounts:
  #対象のAWSアカウント番号 :
    filters:
      __global__:
        - type: glob
          property: tag:  #任意のタグ名
          value: " #任意のバリュー名 "
```

</details>

詳しく見ていきましょう。
* **regions**
```
regions:
  - global
  - us-east-1 # バージニア北部
  ・・・（以下略）
```
Nukeを実行するアカウント内で **削除対象(include)** となるリージョンです。
「ここで記述したリージョンに存在するリソースの中で、指定したタグが付与されていない」ものが削除対象になります。

* **blocklist**
```
blocklist:
  - 999999999999
```
Nukeを実行する上で **削除対象外(exclude)** となるアカウントIDです。
独立したアカウントなら「999999999999(実際に存在しないアカウントID)」で問題ないかと思いますが、OrganizationsのRootアカウント等で使用する際は必要かなと思っています(このあたり知識浅いので独立したアカウントでない場合調べてから記述することをお勧めします)
この項目は省略不可なので何かしらは入力する必要があります。

* **includes**
```
resource-types:
  includes:
    - EC2Address
    - EC2Host
    ・・・（以下略）
```
Nukeを実行するアカウント内かつ指定したリージョン内で **削除対象(include)** となるリソースです。
この下に削除対象としたいものについてつらつらと箇条書きで記述していきます。

:::note warn
警告
AWS Nukeでは順次アップデートが行われていますが、そもそもまだサービス一覧に組み込まれていないものが多くあります（特にAI系サービス）ので、GitHubリポジトリを確認しながら進めてください。
:::

* **excludes**
```
excludes:
    - AWS::AppFlow::ConnectorProfile # Cloud controlリソースのため
    - AWS::AppFlow::Flow # Cloud controlリソースのため
    ・・・（以下略）
```
Nukeを実行するアカウント内かつ指定したリージョン内で **削除対象外(exclude)** となるリソースです。
この下に削除対象から除外したいものについてつらつらと箇条書きで記述していきます。

:::note warn
警告
includeに記述して削除しようとするとエラーとなってしまうサービスがいくつかあります(原因はいまだ不明なものが多い…、環境によって異なる気がします)ので、そのような場合は私の場合excludeに入れてしまっています。
:::
※削除保護が付いたリソースはほぼ確実にエラー原因となってしまいますので事前に除外しておくことをお勧めします。

* **accounts**
```
accounts:
  #対象のAWSアカウント番号 :
```
AWS Nukeの **実行対象** となるアカウントを指定します。
`` #対象のAWSアカウント番号 `` を、AWS Nukeを実行するアカウントのIDに書き換えます。

* **filters**
```
filters:
      __global__:
        - type: glob
          property: tag:  #任意のタグ名
          value: " #任意のバリュー名 "
```
AWS Nukeの **実行対象外** とする条件を記述します。
`` __global__: `` を記述することにより、サービスごとに条件を記述する必要がなくなり、すべてのサービスに対して一括で条件を設定できます。
`` #任意のタグ名 `` `` #任意のバリュー名 `` を、削除対象外の条件とするタグ名・バリュー名に書き換えます。(valueは省略してタグのみの指定も可能です)

## ワークフローファイル
連携したAWSアカウントに対し、Nuke-Configファイルの内容に基づいて任意の時間にAWS Nukeを実行する処理を記述するためのファイルです。
`` .github/workflows `` 配下に作成します。([GitHubリポジトリ参照](https://github.com/Nacchan0524/natsumi-nuke)）

:::note info
インフォメーション
作成までの方法は[前回の記事](https://qiita.com/natsumi_a/items/11d22f4812fcbdc5f98d)をご覧ください。
今回は上記の記事の中の「7.　ワークフローファイルの中身には以下を設定」以降をご紹介します。
:::

<details><summary>実際のワークフローファイル</summary>

```
name: nuke-dry-run-to-slack-educross
on: push

env:
  AWS_NUKE_VERSION: 3.29.1
  NUKE_OUTPUT: "None"

jobs:
  run_aws_nuke:
    name: Run aws-nuke
    runs-on: #Organizationsのランナー名を入れる
    permissions:
      id-token: write
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: ${{ vars.NUKE_ARN }}
          aws-region: ap-northeast-1
          
      - name: Install AWS Nuke
        run: |
          wget https://github.com/ekristen/aws-nuke/releases/download/v${{ env.AWS_NUKE_VERSION }}/aws-nuke-v${{ env.AWS_NUKE_VERSION }}-linux-amd64.tar.gz
          tar -xvf aws-nuke-v${{ env.AWS_NUKE_VERSION }}-linux-amd64.tar.gz
          # 実行ファイルの存在を確認
          ls -la  # 展開したファイルをリスト表示
          ./aws-nuke version  # 正しいコマンドを実行

      - name: Run aws-nuke
        id: run_nuke
        run: |
          ./aws-nuke nuke -c ./nuke-config.yml --no-dry-run --no-prompt > output.txt

      - name: Save output from aws-nuke
        id: save_output
        run: |
          grep "would remove" output.txt | awk -F" - " '{print $2, $3, $5}' > filtered_output.txt
          cat filtered_output.txt
          
          # ファイルの内容を整形 - 改行を環境変数に設定
          OUTPUT=$(awk '{print $0 "\\n"}' filtered_output.txt | tr -d '\n')
          echo "FILTERED_OUTPUT=$OUTPUT" >> $GITHUB_ENV  # 環境変数ファイルに設定

      - name: Send output to Slack
        run: |
          if [ -z "${{ env.FILTERED_OUTPUT }}" ]; then
            MESSAGE="今週の削除対象リソースはありません"
          else
            MESSAGE="今週の削除対象リソースは以下です:\\n${{ env.FILTERED_OUTPUT }}"
          fi
          
          echo "SLACK_MESSAGE=$MESSAGE" >> $GITHUB_ENV  # Slackに送信するメッセージを環境変数に設定

      - name: Send message to Slack
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            {
              "text": "${{ env.SLACK_MESSAGE }}"
            }
```

</details>

今回は前回の記事との差分をご紹介します。
* AWS Nukeのインストール
```
- name: Install AWS Nuke
        run: |
          wget https://github.com/ekristen/aws-nuke/releases/download/v${{ env.AWS_NUKE_VERSION }}/aws-nuke-v${{ env.AWS_NUKE_VERSION }}-linux-amd64.tar.gz
          tar -xvf aws-nuke-v${{ env.AWS_NUKE_VERSION }}-linux-amd64.tar.gz
          # 実行ファイルの存在を確認
          ls -la  # 展開したファイルをリスト表示
          ./aws-nuke version  # 正しいコマンドを実行
```
`` env.AWS_NUKE_VERSION `` には、ワークフローファイル5行目で指定している `` AWS_NUKE_VERSION: 3.51.0 `` が入っています。

* AWS Nukeの実行
```
- name: Run aws-nuke
        id: run_nuke
        run: |
          ./aws-nuke nuke -c ./nuke-config.yml --no-dry-run --no-prompt > output.txt
```
コード内 ``--no-dry-run`` をなくす(省略する)と、実際には削除せず削除予定のリソースを確認できる「dry-run」での実行となります。
Slackへの連携は行わず、AWS Nukeのみの実行であればここまでで処理は終了となります。

### Slackへの連携を行う場合
Slack連携の方法は以下の記事を参考に、そのまま実行させていただきました。とてもわかりやすく解説頂いているのでぜひ参考にしてみて下さい。

https://qiita.com/seratch/items/28d09eacada09134c96c

ではワークフローファイルの説明の続きです。

* 実行結果の保存・整形
```
- name: Save output from aws-nuke
        id: save_output
        run: |
          grep "would remove" output.txt | awk -F" - " '{print $2, $3, $5}' > filtered_output.txt
          cat filtered_output.txt
          
          # ファイルの内容を整形 - 改行を環境変数に設定
          OUTPUT=$(awk '{print $0 "\\n"}' filtered_output.txt | tr -d '\n')
          echo "FILTERED_OUTPUT=$OUTPUT" >> $GITHUB_ENV  # 環境変数ファイルに設定
```
AWS Nukeから出力されるログを見やすいように整えます。

* Slackに送るメッセージを設定
```
- name: Send output to Slack
        run: |
          if [ -z "${{ env.FILTERED_OUTPUT }}" ]; then
            MESSAGE="今週の削除対象リソースはありません"
          else
            MESSAGE="今週の削除対象リソースは以下です:\\n${{ env.FILTERED_OUTPUT }}"
          fi
          
          echo "SLACK_MESSAGE=$MESSAGE" >> $GITHUB_ENV  # Slackに送信するメッセージを環境変数に設定
```
AWS Nukeを実行した結果、削除リソースが0だった場合とそうでない場合に分けて、Slackに送る文章を設定しておきます。

* Slackチャンネルにメッセージ(実行結果)を連携
```
- name: Send message to Slack
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            {
              "text": "${{ env.SLACK_MESSAGE }}"
            }
```
先ほど設定したメッセージを、[こちら](https://qiita.com/seratch/items/28d09eacada09134c96c)を参考に設定したSlackチャンネルに送信します。

以上で、GitHub Actions×AWS Nukeによる不要リソース削除のご説明でした！

## AWS Nuke利用時の注意点
**★ 実行後の影響が大きい！！**
実行後、私はIAM Identity Centerの認証情報まで消してしまったりIAMロールとIAMポリシーの紐づけを消してしまったりといろいろご迷惑をおかけしてしまいました笑
みなさんも実行時には十分お気を付けください！！(;´д｀)笑
* まだAWS Nukeが対応していないサービスがある(Bedrockまわりなど特に…)
* 削除保護等がついていたりすると削除エラーになりやすい(私はEBS、AMI、スナップショット、サブネットがエラーにより削除できなかったためスクリプト作成して削除しました)

# 最後に
ここまでご覧いただきありがとうございます。

もちろんAWS Nuke導入までに手作業にて不要リソースの選定や削除、環境利用者への声がけを行ったというのも大きいですが、最終的に約100万円の環境コスト削減に成功しました！
管理されてないリソース、こんなにあったんだ…って感じです。笑

皆様も不要リソースの大掃除、行ってみてはいかがでしょうか✨

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867349/a687908d-bcf3-4627-ab92-1fa68bd1262c.png)

今回も長い文章お読みいただき、ありがとうございました。
お疲れ様でした！(^O^)／