はじめに
「おぼろげながら浮かんできたんです。EKSという単語が。」
なんとなくですが、EKSを使ってみたくなったので使ってみました。EKSというのはAWSで使用できるkubernetesのサービスです。AWSにはEKSの他にもECSというAWSが独自に用意したコンテナのオーケストレーションツールも存在します。
学習コストの低さやメンテナンスの運用などを考えると、自分は試すならECSからかな、と今になっては思うんですがしょうがありません。なぜなら、おぼろげながら浮かんじゃったから。。。
Kubernetesとは何?
この記事を読んでいる聡明な皆様なら既に知っていると思いますが、念のためKubernetesについても説明します。(「クバネティス」、「クーバネティス」と読みます。私はどや顔で「くー↑ばねいつ↓」と読んでいました)
Kubernetes (K8s)は、デプロイやスケーリングを自動化したり、コンテナ化されたアプリケーションを管理したりするための、オープンソースのシステムです。
引用:kubernetes公式ドキュメント
一見するとよくわかりませんが、三回ぐらい読んでもよくわかりません。
皆さんはアプリをコンテナ化したことはあるでしょうか?
では、複数のコンテナをアプリとしてデプロイした際のコンテナ同士の連携、リソースの管理、デプロイなどを経験したことはあるでしょうか?
これらの工程にはかなりの工数がかかり、経験豊富な方でないと難しい部分があります。
(実はそういった経験がないので何がそんなにめんどくさいのかピンと来ていません)
そいつをバシッと解決してくれるのがKubernetesになります。
Kubernetesは、複数コンテナの運用を考えたときに出てくるそういっためんどくさい部分をなんかうまいぐわいにやってくれる機能、という理解で大丈夫です。
そのような機能を相称して「オーケストレーションツール」と呼びます。
EKSとは
EKSはAWSでKubernetesを実行できるサービスになります。
といっても、AWSのコンソール上でバチバチに開発するというわけではなく、適当なサーバーからアクセスしてEKSを操作することになります。
kubectlというKubernetesをコマンドで操作するためのツールがあるのですが、こちらを開発に用います。
Kubernetesに慣れている人にとっては、新たに勉強する必要がなく、スムーズにEKSに移行できるためこちらの仕様は大変ありがたいものになります。
今まで通りコマンドで開発、見通しの良いコンソール上で作成したリソース等の確認って使い方が一番いいんでしょうか。
コントロールプレーンとワーカー
kubernetesはコントロールプレーンと複数のワーカーで構成されます。
コントロールプレーンとワーカーとまとめてクラスタと呼びます。
ワーカーは、コンテナを入れるところ、コントロールプレーンはワーカーとコンテナを管理するところです。
例えば、複数のワーカーにコンテナとして同一のアプリをデプロイしたとします。
複数の人からアプリにアクセスがあったときに常に同じワーカー(サーバー)のコンテナを動かしていたらそのサーバーのリソースがすぐに枯渇してしまいそうですよね。
そんな時、コントロールプレーンが、それらしくリソースを分配してくれます。
また、そのようなアプリをアップデートする際には、ローリングアップデートという技術を使うこともできます。
複数ワーカーのコンテナを一度にアップデートするのではなく、一つ一つ順番にアップデートすることで、アプリを停止させることなくアップデートが可能になります。その制御もコントロールプレーンが担っています。
料金
皆さん気になりだしてるのがこちらだと思います。料金体系。
比較のためにAWSにある同じオーケストレーションツールであるECSの値段も載せておきます。
機能名 | クラスタ | ワーカー |
---|---|---|
EKS | 月 : 74$ | EC2 or Fargate の料金 |
ECS | 月 : 0$ | EC2 or Fargate の料金 |
EKSはクラスタを作成すると一時間当たり0.10$、大体月に74$ かかります。
ところがどっこい、ECSは0$と中々太っ腹です。
ワーカーは結局サーバーなのでAWSにあるサーバーの機能を利用することになります。どのサーバーを利用するかによってその料金は変わります。
メンテナンス
これからEKSの導入を考えている人には絶対知っておいてほしい事項です。
EKSはKubernetesを利用しているため、その機能はKubernetesのバージョンに依存しています。
そのバージョン管理が問題でして、EKSでは約三か月に一回マイナーバージョンがリリースされます。
しかし、各バージョンのサポート期限はリリースされてから一年間であり、その期限が来る前にクラスタ及びワーカーのEKSバージョンの更新を行う必要があるんです。
仮に今使っている機能の仕様が急に変わったり使えなくなったりなどの可能性を考えると、こちらはかなりのメンテナンスコストが要求されると考えられます。
ところがどっこい、ECSにはバージョンという概念が存在しません。
ECSでは、アップデートによって使えなくなる機能もなければ、定期的な更新作業も必要ないです。
なんかいつの間にか便利機能が追加されてるから、使ってみよーってノリで新しい機能を試すことができます。
ネットワークの設定
ここまで聞くと、「EKSとECSだったらECSの方が良さそう。。。」といった意見が出そうですがそちらの考えは早計です。
ワーカーにコンテナをデプロイする、コンテナ同士を連携するといったときに、適宜ネットワークを引く必要があります。
こちらの設定をEKSは全て自動でやってくれるのに対し、ECSでは適宜自分で設定する必要があります。
クラスタの規模が大きくなり、サービスが複雑になるにつれ、そのネットワークの管理工数というのはどんどん増えていきます。
以上を踏まえると、サービスが複雑になるのであればEKS、シンプルになるのであればECSを採用するのが無難だと思います。
この記事の目的
何となく浮かんできたEKSという単語ですが、とりあえず複数ノードにまたがるアプリを実装してみようと思いました。
で、アプリに関しては普段使い慣れてるReactアプリをとりあえずデプロイしてみようと思います。
最終的な形としては、複数ノードにコンテナ化したReact App + Nginxをぶち込むということで実現しようと思います。
手順
事前準備
EKSでは、適当なサーバーからアクセスして操作する必要があるので、まずはそのサーバーの準備を行います。
私は操作する端末として、とりあえずローカルに入っていたWSL Ubuntu20.04を用いました。
aws-cliのインストール
$ pip install awscli
aws-cliの設定
aws configure
AWS Access Key ID [None]: <アクセスキー>
AWS Secret Access Key [None]: <シークレットキー>
Default region name [None]: ap-northeast-1
Default output format [None]: json
eksctlのインストール(AWS EKSと接続したりするための独自のもの)
$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
$ sudo mv /tmp/eksctl /usr/local/bin
eksctlバージョン確認
$ eksctl version
0.31.0
kubectlのインストール
※2023年9月現在、EKSの最新バージョンが1.27だったため、それに合わせます。
EKSのバージョンとかけ離れている、それより高いバージョンのkubectlは使用できません。
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
sudo apt update
sudo apt install -y kubectl=1.27.5-00
ネットワーク・ロールの構築
KESを使用するために入れ物としてネットワークを準備しておく必要があります。
また、クラスタ、ワーカーを動かす権限としてロールの作成も行う必要があります。
以下のyaml, jsonを用いてcloudformationのコンソールからスタックを作成し、ネットワーク・ロールを作成いたします。
スタック名 EKS-Vpc
※パラメータはIPアドレスはVPCを確認し余ってるところで適宜実装してください
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS Sample VPC - Public subnets only'
Parameters:
VpcBlock:
Type: String
Default: 10.10.0.0/16
Description: The CIDR range for the VPC. This should be a valid private (RFC 1918) CIDR range.
Subnet01Block:
Type: String
Default: 10.10.0.0/24
Description: CidrBlock for subnet 01 within the VPC
Subnet02Block:
Type: String
Default: 10.10.1.0/24
Description: CidrBlock for subnet 02 within the VPC
Subnet03Block:
Type: String
Default: 10.10.2.0/24
Description: CidrBlock for subnet 03 within the VPC. This is used only if the region has more than 2 AZs.
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: "Worker Network Configuration"
Parameters:
- VpcBlock
- Subnet01Block
- Subnet02Block
- Subnet03Block
Conditions:
Has2Azs:
Fn::Or:
- Fn::Equals:
- {Ref: 'AWS::Region'}
- ap-south-1
- Fn::Equals:
- {Ref: 'AWS::Region'}
- ap-northeast-2
- Fn::Equals:
- {Ref: 'AWS::Region'}
- ca-central-1
- Fn::Equals:
- {Ref: 'AWS::Region'}
- cn-north-1
- Fn::Equals:
- {Ref: 'AWS::Region'}
- sa-east-1
- Fn::Equals:
- {Ref: 'AWS::Region'}
- us-west-1
HasMoreThan2Azs:
Fn::Not:
- Condition: Has2Azs
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-VPC'
InternetGateway:
Type: "AWS::EC2::InternetGateway"
VPCGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public Subnets
- Key: Network
Value: Public
Route:
DependsOn: VPCGatewayAttachment
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
Subnet01:
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 01
Properties:
MapPublicIpOnLaunch: true
AvailabilityZone:
Fn::Select:
- '0'
- Fn::GetAZs:
Ref: AWS::Region
CidrBlock:
Ref: Subnet01Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-Subnet01"
- Key: kubernetes.io/role/elb
Value: 1
Subnet02:
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 02
Properties:
MapPublicIpOnLaunch: true
AvailabilityZone:
Fn::Select:
- '1'
- Fn::GetAZs:
Ref: AWS::Region
CidrBlock:
Ref: Subnet02Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-Subnet02"
- Key: kubernetes.io/role/elb
Value: 1
Subnet03:
Condition: HasMoreThan2Azs
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 03
Properties:
MapPublicIpOnLaunch: true
AvailabilityZone:
Fn::Select:
- '2'
- Fn::GetAZs:
Ref: AWS::Region
CidrBlock:
Ref: Subnet03Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-Subnet03"
- Key: kubernetes.io/role/elb
Value: 1
Subnet01RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet01
RouteTableId: !Ref RouteTable
Subnet02RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet02
RouteTableId: !Ref RouteTable
Subnet03RouteTableAssociation:
Condition: HasMoreThan2Azs
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet03
RouteTableId: !Ref RouteTable
ControlPlaneSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Cluster communication with worker nodes
VpcId: !Ref VPC
Outputs:
SubnetIds:
Description: All subnets in the VPC
Value:
Fn::If:
- HasMoreThan2Azs
- !Join [ ",", [ !Ref Subnet01, !Ref Subnet02, !Ref Subnet03 ] ]
- !Join [ ",", [ !Ref Subnet01, !Ref Subnet02 ] ]
SecurityGroups:
Description: Security group for the cluster control plane communication with worker nodes
Value: !Join [ ",", [ !Ref ControlPlaneSecurityGroup ] ]
VpcId:
Description: The VPC Id
Value: !Ref VPC
スタック名 EKS-Service-Role
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS Cluster Role'
Resources:
eksClusterRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- eks.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
Outputs:
RoleArn:
Description: The role that Amazon EKS will use to create AWS resources for Kubernetes clusters
Value: !GetAtt eksClusterRole.Arn
Export:
Name: !Sub "${AWS::StackName}-RoleArn"
スタック名 EKS-Nodegroup-Role
AWSTemplateFormatVersion: "2010-09-09"
Description: Amazon EKS - Node Group Role
Mappings:
ServicePrincipals:
aws-cn:
ec2: ec2.amazonaws.com.cn
aws-us-gov:
ec2: ec2.amazonaws.com
aws:
ec2: ec2.amazonaws.com
Resources:
NodeInstanceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- !FindInMap [ServicePrincipals, !Ref "AWS::Partition", ec2]
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy"
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
Path: /
Outputs:
NodeInstanceRole:
Description: The node instance role
Value: !GetAtt NodeInstanceRole.Arn
クラスターの作成
コンソールでクラスターを作成を行いました。(10分ぐらいかかります)
設定は以下の設定で行いました。
名前:EKS-cluster
バージョン:1.27
クラスターサービスロール EKS-Service-Roleで作成したロール
VPC、サブネット EKS-Vpcで作成したVPC、サブネット
セキュリティグループ EKS-Vpcで作成したセキュリティグループ
アドオン → デフォルト(VPC CNI, kube-proxy, CoreDNS)
kubeconfigファイルの作成
クラスターにアクセスするためのkubekonfigファイルを作成する必要があります。
クラスター作成完了後に実行します。
aws eks --region ap-northeast-1 update-kubeconfig --name EKS-cluster
接続テスト
kubectl get svc
ワーカーノードの作成
コンソール上でワーカーノードを作成します。(5分ぐらいかかります)
コンピューティングでノードグループにあるノードグループの追加を選択し作成します。
設定は以下の設定で行いました。
名前:EKS-worker-nodes
ノードIAMロール EKS-Nodegroup-Roleで作成したロール
EC2の設定は適宜
AMIタイプ「Amazon Linux (AL2_x86_64)
インスタンスタイプ「t3.small」
ディスクサイズ「10」
最小サイズ「2」
最大サイズ「2」
希望サイズ「2」
サブネット EKS-Vpcで作成したサブネット
ノードへのリモートアクセスを許可する ON
SSHキーを選択
次からのリモートアクセスを許可 全て
これにて、KESの基本的な準備が完了いたしました。
ここから先はreact appとnginxをコンテナ化しデプロイしてロードバランサーで公開する手順になります。
React App 作成
nvm use 18.16.1
npx create-react-app test-app
appディレクトリ直下にDockerfile格納します。
Dockerfile
FROM node:18.16.1 as builder
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json", "./"]
RUN npm install
COPY [".", "./"]
RUN npm run build
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY --from=builder ["/usr/src/app/build", "./"]
COPY ["nginx/default.conf", "/etc/nginx/conf.d"]
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
appディレクトリ直下の/nginx にdefault.conf格納します。
default.conf
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm
try_files $uri /index.html;
}
}
Dockerイメージ作成
※kubernetesのネームスペース、ECRのリポジトリ名をとりあえずeks-testで作成します。
Dockerビルド
docker build -t eks-test .
以下コマンドを実行し localhost:3000でアクセスできればOKです。
docker run -p 3000:80 eks-test
ECRにコンテナ格納
まずは、ECRにコンテナのリポジトリを作成します。
コンソール上でECR(Elastic Container Registry)を開き、リポジトリの作成をクリックします。
名前を「eks-test」として入力し作成します。
eks-testの詳細にある「プッシュコマンドの表示」をクリックし、そこにある「認証・イメージタグ付け・プッシュコマンド」をローカルで実行し作成したリポジトリに格納します。
□コンテナイメージのデプロイ・サービス公開
コンテナイメージのデプロイ・サービスを一元管理するために、namespaceの作成を行います。
kubectl create namespace eks-test
以下のデプロイ・サービス公開するための設定ファイルを適当なディレクトリに格納してください。
EKS-Deployment.yaml(ECRのURIを適宜書き換えること)
apiVersion: apps/v1
kind: Deployment
metadata:
name: eks-test
spec:
selector:
matchLabels:
app: eks-test
replicas: 2
template:
metadata:
labels:
app: eks-test
spec:
containers:
- name: eks-test
image: <ECRのURI>
imagePullPolicy: Always
ports:
- containerPort: 80
EKS-Service.yaml
apiVersion: v1
kind: Service
metadata:
name: eks-test
spec:
type: LoadBalancer
selector:
app: eks-test
ports:
- protocol: TCP
port: 80
targetPort: 80
格納したディレクトレイで以下コマンドを実行します。
アプリデプロイ
kubectl apply -f EKS-Deployment.yaml -n eks-test
サービス作成
kubectl apply -f EKS-Service.yaml -n eks-test
以下で取得できるEXTERNAL-IPでアプリにアクセスすることができれば成功です。(なんだかんだ公開に二分ぐらいまちます。)
kubectl get service -n eks-test
以上で、EKSを用いてReactAppを公開する手順を終了します。
全削除
作成したリソースの削除方法です。お片付けはちゃんとしないとね。
ネームスペース削除(紐づいたノード、サービスも消える)
kubectl delete namespace eks-test
ECRリポジトリの削除
aws ecr delete-repository --repository-name eks-test --force --region ap-northeast-1
ノードグループの削除
aws eks delete-nodegroup --nodegroup-name EKS-worker-nodes --cluster-name EKS-cluster --region ap-northeast-1
クラスター削除
aws eks delete-cluster --name EKS-cluster --region ap-northeast-1
クラウドフォーメーション削除
aws cloudformation delete-stack --stack-name EKS-Vpc
aws cloudformation delete-stack --stack-name EKS-Service-Role
aws cloudformation delete-stack --stack-name EKS-Nodegroup-Role
終わりに
ここまで読んでいただきありがとうございました。
記事を書くに当たり、以下サイトを参考にさせていただきました。
また、執筆する気持ちになりましたらその時お会いしましょう。