これは「imtakalab Advent Calendar 2024」の 12日目の記事です。
本記事は研究室の学生に向けたものです。
研究室外の方では内容を実施できなかったり、そのままうまくいかない部分が一部あると思いますがご了承ください。
全体公開の内容のため、Terraform Cloud や Github のアカウント情報は本記事には掲載していません。
実際に移行作業をしたい学生は関係者へのアカウント情報の確認をお願いいします!
研究室で実施している認知実験をTerraformで管理できるようにしました!
Terraformとは、一言で言って仕舞えば、「AWSのリソースをソースコード上で管理できるようにする仕組み」のことです。
「Terraformってなに?全くわからん!」って人向けに書いているので、ぜひ最後まで読んでいただけるとありがたいです!
はじめに
コード例のリポジトリ
筆者の作業履歴が下記プルリクエストにまとまっていますので参照してみてください。
(プルリク作成するのが面倒だったので何も書いてません!コミット単位で見てもらえると助かります🙏)
想定読者
- AWSを少し自分で触ったことがある
- 認知実験をAWS上で作成したことがある
- AWSのリソース管理を効率的に行いたい
- Githubでコード管理をしたことがある
この記事を読んでわかるようになること
- TerraformでAWSリソースを管理する方法
- 自分の認知実験を研究室のTerraformで管理する方法
この記事を通してできるようになったこと
- 研究室の認知実験をTerraformで管理するための基盤整理
- 特定の実験(社会バンディット)の試験的なTerraform移行
この記事ではやらなかったこと
- 認知実験そのものの効率化
- 例1:実験データをEC2サーバー内ではなくS3に吐き出す
- 例2:Elastic Containet Service(ECS)で管理する為にDocker化する
- 既存AWSリソースのTerraform化
- セキュリティグループやユーザーなどはTerrafromには移行していません
- 研究室を担っていく今後の研究生たちに学びつつ進めていってもらいたいと考えてます!
前提
我々の研究室では認知科学の研究を行なっています。
認知科学は、一言で言うと、人間が持っている思考の癖や特徴を明らかにしようとする学問です。これらを明らかにする方法の一つとして、認知実験(アンケート等)を実施することがあります。
我々の研究室ではこの認知実験を、主にWeb上で実施しています。
多くの実験がWebサーバーの構築から行なっており、AWSを使用して構築されています。
AWSリソース(EC2やUserなど)の作成や削除などの管理はAWSコンソール上から手作業で行っています。
今回この内容で記事を書こうと思った経緯
内定者インターンでTerraformを学んだ為
内定先の企業で、集団で効率的にAWSやGCPの管理をする方法の一つとして学んだのがTerraformでした。
私の所属する研究室はかなり大きな組織(40人程度の学生がいる)なので、企業のインフラ管理方法もある程度参考にしていくべきなのでは?と思い、試験的に導入していることにしました。
認知実験の実施・運用コストを削減できると思った為
自分は認知実験を実施したことがないのでいまいちわかりませんが、外から見ている限りでは、少なくとも下記に挙げた問題があるように感じています。
-
作業者の負担が大きい
- インスタンスの作成やリソースの紐付けなどの作業を手動で実施する必要があり、ミスが発生しやすい
- 実施した作業が明示的にわかりづらいため、手順書を丁寧に作成する必要がある
-
研究の引き継ぎが大変(若干上と被るかも?)
- AWSコンソール上での手動管理ではインフラ構築の手順や履歴が明示的に理解しづらい
- そのため、引き継ぎの際に作成方法等についての手順書を綿密に作成する必要がある
-
リソースの管理が大変
- 管理者(教授や博士学生)は、混乱を避けるためもしくは使用料金を最小限に抑える為に不要なリソースは削除したい
- だが、誰が何の目的で作成したリソースなのかがわかりにくいことが多く、不用意に削除できないこともある
研究室のITリテラシーを高める為
Git や Docker などの普及はある程度成功しているように感じますが、まだまだITリテラシーが高いと言える水準ではないと思います。
新しい技術を積極的に導入することで、IT技術に対する研究室の基礎体力のようなものが少しでも向上して、当たり前に使える技術の水準が上がったら良いなと感じています。
前置きが長くなりましたが、それでは本題に入っていこうと思います!
Terraformについて
Terraformで何ができるの?
冒頭にも書きましたが、一言で言って仕舞えば、「AWSのリソースをソースコード上で管理できるようにする仕組み」のことです。(AWSだけにとどまらず、GCPやAzureなど、さまざまなクラウドサービスのリソースが作成可能です。)
基本的に、AWS上の全てのリソースをTerraformで作成できます。(セキュリティの観点などから、Terraformで管理すべきではないリソースも一部あったりします)
もう少し詳細なTerraform概要がこちらのQiita記事に記載されているので、是非こちらも併せて読んでみてください!
TerrafromでAWS上にリソースを作成する際の大まかな流れ
TerraformでAWSリソースを管理する際に実施することの大まかな流れを下記に示します。
- テキストでのTerraformリソース宣言
- 作成するTerraformリソースの確認(Plan)
- Terraformリソース & AWSリソースの作成(Apply)
以降でそれぞれの項目に対する説明を簡単にしてみようと思います!
1. テキストでのTerraformリソースの作成
TerraformはHCL(HashiCorp Configuration Language)言語によってTerraformリソースの宣言を行います。Terraformリソース ≒ AWSリソース と捉えてもらって問題ありません。
EC2インスタンスを作成するためのHCLコードは下記のようになります。((Terraform公式Docs)
「terraform <作成したいリソース名>」で検索すえれば基本的に公式ドキュメントが見つかります。
この公式ドキュメントに指定できるフィールドや周辺情報がわかりやすく記述されているので、何か困った事があったら参照しましょう。
# AMI (Amazon Machine Image) リソースの宣言
data "aws_ami" "ubuntu" {
# filterで検索した結果が複数あった場合、最新のものを使用する
most_recent = true
# nameで指定したデータに対してvaluesで検索をする
# filterのnameに指定できる対象については下記リンクを参照
# https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html#options
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
# AMIの所有者を指定する
owners = ["099720109477"] # AWS公式を指定
}
# EC2インスタンスのリソースの宣言
resource "aws_instance" "web" {
# AMIの指定
# ここでは上記で宣言したリソースを参照している
# idは、上記リソースでは宣言していないが作成後に追加される。下記リンク参照
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami
ami = data.aws_ami.ubuntu.id
# インスタンスのタイプを指定
instance_type = "t3.micro"
# インスタンスの名前を指定
tags = {
Name = "HelloWorld"
}
}
2. 作成するTerraformリソースの確認(Plan)
AWSに作成すべきリソースの確認を行う。
Terraformでは、作成されたTerraformリソースをstateファイルによって管理している。
stateファイルと現在のソースコードを比較し、差分がある場合はその差分がPlan結果として表示される。
下記画像は、1でリソースの宣言を行った後に実際にPlanした結果です。
3. Terraformリソース & AWSリソースの作成(Apply)
2でのPlan結果(作成、変更、削除)の差分を実際にAWSに適用します。
Apply後にAWSへアクセスすれば、実際にリソースが作成されていることを確認できるはずです。
Terraformのメリットは?
冒頭にも少し書きましたが、具体的には下記のようなメリットがあります。
-
共同でのAWS作業がしやすい
- Githubと紐付けて管理することが可能なため、誰がど、のタイミングで、どのAWSリソースを操作したのかが明確になる
- Terraform上でもログが残るため、デバッグや変更作業に対する心理的負担が軽減される
-
AWS環境の共有・再現がしやすい
- Terraformコードの共有のみでクラウドインフラ構築が共有できるため、秘伝のタレ化しづらい
- 手動で構築する場合と異なり、コードで明示的に記述されているため読めば大体状況が分かる
自分の認知実験を Terraform Cloud で管理できるようにする
Terraformを使用するにあたって、今回は Terraform Cloud と呼ばれるTerraformを共同管理できるサービスを使用します。
AWSリソースの管理を、Githubを通して複数人で実施できます。ざっくり言えばGithubみたいなもんです!
今回はTerraform移行の例として socialbandit-ex を例に取り上げています。
Terraform Cloud でAWSリソースの管理をするために必要な手順は下記の通りです!
- terraformコマンドを実行できるようにする
- Terraformコードを管理するGithubリポジトリの準備
- Terraform Cloudの準備
- リポジトリからTerraformを使用してAWSリソースを作成するための準備
- TerraformからAWSリソースを作成してみる
- Terraformで作成したリソースの削除
1. terraformコマンドを実行できるようにする
# terraformのバージョン管理ツールのインストール
$ brew install tfenv
# tfenvで最新のterraformバージョンをインストール
$ tfenv install latest
# 上記でインストールしたバージョンをデフォルトで使用できるように設定
$ tfenv use <上記でinstallしたバージョン>
# terraformが使用できるようになっているか確認
$ which terraform
※ tfenvでインストールしたterraformコマンドが使用できない場合(著者PCで発生)
pathの出力結果は環境ごとに異なる可能性があります。
# tfenvコマンドが参照している先を確認
$ which tfenv
# 出力:/usr/local/Cellar/tfenv/3.0.0/bin/tfenv
# tfenvが参照しているPATH("$ which tfenv"の出力結果)の一つ上階層を確認
$ ls /usr/local/Cellar/tfenv/3.0.0/bin
# 出力:terraform tfenv
# ".../bin/tfenv"にしかPATHが通っていないっぽい
# ".../bin"もPATHに追加する
$ export PATH="/usr/local/Cellar/tfenv/3.0.0/bin:$PATH"
2. Terraformコードを管理するGithubリポジトリの準備
研究室のOrganization配下にリポジトリを作成
既に実験スクリプト等を管理しているリポジトリがあれば、新しくリポジトリを作成する必要はありません
terraformを運用するためのブランチを作成
Terraform CloudはGithubのリポジトリと連携しており、Githubへのpushをトリガーにした実行が出来ます。
基本的にどこでも良いのですが、terraformブランチを新しく作成するのが良いでしょう。
mainブランチでも良いのですが、mainブランチにはマージしたくないがAWSリソースは作成したいというケースも考えられます。
そのため、AWSリソースを作成したい時は developブランチで実装 > terraformブランチにマージしてリソース作成 > mainにマージ
の手順で開発を行うと良いでしょう。
3. Terraform Cloudの準備
Terraform Cloudでは Organization > Project > Workspace
という3つの階層で実行管理がなされています。
運用について何も決まっていないのでなんとも言えませんが、Project = 各種実験(リポジトリ)単位 という分離で良いかなと考えています。(なので、Workspace階層は実質的にあまり意味がないことになる)
それでは早速、Terraform Cloudの準備をしていきましょう!
Terraform Cloud にアクセスしてください。
認証情報は管理者に確認してください。
Projectの作成
project名は基本的にリポジトリと同様の名前にしましょう
Workspaceを作成する
workspace名は、特に理由がなければ基本的にリポジトリと同様の名前にしましょう
-
「Version Control Workflow」> 「Github App」の順に押下
特定のブランチに変更をpushした際に自動でplanが走るようにする
-
作成したワークスペース
> setting > version_control に移動 -
Terraform Working Directory
の追記- main.tfを配置した階層を指定してください
- 見本リポジトリではterraform/main.tf なのでterraform/を指定
-
VCS branche
の指定-
terraformを運用するためのブランチを作成 で作成したブランチを指定
-
terraformを運用するためのブランチを作成 で作成したブランチを指定
4. リポジトリからTerraformを使用してAWSリソースを作成するための準備
ファイル構成は下記の通りです。
Terraformではplan時に同階層の.tfファイルは全て1つのファルに結合して解釈されます。
そのため、ファイルはリポジトリ管理者が管理し易い粒度に分割してしまって構いません。
.
└── terraform
├── main.tf # AWSの接続設定やterraformのバージョン指定など
├── modules # 各種リソースを定義するためのディレクトリ
│ └── aws
│ └── cognitive_experiments
│ ├── ec2.tf # EC2インスタンスのリソース定義ファイル
│ └── variables.tf # 使用する変数を定義するファイル
├── terraform.tfvars
└── variables.tf # 使用する変数を定義するファイル
各種ファル内容
下記に示すコードは "social-bandit-ex" リポジトリでの例です。
自分の実験で必要なAWSリソースに応じて必要な設定を追記してください。
terraform {
# terraformのバージョン指定
required_version = "~> 1.10.1" # 各自インストールしたterraformバージョンを指定
# プロバイダ(AWSに接続するためのインターフェース)の指定
required_providers {
aws = {
source = "hashicorp/aws" # ライブラリのimport元指定(環境に依存しない固定値)
version = "~> 5.0" # バージョン5.0以上を指定
}
}
cloud {
organization = "xxxxxx" # Terraform Cloud の Organization名を指定
workspaces {
name = "xxxxxx" # 先ほど作成したワークスペース名を指定
}
}
}
provider aws {
region = "ap-northeast-1" # リージョンの指定(ap-northeast-1=東京の為、多分問題無いはず)
}
# モジュールの定義("aws_cognitive_experiments"の部分は自由に変更可能)
module aws_cognitive_experiments {
source = "./modules/aws/cognitive_experiments" # リソース定義ファイルがあるディレクトリを指定
# 各種変数情報をモジュール内で参照できるようにする
# ec2インスタンスの設定値
ec2 = var.ec2
# AWSリソース上でgitコマンドを使用するためにつような情報
git_personal_access_token = var.git_personal_access_token
git_username = var.git_username
}
variable "ec2" {
type = object({
instance_setting = object({
display_name = string
key_name = string
security_groups = list(string)
instance_type = string
})
git_settings = object({
organization_name = string
repository_name = string
})
})
}
variable "git_personal_access_token" {
type = string
sensitive = true
description = "EC2内でgit操作を行うために必要な認証情報。Terraform Cloud上の秘密変数を参照しています。"
}
variable "git_username" {
type = string
description = "EC2内でgit操作を行うために必要なユーザー名。Terraform Cloud上の変数を参照しています。"
}
キーペアは秘密鍵を共有する必要があるので、よくわからない人は管理者に相談してください。
管理方法を知っている人は、キーペアをコンソールから手動で作成し、そのkey_nameを指定してください。作成した秘密鍵でssh接続できるようになります。
# variablesに実際に値を注入するためのファイル
# 各自自分の手元で必要な変数情報を注入してください
ec2 = {
instance_setting = {
display_name = "xxxxxxx" # EC2インスタンスの表示名を指定
key_name = "xxxxxxx" # EC2にssh接続するためのキーペア名の指定
security_groups = ["SocialBanditExpGroup"] # 今回は既に作成済みのセキュリティーグループ名を指定
instance_type = "t2.micro" # インスタンスタイプの指定
}
git_settings = {
organization_name = "xxxxx" # gitのOrganization名の指定
repository_name = "social-bandit-ex" # 各自のリポジトリ名を指定
}
}
# 公式Docのexample参照:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#example-usage
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
# 仮想化の型を指定
filter {
name = "virtualization-type"
values = ["hvm"] # 最近のインスタンスは大体これらしい
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
user_data_replace_on_change = true
ami = data.aws_ami.ubuntu.id
instance_type = var.ec2.instance_setting.instance_type
key_name = var.ec2.instance_setting.key_name
security_groups = var.ec2.instance_setting.security_groups
tags = {
Name = var.ec2.instance_setting.display_name
}
# ec2起動時に起動されるシェルスクリプトを指定
# ファイル内容を結合してuser_dataに渡す
user_data = <<-EOF
#!/bin/bash
# リポジトリをpull
git clone https://${var.git_username}:${var.git_personal_access_token}@github.com/${var.ec2.git_settings.organization_name}/${var.ec2.git_settings.repository_name}.git /home/ubuntu/${var.ec2.git_settings.repository_name}
# main.shのあるディレクトリに移動
cd /home/ubuntu/${var.ec2.git_settings.repository_name}
# main.shの修正が反映されたらこのコマンドは消去する
git checkout develop
# main.shに権限付与
chmod -x main.sh
# main.shを実行して実験のセットアップ実施
. main.sh
EOF
}
variable "ec2" {
type = object({
instance_setting = object({
display_name = string
key_name = string
security_groups = list(string)
instance_type = string
})
git_settings = object({
organization_name = string
repository_name = string
})
})
}
variable "git_personal_access_token" {
type = string
sensitive = true
description = "EC2内でgit操作を行うために必要な認証情報。Terraform Cloud上の秘密変数を参照しています。"
}
variable "git_username" {
type = string
description = "EC2内でgit操作を行うために必要なユーザー名。Terraform Cloud上の変数を参照しています。"
}
Terraform Cloudに接続できるようにする
リポジトリからTerraform Cloudに接続するための初期設定を行う
下記コマンドの実行は ".../terraform" 配下で実施してください。
正確には、mainn.tf が配置されているディレクトリで実行してください。
"$ terraform login" は一度実行すれば有効期限が切れるまで実行する必要はありません。
# Terraform Cloudにアクセスするための認証情報取得
# 詳細については後述の補足参照
$ terraform login
# 必要な依存関係の初期化を行う
$ terraform init
補足:terraform login
- コマンドの指示に従うとterraform Cloud Tokenを作成する画面に飛ばされるのでTokenを作成し、作成される文字列をコピー
- コピーした文字列をCLIに入力する
- loginが完了する
5. TerraformからAWSリソースを作成してみる
いくつか実行方法があります。
今回は2つの実行方法を紹介します。
実行方法1:指定ブランチにmergeする
特定のブランチに変更をpushした際に自動でplanが走るようにする で指定したブランチに、先ほど追加したファイルをpush、もしくはmergeしてpushします。
すると、Terraform Cloud上でPlanがトリガーされます。
Runの内容をクリックするとPlanとApplyの結果が確認できます。
Planの詳細は、トグル展開することで確認することができます。
Planの差分に問題がない場合は「Confirm & apply」> 「Confirm plan」の順にボタンを押下する
実行方法2:Terraform Cloudから実行する
Runsの右上にある、「New run」ボタンを押下し、Runの名前を入力して「Start」を押下するとPlanが開始されます。
リソース作成確認
Applyが完了していることが確認できたら、AWSコンソールにアクセスして、実際にリソースが作成されていることを確認しましょう!
6. Terraformで作成したリソースの削除
EC2インスタンスの場合、起動しているだけでお金がかかります。
停止すれば問題ないのですが、リソースが複数あり、いちいち停止するのが面倒な場合はTerraform Cloudからリソースの削除を行うことができます。
今回紹介するリソース削除はワークスペースで管理されている全てのリソースを削除します。
他のワークスペースで管理しているリソースを削除してしまうことはありませんが、必ず削除差分を確認するようにしましょう。
- WorkspaceのSettings > Destruction and Deletion ページに移動します。
- ワークスペース名を入力欄に記入し、「Queue destroy plan」を実行します。
- リソース作成時同様に、Apply作業を行う。
最後に
簡潔にまとめようと思っていましたが、かなり長くなってしまいました...。
AWSをTerraformで管理することができれば、インフラ構築時の無駄な作業が大幅に削減できると思います。
そうすれば、研究の本質的な部分によりフォーカスできるようになり、研究生活がより一層楽しいものになると思います。
さらに、AWSを操作する際の 「環境を破壊してしまったら元に戻せなくなるかもしれない...」 という恐怖感が減少し、心理的な負担も大きく減ることになると思います。
そして、管理が効率化されれば、今よりも大規模な実験ができるようになります。
そうなった際にはDocker化や負荷分散など、もっともっと効率化が必要になることになるかもしれません。
このようにして、効率化が更なる効率化を呼ぶ良い循環がこの記事から生まれたら嬉しく思います。
こんなに長い記事を最後まで読んでくださった方、本当にありがとう!!