普段のお仕事ではAmazon ECSしか使用することがなく、ちゃんとEKSを使用してコンテナをデプロイしたことがなかったので入門してみました。
やること
今回はEKS上にApacheのコンテナをデプロイしてWebページを表示してみます。
Kubernetesとは
Kubernetesはコンテナ化されたアプリケーションを管理・運用するためのオープンソースのシステムです。
負荷に応じてコンテナの数をスケーリングしたり、問題が発生したコンテナを停止して新しいコンテナに入れ替えたりといったことを行ってくれます。
Kubernetesはクラスターと呼ばれる単位の中でコントロールプレーン (ノードの管理などを行う部分) とノード (実際にコンテナが動く環境) と呼ばれる部分に分かれます。
自分たちですべて運用するとコントロールプレーンとノードの管理が必要になってきますが、EKSを使用することでコントロールプレーンの部分をAWS側に任せることができます。
設定
前準備
EKSを触る前にAWS CLI、kubectl、eksctl、Helmをインストールしておきます。
以下のドキュメントの手順でそれぞれご利用の環境に合わせてインストールを行ってください。
- AWS CLI の最新バージョンのインストールまたは更新 - AWS Command Line Interface
- kubectl および eksctl のセットアップ - Amazon EKS
- Installing Helm | Helm
構築
クラスターの作成までは以下のドキュメントを参考に作成を進めていきます。
1. ネットワーク部分作成
AWS CLIをインストールした環境で以下のコマンドを実行します。
コマンドを実行すると東京リージョン (ap-northeast-1) にCloudFormationスタックが作成されネットワークリソースがデプロイされます。
aws cloudformation create-stack \
--region ap-northeast-1 \
--stack-name my-eks-vpc-stack \
--template-url https://s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml
AWSが用意したCloudFormationテンプレートですが、デプロイが完了すると以下の構成が作成されます。

2. クラスター作成
ネットワークリソースの作成が完了したらEKSクラスターを作成していきます。
まずはクラスターの使用するIAMロールを作成します。
以下のコマンドを実行してIAMロールを作成します。
コマンドを実行すると「myAmazonEKSClusterRole」という名前でIAMロールが作成されます。
cat <<EOF > eks-cluster-role-trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "eks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name myAmazonEKSClusterRole \
--assume-role-policy-document file://"eks-cluster-role-trust-policy.json"
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy \
--role-name myAmazonEKSClusterRole
IAMロールの作成が完了したら以下のURLから東京リージョンのEKSクラスター画面へアクセスします。
https://ap-northeast-1.console.aws.amazon.com/eks/clusters?region=ap-northeast-1
クラスターの設定画面から設定オプション欄で「カスタム設定」を選択します。

EKS オートモードは今回使用しないのでオフにします。
クラスター設定欄でクラスターの名前とIAMロールを設定します。

バージョン設定欄ではk8sのバージョンを選択できます。
基本的に新しく作成するときは最新のバージョンを選択しておけば問題ないと思います。

クラスターアクセス欄,エンベロープ暗号化欄、ARC ゾーンシフト欄はデフォルトのままにします。
ここまで設定したら画面を一番下までスクロールして次へをクリックします。
ネットワーキング欄ではVPC、サブネット、セキュリティグループの選択を行います。
サブネットはプライベートサブネットを選択してください。

クラスターエンドポイントアクセス欄では「CIDR ブロック」でご自身の使用しているパブリックIPアドレス (ご自宅のパブリックIPアドレスなど) を入力してください。

入力したら画面下の次へをクリックします。
メトリクス欄で「CloudWatch」にチェックを入れます。
チェックを入れたら次へをクリックします。
アドオンの設定画面ではデフォルトのまま画面下の次へをクリックします。
Amazon CloudWatch Observability欄で「推奨ロールを作成」をクリックします。
クリックするとIAMロール作成画面に推移するのでそのままIAMロールを作成していきます。
アタッチするポリシーはCloudWatchAgentServerPolicyです。
次にAmazon VPC CNI欄でも同じく「推奨ロールを作成」をクリックします。
アタッチするポリシーはAmazonEKS_CNI_Policyです。
次に外部 DNS欄で「推奨ロールを作成」をクリックします。
アタッチするポリシーはAmazonRoute53FullAccessです。
ここまで設定ができたら画面下の次へをクリックします。
最後の確認画面で問題が無ければ画面下の作成をクリックします。
3. ECRとコンテナイメージ作成
クラスターの作成が完了したらECRの作成とコンテナイメージの作成を行います。
ECRの作成は以下のCloudFormationテンプレートで行います。
AWSTemplateFormatVersion: "2010-09-09"
Description: ECR
Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Parameters for env Name
Parameters:
- env
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
env:
Type: String
Default: dev
AllowedValues:
- dev
Resources:
# ------------------------------------------------------------#
# ECR
# ------------------------------------------------------------#
ECR:
Type: AWS::ECR::Repository
Properties:
EmptyOnDelete: true
EncryptionConfiguration:
EncryptionType: AES256
RepositoryName: !Sub ecr-${env}
上記のCloudFormationテンプレートをファイルに保存したら以下のコマンドでデプロイします。
aws cloudformation create-stack \
--stack-name ecr \
--template-body file://CloudFormationテンプレートファイル名 \
--region ap-northeast-1
ECRの作成が完了したら以下のDockerfileを使用してコンテナイメージの作成を行います。
HTMLファイルはお好きなものを使用してください。
FROM public.ecr.aws/docker/library/httpd:2.4
COPY ./html/ /usr/local/apache2/htdocs/
Dockerfileを作成したら以下のコマンドでコンテナイメージの作成とECRへのプッシュを行います。
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t ecr-dev .
docker tag ecr-dev:latest $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-dev:latest
docker push $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-dev:latest
4. コンテナデプロイ
ECRの作成が完了してEKSクラスターのステータスがアクティブになったら以下のコマンドを実行します。
aws eks update-kubeconfig --region ap-northeast-1 --name クラスター名
コマンドの実行が完了したらEKSクラスターの画面でコンピューティングタブを開いてノードグループ欄で「追加」をクリックします。

ノードグループの設定欄で「名前」と「ノード IAM ロール」を設定します。
IAMロールは「推奨ロールを作成」をクリックしてIAMロールを作成するとECRからコンテナイメージを取得するために必要な権限などがアタッチされます。
名前とIAMロールを設定したら画面下の次へをクリックします。

ノードグループのコンピューティング設定欄で「インスタンスタイプ」を選択してください。
今回は負荷のかかるコンテナを起動しないのでt3.microを選択しました。
t3.microで設定したところ、podの起動がうまくできなかったのでt3.mediumで作成しなおしました。
おそらくスペック不足で起動に失敗していたと考えられます。(知識不足でどこに問題があるか確認できなかったのでここはいずれ調べたいと思います)
それ以外の設定はデフォルトとして画面下の次へをクリックします。

ノードグループのネットワーク設定欄ではプライベートサブネットを選択します。

最後の確認画面で設定を確認して問題が無ければ作成してください。
ノードが作成できたらマニフェストファイルを作成します。
マニフェストファイルとはKubernetesクラスター内の設定を記載したものになります。
例えばノード内でいくつPodを起動するかなどを記載して反映します。
今回使用するのはDeployment、Ingress、Serviceを使用します。
apache_deployment.yamlではpodの個数やコンテナイメージの設定を行っています。
以下の設定ではreplicasでpodの個数を指定しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: apache-deployment
labels:
app: apache
spec:
replicas: 2
selector:
matchLabels:
app: apache
template:
metadata:
labels:
app: apache
spec:
containers:
- image: AWSアカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-dev:latest
name: apache
ports:
- containerPort: 80
apache_service.yamlではapache_deployment.yamlで設定したpodを1つのサービスとして公開するための設定を行っています。
apiVersion: v1
kind: Service
metadata:
name: apache-service
spec:
ports:
- port: 80
selector:
app: apache
type: NodePort
apache_ingress.yamlではALBの設定を行っています。
annotations内のパラメータでALBのタグや使用するセキュリティグループの指定などが可能です。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: apache-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: apache-service
port:
number: 80
マニフェストファイルの作成が完了したら以下のドキュメントの手順でOIDCプロバイダーの作成を行います。
OIDCプロバイダーを作成したら以下のコマンドを実行してIAMロールの作成とコントローラーのインストールをします。
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.14.1/docs/install/iam_policy.json
aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json
eksctl create iamserviceaccount \
--cluster=test-cluster \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--region ap-northeast-1 \
--approve
helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=test-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller \
--version 1.14.1
kubectl get deployment -n kube-system aws-load-balancer-controller
コントローラーの追加が完了したら以下のコマンドを実行してサブネットにタグをつけていきます。
どうやらk8s側でALBを配置する際にサブネットへ設定されたタグからプライベートサブネットやパブリックサブネットを判定しているようです。
aws ec2 create-tags \
--resources パブリックサブネットのID プライベートサブネットのID \
--tags Key=kubernetes.io/cluster/test-cluster,Value=shared \
--region ap-northeast-1
# 以下のコマンドはAWSの準備したCFnテンプレートでネットワークリソースを作成している場合実行不要です
aws ec2 create-tags \
--resources プライベートサブネットのID \
--tags Key=kubernetes.io/role/internal-elb,Value=1 \
--region ap-northeast-1
# 以下のコマンドはAWSの準備したCFnテンプレートでネットワークリソースを作成している場合実行不要です
aws ec2 create-tags \
--resources プライベートサブネットのID \
--tags Key=kubernetes.io/role/internal-elb,Value=1 \
--region ap-northeast-1
上記の設定完了後、EKSによって作成された起動テンプレートから新しいバージョンを作成して「メタデータレスポンスのホップ制限」を2へ変更します。
変更後、デフォルトのバージョンを2へ変更します。
というのもこの設定を変更しないとインスタンスメタデータからVPC IDを取得できずにaws-load-balancer-controllerの起動に失敗してしまいます。(ここがわからずにサービスがデプロイできず数時間無駄にしました)
変更後、EC2 AutoScalingから起動テンプレートのバージョンを2へ変更してEC2を更新します。
おそらくマネジメントコンソールからだとこの方法しか現状は変更できなさそうでした。
以下のブログで紹介されているようにTerraformなどAPIからの操作であれば設定できそうです。
EC2が起動したら以下のコマンドでコンテナをデプロイしていきます。
kubectl apply -f apache_deployment.yaml
kubectl apply -f apache_service.yaml
kubectl apply -f apache_ingress.yaml
コマンドを実行するとALBの作成が行われます。
ALBのステータスがアクティブになったらブラウザからALBのDNS名にアクセスするとApacheで配信しているページにアクセスできます。

さいごに
今回はAmazon EKSをマネジメントコンソールから構築してみました。
手順を見ていただいたらわかる通り設定内容がかなり複雑で必要になる知識も広いため使いこなすのはかなり難しそうです。
また、この内容をIaC化していくのはかなり骨が折れそうです。

