ECS初回構築時に自動作成されるIAMロール「AWSServiceRoleForECS」とTerraformでの予期せぬ挙動

AWSServiceRoleForECSとは

ECSを作成すると、勝手に作られるIAMロール。Terraformでaws_ecs_clusterリソースを作ったときにも自動作成される。Service-Linked Rolesというヤツの一種らしい。

公式ドキュメントから、AWSServiceRoleForECSについての説明を引用する。

Amazon Elastic Container Service では、AWS Identity and Access Management (IAM) の「サービスにリンクされたロール」を使用します。

 

ほとんどの状況では、サービスにリンクされたロールを手動で作成する必要はありません。たとえば、新しいクラスターを作成する (たとえば、Amazon ECS の初回実行、クラスターの作成ウィザード、AWS CLI または SDK) か、AWS マネジメントコンソールでサービスを作成または更新するときに、存在しない場合は Amazon ECS によりサービスにリンクされたロールが作成されます。

すごくざっくり言えば、このIAMロールは「ECSをいい感じに動かしてくれる君」である。

リソース詳細

AWSServiceRoleForECSが具体的にどんな権限を持っているか確認してみよう。アタッチされているIAMポリシーを見てみると、ENIやELB・Route53周りの権限を持っていることが分かる。

$ aws iam get-role --role-name AWSServiceRoleForECS
{
    "Role": {
        "Description": "Role to enable Amazon ECS to manage your cluster.",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ecs.amazonaws.com"
                    }
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleId": "AROAJLAXPZR6O4INQMVBW",
        "CreateDate": "2018-08-02T00:27:03Z",
        "RoleName": "AWSServiceRoleForECS",
        "Path": "/aws-service-role/ecs.amazonaws.com/",
        "Arn": "arn:aws:iam::123456789012:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS"
    }
}

$ aws iam list-attached-role-policies --role-name AWSServiceRoleForECS
{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonECSServiceRolePolicy",
            "PolicyArn": "arn:aws:iam::aws:policy/aws-service-role/AmazonECSServiceRolePolicy"
        }
    ]
}

$ aws iam get-policy --policy-arn arn:aws:iam::aws:policy/aws-service-role/AmazonECSServiceRolePolicy
{
    "Policy": {
        "PolicyName": "AmazonECSServiceRolePolicy",
        "Description": "Policy to enable Amazon ECS to manage your cluster.",
        "CreateDate": "2017-10-14T01:18:58Z",
        "AttachmentCount": 1,
        "IsAttachable": true,
        "PolicyId": "ANPAIVUWKCAI7URU4WUEI",
        "DefaultVersionId": "v4",
        "Path": "/aws-service-role/",
        "Arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonECSServiceRolePolicy",
        "UpdateDate": "2018-03-22T01:20:59Z"
    }
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/aws-service-role/AmazonECSServiceRolePolicy --version-id v4
{
    "PolicyVersion": {
        "CreateDate": "2018-03-22T01:20:59Z",
        "VersionId": "v4",
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "ec2:AttachNetworkInterface",
                        "ec2:CreateNetworkInterface",
                        "ec2:CreateNetworkInterfacePermission",
                        "ec2:DeleteNetworkInterface",
                        "ec2:DeleteNetworkInterfacePermission",
                        "ec2:Describe*",
                        "ec2:DetachNetworkInterface",
                        "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                        "elasticloadbalancing:DeregisterTargets",
                        "elasticloadbalancing:Describe*",
                        "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
                        "elasticloadbalancing:RegisterTargets",
                        "route53:ChangeResourceRecordSets",
                        "route53:CreateHealthCheck",
                        "route53:DeleteHealthCheck",
                        "route53:Get*",
                        "route53:List*",
                        "route53:UpdateHealthCheck",
                        "servicediscovery:DeregisterInstance",
                        "servicediscovery:Get*",
                        "servicediscovery:List*",
                        "servicediscovery:RegisterInstance",
                        "servicediscovery:UpdateInstanceCustomHealthStatus"
                    ],
                    "Resource": "*",
                    "Effect": "Allow"
                }
            ]
        },
        "IsDefaultVersion": true
    }
}

AWSServiceRoleForECSの削除

公式ドキュメントに下記の記載があるとおり、

IAM を使用してサービスにリンクされたロールを削除するには、まずそのロールにアクティブなセッションがないことを確認し、すべての AWS リージョンのすべての Amazon ECS クラスターを削除する必要があります。

ECS Cluster が存在する状態で削除しようとすると、削除に失敗する。実際にやってみるとちゃんとエラーが出た。

f:id:tmknom:20180830121248p:plain

AWSServiceRoleForECSのリンクをクリックしてみると、こんな感じ。

f:id:tmknom:20180830121335p:plain

「リソースを表示」ボタンをクリックすると、具体的になんのリソースがあるからダメなのか分かる。

f:id:tmknom:20180830121420p:plain

ドキュメントの記載通り、ECS Clusterを全部削除すると、このIAMロールも削除できる。

f:id:tmknom:20180830121447p:plain

Terraform使用時の予期せぬ挙動

初回のみ発生するエラー

aws_ecs_clusterリソース作成時にも、AWSServiceRoleForECSは自動作成される。そのため、一見すると、TerraformでECSを構築する場合でも、特に考慮は必要ないように思える。

しかし、実際にはaws_ecs_clusterと同時にaws_ecs_serviceを構築しようとするとエラーが出る。しかも初回のみ。実際に表示されるエラーメッセージは下記のようなものだ。

* aws_ecs_service.ecs: InvalidParameterException: Unable to assume the service linked role. Please verify that the ECS service linked role exists.
    status code: 400, request id: fd9180b6-9aa6-11e8-bacf-772b1e327bd2 "nginx"

このエラーが出たあと、もう一度 terraform apply すると、正しくaws_ecs_serviceリソースが作成される。つまり、aws_ecs_clusterリソース作成時に、自動的にAWSServiceRoleForECSが作成されるわけだが、aws_ecs_serviceリソースから参照できるようになるまでには、タイムラグがあるということだ。

対処方針

この挙動は、はじめてECSを構築するときに、エンジニアを無駄に戸惑わせる。特にAWS Organizationsなどで、たくさんAWSアカウントを作ると、遭遇率が上がる。

よって、この問題への対処方法としては2つ。

  1. 初回だけエラーが出るのを無視して、もう一度applyする
  2. 事前にAWSServiceRoleForECSを作成しておいて、それからECSを構築する

1番については、AWSServiceRoleForECSの挙動を正しく理解している人にとっては問題ないが、当然こんなマニアックな仕様を知っている人はほぼいない。よって、メンドウだが、2番を採用すべきだろう。

TerraformでAWSServiceRoleForECSを作成するのは簡単である。

resource "aws_iam_service_linked_role" "ecs" {
  aws_service_name = "ecs.amazonaws.com"
}

aws_iam_service_linked_roleリソースの詳細は、Terraformのドキュメントを参照してほしい。

まとめ

ECS初回構築時に自動作成されるIAMロール「AWSServiceRoleForECS」について紹介した。ECS Cluster作成時に自動作成されることを説明し、Terraform使用時の予期せぬ挙動と対処法を示した。

非常にマニアックな挙動であり、そもそもこんなことを気にする人はほとんどいないと思うが、見知らぬ誰かの役に立てば幸いだ。