woshidan's blog

そんなことよりコードにダイブ。

EC2インスタンスをECSの特定のクラスタに所属させる

今日は、EC2インスタンスをECSの特定のクラスタのコンテナインスタンスとして登録するために必要なものについてまとめておきます。

EC2インスタンスがECSのコンテナインスタンスになる仕組み

ECSのコンテナインスタンスecs-agentが動作していて、ecs-agentがECS APIを叩いてクラスタに登録したコンテナインスタンスなのですが、実は直接的にEC2インスタンスをECSのコンテナインスタンスとして登録するための設定はありません。

EC2インスタンスがECSのコンテナインスタンスとして動作するための条件を満たしていたら勝手にECSのクラスタにコンテナインスタンスとして登録される、という仕組みで初見は結構分かりにくかったです。。

EC2インスタンスがECSのコンテナインスタンスになるために必要なもの

  • ECSに最適化されたAMIの利用
  • AWSのアカウントに対する認証情報(どこの組織のECSインスタンスなのかわかる情報)が含まれ、ECSがコンテナインスタンスの登録やイメージの取得などに使うアクセス権限が付与されたIAMロール
  • IAMロールをEC2インスタンスにアタッチするためのインスタンスプロファイル
  • (EC2起動タイプを選択する場合のみ)SSHログインのためのキーペア
    • 基本的にECSに最適化されたLinux系のAMIを利用するインスタンスでは、パスワードによるSSHログインができないため*1
  • (強く推奨)コンテナインスタンスを起動するVPC
  • ecs-agentがECS APIと通信してコンテナインスタンスを登録するためのアウトバウンドの通信が許可されたセキュリティグループ
  • コンテナインスタンスを登録するクラスタを決定するためのuserdataで指定する環境変数 ECS_CLUSTERの指定
    • userdataに指定がなければデフォルトのクラスタに登録する

それぞれ、Terraformで具体的に指定する様子を確認して今日はおしまいにしようと思います。

必要なものを作成するためのTerraformの設定

ECSに最適化されたAMI

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-optimized_AMI.html などを参考に見繕う。

resource "aws_instance" "ecs_container_instance" {
  ami = "ami-e4657283" // 管理画面を操作しててでてきたのを使った気がする
  ...

IAMロール

このへんは昨日のエントリと同じですが。。

// 信頼ポリシーをロールに付与
resource "aws_iam_role" "ec2_instance_role" {
  name = "ec2_instance_role"
  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
    {
       "Action": "sts:AssumeRole",
       "Principal": {
         "Service": "ec2.amazonaws.com"
        },
        "Effect": "Allow",
        "Sid": ""
    }
    ]
}
EOF
}
// アクセス権限ポリシー
resource "aws_iam_policy" "ecs_container_instance_policy" {
  name = "ecs_container_instance_policy"
  path = "/"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecs:CreateCluster",
        "ecs:DeregisterContainerInstance",
        "ecs:DiscoverPollEndpoint",
        "ecs:Poll",
        "ecs:RegisterContainerInstance",
        "ecs:StartTelemetrySession",
        "ecs:UpdateContainerInstancesState",
        "ecs:Submit*",
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}
// 信頼ポリシーが付与されているロールにさらにアクセス権限ポリシーを付与
resource "aws_iam_role_policy_attachment" "ecs_container_instance_policy_attachment" {
  role       = "${aws_iam_role.ec2_instance_role.name}"
  policy_arn = "${aws_iam_policy.ecs_container_instance_policy.arn}"
}

インスタンスプロファイル

// EC2インスタンスにはインスタンスプロファイルを利用してロールをアタッチ
resource "aws_iam_instance_profile" "ecs_container_instance_profile" {
  name = "ecs_container_instance_profile"
  role = "${aws_iam_role.ec2_instance_role.name}"
}
resource "aws_instance" "ecs_container_instance" {
  ...
  iam_instance_profile = "${aws_iam_instance_profile.ecs_container_instance_profile.name}"
}

キーペア

// public_key_pathで指定したパスに ssh-keygen で作成した公開鍵があるとして
resource "aws_key_pair" "auth" {
  key_name   = "${var.key_name}"
  public_key = "${file(var.public_key_path)}"
}

VPC

デフォルト*2のものを利用する場合、Terraformでの設定は不要。

セキュリティグループ

ECSインスタンスとして登録するだけなら、アウトバウンドの通信だけを許可すればよいです。実際はALBやAutoScalingGroupと組み合わせて利用するのでそれらのヘルスチェックや、連携して動く他のサービスからのリクエストを受け付けられるようにする必要がありますが。

resource "aws_security_group" "test_security_group" {
  name = "test_security_group"

  egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
  }
}

userdataで指定する環境変数 ECS_CLUSTERの指定

#!/bin/bash
echo ECS_CLUSTER=woshidan-test-cluster >> /etc/ecs/ecs.config

こういうスクリプト

$ cat userdata.sh | openssl enc -e -base64

Base64エンコードして

resource "aws_instance" "ecs_container_instance" {
  ...
  user_data = <<EOF
IyEvYmluL2Jhc2gKZWNobyBFQ1NfQ0xVU1RFUj13b3NoaWRhbi10ZXN0LWNsdXN0
ZXIgPj4gL2V0Yy9lY3MvZWNzLmNvbmZpZw==
EOF

のように指定します。

現場からは以上です。

参考