ゲームソリューション部の えがわ です。
本日はJenkinsの実行環境をTerraformで構築してみました。
構成
リソース
コンピューティングリソースはECS on EC2
、ストレージはAmazon FSx for NetApp ONTAP(以降FSxN)
を使用します。
Fargate
ではFSxNをマウントすることができないためEC2を選択しています。
※2024/04/05現在
構成図
EC2はパブリックサブネット、FSxNはプライベートサブネットに配置します。
FSxNとは
高いパフォーマンスと豊富な機能を提供するフルマネージドなストレージサービスです。
Amazon FSx for NetApp ONTAP は、 NetAppの人気のある ONTAP ファイルシステム上に構築された、信頼性が高く、スケーラブルで、パフォーマンスが高く、機能豊富なファイルストレージを提供するフルマネージドサービスです。
FSx for ONTAP は、 NetApp ファイルシステムの使い慣れた機能、パフォーマンス、機能、および API オペレーションと、フルマネージド型の の俊敏性、スケーラビリティ、およびシンプルさを組み合わせますAWS のサービス。
弊社ブログにも記事が豊富にありますので興味がある方はご確認ください。
構築してみる
構築する順番として以下の順で行います。
- VPC
- FSxN
- ECS on EC2
Terraformファイルは他で再利用する可能性があるため分割しています。
VPCの作成
TerraformでVPCを作成します。
リポジトリはこちら
変数ファイルはそのままでも使用できますが、お好きなように修正してください。
/terraform.tfvars
project_prefix = "hyper_project"
region = "us-east-1"
vpc_cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.3.0/24", "10.0.4.0/24"]
Terraformを実行しVPCを作成します。
terraform init
terraform plan
terraform apply
作成したVPCのID、サブネットのIDは以下のコマンドで取得できます。
# VPC IDの取得
aws ec2 describe-vpcs \
--query 'Vpcs[*].{VpcId:VpcId,Name:Tags[?Key==`Name`]|[0].Value}'\
--output text
# サブネットIDの取得
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values={任意のVPC ID}" \
--query 'Subnets[*].{SubnetId:SubnetId,Name:Tags[?Key==`Name`]|[0].Value}' \
--output text
後で使用しますのでIDを保存しておきます。
FSxNの構築
TerraformでFSxNを作成します。
リポジトリはこちら
/modules/fsxn/main.tf
resource "aws_security_group" "nfs_sg" {
name = "${var.project_prefix}-nfs-sg"
vpc_id = var.vpc_id
ingress {
from_port = 2049
to_port = 2049
protocol = "tcp"
cidr_blocks = [var.vpc_cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_fsx_ontap_file_system" "file_system" {
storage_capacity = 1024 # 最小構成
subnet_ids = [var.subnet_id]
preferred_subnet_id = var.subnet_id
deployment_type = "SINGLE_AZ_1"
throughput_capacity = 128 # 最小構成
security_group_ids = [aws_security_group.nfs_sg.id]
fsx_admin_password = var.fsx_admin_password
tags = {
Name = "${var.project_prefix}-fsxn"
}
timeouts {
create = "60m"
update = "60m"
}
}
resource "aws_fsx_ontap_storage_virtual_machine" "svm" {
file_system_id = aws_fsx_ontap_file_system.file_system.id
name = "${var.project_prefix}-svm"
}
resource "aws_fsx_ontap_volume" "volume" {
name = "vol"
junction_path = "/vol1"
size_in_megabytes = 1048576 # ボリュームのサイズを指定
storage_efficiency_enabled = true
storage_virtual_machine_id = aws_fsx_ontap_storage_virtual_machine.svm.id
lifecycle {
ignore_changes = [
tiering_policy[0].cooling_period
]
}
}
変数ファイルはコミットされていません。
以下のファイルを作成し適切な値を設定してください。
/terraform.tfvars
project_prefix = "hyper_project"
region = "us-east-1"
vpc_cidr_block = "10.0.0.0/16"
vpc_id = "vpc-xxxxxxxxxxxxxxx"
subnet_id = "subnet-xxxxxxxxxxxxxxx" # プライベートサブネットを選択
fsx_admin_password = "英数文字混合8文字以上"
Terraformを実行しFSxNを作成します。
terraform init
terraform plan
terraform apply
こちらでもありますが、作成には30分ほどかかります。
module.fsxn.aws_fsx_ontap_file_system.file_system: Creating...
module.fsxn.aws_fsx_ontap_file_system.file_system: Still creating... [10s elapsed]
module.fsxn.aws_fsx_ontap_file_system.file_system: Still creating... [20s elapsed]
...中略
module.fsxn.aws_fsx_ontap_file_system.file_system: Still creating... [24m10s elapsed]
module.fsxn.aws_fsx_ontap_file_system.file_system: Still creating... [24m20s elapsed]
module.fsxn.aws_fsx_ontap_file_system.file_system: Creation complete after 24m24s [id=fs-xxxxxxxxxxxxxx]
ECS on EC2を作成
リポジトリはこちら
/modules/ecs/main.tf
resource "aws_ecs_cluster" "this" {
name = "${var.project_prefix}-cluster"
}
resource "aws_security_group" "ecs_sg" {
name = "${var.project_prefix}-ecs-sg"
vpc_id = var.vpc_id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
data "aws_ssm_parameter" "amzn_ami" {
name = "/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id"
}
resource "aws_launch_template" "ecs_ec2" {
name = "${var.project_prefix}-ecs-ec2-template"
image_id = data.aws_ssm_parameter.amzn_ami.value
instance_type = var.ecs_instance_type
vpc_security_group_ids = [aws_security_group.ecs_sg.id]
iam_instance_profile { arn = var.instance_profile_arn }
monitoring { enabled = true }
user_data = base64encode(<<-EOF
#!/bin/bash
echo ECS_CLUSTER=${aws_ecs_cluster.this.name} >> /etc/ecs/ecs.config;
mkdir -p ${var.source_path}
echo -e "${var.nfs_dns_name}:/${var.junction_path}\t${var.source_path}\tnfs\tnfsvers=4,_netdev,noresvport,defaults\t0\t0" | tee -a /etc/fstab
systemctl daemon-reload
mount -a
chmod 775 ${var.source_path}
chown 1000:1000 ${var.source_path}
EOF
)
}
resource "aws_autoscaling_group" "ecs" {
name = "${var.project_prefix}-ecs-asg"
vpc_zone_identifier = var.subnet_ids
health_check_grace_period = 0
health_check_type = "EC2"
protect_from_scale_in = false
desired_capacity = 1
min_size = 1
max_size = 1
launch_template {
id = aws_launch_template.ecs_ec2.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project_prefix}-ecs-instance"
propagate_at_launch = true
}
}
resource "aws_ecs_capacity_provider" "this" {
name = "${var.project_prefix}-ecs-ec2-cp"
auto_scaling_group_provider {
auto_scaling_group_arn = aws_autoscaling_group.ecs.arn
managed_termination_protection = "DISABLED"
managed_scaling {
maximum_scaling_step_size = 1
minimum_scaling_step_size = 1
status = "ENABLED"
target_capacity = 100
}
}
}
resource "aws_ecs_cluster_capacity_providers" "main" {
cluster_name = aws_ecs_cluster.this.name
capacity_providers = [aws_ecs_capacity_provider.this.name]
default_capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.this.name
base = 1
weight = 100
}
}
resource "aws_cloudwatch_log_group" "jenkins" {
name = "${var.project_prefix}-log"
retention_in_days = 14
}
resource "aws_ecs_task_definition" "jenkins" {
family = "${var.project_prefix}-taskdef"
network_mode = "bridge"
cpu = 512
memory = 512
execution_role_arn = var.exec_role_arn
task_role_arn = var.task_role_arn
volume {
name = var.source_volume
host_path = var.source_path
}
container_definitions = jsonencode([
{
name = "jenkins"
image = "jenkins/jenkins:lts"
portMappings = [
{
containerPort = 8080
hostPort = 8080
},
{
containerPort = 50000
hostPort = 50000
}
]
environment = [
{
name = "JAVA_OPTS"
value = "-Duser.timezone=Asia/Tokyo -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Xmx1524m -Xms256m"
}
]
mountPoints = [
{
sourceVolume = var.source_volume
containerPath = var.container_path
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.jenkins.name
"awslogs-region" = var.region
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "jenkins" {
name = "${var.project_prefix}-ecs-service"
cluster = aws_ecs_cluster.this.id
task_definition = aws_ecs_task_definition.jenkins.arn
desired_count = 1
enable_execute_command = true
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.this.name
base = 1
weight = 100
}
ordered_placement_strategy {
type = "spread"
field = "attribute:ecs.availability-zone"
}
lifecycle {
ignore_changes = [desired_count]
}
}
こちらも変数ファイルはリポジトリに含まれていません。
以下のファイルを作成し適切な値を設定してください。
/terraform.tfvars
project_prefix = "hyper_project"
region = "us-east-1"
vpc_cidr_block = "10.0.0.0/16"
vpc_id = "vpc-xxxxxxxxxxxxxxxx"
subnet_ids = ["subnet-xxxxxxxxxxxxxxxx", "subnet-xxxxxxxxxxxxxxxx"]
ecs_instance_type = "t3.medium"
source_volume = "fsxn-vol1"
source_path = "/mnt/fsxn/vol1"
container_path = "/var/jenkins_home"
junction_path = "vol1"
nfs_dns_name = "svm-xxxxxxxxxxxxxxxx.fs-xxxxxxxxxxxxxxxx.fsx.us-east-1.amazonaws.com"
nfs_dns_name
はマネジメントコンソールから確認できます。
Terraformを実行しECS on EC2環境を作成します。
terraform init
terraform plan
terraform apply
Jenkinsにアクセス
EC2のパブリックIPアドレスを取得します。
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--query 'Reservations[*].Instances[*].[Tags[?Key==`Name`].Value|[0],PublicIpAddress]' \
--output text
ブラウザでアクセスしてみます。
http://{取得したパブリックIP}:8080/
Jenkinsの初期画面が表示されました。
初期パスワードに関しては起動したEC2にSessionManagerで接続し、以下のコマンドで取得できます
sudo cat /mnt/fsxn/vol1/secrets/initialAdminPassword
Jenkinsが利用可能になりました。
最後に
Jenkinsの実行環境をTerraformで構築する過程を紹介しました。
ECSとFSxNを組み合わせることで、高パフォーマンスかつ機能豊富なストレージを活用できるJenkinsの実行環境を構築できます。
本記事がどなたかの参考になれば幸いです。