既存VPCを流用したEKS環境の構築

みんなのウェディングのインフラエンジニア横山です。
今回は、既存VPCを流用したEKS環境の構築手法について書きます。

みんなのウェディングのEKS環境について

みんなのウェディングでは現在、インフラ基盤のEKS移行を進めています。
EKS環境を作成する場合、通常は以下のようにeksctlやCloudFormationを利用します。 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html
しかしながら、eksctlを使って構築した場合、今後リソースの内容を更新したいときにeksctlを使い続けることになります。
弊社ではインフラ構成管理に既にterraformを使っています。先々の運用を考えると、使うツールは必要最小限にしておきたいところです。
また、CloudFromtaionを使った場合、新規にVPCを作成することになります。
VPCピアリングを使えばDBなどが存在する既存VPCとの接続性を確保することもできますが、ネットワーク構成は複雑になります。
以上のことから、CloudFromtaionで作成される設定内容を読み解き、terraformを利用して、既存VPCを流用したEKS環境構築を行うことにしました。

EKS環境構築手法

ネットワーク環境の整備

  • 利用するVPCにタグ付けを行います。
    EKSコントロールプレーンを作成すると自動で付与されますが、terraformで管理しておきたいので事前に設定しておきます。
resource "aws_vpc" "test" {
  cidr_block = "192.168.0.0/16"

  tags = {
    Name                                    = "test"
    "kubernetes.io/cluster/<EKSクラスター名>" = "shared"
  }
}
  • ワーカノードのデプロイ先となるサブネットにタグ付けを行ないます。
    VPCと同じく、EKSコントロールプレーンを作成すると自動で付与されますが、terraformで管理しておきたいので事前に設定しておきます。
    kubernetes.io/role/internal-elbを付与することで、内部向けELBが作成できるようになります。
resource "aws_subnet" "subnet-c" {
    vpc_id                  = "vpc-5f9a3b3a"
    cidr_block              = "192.168.0.0/24"
    availability_zone       = "ap-northeast-1c"
    map_public_ip_on_launch = false

    tags = {
        Name = "subnet-ac"
        "kubernetes.io/cluster/<EKSクラスター名>" = "shared"
        "kubernetes.io/role/internal-elb" = 1
    }
}

resource "aws_subnet" "subnet-a" {
    vpc_id                  = "vpc-5f9a3b3a"
    cidr_block              = "192.168.1.0/24"
    availability_zone       = "ap-northeast-1a"
    map_public_ip_on_launch = true

    tags = {
        Name = "subnet-a"
        "kubernetes.io/cluster/<EKSクラスター名>" = "shared"
        "kubernetes.io/role/internal-elb" = 1
    }
}
  • EKSコントロールプレーンに設定するセキュリティグループを作成します。
    EKSコントロールプレーン、ワーカーノード間の通信に必要なポートを開けています。
resource "aws_security_group" "eks-controle-plane" {
  description = "eks-controle-plane"

  egress {
    security_groups = [
      "${aws_security_group.eks-worker-node.id}",
    ]

    from_port   = "1025"
    to_port     = "65535"
    protocol    = "6"
    self        = false
    description = "Allow the cluster control plane to communicate with worker Kubelet and pods"
  }

  egress {
    security_groups = [
      "${aws_security_group.eks-worker-node.id}",
    ]

    from_port   = "443"
    to_port     = "443"
    protocol    = "TCP"
    self        = false
    description = "Allow the cluster control plane to communicate with pods running extension API servers on port 443"
  }

  ingress {
    security_groups = [
      "${aws_security_group.eks-worker-node.id}",
    ]

    from_port   = "443"
    to_port     = "443"
    protocol    = "TCP"
    self        = false
    description = "Allow pods to communicate with the cluster API Server"
  }

  name                   = "eks-controle-plane"
  revoke_rules_on_delete = false

  tags = {
    Name = "eks-controle-plane"
  }

  vpc_id = "vpc-xxxxxxxxx"
}
  • ワーカーノードに設定するセキュリティグループを作成します。
    EKSコントロールプレーン、ワーカーノード間の通信に必要なポートを開けています。
    また、ワーカーノード間の通信は全て許可するようにしています。
resource "aws_security_group" "eks-worker-node" {
  description = "eks-worker-node"

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

  name                   = "eks-worker-node"
  revoke_rules_on_delete = false

  tags = {
    Name                                    = "eks-worker-node"
    "kubernetes.io/cluster/wedding-staging" = "owned"
  }

  vpc_id = "vpc-xxxxxxxxx"
}

resource "aws_security_group_rule" "eks-worker-node_extra_rule_1" {
  security_group_id        = "${aws_security_group.eks-worker-node.id}"
  from_port                = "1025"
  to_port                  = "65535"
  protocol                 = "6"
  type                     = "ingress"
  source_security_group_id = "${aws_security_group.eks-controle-plane.id}"
}

resource "aws_security_group_rule" "eks-worker-node_extra_rule_2" {
  security_group_id        = "${aws_security_group.eks-worker-node.id}"
  from_port                = "443"
  to_port                  = "443"
  protocol                 = "TCP"
  type                     = "ingress"
  source_security_group_id = "${aws_security_group.eks-controle-plane.id}"
}

resource "aws_security_group_rule" "eks-worker-node_extra_rule_3" {
  security_group_id        = "${aws_security_group.eks-worker-node.id}"
  from_port                = "0"
  protocol                 = "-1"
  to_port                  = "0"
  type                     = "ingress"
  source_security_group_id = "${aws_security_group.eks-worker-node.id}"
}

IAMロールの作成

  • EKSコントロールプレーンに設定するIAMロールを作成します。
resource "aws_iam_role" "eks-controle-plane" {
    name               = "eks-controle-plane"
    path               = "/"
    description        = "Allows EKS to manage clusters on your behalf."
    assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "eks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_policy_attachment" "AmazonEKSClusterPolicy" {
  name       = "AmazonEKSClusterPolicy"
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  roles      = [
    "eks-controle-plane"
  ]
}

resource "aws_iam_policy_attachment" "AmazonEKSServicePolicy" {
  name       = "AmazonEKSServicePolicy"
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy"
  roles      = [
    "eks-controle-plane"
  ]
}
  • ワーカーノードに設定するIAMロールを作成します。
resource "aws_iam_role" "eks-worker-node" {
    name               = "eks-worker-node"
    path               = "/"
    assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_policy_attachment" "AmazonEKSWorkerNodePolicy" {
  name       = "AmazonEKSWorkerNodePolicy"
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
  roles      = [
    "eks-worker-node",
  ]
}

resource "aws_iam_policy_attachment" "AmazonEKS_CNI_Policy" {
  name       = "AmazonEKS_CNI_Policy"
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
  roles      = [
    "eks-worker-node",
  ]
}

resource "aws_iam_policy_attachment" "AmazonEC2ContainerRegistryReadOnly" {
  name       = "AmazonEC2ContainerRegistryReadOnly"
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
  roles      = [
    "eks-worker-node",
  ]
}

EKSコントロールプレーンの作成

コンソールでEKSコントロールプレーンの作成を行います。
https://ap-northeast-1.console.aws.amazon.com/eks/home?region=ap-northeast-1#/clusters

サブネットには、先ほどタグ付けした、ワーカノードのデプロイ先となるサブネットを指定します。 セキュリティグループは、先ほど作成したEKSコントロールプレーン用セキュリティグループを指定します。 ログの設定は必要に応じて設定します。

ワーカーノードの作成

  • 起動テンプレートを作成する
    security_groupsにはワーカーノード用セキュリティグループを指定します。
    user_dataには以下の内容をbase64エンコードしたものを入れます。.
#!/bin/bash
set -o xtrace
/etc/eks/bootstrap.sh <EKSクラスター名>
resource "aws_launch_template" "eks-worker-node-template" {
  name = "eks-worker-node-template"

  iam_instance_profile {
    name = "eks-worker-node"
  }

  # EKS最適化AMI https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-optimized-ami.html
  image_id      = "ami-0fde798d17145fae1"
  instance_type = "t3.medium"
  key_name      = <キーペア名>

  network_interfaces {
    associate_public_ip_address = true
    delete_on_termination       = true

    security_groups = [
      <ワーカーノード用セキュリティグループ>
    ]
  }

  user_data = "IyEvYmluL2Jhc2gKc2V0IC1vIHh0cmFjZQovZXRjL2Vrcy9ib290c3RyYXAuc2ggdGVzdC1jbHVzdGVyCg=="
}
  • AutoScaligグループを作成する
resource "aws_autoscaling_group" "eks-worker-node" {
  name             = "eks-worker-node"
  max_size         = 4
  min_size         = 1
  desired_capacity = 3
  default_cooldown = 300

  launch_template {
    id      = "${aws_launch_template.eks-worker-node-template.id}"
    version = "$Latest"
  }

  health_check_grace_period = 300
  force_delete              = false

  vpc_zone_identifier = [
    "subnet-11111111",
    "subnet-22222222",
  ]

  termination_policies = [
    "OldestLaunchTemplate",
  ]

  tags = [
    {
      key                 = "Name"
      value               = "eks-worker-node"
      propagate_at_launch = true
    },
    {
      key                 = "kubernetes.io/cluster/<EKSクラスター名>"
      value               = "owned"
      propagate_at_launch = true
    }
  ]
}

RBACの設定

このままだとEKSクラスターを作成したIAMユーザーしかkuberntesを利用できません。
以下の手順で適宜、権限が必要な人のIAMユーザを追加します。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/amazon-eks-cluster-access/

まとめ

以上、既存VPCを流用したEKS環境の構築手法についてまとめました。
公式ドキュメントにはeksctl or CloudFromtaionという2パターンの構築手法がありますが、
既存VPCがある場合は、今回ご紹介したような手法を選択するのも良いのではないでしょうか。
次回は、EKS上でSpinnakerを稼働させる方法についてお伝えしたいと思います。