💡

AWSコスト配分タグの運用に入門してみた

2023/01/01に公開

あけましておめでとうございます!🎍
昨年は円安なども相まってAWSのコスト高騰が度外視できない状況があった年かと思います。
今回はコスト削減やコスト最適化を考える上で重要となる現状把握や分析のために役立ちそうなAWSコスト配分タグの運用を、他のAWSサービス・機能とも絡ませながら取り上げようと思います。

AWSコスト配分タグとは

AWS コスト配分タグ とはAWSリソースのコストを詳細に追跡するためのタグです。どのタグをつけたリソースにいくらコストが掛かっているかの可視化に利用できます。
前提として、AWSにはリソースにメタデータを付与するための AWS リソースのタグ付けという機能が存在しますが、このタグ付け機能を元にコスト管理にも応用したものと考えられます。

コスト配分タグの種類

コスト配分タグには以下の2種類が存在します。

  • AWS生成のタグ
  • ユーザー定義タグ

AWS生成のタグはその名の通りAWS側で生成されるもので、ユーザー定義タグはユーザーが作成するものです。例としてAWS生成のタグには、createdBy があります。AWS生成のタグとユーザー定義タグについては公式ドキュメント AWS-生成コスト配分タグユーザー定義のコスト配分タグ が参考になるかと思います。

コスト配分タグの有効化方法

何かしらのリソースにタグ付けを行うと、AWSコンソール > Billing > コスト配分タグ の一覧にタグキーがあがってきます。ここであがってきたタグキーを有効化し、コスト配分タグとして利用できる状態にしていく流れとなります。AWS生成のタグを使用する場合も、ユーザー定義タグを使用する場合も同様にタグの有効化が必要になります。下記はユーザー定義タグ Env とAWS生成のタグ createdBy をアクティブ化した例です。コスト配分タグメニューから対象のタグにチェックを入れ有効化を行います。

このようにアクティブ化したコスト配分タグは、コスト配分レポートAWS Cost Explorer で利用できます。

コスト配分レポートでは aws:user: のprefixのついた列が出力されます。(CSVファイル)

AWS Cost Explorerではサイドバーのレポートパラメータから選択できるようになります。

前提として、上記の機能の利用には、有効化設定やレポート設定が必要となります。AWS Cost Explorerについては、 Cost Explorer を有効にする 、コスト配分レポートについては、 毎月のコスト配分レポート あたりを参考にしてください。

併せて使いたいAWSサービス・機能

ここからはコスト配分タグを利用する上で併せて使うと便利になりそうなサービスや機能などを取り上げていきます。

タグエディタ

タグエディタ は複数のリソースへのタグ付けやタグの編集・削除などを一括で行える機能です。
タグエディタのインターフェースを利用することにより、各サービス画面などからのタグ操作が不要になり、一元的に管理できるようになるので便利です。

使い方を見ていきます。

AWSコンソール > Resource Groups & Tag Editor > Tag Editor へ移動すると検索画面のUIで、リージョン、リソースタイプ、タグなどでリソースを検索できるようになっています。

東京リージョンのVPCリソースを検索しました。

「選択したリソースのタグを管理する」から、一括でタグの操作をしていきます。

Envというキーとdevの値を持つタグを付与しました。

タグポリシー

タグ ポリシー はタグの運用を標準化するのに役立つ機能です。例えば特定のタグは特定の値のみ許可することにより、大文字小文字の表記揺れや値の設定のばらつきなどを防ぐことができます。

使い方を見ていきます。

前提として、この機能は AWS Organizations 内のサービスであるため、AWS Organizationsを有効にしておく必要があるので済ませておいてください。

まず、タグポリシーを有効化します。

ポリシーを作成していきます。

「environment」という名前のポリシーを作ります。環境ごとのタグを設定するためのポリシーとして利用する想定です。

主要な設定項目が3つほどあります。

  • タグキー大文字化コンプライアンス
  • タグ値コンプライアンス
  • 強制するリソースタイプ

順に見ていきましょう。

  • タグキー大文字化コンプライアンス

タグのKeyの指定に関するものです。チェックを入れると大文字スタートのタグキーの指定が必須となります。上記の例だと「Env」を指定しています。Keyの大文字小文字の表記揺れなど防止するためのものだと思われます。すなわちこの設定例だとKeyに「Env」ではなく「env」を指定しようとした際にエラーで弾かれることとなります。

  • タグ値コンプライアンス

指定したタグに許容する値を指定できます。上記の例だと、Envを設定しており、これにはdevやprdなどの決まった選択肢の値が入ってほしい想定なので、あらたかじめ値として選択できるものを指定しておきます。ここではdevとprdを指定します。

  • 強制するリソースタイプ

実際にタグの準拠を強制するリソースタイプを指定します。上記の例の場合は全てのリソースタイプを選択しました。

また、ポリシーの設定はJSONでも行えます。ここまでのポリシーをJSONに起こすと以下のような感じです。

{
    "tags": {
        "Env": {
            "tag_key": {
                "@@assign": "Env"
            },
            "tag_value": {
                "@@assign": [
                    "dev",
                    "prd"
                ]
            },
            "enforced_for": {
                "@@assign": [
                    "amplifyuibuilder:component",
                    "amplifyuibuilder:app/environment/components",
                    "amplifyuibuilder:app/environment/themes",
                    "amplifyuibuilder:theme",
                    "apigateway:apikeys",
                    "apigateway:domainnames",
                    "apigateway:restapis",
                    "apigateway:stages",
                    "appmesh:*",
                    "appconfig:application",
                    "appconfig:configurationprofile",
                    "appconfig:deployment",
                    "appconfig:deploymentstrategy",
                    "appconfig:environment",
                    "athena:*",
                    "auditmanager:assessment",
                    "auditmanager:assessmentControlSet",
                    "auditmanager:assessmentFramework",
                    "auditmanager:control",
                    "backup:backupPlan",
                    "backup:backupVault",
                    "backup-gateway:gateway",
                    "backup-gateway:hypervisor",
                    "backup-gateway:vm",
                    "batch:job",
                    "batch:job-definition",
                    "batch:job-queue",
                    "bugbust:event",
                    "acm:*",
                    "acm-pca:certificate-authority",
                    "chime:app-instance",
                    "chime:app-instance-user",
                    "chime:app-instance/channel",
                    "chime:app-instance/user",
                    "chime:channel",
                    "chime:media-pipeline",
                    "chime:meeting",
                    "cloud9:environment",
                    "cloudfront:*",
                    "cloudtrail:*",
                    "cloudwatch:*",
                    "events:*",
                    "logs:log-group",
                    "codebuild:*",
                    "codecommit:*",
                    "codeguru-reviewer:association",
                    "codepipeline:*",
                    "codestar-connections:connection",
                    "codestar-connections:host",
                    "cognito-identity:*",
                    "cognito-idp:*",
                    "comprehend:*",
                    "config:*",
                    "connect:contact-flow",
                    "connect:instance/agent",
                    "connect:instance/contact-flow",
                    "connect:instance/integration-association",
                    "connect:instance/queue",
                    "connect:instance/routing-profile",
                    "connect:instance/transfer-destination",
                    "connect:integration-association",
                    "connect:queue",
                    "connect:quick-connect",
                    "connect:routing-profile",
                    "connect:user",
                    "dlm:policy",
                    "directconnect:*",
                    "dms:*",
                    "dynamodb:*",
                    "ec2:capacity-reservation",
                    "ec2:client-vpn-endpoint",
                    "ec2:customer-gateway",
                    "ec2:dhcp-options",
                    "ec2:elastic-ip",
                    "ec2:fleet",
                    "ec2:fpga-image",
                    "ec2:host-reservation",
                    "ec2:image",
                    "ec2:instance",
                    "ec2:internet-gateway",
                    "ec2:launch-template",
                    "ec2:natgateway",
                    "ec2:network-acl",
                    "ec2:network-interface",
                    "ec2:reserved-instances",
                    "ec2:route-table",
                    "ec2:security-group",
                    "ec2:snapshot",
                    "ec2:spot-instance-request",
                    "ec2:subnet",
                    "ec2:traffic-mirror-filter",
                    "ec2:traffic-mirror-session",
                    "ec2:traffic-mirror-target",
                    "ec2:volume",
                    "ec2:vpc",
                    "ec2:vpc-endpoint",
                    "ec2:vpc-endpoint-service",
                    "ec2:vpc-peering-connection",
                    "ec2:vpn-connection",
                    "ec2:vpn-gateway",
                    "elasticfilesystem:*",
                    "elastic-inference:accelerator",
                    "eks:cluster",
                    "elasticbeanstalk:application",
                    "elasticbeanstalk:applicationversion",
                    "elasticbeanstalk:configurationtemplate",
                    "elasticbeanstalk:platform",
                    "ecr:repository",
                    "ecs:cluster",
                    "ecs:service",
                    "ecs:task-set",
                    "elasticache:cluster",
                    "es:domain",
                    "elasticloadbalancing:*",
                    "elasticmapreduce:cluster",
                    "elasticmapreduce:editor",
                    "emr-serverless:applications",
                    "firehose:*",
                    "frauddetector:detector",
                    "frauddetector:detector-version",
                    "frauddetector:model",
                    "frauddetector:rule",
                    "frauddetector:variable",
                    "fsx:*",
                    "globalaccelerator:accelerator",
                    "greengrass:bulkDeployment",
                    "greengrass:connectorDefinition",
                    "greengrass:coreDefinition",
                    "greengrass:deviceDefinition",
                    "greengrass:functionDefinition",
                    "greengrass:loggerDefinition",
                    "greengrass:resourceDefinition",
                    "greengrass:subscriptionDefinition",
                    "guardduty:detector",
                    "guardduty:filter",
                    "guardduty:ipset",
                    "guardduty:threatintelset",
                    "healthlake:datastore",
                    "iam:instance-profile",
                    "iam:mfa",
                    "iam:oidc-provider",
                    "iam:policy",
                    "iam:saml-provider",
                    "iam:server-certificate",
                    "inspector2:filter",
                    "iotanalytics:*",
                    "iotevents:*",
                    "iotsitewise:asset",
                    "iotsitewise:asset-model",
                    "iotfleethub:application",
                    "kinesisanalytics:*",
                    "kms:*",
                    "lambda:*",
                    "macie2:custom-data-identifier",
                    "mediastore:container",
                    "mq:broker",
                    "mq:configuration",
                    "network-firewall:firewall",
                    "network-firewall:firewall-policy",
                    "network-firewall:stateful-rulegroup",
                    "network-firewall:stateless-rulegroup",
                    "oam:link",
                    "oam:sink",
                    "organizations:account",
                    "organizations:ou",
                    "organizations:policy",
                    "organizations:root",
                    "sms-voice:configuration-set",
                    "sms-voice:opt-out-list",
                    "sms-voice:phone-number",
                    "sms-voice:pool",
                    "sms-voice:sender-id",
                    "rbin:rule",
                    "rds:cluster-endpoint",
                    "rds:cluster-pg",
                    "rds:db-proxy",
                    "rds:db-proxy-endpoint",
                    "rds:es",
                    "rds:og",
                    "rds:pg",
                    "rds:ri",
                    "rds:secgrp",
                    "rds:subgrp",
                    "rds:target-group",
                    "redshift:*",
                    "ram:*",
                    "resource-groups:*",
                    "route53:hostedzone",
                    "route53resolver:*",
                    "s3:bucket",
                    "sagemaker:action",
                    "sagemaker:app-image-config",
                    "sagemaker:artifact",
                    "sagemaker:context",
                    "sagemaker:experiment",
                    "sagemaker:flow-definition",
                    "sagemaker:human-task-ui",
                    "sagemaker:model-package",
                    "sagemaker:model-package-group",
                    "sagemaker:pipeline",
                    "sagemaker:processing-job",
                    "sagemaker:project",
                    "sagemaker:training-job",
                    "scheduler:schedule-group",
                    "secretsmanager:*",
                    "servicecatalog:application",
                    "servicecatalog:attributeGroup",
                    "servicecatalog:portfolio",
                    "servicecatalog:product",
                    "sns:topic",
                    "sqs:queue",
                    "ssm-contacts:contact",
                    "states:*",
                    "storagegateway:*",
                    "ssm:automation-execution",
                    "ssm:document",
                    "ssm:maintenancewindowtask",
                    "ssm:managed-instance",
                    "ssm:opsitem",
                    "ssm:patchbaseline",
                    "ssm:session",
                    "transfer:server",
                    "transfer:user",
                    "transfer:workflow",
                    "wellarchitected:workload",
                    "wisdom:assistant",
                    "wisdom:association",
                    "wisdom:content",
                    "wisdom:knowledge-base",
                    "wisdom:session",
                    "worklink:fleet",
                    "workspaces:*"
                ]
            }
        }
    }
}

ポリシー作成後は、対象のアカウントにアタッチする必要があるのでアタッチします。

アタッチ後、ためしに適当なリソースで、ポリシーに反したタグづけをおこなってみます。(タグEnvの値にdevではなく、developを指定)すると、リソースへのタグ付けがエラーとなり、しっかりとタグポリシーが機能していることが確認できました。

AWS Config ルール

AWS Config ルール はAWSリソースが決められた設定内容のルールに沿って作成されるかを評価するサービスです。このAWS Configルールの中に required-tags というマネージドルールがあり、これは指定したタグがリソースにあるかチェックしてくれます。このルールを使用することによって、指定のタグが付いていないリソースや、ルールに非準拠なタグの値を指定しているリソースなどを検出できます。

使い方を見ていきます。

前提として、AWS Config のセットアップ を済ませておく必要がありますので行っておいてください。

AWSコンソール > AWS Config > ルール からルールを追加していきます。

マネージドルールのrequired-tagsを選択します。

設定を進めます。

「詳細」部分の設定はデフォルト設定にして飛ばします。

「トリガー」部分の設定も今回は基本的にデフォルトで飛ばしますが、いくつか気になる設定項目があるので、どういった内容なのか軽く見ておきます。

  • トリガータイプ
  • 変更範囲
  • リソース

順に見ていきます。

  • トリガータイプ

何をトリガーにルールの適用と評価を行うかのタイプ設定です。
今回、マネージドルールrequired-tagsの使用時には非活性となっており、既に「設定変更」が選択されていたのですが、トリガータイプの設定には以下の2種類があるみたいです。

  • 設定変更
  • 定期的

前者の「設定変更」では特定のタイプのリソースが作成、変更、または削除された際にルールを適用して評価を実行します。後者の「定期的」では、指定した時間の間隔(24時間ごとなど)でのそれを行うことなどができます。

詳細は公式ドキュメントの トリガーの指定 が参考になりそうです。

  • 変更範囲

範囲に基づきいつ評価を行うかの設定です。
下記の3つがあります。

  • すべての変更
  • リソース
  • タグ

「すべての変更」は、全リソースが対象となります。「リソース」ではリソースタイプに基づく指定のリソースなどを対象にできます。「タグ」は指定されたタグを持つリソースを対象にできます。

このあたりは公式ドキュメント マネージドルールの使用 が参考になりそうです。

  • リソース

ルール適用の対象となるリソースの指定です。デフォルトではマルチセレクトでいくらかのリソースが選択されていました。リソース識別子も指定できるみたいです。

「パラメータ」部分の設定も進めていきます。

tag1Key、tag1Value、tag2Key、tag2Value...といったようにパラメータが用意されています。基本的にhogeKeyにはタグのキー名を指定(これにより指定したタグが付与されていないリソースが非準拠として評価される)、hogeValueに、タグの値として実際に入っておくべき値を指定(これにより指定した値以外のものが値として設定されていた場合に非準拠として評価される)します。

ルール作成後、非準拠のリソースが検出されました。(EC2インスタンス)

この上記の2つのインスタンスはあらかじめ、片方がタグ付けなし、もう片方がルールに反した値の設定(Envタグの値としてdevではなく、developを指定)をし、わざと非準拠になるようにしておきました。(さきほど設定していたタグポリシー機能は削除済み)

この2インスタンスを正しいタグ付け設定に設定しなおします。

すると、再評価が実行され、非準拠のリソースがなくなりました。

おわりに

今回はAWSコスト配分タグをはじめ、併せて利用すると便利になりそうなサービスや機能などにも触れながらAWSコスト配分タグの運用に入門してみました。コスト削減や最適化には現状把握と分析が重要になってくると思いますので、機会があればそうした取り組みに役立つツールとして本記事で取り上げたサービスや機能などを利用してみてはいかがでしょうか。

Discussion