JenkinsをTerraformで構築してみた(ECS on EC2 + Amazon FSx for NetApp ONTAP)
ゲームソリューション部の えがわ です。
本日は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を作成します。
リポジトリはこちら
変数ファイルはそのままでも使用できますが、お好きなように修正してください。
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を作成します。
リポジトリはこちら
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 ] } }
変数ファイルはコミットされていません。
以下のファイルを作成し適切な値を設定してください。
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を作成
リポジトリはこちら
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] } }
こちらも変数ファイルはリポジトリに含まれていません。
以下のファイルを作成し適切な値を設定してください。
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の実行環境を構築できます。
本記事がどなたかの参考になれば幸いです。