はじめに
KubernetesにおけるCICDとしては、GitOpsと呼ばれるデプロイ手法がベストプラクティスとされているようです。
個人的なお話をすると、担当案件ではECSというAWS独自のコンテナオーケストレーションサービスを使っているので、そもそもKubernetes自体あまり触れる機会がなかったりするのですが、ちょっと今回はEKSを使ってGitOpsを試してみようかなと思います。
環境構築についても、IaCを活用するケースで個人的によく使うのはCloudFormationなので、今回は普段使わないTerraformを使って構築しようかなと思います。
※尺の関係で今回(前編)はTerraformメインの記事となっております。
このシリーズの記事
Terraformを使ってArgo CDによるGitOpsなリリースができるEKS環境を構築してみる(前編~TerraformでAWSリソースを構築してみた~)←今回はこちら
Terraformを使ってArgo CDによるGitOpsなリリースができるEKS環境を構築してみる(中編~AWS Load Balancer Controller使ってみた~)
Terraformを使ってArgo CDによるGitOpsなリリースができるEKS環境を構築してみる(後編~Argo CDでGitOpsなデプロイやってみた~)
CIOpsとGitOpsについて
GitOpsとは何かというお話をする際にしばしば比較されるデプロイ手法がCIOpsです。関係性としては、従来のデプロイ手法がCIOpsで、それにまつわる課題を解消した手法がGitOpsであるというところ。
端的にいうと、CIOpsはPush型のデプロイであるのに対し、GitOpsはPull型のデプロイであるという違いがあります。ソースコードがリポジトリへPushされ、CIツールでビルドされた後、CIツール上でkubectl applyを実行してクラスタへデプロイするのがCIOpsであるのに対し、ビルド資材をクラスタ側が取りに行ってデプロイするのがGitOpsということです。
CIOpsの場合はクラスタへデプロイするための強めの権限をCIツールに付与しないといけないのですが、GitOpsではそれが回避できたり、リポジトリ上で管理しているマニフェストとクラスタにデプロイされているアプリのバージョンが常に同期された状態にできたりするというのがGitOpsの主なメリットとのこと。
ちなみに、GitOpsは2017年にWeaveWorks社が提唱した手法です。
構成図
極力シンプルに、必要最低限のリソースを作るというコンセプトでまいります。
構成のポイントはこんなところです。
- EKSクラスター内に簡単なアプリとして機能するPodを配置し、ALB→NodePort→Podと通信
- ALBはAWS Load Balancer Controllerというものを使って作成。そのために必要なリソースとしてIngress ControllerとIngressを配置
- アプリケーション資材とマニフェストを格納するCodeCommitを配置。CIツールとしてはCodeBuildを使用
- CDツールはArgo CDを使用
TerraformでAWSリソースを構築する
構成要素のうち、Kubernetesリソースと、別途AWS Load Balancer Controllerで作成される予定であるALBを除いたAWSリソースをTerraformで構築しようと思います。
ディレクトリ構成
実際の案件では単一環境ではなく、開発、ステージング、本番と、複数の環境を構築する必要がある場合が多いと思います。今回は開発環境の位置づけにあたる環境を1つだけ作る予定ですが、ディレクトリ構成としては複数環境構築に対応できることを意識したかたちにしてみました。
.
├── common
│ ├── main.tf
│ ├── modules
│ │ ├── codecommit.tf
│ │ ├── ecr.tf
│ │ └── variables.tf
│ ├── provider.tf
│ ├── variables.tf
│ └── terraform.tfvars
├── dev
│ ├── main.tf -> ../shared/main.tf
│ ├── provider.tf -> ../shared/provider.tf
│ ├── variables.tf -> ../shared/variables.tf
│ └── terraform.tfvars
└── shared
├── modules
│ ├── codebuild.tf
│ ├── iam.tf
│ ├── securitygroup.tf
│ └── variables.tf
├── main.tf
├── provider.tf
└── variables.tf
構成のポイント
ディレクトリをcommon、dev、sharedと分けています。
commonディレクトリには、環境共通のリソースとなりうるものについての資材を格納しています。開発環境、ステージング環境、本番環境とつくるにしてもCodeCommitとECRは環境共通で使うことになりそうだなということで、それらを構築するための資材はcommonディレクトリに用意しています。
devディレクトリには、開発環境としてのリソースを構築するための資材を格納しています。今回はdevディレクトリ配下でterraform applyを実行し、前述の構成図の内容を開発環境という位置づけで構築します。仮にステージング環境や本番環境など複数の環境を構築する必要がある場合は、devディレクトリと並列させるかたちでstgディレクトリ、prodディレクトリと作成するイメージです。
sharedディレクトリには、環境ごとに必要なリソースの定義を記述したtfファイルそのものをmoduleとして格納し、さらにterraformにより直接参照されるmain.tfや、その他variables.tfやprovider.tfを環境共通資材として格納しています。これらは環境ごとに個別の実装をすることはせず共通資材として利用し、個別の環境用のディレクトリ(今回であればdevディレクトリ)にはこれらのシンボリックリンクを配置しています。
環境固有の設定値は、terraform.tfvarsに記述し、それぞれのディレクトリ下でterraform applyを実行した時に、こちらに記述している環境ごとに設定したい設定値が反映されるようにしています。今回は以下の設定値をterraform.tfvarsにて定義しています。
# 環境識別子(リソースの命名に使用)
env_name = "dev"
# VPCおよびsubnetのCIDR
cidr_vpc = "10.0.0.0/16"
cidr_pub_c = "10.0.1.0/24"
cidr_pub_d = "10.0.2.0/24"
cidr_pri_c = "10.0.11.0/24"
cidr_pri_d = "10.0.12.0/24"
# EKSのノード数とインスタンスタイプ
eks_node_desired_size = 2
eks_node_instancetype = "t3.small"
main.tf
main.tfはこんなかんじでございます。
module "resources" {
source = "../shared/modules"
env_name = "${var.env_name}"
vpc_id = module.vpc.vpc_id
node_security_group_id = module.eks.node_security_group_id
cluster_security_group_id = module.eks.cluster_security_group_id
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.4"
name = "${var.env_name}-vpc"
cidr = "${var.cidr_vpc}"
azs = ["ap-northeast-1c", "ap-northeast-1d"]
public_subnets = ["${var.cidr_pub_c}", "${var.cidr_pub_d}"]
private_subnets = ["${var.cidr_pri_c}", "${var.cidr_pri_d}"]
# AWS Load Balancer Controllerの導入にて、subnetに「kubernetes.io/role/elb」タグの付与が必要なので仕込んでおく
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "18.29.0"
cluster_name = "${var.env_name}-eks-cluster"
cluster_version = "1.22"
cluster_endpoint_private_access = false
cluster_endpoint_public_access = true
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
eks_managed_node_groups = {
nodegroup = {
desired_size = "${var.eks_node_desired_size}"
instance_types = ["${var.eks_node_instancetype}"]
}
}
}
main.tfでは3つのmoduleブロックを呼び出しています。
resources moduleは、shared/modulesディレクトリ配下のtfファイルを実行しています。加えて、これらの実行に必要な変数定義を列挙しています。(env_nameとかvpc_idとか)
vpc moduleとeks moduleはTerraformにて公開されている「Terraform Module Resistry」のモジュールを使用した実装となっています。今回はこちらのモジュールを使いつつ、設定したい値だけ各ブロック内で記述しています。
variables.tf
variables.tfには、main.tfやmodulesディレクトリ配下の各tfファイルが使用する変数を定義します。sharedディレクトリ直下とその配下のmodulesディレクトリにそれぞれvariables.tfを配置していますが、それぞれ以下のように記述しています。
variable "env_name" {}
variable "cidr_vpc" {}
variable "cidr_pub_c" {}
variable "cidr_pub_d" {}
variable "cidr_pri_c" {}
variable "cidr_pri_d" {}
variable "eks_node_desired_size" {}
variable "eks_node_instancetype" {}
variable "env_name" {}
variable "vpc_id" {}
variable "node_security_group_id" {}
variable "cluster_security_group_id" {}
おそらくはデフォルト値やdescriptionを記述しておくほうが望ましいのですが、今回は割愛しています。
実行結果
commonディレクトリ配下、devディレクトリ配下でそれぞれterraform applyを実行した結果、うす~くしているものを除いた以下のリソースが作成されます。
ここまでのまとめ
長くなりそうなので、ALBを作る工程と、Argo CD環境を作る工程&デプロイの試行は次回にまわしたいと思います。
今回はTerraformを使って環境構築をしてみました。いざ使ってみようとすると、複数環境の構築などの方針を踏まえたモジュール設計が主な検討要素になってくるのかなといった印象を受けました。とはいえ一度作ってしまえば後はterraform applyポチっとなで構築できますし、不要になればterraform destroyポチっとなで削除できますし、修正も画面ポチポチより楽ですし(ポチポチポチポチすみません)、言うまでもないかもしれませんが、もはやIaCを使わない手はないなという所感です。
次回以降は、AWS Load Balancer ControllerによるALBの作成と、Argo CD環境構築&GitOpsなデプロイを実施したいと思います。