見出し画像

Elastic Beanstalk から EKS へ移行した話 (1/3) ~移行概要 & EKS クラスタ作成編 ~

おはようございます。
株式会社 POL にてエンジニアをしている山田高寛です。
以前「Amazon Personalize を利用したレコメンドシステムの構築」という記事でチラッと出した、Kubernetes への移行について話します。

株式会社 POL では理系学生に特化した採用サービス LabBase を運営しております。このサービスのインフラ構成は ConoHa の VPS → AWS Elastic Beanstalk と変化してきました。(この辺りの話は同じプロダクト部の星牟禮が AWS のユーザーグループである JAWS にて登壇した際に発表しました)。

今回、この LabBase のインフラ構成を AWS Elastic Beanstalk から Amazon EKS へと移行したのでその知見を共有します。

なお、長くなってしまったので、3 部構成にして今回は移行の概要と EKS クラスタの作成について書きます。

移行の背景について

まず、LabBase は以下のような典型的な Web アプリケーションの構成をとっておりました。バックエンドアプリケーションは Elastic Beanstalk 上で稼働しており、Develop 環境、Staging 環境、Production 環境ごとに Elastic Beanstalk の環境がありました。

画像4

Elastic Beanstalk のプラットフォームとして「Docker プラットフォーム」を利用していたため 1 台の EC2 インスタンスに対して 1 コンテナが立ち上がります。一方で、バックエンドアプリケーションのリソース使用量を確認したところ EC2 インスタンスのリソースを持て余しており、リソースが無駄になっていることがわかりました。そこで、リソースを効率的に利用するために 1 台の EC2 インスタンスに対して複数のコンテナを立ち上げることで、コンテナの集積度を高めようとなりました (注 1)。

AWS のサービスを利用して 1 台の EC2 インスタンスに対して複数のコンテナを立ち上げるには、以下のような方法があります。

・ Elastic Beanstalk の複数 Docker プラットフォームを利用する
・ Amazon ECS を利用する
・ Amazon EKS を利用する

「Elastic Beanstalk の複数 Docker プラットフォームを利用する」は ECS 環境を利用しているため、実質 ECS か EKS の選択となります。今回は下記のような理由から、EKS を採用することにしました。

・ Kubernetes エコシステムを利用できる
・ ベンダーロックインの回避
・ 採用面での魅力

移行したあとに上記採用理由を振り返ってみると、個人的には 1 番目の「Kubernetes エコシステムを利用できる」というのが良かったです。Kubernetes には Custom Resource Definition (CRD) や Custom Controller という機能があり、ユーザーが独自に定義したリソースの作成・管理を実施できます。これを利用することで、運用負荷の低下や特定のアプリケーションを短期間で導入ができます (今回の移行でも利用しました)。

2 番目の「ベンダーロックインの回避」について、 Kubernetes を採用することでクラウドプラットフォーム (AWS, GCP) に対する依存性を減らすことができましたが、完全に AWS を意識せずに Kubernetes 環境を作成するのは難しいと感じました。例えば、ALB Ingress Controller を利用することで ALB を Kubernetes の Ingress リソースとして定義できますが、annotation などに AWS 固有の設定が入り込んでしまいます。

3 番目の「採用面での魅力」については、エンジニアの採用に関わっている際に「Kubernetes 環境を触ってみたい、チャレンジしてみたい」という候補者さんが多い印象を感じたため (バイアスがかかっている可能性もあります)、採用面で効果があるのではないか考えました。まだ移行したばかりなので、Kubernetes の導入で変化があったかどうかについては確認できておりません。

もちろん、EKS にもデメリットはあり、例えば ECS に比べて利用料金が割増になってしまいます。ECS では利用している EC2 インスタンスの料金しかかかりませんが、EKS では EC2 インスタンスの料金の他に EKS クラスタ 1 つに対して毎時 0.10 USD の料金がかかります。また、Kubernetes のマイナーバージョンは 3 ヶ月ごとに更新されるため、最新バージョンに追従するには運用負荷がかかります (EKS ではマイナーバージョンが 9 ヶ月サポートされます [1])。このように、一概にどちらが良いかは正解を決めることが難しいと考えており、ユースケース・解決したい課題次第なのではないかと考えています (ぶっちゃけ好きな方でも良い気がします)。

移行の全体像について

まず、移行の全体像について説明します。
移行対象は Elastic Beanstalk 環境の docker コンテナと ALB を EKS 環境へと移行しました。移行期間は 3 ヶ月かかり、はじめは Develop 環境を移行し、問題が見つからなければ順番に Staging 環境、Production 環境の移行を実施しました。Production 環境 (本番環境) の移行時には事前に EKS 環境を立ち上げておき、CloudFront の Origin の ALB を切り替えることで無停止状態での切り替えを実施しました。

また、移行に際して、下記の特徴・機能を実現することにしました。

・ Cloud9 での EKS クラスタ管理
・ Spinnaker による Deploy
・ Node Group、スポットインスタンスを活用した EKS クラスタ戦略
・ Git ブランチに固有な Develop 環境
・ Git Ops の導入
・ メトリクス・ログ収集基盤の構築

それぞれの詳細について、以下で説明していきます。

Cloud9 での EKS クラスタ管理

画像2

今回 EKS クラスタの管理には Cloud9 を利用しました。
Cloud9 は AWS 提供の IDE で Web ブラウザ経由でエディタやターミナルを使用することができます [2]。
Cloud9 を EKS クラスタの管理に採用した理由は下記のとおりです。

・Elastic IP アドレスを用いて Kubernetes コントロールプレーンとの通信に接続元 IP アドレス制限を適用できる
・複数人で共有可能かつ IAM による制限ができる
・Cloud9 環境の localhost 8080 番ポートを利用できる

それぞれ順番に説明します。
まず、Cloud9 は EC2 インスタンス上で稼働します (オンプレミスのサーバー上でも稼働できます)。なので、Cloud9 が稼働している EC2 インスタンスに対して Elastic IP アドレスを割り当てれば、Cloud9 環境の IP アドレスを固定化することができます。また、EKS では Kubernetes コントロールプレーンとの通信を接続元 IP アドレスによって制限ができます。この、接続元 IP アドレスに Cloud9 に割り当てた Elastic IP アドレスを使用することで Kubernetes コントロールプレーン (API Server) を不用意に外部に晒すことがありません。

次に、Cloud9 は複数人 (複数 IAM ユーザー) と共有したり、更に権限管理 (Read / Write) を設定することも可能です。従って、EKS クラスタに対する操作をサイロ化することがありません。また、Cloud9 上に各種ツール (eksctl, kubectl) もインストールできるので、バージョンを揃えたりする手間がかかりません。更に、各種ツールから利用する構成ファイルも Cloud9 を通じて Git レポジトリにて管理することで、構成ファイルのバージョン管理やバックアップを実施できます。今回は構成ファイルに機密情報が入りやすいため、 IAM ユーザーによる制限をするため Git レポジトリに AWS CodeCommit を採用しました。

最後に、Cloud9 では localhost 8080 番ポートを Web ブラウザ上から接続可能です [3]。
元々、Cloud9 上で開発した Web Server Application などの動作確認用にあるのですが、kubectl proxy コマンドや kubectl port-forward コマンドを利用して、任意の Pod のポートをリッスンでき、動作確認用途に使用できます。

もちろん、EC2 やローカル環境でも上記のようなことは実施できますが、Cloud9 を用いると設定手順の共有や環境構築の手間のかかりません。
更に、Cloud9 では追加料金が発生するわけではなく、利用した EC2 インスタンスの料金 + EBS のみがかかります。

Node Group・スポットインスタンスを活用した EKS クラスタ戦略

Kubernetes クラスタに対して各種アプリケーションと各種環境 (Develop, Staging, Production) をどのように割り当てるかを考える必要があります。1 つのクラスタに対して複数のアプリケーションを配置するのか、それとも 1 つのクラスタに対して 1 つのアプリケーションを配置するのかを決めなければなりません。1 つのクラスタに複数アプリケーションを展開した場合、1 つの クラスタのみを管理すれば良いので運用負荷は下がります。しかし、クラスタ全体に影響する不具合・障害があった場合に、複数のアプリケーションに影響がかかってしまいます。また、リソースの適切な分配なども考えなければなりません。逆に、1 つのクラスタに対して 1 つのアプリケーションを展開すればこれらのデメリットはなくなりますが、複数のクラスタを管理しなければならないのと、EKS を利用している場合は Kubernetes クラスタ 1 台に対して追加料金が発生するので金銭面でのコストも増加します。

今回は、環境に応じてクラスタとアプリケーションの対応関係を決めました。具体的には、Develop 環境と Staging 環境は 1 つの Kubernetes クラスタを共有させ、Production 環境は 1 つの Kubernetes クラスタを専有する形にしました。Develop 環境と Staging 環境は本番のワークロードが流れるわけでもないため明確にクラスタを分離をさせる必要がないと考えました。
一方で、Production 環境は他環境の影響を受けたくないので分けることにしました。

ただ、Develop 環境と Staging 環境もそのままではなく、Nodegroup を別々にすることで EC2 ノード に異なる Label を割当、Node Affinity と Taint / Torelation を組み合わせることで Develop 環境と Staging 環境で利用される EC2 ノード を分離させました。

画像3

上記のように、Develop 環境と Staging 環境で利用したい EC2 ノード ごとに別々のLabelを付与 (利用しているインスタンスのライフサイクル) して、Develop 環境のみ Taint を付与します。そして、Develop 環境の Pod には lifecycle: Ec2Spot が付与された Node Affinity と spotInstance: true:NoSchedule が許容できる Taint を付与します。

     affinity:
       nodeAffinity:
         requiredDuringSchedulingIgnoredDuringExecution:
           nodeSelectorTerms:
           - matchExpressions:
             - key: lifecycle
               operator: In
               values:
                 - Ec2Spot
     tolerations:
       - key: spotInstance
         operator: Equal
         value: 'true'
         effect: NoSchedule

こうすることで、Develop 環境の Pod は lifecycle: Ec2Spot が付与された EC2 ノード にスケジューリングされ、Staging 環境の Pod は spotInstance: true:NoSchedule に対する Taint が無いため、Develop 環境の EC2 ノード にはスケジューリングされず、Staging 環境の EC2 ノード にスケジューリングされます。

更に、Develop 環境は高可用性が必要ないため、スポットインスタンスを利用しました。後述するように、Develop 環境は大量のリソースを必要とするため、EC2 インスタンスも大量に起動する必要があります。スポットインスタンスを利用すれば、EC2 インスタンスの利用料金も 7 割削減できるので、インフラコストを抑えることができます。

EKS クラスタの作成には eksctl を利用しました [4]。eksctl は Weaveworks 社が開発している OSS です。AWS が公式に開発しているツールではないため、EKS 側で新しい Kubernetes バージョンが利用可能になってもすぐ eksctl で利用できない場合があります。しかし、AWS のドキュメントにも記載がされているので、野良ツールというわけでもなく、半公式的な扱いなのではないかと思います。eksctl を利用することで EKS クラスタや NodeGroup の管理を実施できます。細かな部分をよしなにしてくれるため、初めのうちは eksctl を使うのが良いのではないかと思います。

eksctl では yaml ファイルをもとに EKS クラスタの作成を実行できるので、IaC (Infrastructure as Code) を意識した運用が可能です。この yaml ファイルで記載する設定は以下のようなものです。

・EC2 が所属する VPC, subnet
・EKS クラスタエンドポイント (コントロールプレーンのエンドポイント) のアクセシビリティ (Private, Public)
・Nodegroup で利用する AutoScaling の設定 (インスタンスタイプ、キャパシティ、インスタンスライフサイクル ...)
・EC2 ノードに割り当てる Label と Taint

Cloud9 の紹介時に「Kubernetes コントロールプレーンとの通信に接続元 IP アドレス制限をかける」ことを挙げましたが、ここで適用させます。

 clusterEndpoints:   
   publicAccess: true
   privateAccess: true
 publicAccessCIDRs: ["w.x.y.z/32"]

具体的には、EKS クラスタを作成する yaml ファイルに上記のように記述をします。publicAccessCIDRs の値 "w.x.y.z/32" には Kubernetes コントロールプレーンとの通信を許可する IP アドレス、つまり、Cloud9 に割り当てた IP アドレスを記載します。publicAccessCIDRs の値は複数指定が可能なので、必要に応じて Kubernetes コントロールプレーンにアクセスする IP アドレスを追加しておきます。もちろん、eksctl も Cloud9 上にインストールして、クラスタを作成する yaml ファイルも Cloud9 を通じて Git レポジトリにて管理しておきました。


今回はここまでにして、次回は Kubernetes リソースのデプロイ周りを紹介します。

https://note.com/tyrwzl/n/nf22bbe4843c5


脚注

(注 1)「EC2 インスタンスタイプを下げれば良いのでは?」と疑問を抱くかもしれません。しかし、既に large 系統のインスタンスタイプを利用していた、かつ、T2/T3 は本番環境では利用したくないため、インスタンスタイプを下げることは選択肢から外しました。

参考文献

[1] Amazon EKS Kubernetes versions - Amazon EKS
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/kubernetes-versions.html
[2] AWS Cloud9(Cloud IDE でコードを記述、実行、デバッグ)| AWS
https://aws.amazon.com/jp/cloud9/
[3] AWS Cloud9 Integrated Development Environment (IDE) で実行中のアプリケーションをプレビューする - AWS Cloud9
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/app-preview.html
[4] eksctl
https://eksctl.io/





この記事が気に入ったらサポートをしてみませんか?