お約束
- 本記事は ARISE analytics Advent Calendar 2022 の16日目の記事です
- 昨日は @sakaihiroaki さんの Flutterでアクションゲーム開発(Flappy Bird)をやってみた
はじめに
以前Kubernetesについて学ぶ機会があったので、今回はクラウドでお手軽にk8s環境を構築する方法をご紹介します!
これから書く内容はこちらの素晴らしい記事の内容を大いに参考にさせていただいているので、説明不十分な箇所があればこちらを覗いてみると解決するかもです。
Kubernetesについて
Kubernetes(以下、k8sと記述)については、インターネットの海に大量に情報が落ちているので、ここでは軽く触れるだけにします。公式では以下のように紹介されています。
Kubernetesは、宣言的な構成管理と自動化を促進し、コンテナ化されたワークロードやサービスを管理するための、ポータブルで拡張性のあるオープンソースのプラットフォームです。Kubernetesは巨大で急速に成長しているエコシステムを備えており、それらのサービス、サポート、ツールは幅広い形で利用可能です。
宣言的であるとは、あるべき状態を定義するとそれに合わせて現在の状態を変化させることであるべき状態が実現される仕組みがあることを言います。
k8sでは、様々な役割を持つ各種コンポーネントによってクラスターが構成されますが、それらはいずれもyaml形式のファイル(マニフェストと呼ばれる)で宣言的に定義されます。クラスターの司令的な役割を担うノードがこのマニフェストを読み取り、最終的にマニフェストに定義された状態を実現しようとします。
「宣言的である」と並列でよく語られるのが「手続き的である」ですが、どちらが良い悪いといった話ではなくそれぞれにメリットデメリットがあり、k8sは宣言的であることのメリットを享受した仕組みであると言えます。この辺の違いはこちらのサイトで挙げられている例がわかりやすいかと思います。
k8sはこのようにマニフェストを用いてクラスター構成を管理し、宣言的にクラスターのあるべき状態を定義することで、その状態を実現するために必要な様々な手続きを意識することなく自動で実行してくれるというのが利点になっています。
ワークロードやサービスといっているのは、おそらくバッチ的な処理やWebアプリケーションのことを指していると思いますが、k8s上のワークロードやサービスは全てコンテナ上で動作しており、これによってワークロード・サービスのスケールや監視等が容易になっています。
Terraformについて
Terraformは最近流行りのIaCを実現するためのツールです。
↓に書いてある通り、コンピュータやネットワークの構築を自動化することができて、AWSやAzure、GCPなど複数のクラウドで使えます。
HashiCorp社が提供するTerraformは、マルチクラウド上のコンピュータやネットワークの構築を自動化する、エンジニアにとても人気のあるツールです。
AWSなどのクラウドサービスを使ったことがある方はわかると思いますが、GUIのコンソール上でぽちぽちマウスカーソルをクリックしてVPCやEC2なんかを作成してしまうと、しばらくたってから全く同じ構成で新しいシステムを構築しないといけなくなった場合だとか、インフラ担当者の交代時に引き継ぎ漏れが生じた場合なんかに、一体今このシステムはどんな構成で動いているのかが一見してわからなくて困ってしまうんですよね。コンソール画面を見てある程度どんな構成かはわかるかもしれませんが、細かなところまで把握するには正確な仕様書などがないと厳しいかと思います。
Terraformでは、システム構築時にコンソール上で実施する手続きをコードとして記述することで、再現性のあるシステムを構築することができるようになります。システムを新たに引き継ぐ人であったり、最初に構築した際の記憶を忘れてしまった自分であっても、コードを見ることで詳細まで構成を把握することができます。また、途中でシステムの構成を変えられるので、コードをバージョン管理してあげることでシステム構成の変遷を辿ることなんかもできます。
TerraformによるEKSクラスター構築
今回は、Amazon EKSを使ってk8s環境を構築していきます。
後に出てくるterraformのコードやマニフェストファイルなどをコピペしていけば構築できるように意識しているので、気軽に見ていただければと思います。
出来上がりイメージ
パパッと作ったのでもしかしたら間違い等あるかもですが、お手軽ということでこんな感じのシステム構成で作っていきます!
アプリはフロントとバックエンドを分けてDB作って…ってな感じでもう少し凝ったものを展開したいところですが、今回は簡潔にnginxのコンテナだけを乗っけてアクセスできるようにすることを目標にします。冗長性を考慮してnginxを二つのリージョンに配置し、それぞれNodeを一つ立ててその中にPodを複数用意して負荷分散するイメージです。InternetGateway、Ingress、NodePortを通してインターネット環境からPodにアクセスできるようにします。
用意するもの
- 開発環境
- AWSのクレデンシャル情報
- AWS CLIを使用するため、アクセスキーとシークレットキーが必要になります
- 多少のお金
- 確かAmazon EKSは無料枠の対象外だった気がするので、一応軽く調べてからの実行をお勧めします
準備
とりあえずサクッと準備を進めます。
AWSクレデンシャルを以下のように環境変数として設定することで、terraformを使ってAWSのリソースを操作できるようになります(※1)。
環境変数設定
# AWSクレデンシャルの設定
export AWS_ACCESS_KEY_ID=xxxxx
export AWS_SECRET_ACCESS_KEY=xxxxx
export AWS_DEFAULT_REGION=ap-northeast-1
# 各ツールのバージョン設定
export TERRAFORM_VERSION=1.3.3
export AWSCLI_VERSION=2.7.10
export TFLINT_VERSION=0.30.0
各種コマンドのインストール
terraform、aws-cli、kubectlをインストールしていきます。
# install terraform command
RUN curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - \
&& apt-add-repository "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
&& apt-get -y install terraform=${TERRAFORM_VERSION}
# install aws-cli
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-${AWSCLI_VERSION}.zip" -o "awscliv2.zip" \
&& unzip awscliv2.zip \
&& ./aws/install \
&& rm -rf ./aws ./awscliv2.zip
# install kubectl
RUN curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.6/2022-03-09/bin/linux/amd64/kubectl \
&& chmod +x ./kubectl \
&& mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin \
&& echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc \
&& kubectl version --short --client
資材作成
tfファイルとマニフェストファイルを作っていきます。Terraformでは、AWS上に展開するサービスはtfファイルを使って定義します。EKSクラスターもここで作ります。EKSクラスターを作った後で、マニフェストを使ってクラスター上にアプリを展開していきます。
ディレクトリ構成
Terraformに関しては理想的なディレクトリ構成のパターン(※2)がいくつかあるみたいですが、今回は一旦無視して以下の構成で資材を配置していきます。
.
├── terraform // EKSクラスター環境をどかっと作るための資材
│ ├── backend.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ ├── variables.tf
│ └── terraform.tfvars
│
└── manifest // EKSクラスター上にアプリを展開するための資材
├── cluster.yaml
├── ingress.yaml
└── app.yaml
variables.tf
variables.tf
で、terraformでサービスを構築する際に使う環境変数を宣言します。
環境変数の代入はterraform.tfvars
で行います。
ここで定義した環境変数は、他のtfファイルで使用します。
# provider用環境変数
variable "provider_region" {}
# VPC環境変数
variable "vpc_name" {}
variable "vpc_cidr" {}
variable "azs" {}
variable "public_subnets" {}
variable "private_subnets" {}
## EKS環境変数
variable "cluster_name" {}
variable "eks_managed_node_group_name" {}
variable "policy_aws_load_balancer_controller" {}
variable "role_aws_load_balancer_controller" {}
# provider用
provider_region = "ap-northeast-1"
# VPC用
vpc_name = "vpc-tf-eks"
vpc_cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnets = ["10.0.0.0/19", "10.0.32.0/19"]
private_subnets = ["10.0.64.0/19", "10.0.96.0/19"]
# EKS用
cluster_name = "tf-eks-cluster"
eks_managed_node_group_name = "tf-eks-managed-ng"
policy_aws_load_balancer_controller = "EKSIngressAWSLoadBalancerControllerPolicy"
role_aws_load_balancer_controller = "EKSIngressAWSLoadBalancerControllerRole"
backend.tf
次に、terraformとterraform内で使用するproviderのバージョン条件を指定するtfファイルを作成します。
このファイルにはtfvars
で変数を定義できないので、直接書き込んでいきます(※)。
terraform {
# terraformのバージョン条件
required_version = "~> 1.3.3"
# 実行するproviderの条件
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.37.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~>2.14.0"
}
}
}
provider.tf
続いて、providerを定義していきます。
Terraformを使ってAWS上にサービスを展開する際にはaws
を、k8sを使用する際にはkubernetes
を定義する必要があるみたいです。
providerについては正直あまりイメージが湧いていないのですが、こちらのサイトなどが参考になるかもです。
provider "aws" {
region = var.provider_region
}
# EKSクラスタリソースを参照
data "aws_eks_cluster" "eks" {
name = module.eks.cluster_id
}
data "aws_eks_cluster_auth" "eks" {
name = module.eks.cluster_id
}
provider "kubernetes" {
host = data.aws_eks_cluster.eks.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.eks.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.eks.token
}
main.tf
次に、AWS上でEKSクラスターを作成するための権限周りのリソースを定義していきます。
まず、事前にIAMポリシーの設定用ファイルをダウンロードする必要があるので、terraform
ディレクトリに移動して以下のコマンドを実行します。
cd ./terraform
curl -o alb-ingress-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/install/iam_policy.json
権限周りのリソースとして以下の3つを定義します。
- Ingress Controller用のIAMポリシー
- ↑のポリシーを適用するIAMロール
- ↑のロールを使用するサービスアカウント
Ingress Controllerはk8s上のロードバランサ的なやつ(=Ingress)を管理する主体になります。Ingress Controllerにこのサービスアカウントを紐づけることで、Ingress ControllerはAWS上のリソースを使ってIngressを管理できるようになります。
resource "aws_iam_policy" "aws_loadbalancer_controller" {
name = var.policy_aws_load_balancer_controller
policy = file("${path.module}/alb-ingress-policy.json")
}
module "iam_assumable_role_admin" {
source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
version = "~> 4.0"
create_role = true
role_name = var.role_aws_load_balancer_controller
provider_url = replace(module.eks.cluster_oidc_issuer_url, "https://", "")
role_policy_arns = [aws_iam_policy.aws_loadbalancer_controller.arn]
oidc_subjects_with_wildcards = ["system:serviceaccount:*:*"]
}
resource "kubernetes_service_account" "aws_loadbalancer_controller" {
metadata {
name = "aws-load-balancer-controller"
namespace = "kube-system"
annotations = {
"eks.amazonaws.com/role-arn" = module.iam_assumable_role_admin.iam_role_arn
}
}
}
次に、VPCを定義していきます。変数周りはtfvars
ファイルで定義しているのでそれを使います。
一点注意しないといけないのは、これはterraformに限った話ではないのですが、EKSでk8s環境を構築する場合、以下のようにサブネットのタグに"kubernetes.io/role/elb" = "1"
と設定する必要があります。これを設定することで、Ingress Controllerがどのサブネットを使用してk8s環境を構築するのかを自動で見分けて動いてくれます。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~>3.18.0"
name = var.vpc_name
cidr = var.vpc_cidr
azs = var.azs
public_subnets = var.public_subnets
private_subnets = var.private_subnets
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
enable_nat_gateway = true
single_nat_gateway = false
enable_vpn_gateway = false
}
次に、EKSクラスターを作成していきます。こちらも基本的にtfvars
ファイルで設定した変数や、上で定義したVPCに紐づく値を使って定義します。
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~>18.0"
cluster_name = var.cluster_name
cluster_version = "1.22"
cluster_endpoint_private_access = true
cluster_endpoint_public_access = true
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
enable_irsa = true
eks_managed_node_groups = {
"${var.eks_managed_node_group_name}" = {
desired_size = 2
instance_types = ["t3.small"]
}
}
node_security_group_additional_rules = {
# AdmissionWebhookが動作しないので追加指定
admission_webhook = {
description = "Admission Webhook"
protocol = "tcp"
from_port = 0
to_port = 65535
type = "ingress"
source_cluster_security_group = true
}
# Node間通信を許可
ingress_mode_communications = {
description = "Ingress Node to Node"
protocol = "tcp"
from_port = 0
to_port = 65535
type = "ingress"
self = true
}
egress_node_communications = {
description = "Egress Node to Node"
protocol = "tcp"
from_port = 0
to_port = 65535
type = "egress"
self = true
}
# Node -> Localの通信を許可
egress_node_to_local = {
description = "Egress Node to Local"
type = "egress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
}
}
}
outputs.tf
最後に、作成したEKSクラスターを利用するための「設定ファイル」の出力を定義します。
output "aws_auth_config_map" {
value = module.eks.aws_auth_configmap_yaml
}
ここまで資材を作成したら、以下のコマンドを実行してどかっと環境を構築できます。
構築完了までには10分程度かかります。
# 初期化
terraform init
# 環境構築
terraform apply
クラスター上へアプリケーションを展開
ここまででTerraformを使ったEKSクラスター構築は完了しました!
ここからは、クラスター上にアプリケーションを展開していきます。
まずmanifest
ディレクトリに移動して、outputs.tfで定義した出力からマニフェストファイルを作成します。
cd ../manifest
terraform output aws_auth_config_map > ./manifest/aws-auth-configmap.yaml
以下を参考にしながら↑で作成したファイルを編集し、k8sの操作を許可したい任意のIAMユーザを追加します。
(先頭・末尾のEOTは削除してください)
いない場合、この作業は不要になります。
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: arn:aws:iam::xxxxxxxxxxx:role/tf-eks-node-group-xxxxxxxxxxxxxxxx
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
# 以下を追加
mapUsers: |
- userarn: arn:aws:iam::xxxxxxxxxxx:user/xxxxxx
username: xxxxxx
groups:
- system:administrator
ユーザを追加できたら、以下のコマンドを実行します。
ここからはkubectlを使ってk8s環境を操作していくので、まずはkubectlで扱うクラスターの設定を更新します。
# kubectlで扱うクラスターの設定更新
export CLUSTER_NAME=tf-eks-cluster
aws eks update-kubeconfig --name ${CLUSTER_NAME}
設定を更新したら、↑で作成したマニフェストを適用してterraform以外の一般ユーザにもEKSクラスターを操作できるようにします。例のごとく、マニフェストを作成していない場合はこのコマンドは実行不要です。
# Terraform以外の一般ユーザがEKSへ接続できるようにする
kubectl apply -f aws-auth-configmal.yaml
cert-managerについては正直完全にノータッチだったので、他の素晴らしい記事に解説をお譲りします。
# cert-manager作成
kubectl apply \
--validate=false \
-f https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.yaml
次に、Ingress Controllerを作ります。マニフェストファイルはGitHubからコピってきていい感じに修正したら、クラスターに適用すれば作成できます。
# Ingress Controller用マニフェスト作成
curl -Lo ingress-controller.yaml https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.4.4/v2_4_4_full.yaml
sed -i.bak -e '480,488d' ./ingress-controller.yaml
sed -i.bak -e 's|your-cluster-name|${CLUSTER_NAME}|' ./ingress-controller.yaml
curl -Lo ingclass.yaml https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.4.4/v2_4_4_ingclass.yaml
# Ingress Controller作成
kubectl apply -f ingress-controller.yaml
kubectl apply -f ingclass.yaml
次に、Ingress用のマニフェストを作成します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
name: ingress-tf-eks
spec:
ingressClassName: alb
rules:
- http:
paths:
- backend:
service:
name: web-tf-eks
port:
number: 80
path: /
pathType: Prefix
マニフェストからIngressを作成します。
# Ingress作成
kubectl apply -f ingress.yaml
最後に、アプリケーション用コンテナを配置するマニフェストを作成します。ここではアプリケーションを実行してくれるコンテナ用イメージか、イメージが格納されたリポジトリを指定する必要があります。今回はサンプルとして、nginxのイメージを指定します。
NodePortの設定も併せて同じファイルに記述します。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web-tf-eks
name: web-tf-eks
spec:
replicas: 2
selector:
matchLabels:
app: web-tf-eks
strategy: {}
template:
metadata:
labels:
app: web-tf-eks
spec:
containers:
- image: nginx:latest
name: web-tf-eks
---
apiVersion: v1
kind: Service
metadata:
name: web-tf-eks
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: web-tf-eks
type: NodePort
マニフェストからnginxコンテナを作成します。
# アプリケーション作成
kubectl apply -f app.yaml
お疲れ様でした!これでk8s環境にアプリケーションを展開することができました!
ここまでに作成したリソースの状態を確認してみましょう。
# 確認
kubectl get pod,svc,ingress
↓こんな感じで、PodやService、Ingressなどの状態が確認できます。
Podは2つともSTATUSがRunningということで、正常に稼働していることがわかります。
これでアプリケーションの準備が整ったので、早速アクセスしてみましょう!
上で確認したIngressの状態から、ADRESS欄に表示されるアドレスをブラウザに打ち込んでアクセスできます。
実際にアクセスすると、
こんな感じでnginxのページが表示されているのが確認できます!
以上が今回の内容になります。
最後に、ここまでに立ち上げたサービスを全て削除するのを忘れないようにしましょう!
# アプリケーションを削除
kubectl delete -f app.yaml
# Ingressを削除
kubectl delete -f ingress.yaml
# Ingress Controllerを削除
kubectl delete -f ingress-controller.yaml
# EKS環境を削除
cd ../terraform/
terraform destroy
まとめ
今回はTerraformとAmazon EKSを使ってk8s環境を構築しました!
構築したシステム自体はかなり控えめだったのでTerraformやk8sの恩恵を感じるところまでは至らなかったかもしれませんが、これらの技術に興味を持つ足掛かりとなれば幸いです。
参考・お役立ちサイト
- ※1: 他にもクレデンシャルの設定方法はあります。参考: https://qiita.com/Hikosaburou/items/1d3765d85d5398e3763f
- ※2: 参考: https://dev.classmethod.jp/articles/terraform-bset-practice-jp/
- ※3: 直接書き込む以外にも一応方法はあります。参考: https://qiita.com/ymmy02/items/e7368abd8e3dafbc5c52)