「eksctl」コマンドを使ったAmazon EKS構築入門

Amazon EKS (Elastic Kubernetes Service) の環境を簡単に構築できるコマンドラインツール「eksctl」の使い方を解説します。
2019.10.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。

今回は「Amazon EKS (Elastic Kubernetes Service)」の入門記事をお送りします。

EKSの入門記事と言うと、当ブログ(Developers.IO)でも何度か紹介されましたし、巷にはKubernetesの入門書や初心者向けWeb記事が多く出版・公開されています。
それらの入門記事・書籍では、EKS(あるいはGKEやMinikubeなど)を使ったKubernetes環境構築から、構築したKubernetes環境の上で「Pod」や「Service」等のリソースを起動するところまでを一通りやってみる、という流れが多いようです。

一方、今回の記事では「EKSによるKubernetes環境の構築」の部分にフォーカスを当てました。
EKS専用のコマンドラインツール「eksctl」の使い方や、EKS環境の構築によってどのようなAWSリソースが作成・設定されるのか、という点を説明していきたいと思います。

EKS環境構築手順の「過去」と「現在」

本題に入る前に、まずお伝えしたいことは・・・
EKS環境構築の手順は「過去」と「現在」で大きく変わった、ということです!

「過去」

Amazon EKSが正式リリースされたのは2018年6月のことで、当時はバージニア北部リージョンとオレゴンリージョンのみで利用できました。

リリース当初のEKS環境の構築手順は以下の通りでした。

  • 1. EKSサービス用のIAMロールを作成する
    • マネジメントコンソールまたはAWS CLIを使って作成する
  • 2. EKS環境を配置するVPCネットワーク環境を作成する
    • CloudFormationのサンプルテンプレートをダウンロードして手で修正を行い、実行する
  • 3. EKSコントロールプレーンを作成する
    • マネジメントコンソールまたはAWS CLIを使って作成する
  • 4. 作成したEKSコントロールプレーンへkubectlコマンドで接続する
    • heptio-authenticator-aws(*1)をダウンロードしてインストールする
    • ~/.kube/configファイルを手で編集して認証情報を書き込む
    • kubectlコマンドでEKSコントロールプレーンへ接続できることを確認する
  • 5. EKSワーカーノードとなるEC2インスタンスをプロビジョニングする
    • CloudFormationのサンプルテンプレートをダウンロードして手で修正を行い、実行する
  • 6. 作成したEC2インスタンスを「EKSワーカーノード」としてEKSコントロールプレーンから認識させる
    • Kubernetesコンフィグマップ(*2)の定義ファイルを手で編集する
    • kubectl applyコマンドでコンフィグマップを適用する
  • 7. EC2インスタンスがEKSワーカーノードとして組み込まれたことを確認する
    • kubectl get nodesコマンドで確認する

(*1) IAM経由でKubernetesのログイン認証を行うためのヘルパープログラム。
heptio-authenticator-awsaws-iam-authenticator と名前が変わり、その後AWS CLIに機能が組み込まれたため不要となりました。
(*2) Kubernetes上で様々な設定情報を格納するためのリソースであり、定義ファイルはYAMLで記述します。

う~ん、箇条書きで流れを列挙するだけでも、かなり煩雑そうな手順ですね。
実際に行うとハマりポイントも多く、リリース当初からEKSを触っていた人は苦労したのではないかと思います。(苦労しました!)

「現在」

一方、現在のEKS環境の構築手順は、以下の通りです。

$ eksctl create cluster

「過去」の構築手順と比べるとあまりにも簡潔ですが、本当にコレでEKS環境が構築できてしまいます。

もちろん、上記の構築手順はパラメータを最大限に省略したものであり、実際に構築する際はいくつかオプションパラメータを指定するのが普通です。

ここからは「eksctl」コマンドを使ったEKS環境の構築手順について説明していきます。

「eksctl」コマンドの使い方 (基本編)

eksctlコマンドは、以下のような文法となっています。

$ eksctl <機能> <対象>

機能と対象の組み合わせで、以下のコマンド群が用意されています。
(執筆時のeksctl最新バージョン0.7.0の場合)

eksctl
├── create
│   ├── cluster
│   ├── nodegroup
│   ├── iamserviceaccount
│   └── iamidentitymapping
├── get
│   ├── cluster
│   ├── nodegroup
│   ├── iamserviceaccount
│   └── iamidentitymapping
├── update
│   └── cluster
├── delete
│   ├── cluster
│   ├── nodegroup
│   ├── iamserviceaccount
│   └── iamidentitymapping
├── scale
│   └── nodegroup
├── drain
│   └── nodegroup
├── utils
│   ├── wait-nodes
│   ├── write-kubeconfig
│   ├── describe-stacks
│   ├── update-cluster-stack
│   ├── update-kube-proxy
│   ├── update-aws-node
│   ├── update-coredns
│   ├── update-cluster-logging
│   ├── associate-iam-oidc-provider
│   └── install-vpc-controllers
├── completion
│   ├── bash
│   └── zsh
├── version
└── help

いきなりこれだけの数を見せられると困りますよね?
大丈夫、今回はこれらの中から最も基本となるeksctl create clusterコマンドについて説明します。

「eks create cluster」コマンドで作成されるもの

eksctl create clusterコマンドは字句通り「EKSクラスターを作成する」コマンドです。

ここで言う「EKSクラスター」は、Kubernetesインフラ基盤を構成するコンポーネントの集まりを指します。(ここまで「EKS環境」と呼んできたものとほぼ同義です)

EKSクラスターは、大まかに以下の要素で構成されます。

  1. VPCネットワーク環境
  2. EKSコントロールプレーン
  3. EKSワーカーノード

「VPCネットワーク環境」は、EKSクラスターが動作するために必要なVPC/サブネット等のネットワーク環境を指します。

「EKSコントロールプレーン」は、Kubernetesの様々な管理を行うコンポーネントの集まりを指します。
なお、コントロールプレーンを構成するサーバーを意味する「マスターノード」という用語が登場する場合がありますが、EKSではコントロールプレーン機能はマネージドサービス化されているため、総称としての「コントロールプレーン」という呼び方をすることがほとんどです。

「EKSワーカーノード」は、Kubernetesでコンテナが動作するコンピューティングリソースとしてのEC2インスタンスを指します。
ワーカーノードの集まりの単位を「ノードグループ」と呼び、EKS上でワーカーノードを取り扱う際は「ノードグループ」という呼び方をする場合の方が多いかもしれません。
また、Kubernetesにおいて「コントロールプレーン」と対になる概念として「データプレーン」という用語があり、ワーカーノードやノードグループの全体を総称して呼ぶ場合があります。(EKSのドキュメント等にはあまり出てこないので、頭の片隅にでも留めておいてください)

EKSの公式ドキュメントやAWSマネジメントコンソール画面などにおいて、「コントロールプレーン」を指して「クラスター」と呼んでいる場合があります。
「クラスター」がEKS環境全体を指しているのか、コントロールプレーンのみを指しているのかは、文脈から適宜解釈する必要があります。

eksctlコマンドとCloudFormationスタックの関係

eksctl create clusterコマンドを実行すると、内部ではCloudFormationのスタックが自動的に生成され、CloudFormationスタックによって各種AWSリソースが作成されます。

自動生成されるCloudFormationスタックは2つあり、それぞれのスタックによって作成されるAWSリソースは以下の通りです。

  • eksctl-XXXXXXXX-cluster
    →「1. VPCネットワーク環境」および「2. EKSコントロールプレーン」を構成するリソースを作成
  • eksctl-XXXXXXXX-nodegroup-XXXXXXXX
    →「3. EKSワーカーノード」を構成するリソースを作成

これらのCloudFormationスタックのテンプレートの内容を参照すると、eksctl create clusterコマンドの実行によって、どのようなAWSリソースが作成されるのかが見えてきます。
(興味がある方は、マネジメントコンソールでCloudFormationスタックの「テンプレート」タブを参照したり、aws cloudformation describe-stacksコマンドで確認したりしてみてください)

各カテゴリそれぞれで「どのようなAWSリソースが作成されるのか」、また「各カテゴリで指定可能なeksctl create clusterコマンドオプションにはどのようなものがあるのか」について、順に解説していきます。

1. VPCネットワーク環境

VPCネットワーク環境の構成要素として作成されるAWSリソースは以下の通りです。

  • VPC
  • サブネット (パブリック×3、プライベート×3)
  • インターネットゲートウェイ
  • NATゲートウェイ

これらは、EKSワーカーノード(EC2インスタンス)の配置先となるVPC/サブネットとなります。

VPCネットワーク環境に関するeksctl create clusterコマンドのオプションは以下の通りです。

オプション 説明
--vpc-cidr VPCに指定するCIDR
(省略時のデフォルト値:192.168.0.0/16)
--vpc-nat-mode NATゲートウェイの構成をHighlyAvailable, Single, Disableから選択
(省略時のデフォルト値:Single)

サブネットのCIDRや、サブネット数・アベイラビリティゾーン数などを指定することはできません。
(これらを変更したい場合については、後ほど「『eksctl』コマンドの使い方 (応用編)」で説明します)

2. EKSコントロールプレーン

EKSコントロールプレーンの構成要素として作成されるAWSリソースは以下の通りです。

  • コントロールプレーン (または、狭義の「クラスター」)
  • コントロールプレーン用セキュリティグループ
  • コントロールプレーン用IAMロール

コントロールプレーンはKubernetesの様々な管理を行うコンポーネントの集まりです。
(EKSではコントロールプレーン機能はマネージドサービス化されています)

また、コントロールプレーンに対するアクセスを制御するための「セキュリティグループ」と「IAMロール」も作成されます。
これらのリソースは、EKSドキュメントの該当ページに記載されている内容に沿って作成されますので、基本的に変更する必要はありません。

クラスターセキュリティグループの考慮事項 - Amazon EKS
Amazon EKS サービス IAM ロール - Amazon EKS

EKSコントロールプレーンに関するeksctl create clusterコマンドのオプションは以下の通りです。

オプション 説明
--name クラスターの名前
(省略時のデフォルト値:ランダムな単語の組み合わせで命名)
--version Kubernetesバージョン (現在指定可能なのは 1.14, 1.13, 1.12, 1.11)
(省略時のデフォルト値:1.13)

3. EKSワーカーノード

EKSワーカーノードの構成要素として作成されるAWSリソースは以下の通りです。

  • EC2起動テンプレート
  • EC2 Auto Scalingグループ
  • ワーカーノード用セキュリティグループ
  • ワーカーノード用IAMロール

ワーカーノードの集まりの単位を「ノードグループ」と言います。
ノードグループの構成要素は「EC2起動テンプレート」と「EC2 Auto Scalingグループ」であり、これらにより必要台数のEC2インスタンスが起動されます。
そして、起動されたEC2インスタンス群はコントロールプレーンから自動的に認識されて、EKSのワーカーノードとして動作します。

また、コントロールプレーンと同様に、ワーカーノードに対するアクセスを制御するための「セキュリティグループ」と「IAMロール」も作成されます。
これらのリソースも、EKSドキュメントの該当ページに記載されている内容に沿って作成されますので、基本的に変更する必要はありません。

クラスターセキュリティグループの考慮事項 - Amazon EKS (前出のリンク先と同じです)
Amazon EKS ワーカーノード IAM ロール - Amazon EKS

EKSワーカーノードに関するeksctl create clusterコマンドのオプションは以下の通りです。

オプション 説明
--nodegroup-name ノードグループの名前 (省略時のデフォルト値:ランダムな文字列)
--node-type ワーカーノードのEC2インスタンスタイプ (省略時のデフォルト値:m5.large)
--nodes ワーカーノードのノード数 (省略時のデフォルト値:2)
--nodes-min ワーカーノードのスケーリング最小サイズ (省略時のデフォルト値:2)
--nodes-max ワーカーノードのスケーリング最大サイズ (省略時のデフォルト値:2)
--node-volume-size ワーカーノードのボリュームサイズ (単位:GB)
--node-volume-type ワーカーノードのボリュームタイプ (省略時のデフォルト値:gp2)
--ssh-public-key ワーカーノードへの接続時のSSH公開鍵 (EC2キーペアなどを指定)
--node-ami ワーカーノードの起動に用いるAMI
(標準のAMI以外を使用したい場合は明示的に指定)
--node-ami-family ワーカーノードの起動に用いるAMIの種別
(省略時のデフォルト値:AmazonLinux2、他にGPU対応インスタンスやUbuntu等の指定が可能)

実際にeksctlコマンドを使ってEKSクラスターを作成してみる

それでは、実際にeksctlコマンドを使ってEKSクラスターを作成してみましょう。

まず準備として、eksctlコマンドラインツールをインストールします。
インストール手順は下記リンク先を参照してください。
eksctl のインストールまたはアップグレード
Installation | eksctl

また、AWS CLIkubectlコマンドもインストールされている必要があります。
念のために最新バージョンへアップデートしておきましょう。

準備ができましたら、eksctl create clusterコマンドを実行します。
ここまでで説明した各種オプションをいくつか指定してみます。

$ eksctl create cluster \
    --vpc-cidr 10.0.0.0/16 \
    --vpc-nat-mode HighlyAvailable \
    --name eks-sample \
    --version 1.14 \
    --nodegroup-name ng-sample \
    --node-type t3.large \
    --nodes 3 \
    --nodes-min 2 \
    --nodes-max 4

コマンドを実行すると、以下のようなメッセージが順次出力されます。
VPCネットワーク環境、EKSコントロールプレーン、EKSワーカーノードのパラメータが設定され、2つのCloudFormationスタックが生成されていることが読み取れると思います。

[ℹ]  eksctl version 0.7.0
[ℹ]  using region ap-northeast-1
[ℹ]  setting availability zones to [ap-northeast-1c ap-northeast-1d ap-northeast-1a]
[ℹ]  subnets for ap-northeast-1c - public:10.0.0.0/19 private:10.0.96.0/19
[ℹ]  subnets for ap-northeast-1d - public:10.0.32.0/19 private:10.0.128.0/19
[ℹ]  subnets for ap-northeast-1a - public:10.0.64.0/19 private:10.0.160.0/19
[ℹ]  nodegroup "ng-sample" will use "ami-02e124a380df41614" [AmazonLinux2/1.14]
[ℹ]  using Kubernetes version 1.14
[ℹ]  creating EKS cluster "eks-sample" in "ap-northeast-1" region
[ℹ]  will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup
[ℹ]  if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-1 --name=eks-sample'
[ℹ]  CloudWatch logging will not be enabled for cluster "eks-sample" in "ap-northeast-1"
[ℹ]  you can enable it with 'eksctl utils update-cluster-logging --region=ap-northeast-1 --name=eks-sample'
[ℹ]  2 sequential tasks: { create cluster control plane "eks-sample", create nodegroup "ng-sample" }
[ℹ]  building cluster stack "eksctl-eks-sample-cluster"
[ℹ]  deploying stack "eksctl-eks-sample-cluster"
[ℹ]  building nodegroup stack "eksctl-eks-sample-nodegroup-ng-sample"
[ℹ]  deploying stack "eksctl-eks-sample-nodegroup-ng-sample"
[✔]  all EKS cluster resources for "eks-sample" have been created
[✔]  saved kubeconfig as "/home/ubuntu/.kube/config"
[ℹ]  adding identity "arn:aws:iam::123456789012:role/eksctl-eks-sample-nodegroup-ng-sa-NodeInstanceRole-1902SEAFAQNV1" to auth ConfigMap
[ℹ]  nodegroup "ng-sample" has 0 node(s)
[ℹ]  waiting for at least 2 node(s) to become ready in "ng-sample"
[ℹ]  nodegroup "ng-sample" has 3 node(s)
[ℹ]  node "ip-10-0-17-77.ap-northeast-1.compute.internal" is ready
[ℹ]  node "ip-10-0-63-243.ap-northeast-1.compute.internal" is not ready
[ℹ]  node "ip-10-0-71-167.ap-northeast-1.compute.internal" is ready
[ℹ]  kubectl command should work with "/home/ubuntu/.kube/config", try 'kubectl get nodes'
[✔]  EKS cluster "eks-sample" in "ap-northeast-1" region is ready

EKSクラスターの構築が完了するまで、10~20分程度の時間がかかります。
コマンドの処理が完了して、正常終了のメッセージが表示されたことを確認します。

構築が完了しましたら、kubectlコマンドでEKSクラスターへ接続します。

eks create clusterコマンドは~/.kube/configファイルの編集も自動で行ってくれるため、接続のための設定を特に行う必要はありません。
したがって、以下のようにkubectlコマンドを実行することで、EKSクラスターへ接続できることが確認できると思います。

$ kubectl get nodes
NAME                                             STATUS   ROLES    AGE   VERSION
ip-10-0-17-77.ap-northeast-1.compute.internal    Ready    <none>   75s   v1.14.7-eks-1861c5
ip-10-0-63-243.ap-northeast-1.compute.internal   Ready    <none>   75s   v1.14.7-eks-1861c5
ip-10-0-71-167.ap-northeast-1.compute.internal   Ready    <none>   75s   v1.14.7-eks-1861c5

EKSクラスターへ接続できましたので、この後は、各種Kubernetes入門コンテンツを参考に「Pod」や「Service」等のリソースの起動を試してみるのもよいでしょう。

最後に、EKSクラスターを削除する手順です。
以下の通り、削除するEKSクラスターの名前を指定してeksctl delete clusterコマンドを実行します。

$ eksctl delete cluster \
    --name eks-sample \
    --wait

削除コマンドを実行すると、内部ではCloudFormationスタックの削除が呼び出され、作成されたAWSリソースが順次削除されていきます。

--waitオプションは必須ではないですが、付けるとCloudFormationスタックの削除完了を待ち合わせてからコマンドが終了します。
確実に削除が完了したことが分かるため、特に理由が無ければ付けるのをお勧めします。
(--waitオプションを付けなかった場合は、コマンドが終了した後もバックグラウンドでCloudFormationスタックの削除処理が行われます)

以下は--waitオプションを付けた時の出力メッセージ例です。
2つのCloudFormationスタックの削除が行われ、それぞれのスタックの削除完了を待ち合わせているのが分かります。

[ℹ]  eksctl version 0.7.0
[ℹ]  using region ap-northeast-1
[ℹ]  deleting EKS cluster "eks-sample"
[✔]  kubeconfig has been updated
[ℹ]  cleaning up LoadBalancer services
[ℹ]  2 sequential tasks: { delete nodegroup "ng-sample", delete cluster control plane "eks-sample" }
[ℹ]  will delete stack "eksctl-eks-sample-nodegroup-ng-sample"
[ℹ]  waiting for stack "eksctl-eks-sample-nodegroup-ng-sample" to get deleted
[ℹ]  will delete stack "eksctl-eks-sample-cluster"
[ℹ]  waiting for stack "eksctl-eks-sample-cluster" to get deleted
[✔]  all cluster resources were deleted

CloudFormationスタックの削除が完了してAWSリソースが完全に削除されたことが確認できれば、一連の作業は完了です。

「eksctl」コマンドの使い方 (応用編)

さて、一つのコマンドで「VPCネットワーク環境」「EKSコントロールプレーン」「EKSワーカーノード」を作成してくれるeksctl create clusterコマンドですが、使い方により、それぞれを独立して別々に作成することもできます。

各カテゴリを別々に作成する理由はいろいろありますが、例えば以下のような場合です。

  • サブネットのCIDRやサブネット数・アベイラビリティゾーン数をデフォルトから変更したい
  • 複数のノードグループから成るEKSクラスターを構築する際、コントロールプレーンの管理とノードグループの管理を独立させたい

それぞれのパターンについて、構築手順を説明していきます。

パターン1の構築手順

STEP 1: VPCネットワーク環境の作成

まず、VPCネットワーク環境をCloudFormationやTerraform等を使って作成します。

例えば以下のようなCloudFormationテンプレートを用意します。

サンプルCloudFormationテンプレート (クリックして展開できます)

vpc-public-and-private-subnets.yaml

---
AWSTemplateFormatVersion: "2010-09-09"
Description: "VPC network environment (each three public and private subnets with a NAT gateway)"

Parameters:
  CidrBlockVPC:
    Type: String
    Default: 192.168.0.0/16

  CidrBlockSubnetPublic1:
    Type: String
    Default: 192.168.0.0/19

  CidrBlockSubnetPublic2:
    Type: String
    Default: 192.168.32.0/19

  CidrBlockSubnetPublic3:
    Type: String
    Default: 192.168.64.0/19

  CidrBlockSubnetPrivate1:
    Type: String
    Default: 192.168.96.0/19

  CidrBlockSubnetPrivate2:
    Type: String
    Default: 192.168.128.0/19

  CidrBlockSubnetPrivate3:
    Type: String
    Default: 192.168.160.0/19

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Network Configuration"
        Parameters:
          - CidrBlockVPC
          - CidrBlockSubnetPublic1
          - CidrBlockSubnetPublic2
          - CidrBlockSubnetPublic3
          - CidrBlockSubnetPrivate1
          - CidrBlockSubnetPrivate2
          - CidrBlockSubnetPrivate3

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:  !Ref CidrBlockVPC
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/VPC"

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/InternetGateway"

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  SubnetPublic1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPublic1
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPublic1"
        - Key: kubernetes.io/role/elb
          Value: 1

  SubnetPublic2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 1
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPublic2
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPublic2"
        - Key: kubernetes.io/role/elb
          Value: 1

  SubnetPublic3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 2
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPublic3
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPublic3"
        - Key: kubernetes.io/role/elb
          Value: 1

  SubnetPrivate1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPrivate1
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPrivate1"
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  SubnetPrivate2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 1
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPrivate2
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPrivate2"
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  SubnetPrivate3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 2
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPrivate3
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/SubnetPrivate3"
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  NATGatewayEIP1:
    DependsOn:
      - VPCGatewayAttachment
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGateway1:
    DependsOn:
      - NATGatewayEIP1
      - SubnetPublic1
      - VPCGatewayAttachment
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NATGatewayEIP1.AllocationId
      SubnetId: !Ref SubnetPublic1
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/NATGateway1"

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/PublicRouteTable"

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/PrivateRouteTable1"

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/PrivateRouteTable2"

  PrivateRouteTable3:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}/PrivateRouteTable3"

  PublicSubnetRoute:
    DependsOn:
      - VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  NATPrivateSubnetRoute1:
    DependsOn:
      - VPCGatewayAttachment
      - NATGateway1
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway1

  NATPrivateSubnetRoute2:
    DependsOn:
      - VPCGatewayAttachment
      - NATGateway1
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway1

  NATPrivateSubnetRoute3:
    DependsOn:
      - VPCGatewayAttachment
      - NATGateway1
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable3
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway1

  RouteTableAssociationPublic1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPublic1
      RouteTableId: !Ref PublicRouteTable

  RouteTableAssociationPublic2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPublic2
      RouteTableId: !Ref PublicRouteTable

  RouteTableAssociationPublic3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPublic3
      RouteTableId: !Ref PublicRouteTable

  RouteTableAssociationPrivate1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPrivate1
      RouteTableId: !Ref PrivateRouteTable1

  RouteTableAssociationPrivate2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPrivate2
      RouteTableId: !Ref PrivateRouteTable2

  RouteTableAssociationPrivate3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPrivate3
      RouteTableId: !Ref PrivateRouteTable3

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${AWS::StackName}::VPC"

  SubnetsPublic:
    Value: !Join [ ",", [ !Ref SubnetPublic1, !Ref SubnetPublic2, !Ref SubnetPublic3 ] ]
    Export:
      Name: !Sub "${AWS::StackName}::SubnetsPublic"

  SubnetsPrivate:
    Value: !Join [ ",", [ !Ref SubnetPrivate1, !Ref SubnetPrivate2, !Ref SubnetPrivate3 ] ]
    Export:
      Name: !Sub "${AWS::StackName}::SubnetsPrivate"

特別な記述内容はほとんどありませんが、1点だけ留意事項があります。

サブネットのリソースを記述する際、そのサブネットが「パブリック」なのか「プライベート」なのかをEKSコントロールプレーンに識別させるためにタグを付与する必要があります。

パブリックサブネットに付与するタグ:

Tags:
  - Key: kubernetes.io/role/elb
    Value: 1

プライベートサブネットに付与するタグ:

Tags:
  - Key: kubernetes.io/role/internal-elb
    Value: 1

※ これらのタグにより、Kubernetesの「LoadBalancer」リソースを作成する際に、ELBのスキームをinternet-facedinternelのどちらにすべきかが判断されます。(この点についての詳細な説明は割愛します)

STEP 2: EKSコントロールプレーン+EKSワーカーノードの作成

VPCネットワーク環境が作成されましたら、eksctl create clusterコマンドを以下のようなオプションを指定して実行します。
(説明のため、その他のオプションは省略しています)

$ eksctl create cluster \
    --vpc-public-subnets <パブリックサブネットのリソースIDをカンマ区切りで指定> \
    --vpc-private-subnets <プライベートサブネットのリソースIDをカンマ区切りで指定>

これにより、eksctl create clusterコマンドはVPCネットワーク環境の新規作成をスキップし、指定したVPCネットワーク環境の上にEKSコントロールプレーンとEKSワーカーノードを作成します。

パターン2の構築手順

STEP 1: VPCネットワーク環境+EKSコントロールプレーンの作成

まず、eksctl create clusterコマンドを以下のようなオプションを指定して実行します。
(説明のため、その他のオプションは省略しています)

$ eksctl create cluster \
    --name <コントロールプレーンに付ける名前> \
    --without-nodegroup

--without-nodegroupオプションを指定することにより、eksctl create clusterコマンドはEKSワーカーノードを作成せず、VPCネットワーク環境とEKSコントロールプレーンのみを作成します。

STEP 2: EKSワーカーノードの作成

VPCネットワーク環境とEKSコントロールプレーンが作成されたら、eksctl create nodegroupコマンドを以下のように実行します。

$ eksctl create nodegroup \
    --cluster <作成済みのコントロールプレーンの名前> \
    --name <ノードグループに付ける名前> \
    --node-type m5.large \
    --nodes 2

これにより、作成済みのコントロールプレーンに対して、ノードグループを追加作成して組み込むことができます。
(説明のため、その他のオプションは省略しています)

なお、eksctl create nodegroupコマンドのオプションは、eksctl create clusterコマンドのEKSワーカーノード関連のオプションとほとんど同じです。
(詳細はeksctl create nodegroup --helpを実行して確認してください)

パターン3の構築手順

パターン3は、これまで説明した「パターン1」と「パターン2」の組み合わせで実現できます。

すなわち、以下の3ステップとなります。

  • STEP 1: CloudFormationやTerraform等でVPCネットワーク環境を作成する
  • STEP 2: eksctl create clusterコマンドに--without-nodegroupオプションを付けて実行する
  • STEP 3: eksctl create nodegroupコマンドを実行する

おわりに

今回は、eksctlコマンドを使用したEKSクラスター環境の構築手順について、オプションの使い方や作成されるAWSリソースの解説を交えて説明しました。

これで基本的なEKSクラスター環境は構築できるようになるのではないかと思います。
是非、いろいろなオプションを指定したり、構築パターンを変えたりして、EKSクラスター環境を作成してみてください。

また、最初の方で説明したようにeksctlコマンドには今回紹介したもの以外にも様々な機能が含まれています。
それらについては、また別の機会にご紹介できればと思います。