-
Notifications
You must be signed in to change notification settings - Fork 107
Provision the Graviton 5 rustc-perf collector #1173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| terraform { | ||
| source = "../../../modules//rustc-perf-collector" | ||
| } | ||
|
|
||
| include { | ||
| path = find_in_parent_folders() | ||
| merge_strategy = "deep" | ||
| } | ||
|
|
||
| dependency "quota" { | ||
| config_path = "../ec2-quota" | ||
| } | ||
|
|
||
| inputs = { | ||
| # Reading an output creates a Terragrunt dependency on the separately | ||
| # applied quota request. The module also checks the account's *current* | ||
| # quota, so a requested-but-not-yet-approved increase cannot allocate the | ||
| # Dedicated Host and start billing it. | ||
| required_dedicated_hosts = dependency.quota.outputs.requested_value | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| terraform { | ||
| required_version = "~> 1.0" | ||
|
|
||
| required_providers { | ||
| aws = { | ||
| source = "hashicorp/aws" | ||
| version = "~> 6.27" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| locals { | ||
| // The project requested the M9g (Graviton 5) family on a Dedicated Host. | ||
| // Start with a 12xlarge partition: it provides 48 vCPUs and 192 GiB while | ||
| // using one of the eight 12xlarge slots listed for an empty M9g host, leaving | ||
| // capacity available for future M9g collectors. | ||
| instance_family = "m9g" | ||
| instance_type = "m9g.12xlarge" | ||
|
|
||
| // New instance families are not necessarily offered in every AZ. Select a | ||
| // stable AZ ID from AWS's live offerings instead of hard-coding an AZ name | ||
| // that can map differently between accounts. | ||
| availability_zone_id = try(sort(data.aws_ec2_instance_type_offerings.collector.locations)[0], null) | ||
| } | ||
|
|
||
| // Use Canonical's official arm64 image: Graviton cannot boot the repository's | ||
| // usual amd64 AMIs. most_recent is safe here because instance.tf ignores later | ||
| // AMI changes to avoid silently replacing a benchmark machine. | ||
| data "aws_ami" "ubuntu" { | ||
| most_recent = true | ||
| owners = ["099720109477"] # Canonical | ||
|
|
||
| filter { | ||
| name = "name" | ||
| values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-arm64-server-*"] | ||
| } | ||
|
|
||
| filter { | ||
| name = "architecture" | ||
| values = ["arm64"] | ||
| } | ||
|
|
||
| filter { | ||
| name = "virtualization-type" | ||
| values = ["hvm"] | ||
| } | ||
| } | ||
|
|
||
| data "aws_ec2_instance_type_offerings" "collector" { | ||
| filter { | ||
| name = "instance-type" | ||
| values = [local.instance_type] | ||
| } | ||
|
|
||
| location_type = "availability-zone-id" | ||
| } | ||
|
|
||
| // The quota request records the desired value; this data source reads the | ||
| // actually approved value used by the host-allocation precondition. | ||
| data "aws_servicequotas_service_quota" "m9g_hosts" { | ||
| service_code = "ec2" | ||
| quota_code = "L-9F9F275C" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // A Dedicated Host is an entire physical EC2 server allocated to this AWS | ||
| // account. Selecting the family rather than one instance type lets us divide | ||
| // its capacity among supported M9g sizes instead of fixing the host layout at | ||
| // allocation time. aws_ec2_host allocates an On-Demand host; it deliberately | ||
| // does not purchase a one- or three-year Dedicated Host Reservation because | ||
| // that is a separate, irreversible billing commitment with payment-term input. | ||
| resource "aws_ec2_host" "collector" { | ||
| availability_zone = aws_subnet.collector.availability_zone | ||
| instance_family = local.instance_family | ||
|
|
||
| // Require every instance to name this host explicitly. This prevents an | ||
| // unrelated M9g launch in the account from silently consuming benchmark | ||
| // capacity through EC2 auto-placement. | ||
| auto_placement = "off" | ||
|
|
||
| // M9g supports Dedicated Host recovery, so EC2 can allocate replacement | ||
| // hardware after supported power or network failures. It does not cover | ||
| // every failure mode (notably scheduled host retirement), which still needs | ||
| // operator action. | ||
| host_recovery = "on" | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-graviton5" | ||
| Environment = "prod" | ||
| Service = "rustc-perf" | ||
| } | ||
|
|
||
| lifecycle { | ||
| precondition { | ||
| condition = local.availability_zone_id != null | ||
| error_message = "${local.instance_type} is not offered in this AWS region." | ||
| } | ||
|
|
||
| // A quota request can exist in state while AWS is still reviewing it. | ||
| // Gate host allocation on the applied quota because billing begins when | ||
| // the Dedicated Host is allocated, even if it contains no instances. | ||
| precondition { | ||
| condition = data.aws_servicequotas_service_quota.m9g_hosts.value >= var.required_dedicated_hosts | ||
| error_message = "The Running Dedicated m9g Hosts quota increase must be approved before allocating the host." | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // The instance role is intentionally limited to Systems Manager. Workload | ||
| // access to rustc-perf storage or secrets should be added separately and with | ||
| // narrower policies once the collector is enrolled. | ||
| resource "aws_iam_role" "collector" { | ||
| name = "rustc-perf-collector" | ||
|
|
||
| assume_role_policy = jsonencode({ | ||
| Version = "2012-10-17" | ||
| Statement = [ | ||
| { | ||
| Effect = "Allow" | ||
| Action = "sts:AssumeRole" | ||
| Principal = { | ||
| Service = "ec2.amazonaws.com" | ||
| } | ||
| } | ||
| ] | ||
| }) | ||
| } | ||
|
|
||
| resource "aws_iam_role_policy_attachment" "ssm" { | ||
| // This managed policy lets the *agent* establish its outbound SSM control | ||
| // channel. Human permission to start a session comes from Identity Center. | ||
| role = aws_iam_role.collector.name | ||
| policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" | ||
| } | ||
|
|
||
| // EC2 receives IAM roles through an instance-profile wrapper rather than by | ||
| // attaching aws_iam_role directly to aws_instance. | ||
| resource "aws_iam_instance_profile" "collector" { | ||
| name = "rustc-perf-collector" | ||
| role = aws_iam_role.collector.name | ||
| } | ||
|
|
||
| // SSM documents are account-and-region-local configuration consumed by | ||
| // Systems Manager. This one defines the default interactive shell used by | ||
| // `aws ssm start-session`; it is not a startup script for the instance. | ||
| // Managing it here makes the no-SSH access path available without a manual | ||
| // console step, runs operators as Ubuntu's normal sudo-capable user, and puts | ||
| // finite idle and total limits on forgotten sessions. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can try SSM as the access mechanism to start, but we should think about whether just exposing ssh on port 22 to the bastion is reasonable. We do that for other instances we manage and it feels fairly reasonable to me. |
||
| resource "aws_ssm_document" "session_preferences" { | ||
| name = "SSM-SessionManagerRunShell" | ||
| document_format = "JSON" | ||
| document_type = "Session" | ||
|
|
||
| content = jsonencode({ | ||
| schemaVersion = "1.0" | ||
| description = "Regional Session Manager settings for rustc-perf-prod" | ||
| sessionType = "Standard_Stream" | ||
| inputs = { | ||
| // There is no account-local log destination yet, so session contents are | ||
| // not streamed. Session API activity is still captured by CloudTrail. | ||
| s3BucketName = "" | ||
| s3KeyPrefix = "" | ||
| s3EncryptionEnabled = true | ||
| cloudWatchLogGroupName = "" | ||
| cloudWatchEncryptionEnabled = true | ||
| cloudWatchStreamingEnabled = false | ||
| kmsKeyId = "" | ||
| runAsEnabled = true | ||
| runAsDefaultUser = "ubuntu" | ||
| idleSessionTimeout = "20" | ||
| maxSessionDuration = "240" | ||
| shellProfile = { | ||
| windows = "" | ||
| linux = "cd /home/ubuntu" | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| resource "aws_instance" "collector" { | ||
| ami = data.aws_ami.ubuntu.id | ||
| instance_type = local.instance_type | ||
| // host tenancy plus the explicit host ID places this instance on the | ||
| // account's M9g physical server instead of the normal shared EC2 fleet. | ||
| host_id = aws_ec2_host.collector.id | ||
| tenancy = "host" | ||
| subnet_id = aws_subnet.collector.id | ||
| vpc_security_group_ids = [aws_security_group.collector.id] | ||
| // The public address is only for outbound package, toolchain, and benchmark | ||
| // downloads. network.tf defines no ingress rules; administration goes | ||
| // through SSM. This avoids a continuously billed NAT gateway for one host. | ||
| associate_public_ip_address = true | ||
| iam_instance_profile = aws_iam_instance_profile.collector.name | ||
|
|
||
| // Accidental deletion would discard a calibrated benchmark environment. A | ||
| // guest shutdown stops the partition rather than terminating its EBS volume; | ||
| // it does not stop Dedicated Host billing while the physical host is | ||
| // allocated. Detailed EC2 monitoring is unrelated to PMU counters. | ||
| disable_api_termination = true | ||
| ebs_optimized = true | ||
| instance_initiated_shutdown_behavior = "stop" | ||
| monitoring = false | ||
|
|
||
| // EC2 gives this script to cloud-init on the first boot. It installs the | ||
| // native perf tooling, enables unprivileged PMU access, starts the SSM agent, | ||
| // and runs a counter smoke test. It is bootstrap configuration, not a script | ||
| // rerun by every Terraform apply. | ||
| user_data = file("${path.module}/user-data.sh") | ||
|
|
||
| // Require IMDSv2 and keep its packets local to the host, reducing the chance | ||
| // that a benchmark process can accidentally expose instance-role credentials. | ||
| metadata_options { | ||
| http_endpoint = "enabled" | ||
| http_protocol_ipv6 = "disabled" | ||
| http_put_response_hop_limit = 1 | ||
| http_tokens = "required" | ||
| instance_metadata_tags = "disabled" | ||
| } | ||
|
|
||
| // M9g is EBS-only. Provisioned gp3 performance reduces storage variance | ||
| // during compilation, while 500 GiB leaves room for toolchains, sources, and | ||
| // build artifacts. Encryption protects the persistent volume when stopped. | ||
| root_block_device { | ||
| delete_on_termination = true | ||
| encrypted = true | ||
| iops = 12000 | ||
| throughput = 500 | ||
| volume_size = 500 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 12000 IOPS seems very much excessive to me. I would keep this at defaults personally unless we encounter issues. Most of the perf workload should be able to fit in memory. 500 gb is probably fine (I doubt we need that much though...). |
||
| volume_type = "gp3" | ||
| } | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-graviton5" | ||
| Environment = "prod" | ||
| Service = "rustc-perf" | ||
| } | ||
|
|
||
| lifecycle { | ||
| # Do not replace the collector just because Canonical published a new AMI. | ||
| ignore_changes = [ami] | ||
| } | ||
|
|
||
| // Ensure the documented SSM access method is configured before EC2 can | ||
| // register as a managed node and an operator attempts the first session. | ||
| depends_on = [aws_ssm_document.session_preferences] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // This service needs one outbound-only host, so a small dedicated VPC is easier | ||
| // to reason about than adopting a default VPC or deploying the multi-AZ/NAT | ||
| // topology used by web services. | ||
| resource "aws_vpc" "collector" { | ||
| cidr_block = "10.0.0.0/24" | ||
| enable_dns_hostnames = true | ||
| enable_dns_support = true | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-prod" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_internet_gateway" "collector" { | ||
| vpc_id = aws_vpc.collector.id | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-prod" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_subnet" "collector" { | ||
| availability_zone_id = local.availability_zone_id | ||
| cidr_block = "10.0.0.0/26" | ||
| map_public_ip_on_launch = true | ||
| vpc_id = aws_vpc.collector.id | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-prod" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_route_table" "collector" { | ||
| vpc_id = aws_vpc.collector.id | ||
|
|
||
| route { | ||
| cidr_block = "0.0.0.0/0" | ||
| gateway_id = aws_internet_gateway.collector.id | ||
| } | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-prod" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_route_table_association" "collector" { | ||
| route_table_id = aws_route_table.collector.id | ||
| subnet_id = aws_subnet.collector.id | ||
| } | ||
|
|
||
| // Deliberately define no ingress rules. SSM establishes its management channel | ||
| // outbound, so neither SSH nor a bastion needs to be exposed to the internet. | ||
| resource "aws_security_group" "collector" { | ||
| name = "rustc-perf-collector" | ||
| description = "No-ingress security group for the rustc-perf collector" | ||
| vpc_id = aws_vpc.collector.id | ||
|
|
||
| tags = { | ||
| Name = "rustc-perf-collector" | ||
| } | ||
| } | ||
|
|
||
| resource "aws_vpc_security_group_egress_rule" "collector" { | ||
| security_group_id = aws_security_group.collector.id | ||
| cidr_ipv4 = "0.0.0.0/0" | ||
| ip_protocol = "-1" | ||
| description = "Allow the collector to fetch toolchains and benchmark sources" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| output "instance_id" { | ||
| description = "EC2 instance ID of the Graviton 5 collector" | ||
| value = aws_instance.collector.id | ||
| } | ||
|
|
||
| output "dedicated_host_id" { | ||
| description = "EC2 Dedicated Host containing the collector" | ||
| value = aws_ec2_host.collector.id | ||
| } | ||
|
|
||
| output "instance_type" { | ||
| description = "EC2 instance type of the collector" | ||
| value = aws_instance.collector.instance_type | ||
| } | ||
|
|
||
| output "availability_zone" { | ||
| description = "Availability Zone selected for the collector" | ||
| value = aws_instance.collector.availability_zone | ||
| } | ||
|
|
||
| output "perf_check_marker" { | ||
| description = "File created by cloud-init after hardware counters pass their smoke test" | ||
| value = "/var/lib/rustc-perf/perf-counters-ready" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we switch to 26.04 from the start?