commonware_deployer/ec2/
create.rs

1//! `create` subcommand for `ec2`
2
3use crate::ec2::{
4    aws::*, deployer_directory, services::*, utils::*, Config, Error, Host, Hosts, InstanceConfig,
5    CREATED_FILE_NAME, LOGS_PORT, MONITORING_NAME, MONITORING_REGION, PROFILES_PORT, TRACES_PORT,
6};
7use futures::future::try_join_all;
8use std::{
9    collections::{BTreeSet, HashMap, HashSet},
10    fs::File,
11    net::IpAddr,
12    path::PathBuf,
13    slice,
14};
15use tokio::process::Command;
16use tracing::info;
17
18/// Represents a deployed instance with its configuration and public IP
19#[derive(Clone)]
20pub struct Deployment {
21    pub instance: InstanceConfig,
22    pub id: String,
23    pub ip: String,
24}
25
26/// Represents AWS resources created in a specific region
27pub struct RegionResources {
28    pub vpc_id: String,
29    pub vpc_cidr: String,
30    pub route_table_id: String,
31    pub subnet_id: String,
32    pub binary_sg_id: Option<String>,
33    pub monitoring_sg_id: Option<String>,
34}
35
36/// Sets up EC2 instances, deploys files, and configures monitoring and logging
37pub async fn create(config: &PathBuf) -> Result<(), Error> {
38    // Load configuration from YAML file
39    let config: Config = {
40        let config_file = File::open(config)?;
41        serde_yaml::from_reader(config_file)?
42    };
43    let tag = &config.tag;
44    info!(tag = tag.as_str(), "loaded configuration");
45
46    // Create a temporary directory for local files
47    let tag_directory = deployer_directory(tag);
48    if tag_directory.exists() {
49        return Err(Error::CreationAttempted);
50    }
51    std::fs::create_dir_all(&tag_directory)?;
52    info!(path = ?tag_directory, "created tag directory");
53
54    // Ensure no instance is duplicated or named MONITORING_NAME
55    let mut instance_names = HashSet::new();
56    for instance in &config.instances {
57        if instance_names.contains(&instance.name) || instance.name == MONITORING_NAME {
58            return Err(Error::InvalidInstanceName(instance.name.clone()));
59        }
60        instance_names.insert(instance.name.clone());
61    }
62
63    // Get public IP address of the deployer
64    let deployer_ip = get_public_ip().await?;
65    info!(ip = deployer_ip.as_str(), "recovered public IP");
66
67    // Generate SSH key pair
68    let key_name = format!("deployer-{tag}");
69    let private_key_path = tag_directory.join(format!("id_rsa_{tag}"));
70    let public_key_path = tag_directory.join(format!("id_rsa_{tag}.pub"));
71    let output = Command::new("ssh-keygen")
72        .arg("-t")
73        .arg("rsa")
74        .arg("-b")
75        .arg("4096")
76        .arg("-f")
77        .arg(private_key_path.to_str().unwrap())
78        .arg("-N")
79        .arg("")
80        .output()
81        .await?;
82    if !output.status.success() {
83        return Err(Error::KeygenFailed);
84    }
85    let public_key = std::fs::read_to_string(&public_key_path)?;
86    let private_key = private_key_path.to_str().unwrap();
87
88    // Determine unique regions
89    let mut regions: BTreeSet<String> = config.instances.iter().map(|i| i.region.clone()).collect();
90    regions.insert(MONITORING_REGION.to_string());
91
92    // Determine instance types by region
93    let mut instance_types_by_region: HashMap<String, HashSet<String>> = HashMap::new();
94    for instance in &config.instances {
95        instance_types_by_region
96            .entry(instance.region.clone())
97            .or_default()
98            .insert(instance.instance_type.clone());
99    }
100    instance_types_by_region
101        .entry(MONITORING_REGION.to_string())
102        .or_default()
103        .insert(config.monitoring.instance_type.clone());
104
105    // Initialize resources for each region
106    info!(?regions, "initializing resources");
107    let mut vpc_cidrs = HashMap::new();
108    let mut subnet_cidrs = HashMap::new();
109    let mut ec2_clients = HashMap::new();
110    let mut region_resources = HashMap::new();
111    for (idx, region) in regions.iter().enumerate() {
112        // Create client for region
113        let ec2_client = create_ec2_client(Region::new(region.clone())).await;
114        ec2_clients.insert(region.clone(), ec2_client);
115        info!(region = region.as_str(), "created EC2 client");
116
117        // Assert all instance types are ARM-based
118        let instance_types: Vec<String> =
119            instance_types_by_region[region].iter().cloned().collect();
120        assert_arm64_support(&ec2_clients[region], &instance_types).await?;
121
122        // Find availability zone that supports all instance types
123        let az = find_availability_zone(&ec2_clients[region], &instance_types).await?;
124        info!(
125            az = az.as_str(),
126            region = region.as_str(),
127            "selected availability zone"
128        );
129
130        // Create VPC, IGW, route table, subnet, security groups, and key pair
131        let vpc_cidr = format!("10.{idx}.0.0/16");
132        vpc_cidrs.insert(region.clone(), vpc_cidr.clone());
133        let vpc_id = create_vpc(&ec2_clients[region], &vpc_cidr, tag).await?;
134        info!(
135            vpc = vpc_id.as_str(),
136            region = region.as_str(),
137            "created VPC"
138        );
139        let igw_id = create_and_attach_igw(&ec2_clients[region], &vpc_id, tag).await?;
140        info!(
141            igw = igw_id.as_str(),
142            vpc = vpc_id.as_str(),
143            region = region.as_str(),
144            "created and attached IGW"
145        );
146        let route_table_id =
147            create_route_table(&ec2_clients[region], &vpc_id, &igw_id, tag).await?;
148        info!(
149            route_table = route_table_id.as_str(),
150            vpc = vpc_id.as_str(),
151            region = region.as_str(),
152            "created route table"
153        );
154        let subnet_cidr = format!("10.{idx}.1.0/24");
155        subnet_cidrs.insert(region.clone(), subnet_cidr.clone());
156        let subnet_id = create_subnet(
157            &ec2_clients[region],
158            &vpc_id,
159            &route_table_id,
160            &subnet_cidr,
161            &az,
162            tag,
163        )
164        .await?;
165        info!(
166            subnet = subnet_id.as_str(),
167            vpc = vpc_id.as_str(),
168            region = region.as_str(),
169            "created subnet"
170        );
171
172        // Create monitoring security group in monitoring region
173        let monitoring_sg_id = if *region == MONITORING_REGION {
174            let sg_id =
175                create_security_group_monitoring(&ec2_clients[region], &vpc_id, &deployer_ip, tag)
176                    .await?;
177            info!(
178                sg = sg_id.as_str(),
179                vpc = vpc_id.as_str(),
180                region = region.as_str(),
181                "created monitoring security group"
182            );
183            Some(sg_id)
184        } else {
185            None
186        };
187
188        // Import key pair
189        import_key_pair(&ec2_clients[region], &key_name, &public_key).await?;
190        info!(
191            key = key_name.as_str(),
192            region = region.as_str(),
193            "imported key pair"
194        );
195
196        // Store resources for region
197        info!(
198            vpc = vpc_id.as_str(),
199            subnet = subnet_id.as_str(),
200            subnet_cidr = subnet_cidr.as_str(),
201            region = region.as_str(),
202            "initialized resources"
203        );
204        region_resources.insert(
205            region.clone(),
206            RegionResources {
207                vpc_id,
208                vpc_cidr: vpc_cidr.clone(),
209                route_table_id,
210                subnet_id,
211                binary_sg_id: None,
212                monitoring_sg_id,
213            },
214        );
215    }
216    info!(?regions, "initialized resources");
217
218    // Setup VPC peering connections
219    info!("initializing VPC peering connections");
220    let monitoring_region = MONITORING_REGION.to_string();
221    let monitoring_resources = region_resources.get(&monitoring_region).unwrap();
222    let monitoring_vpc_id = &monitoring_resources.vpc_id;
223    let monitoring_cidr = &monitoring_resources.vpc_cidr;
224    let binary_regions: HashSet<String> =
225        config.instances.iter().map(|i| i.region.clone()).collect();
226    for region in &regions {
227        if region != &monitoring_region && binary_regions.contains(region) {
228            let binary_resources = region_resources.get(region).unwrap();
229            let binary_vpc_id = &binary_resources.vpc_id;
230            let binary_cidr = &binary_resources.vpc_cidr;
231            let peer_id = create_vpc_peering_connection(
232                &ec2_clients[&monitoring_region],
233                monitoring_vpc_id,
234                binary_vpc_id,
235                region,
236                tag,
237            )
238            .await?;
239            info!(
240                peer = peer_id.as_str(),
241                monitoring = monitoring_vpc_id.as_str(),
242                binary = binary_vpc_id.as_str(),
243                region = region.as_str(),
244                "created VPC peering connection"
245            );
246            wait_for_vpc_peering_connection(&ec2_clients[region], &peer_id).await?;
247            info!(
248                peer = peer_id.as_str(),
249                region = region.as_str(),
250                "VPC peering connection is available"
251            );
252            accept_vpc_peering_connection(&ec2_clients[region], &peer_id).await?;
253            info!(
254                peer = peer_id.as_str(),
255                region = region.as_str(),
256                "accepted VPC peering connection"
257            );
258            add_route(
259                &ec2_clients[&monitoring_region],
260                &monitoring_resources.route_table_id,
261                binary_cidr,
262                &peer_id,
263            )
264            .await?;
265            add_route(
266                &ec2_clients[region],
267                &binary_resources.route_table_id,
268                monitoring_cidr,
269                &peer_id,
270            )
271            .await?;
272            info!(
273                peer = peer_id.as_str(),
274                monitoring = monitoring_vpc_id.as_str(),
275                binary = binary_vpc_id.as_str(),
276                region = region.as_str(),
277                "added routes for VPC peering connection"
278            );
279        }
280    }
281    info!("initialized VPC peering connections");
282
283    // Launch monitoring instance
284    info!("launching monitoring instance");
285    let monitoring_instance_id;
286    let monitoring_ip;
287    let monitoring_private_ip;
288    let monitoring_sg_id;
289    {
290        let monitoring_ec2_client = &ec2_clients[&monitoring_region];
291        let ami_id = find_latest_ami(monitoring_ec2_client).await?;
292        let monitoring_instance_type = InstanceType::try_parse(&config.monitoring.instance_type)
293            .expect("Invalid instance type");
294        let monitoring_storage_class =
295            VolumeType::try_parse(&config.monitoring.storage_class).expect("Invalid storage class");
296        monitoring_sg_id = monitoring_resources
297            .monitoring_sg_id
298            .as_ref()
299            .unwrap()
300            .clone();
301        monitoring_instance_id = launch_instances(
302            monitoring_ec2_client,
303            &ami_id,
304            monitoring_instance_type,
305            config.monitoring.storage_size,
306            monitoring_storage_class,
307            &key_name,
308            &monitoring_resources.subnet_id,
309            &monitoring_sg_id,
310            1,
311            MONITORING_NAME,
312            tag,
313        )
314        .await?[0]
315            .clone();
316        monitoring_ip = wait_for_instances_running(
317            monitoring_ec2_client,
318            slice::from_ref(&monitoring_instance_id),
319        )
320        .await?[0]
321            .clone();
322        monitoring_private_ip =
323            get_private_ip(monitoring_ec2_client, &monitoring_instance_id).await?;
324    }
325    info!(ip = monitoring_ip.as_str(), "launched monitoring instance");
326
327    // Create binary security groups
328    info!("creating security groups");
329    for (region, resources) in region_resources.iter_mut() {
330        let binary_sg_id = create_security_group_binary(
331            &ec2_clients[region],
332            &resources.vpc_id,
333            &deployer_ip,
334            &monitoring_ip,
335            tag,
336            &config.ports,
337        )
338        .await?;
339        info!(
340            sg = binary_sg_id.as_str(),
341            vpc = resources.vpc_id.as_str(),
342            region = region.as_str(),
343            "created binary security group"
344        );
345        resources.binary_sg_id = Some(binary_sg_id);
346    }
347    info!("created security groups");
348
349    // Launch binary instances
350    info!("launching binary instances");
351    let mut launch_futures = Vec::new();
352    for instance in &config.instances {
353        let key_name = key_name.clone();
354        let region = instance.region.clone();
355        let resources = region_resources.get(&region).unwrap();
356        let ec2_client = ec2_clients.get(&region).unwrap();
357        let ami_id = find_latest_ami(ec2_client).await?;
358        let instance_type =
359            InstanceType::try_parse(&instance.instance_type).expect("Invalid instance type");
360        let storage_class =
361            VolumeType::try_parse(&instance.storage_class).expect("Invalid storage class");
362        let binary_sg_id = resources.binary_sg_id.as_ref().unwrap();
363        let tag = tag.clone();
364        let future = async move {
365            let instance_id = launch_instances(
366                ec2_client,
367                &ami_id,
368                instance_type,
369                instance.storage_size,
370                storage_class,
371                &key_name,
372                &resources.subnet_id,
373                binary_sg_id,
374                1,
375                &instance.name,
376                &tag,
377            )
378            .await?[0]
379                .clone();
380            let ip = wait_for_instances_running(ec2_client, slice::from_ref(&instance_id)).await?
381                [0]
382            .clone();
383            info!(
384                ip = ip.as_str(),
385                instance = instance.name.as_str(),
386                "launched instance"
387            );
388            Ok::<Deployment, Error>(Deployment {
389                instance: instance.clone(),
390                id: instance_id,
391                ip,
392            })
393        };
394        launch_futures.push(future);
395    }
396    let deployments: Vec<Deployment> = try_join_all(launch_futures).await?;
397    info!("launched binary instances");
398
399    // Write systemd service files
400    let prometheus_service_path = tag_directory.join("prometheus.service");
401    std::fs::write(&prometheus_service_path, PROMETHEUS_SERVICE)?;
402    let loki_service_path = tag_directory.join("loki.service");
403    std::fs::write(&loki_service_path, LOKI_SERVICE)?;
404    let pyroscope_service_path = tag_directory.join("pyroscope.service");
405    std::fs::write(&pyroscope_service_path, PYROSCOPE_SERVICE)?;
406    let tempo_service_path = tag_directory.join("tempo.service");
407    std::fs::write(&tempo_service_path, TEMPO_SERVICE)?;
408    let promtail_service_path = tag_directory.join("promtail.service");
409    std::fs::write(&promtail_service_path, PROMTAIL_SERVICE)?;
410    let node_exporter_service_path = tag_directory.join("node_exporter.service");
411    std::fs::write(&node_exporter_service_path, NODE_EXPORTER_SERVICE)?;
412    let pyroscope_agent_service_path = tag_directory.join("pyroscope-agent.service");
413    std::fs::write(&pyroscope_agent_service_path, PYROSCOPE_AGENT_SERVICE)?;
414    let pyroscope_agent_timer_path = tag_directory.join("pyroscope-agent.timer");
415    std::fs::write(&pyroscope_agent_timer_path, PYROSCOPE_AGENT_TIMER)?;
416    let binary_service_path = tag_directory.join("binary.service");
417    std::fs::write(&binary_service_path, BINARY_SERVICE)?;
418
419    // Write logrotate configuration file
420    let logrotate_conf_path = tag_directory.join("logrotate.conf");
421    std::fs::write(&logrotate_conf_path, LOGROTATE_CONF)?;
422
423    // Add BBR configuration file
424    let bbr_conf_path = tag_directory.join("99-bbr.conf");
425    std::fs::write(&bbr_conf_path, BBR_CONF)?;
426
427    // Configure monitoring instance
428    info!("configuring monitoring instance");
429    wait_for_instances_ready(&ec2_clients[&monitoring_region], &[monitoring_instance_id]).await?;
430    let instances: Vec<(&str, &str, &str)> = deployments
431        .iter()
432        .map(|d| {
433            (
434                d.instance.name.as_str(),
435                d.ip.as_str(),
436                d.instance.region.as_str(),
437            )
438        })
439        .collect();
440    let prom_config = generate_prometheus_config(&instances);
441    let prom_path = tag_directory.join("prometheus.yml");
442    std::fs::write(&prom_path, prom_config)?;
443    let datasources_path = tag_directory.join("datasources.yml");
444    std::fs::write(&datasources_path, DATASOURCES_YML)?;
445    let all_yaml_path = tag_directory.join("all.yml");
446    std::fs::write(&all_yaml_path, ALL_YML)?;
447    let loki_config_path = tag_directory.join("loki.yml");
448    std::fs::write(&loki_config_path, LOKI_CONFIG)?;
449    let pyroscope_config_path = tag_directory.join("pyroscope.yml");
450    std::fs::write(&pyroscope_config_path, PYROSCOPE_CONFIG)?;
451    let tempo_yml_path = tag_directory.join("tempo.yml");
452    std::fs::write(&tempo_yml_path, TEMPO_CONFIG)?;
453    rsync_file(
454        private_key,
455        prom_path.to_str().unwrap(),
456        &monitoring_ip,
457        "/home/ubuntu/prometheus.yml",
458    )
459    .await?;
460    rsync_file(
461        private_key,
462        datasources_path.to_str().unwrap(),
463        &monitoring_ip,
464        "/home/ubuntu/datasources.yml",
465    )
466    .await?;
467    rsync_file(
468        private_key,
469        all_yaml_path.to_str().unwrap(),
470        &monitoring_ip,
471        "/home/ubuntu/all.yml",
472    )
473    .await?;
474    rsync_file(
475        private_key,
476        &config.monitoring.dashboard,
477        &monitoring_ip,
478        "/home/ubuntu/dashboard.json",
479    )
480    .await?;
481    rsync_file(
482        private_key,
483        prometheus_service_path.to_str().unwrap(),
484        &monitoring_ip,
485        "/home/ubuntu/prometheus.service",
486    )
487    .await?;
488    rsync_file(
489        private_key,
490        loki_config_path.to_str().unwrap(),
491        &monitoring_ip,
492        "/home/ubuntu/loki.yml",
493    )
494    .await?;
495    rsync_file(
496        private_key,
497        loki_service_path.to_str().unwrap(),
498        &monitoring_ip,
499        "/home/ubuntu/loki.service",
500    )
501    .await?;
502    rsync_file(
503        private_key,
504        node_exporter_service_path.to_str().unwrap(),
505        &monitoring_ip,
506        "/home/ubuntu/node_exporter.service",
507    )
508    .await?;
509    rsync_file(
510        private_key,
511        pyroscope_config_path.to_str().unwrap(),
512        &monitoring_ip,
513        "/home/ubuntu/pyroscope.yml",
514    )
515    .await?;
516    rsync_file(
517        private_key,
518        pyroscope_service_path.to_str().unwrap(),
519        &monitoring_ip,
520        "/home/ubuntu/pyroscope.service",
521    )
522    .await?;
523    rsync_file(
524        private_key,
525        tempo_yml_path.to_str().unwrap(),
526        &monitoring_ip,
527        "/home/ubuntu/tempo.yml",
528    )
529    .await?;
530    rsync_file(
531        private_key,
532        tempo_service_path.to_str().unwrap(),
533        &monitoring_ip,
534        "/home/ubuntu/tempo.service",
535    )
536    .await?;
537    enable_bbr(private_key, &monitoring_ip, bbr_conf_path.to_str().unwrap()).await?;
538    ssh_execute(
539        private_key,
540        &monitoring_ip,
541        &setup_node_exporter_cmd(NODE_EXPORTER_VERSION),
542    )
543    .await?;
544    poll_service_active(private_key, &monitoring_ip, "node_exporter").await?;
545    ssh_execute(
546        private_key,
547        &monitoring_ip,
548        &install_monitoring_cmd(
549            PROMETHEUS_VERSION,
550            GRAFANA_VERSION,
551            LOKI_VERSION,
552            PYROSCOPE_VERSION,
553            TEMPO_VERSION,
554        ),
555    )
556    .await?;
557    poll_service_active(private_key, &monitoring_ip, "prometheus").await?;
558    poll_service_active(private_key, &monitoring_ip, "loki").await?;
559    poll_service_active(private_key, &monitoring_ip, "pyroscope").await?;
560    poll_service_active(private_key, &monitoring_ip, "tempo").await?;
561    poll_service_active(private_key, &monitoring_ip, "grafana-server").await?;
562    info!("configured monitoring instance");
563
564    // Generate hosts.yaml
565    let hosts = Hosts {
566        monitoring: monitoring_private_ip.clone().parse::<IpAddr>().unwrap(),
567        hosts: deployments
568            .iter()
569            .map(|d| Host {
570                name: d.instance.name.clone(),
571                region: d.instance.region.clone(),
572                ip: d.ip.clone().parse::<IpAddr>().unwrap(),
573            })
574            .collect(),
575    };
576    let hosts_yaml = serde_yaml::to_string(&hosts)?;
577    let hosts_path = tag_directory.join("hosts.yaml");
578    std::fs::write(&hosts_path, hosts_yaml)?;
579
580    // Configure binary instances
581    info!("configuring binary instances");
582    let mut start_futures = Vec::new();
583    for deployment in &deployments {
584        let tag_directory = tag_directory.clone();
585        let instance = deployment.instance.clone();
586        wait_for_instances_ready(
587            &ec2_clients[&instance.region],
588            slice::from_ref(&deployment.id),
589        )
590        .await?;
591        let ip = deployment.ip.clone();
592        let monitoring_private_ip = monitoring_private_ip.clone();
593        let hosts_path = hosts_path.clone();
594        let logrotate_conf_path = logrotate_conf_path.clone();
595        let bbr_conf_path = bbr_conf_path.clone();
596        let promtail_service_path = promtail_service_path.clone();
597        let node_exporter_service_path = node_exporter_service_path.clone();
598        let binary_service_path = binary_service_path.clone();
599        let pyroscope_agent_service_path = pyroscope_agent_service_path.clone();
600        let pyroscope_agent_timer_path = pyroscope_agent_timer_path.clone();
601        let future = async move {
602            rsync_file(private_key, &instance.binary, &ip, "/home/ubuntu/binary").await?;
603            rsync_file(
604                private_key,
605                &instance.config,
606                &ip,
607                "/home/ubuntu/config.conf",
608            )
609            .await?;
610            rsync_file(
611                private_key,
612                hosts_path.to_str().unwrap(),
613                &ip,
614                "/home/ubuntu/hosts.yaml",
615            )
616            .await?;
617            let promtail_config_path =
618                tag_directory.join(format!("promtail_{}.yml", instance.name));
619            std::fs::write(
620                &promtail_config_path,
621                promtail_config(
622                    &monitoring_private_ip,
623                    &instance.name,
624                    ip.as_str(),
625                    instance.region.as_str(),
626                ),
627            )?;
628            rsync_file(
629                private_key,
630                promtail_config_path.to_str().unwrap(),
631                &ip,
632                "/home/ubuntu/promtail.yml",
633            )
634            .await?;
635            rsync_file(
636                private_key,
637                promtail_service_path.to_str().unwrap(),
638                &ip,
639                "/home/ubuntu/promtail.service",
640            )
641            .await?;
642            rsync_file(
643                private_key,
644                node_exporter_service_path.to_str().unwrap(),
645                &ip,
646                "/home/ubuntu/node_exporter.service",
647            )
648            .await?;
649            rsync_file(
650                private_key,
651                binary_service_path.to_str().unwrap(),
652                &ip,
653                "/home/ubuntu/binary.service",
654            )
655            .await?;
656            rsync_file(
657                private_key,
658                logrotate_conf_path.to_str().unwrap(),
659                &ip,
660                "/home/ubuntu/logrotate.conf",
661            )
662            .await?;
663            rsync_file(
664                private_key,
665                pyroscope_agent_service_path.to_str().unwrap(),
666                &ip,
667                "/home/ubuntu/pyroscope-agent.service",
668            )
669            .await?;
670            let pyroscope_agent_script_path =
671                tag_directory.join(format!("pyroscope-agent_{}.sh", instance.name));
672            std::fs::write(
673                &pyroscope_agent_script_path,
674                generate_pyroscope_script(
675                    &monitoring_private_ip,
676                    &instance.name,
677                    &ip,
678                    &instance.region,
679                ),
680            )?;
681            rsync_file(
682                private_key,
683                pyroscope_agent_script_path.to_str().unwrap(),
684                &ip,
685                "/home/ubuntu/pyroscope-agent.sh",
686            )
687            .await?;
688            rsync_file(
689                private_key,
690                pyroscope_agent_timer_path.to_str().unwrap(),
691                &ip,
692                "/home/ubuntu/pyroscope-agent.timer",
693            )
694            .await?;
695            enable_bbr(private_key, &ip, bbr_conf_path.to_str().unwrap()).await?;
696            ssh_execute(private_key, &ip, &setup_promtail_cmd(PROMTAIL_VERSION)).await?;
697            poll_service_active(private_key, &ip, "promtail").await?;
698            ssh_execute(
699                private_key,
700                &ip,
701                &setup_node_exporter_cmd(NODE_EXPORTER_VERSION),
702            )
703            .await?;
704            poll_service_active(private_key, &ip, "node_exporter").await?;
705            ssh_execute(private_key, &ip, &install_binary_cmd(instance.profiling)).await?;
706            poll_service_active(private_key, &ip, "binary").await?;
707            info!(
708                ip = ip.as_str(),
709                instance = instance.name.as_str(),
710                "configured instance"
711            );
712            Ok::<String, Error>(ip)
713        };
714        start_futures.push(future);
715    }
716    let all_binary_ips = try_join_all(start_futures).await?;
717    info!("configured binary instances");
718
719    // Update monitoring security group to restrict Loki port (3100)
720    info!("updating monitoring security group to allow traffic from binary instances");
721    let monitoring_ec2_client = &ec2_clients[&monitoring_region];
722    if binary_regions.contains(&monitoring_region) {
723        let binary_sg_id = region_resources[&monitoring_region]
724            .binary_sg_id
725            .clone()
726            .unwrap();
727        monitoring_ec2_client
728            .authorize_security_group_ingress()
729            .group_id(&monitoring_sg_id)
730            .ip_permissions(
731                IpPermission::builder()
732                    .ip_protocol("tcp")
733                    .from_port(LOGS_PORT as i32)
734                    .to_port(LOGS_PORT as i32)
735                    .user_id_group_pairs(
736                        UserIdGroupPair::builder()
737                            .group_id(binary_sg_id.clone())
738                            .build(),
739                    )
740                    .build(),
741            )
742            .ip_permissions(
743                IpPermission::builder()
744                    .ip_protocol("tcp")
745                    .from_port(PROFILES_PORT as i32)
746                    .to_port(PROFILES_PORT as i32)
747                    .user_id_group_pairs(
748                        UserIdGroupPair::builder()
749                            .group_id(binary_sg_id.clone())
750                            .build(),
751                    )
752                    .build(),
753            )
754            .ip_permissions(
755                IpPermission::builder()
756                    .ip_protocol("tcp")
757                    .from_port(TRACES_PORT as i32)
758                    .to_port(TRACES_PORT as i32)
759                    .user_id_group_pairs(
760                        UserIdGroupPair::builder()
761                            .group_id(binary_sg_id.clone())
762                            .build(),
763                    )
764                    .build(),
765            )
766            .send()
767            .await
768            .map_err(|err| err.into_service_error())?;
769        info!(
770            monitoring = monitoring_sg_id.as_str(),
771            binary = binary_sg_id.as_str(),
772            region = monitoring_region.as_str(),
773            "linked monitoring and binary security groups in monitoring region"
774        );
775    }
776    for region in &regions {
777        if region != &monitoring_region && binary_regions.contains(region) {
778            let binary_cidr = &region_resources[region].vpc_cidr;
779            monitoring_ec2_client
780                .authorize_security_group_ingress()
781                .group_id(&monitoring_sg_id)
782                .ip_permissions(
783                    IpPermission::builder()
784                        .ip_protocol("tcp")
785                        .from_port(LOGS_PORT as i32)
786                        .to_port(LOGS_PORT as i32)
787                        .ip_ranges(IpRange::builder().cidr_ip(binary_cidr).build())
788                        .build(),
789                )
790                .ip_permissions(
791                    IpPermission::builder()
792                        .ip_protocol("tcp")
793                        .from_port(PROFILES_PORT as i32)
794                        .to_port(PROFILES_PORT as i32)
795                        .ip_ranges(IpRange::builder().cidr_ip(binary_cidr).build())
796                        .build(),
797                )
798                .ip_permissions(
799                    IpPermission::builder()
800                        .ip_protocol("tcp")
801                        .from_port(TRACES_PORT as i32)
802                        .to_port(TRACES_PORT as i32)
803                        .ip_ranges(IpRange::builder().cidr_ip(binary_cidr).build())
804                        .build(),
805                )
806                .send()
807                .await
808                .map_err(|err| err.into_service_error())?;
809            info!(
810                monitoring = monitoring_sg_id.as_str(),
811                binary = binary_cidr.as_str(),
812                region = region.as_str(),
813                "opened monitoring part to traffic from binary VPC"
814            );
815        }
816    }
817    info!("updated monitoring security group");
818
819    // Mark deployment as complete
820    File::create(tag_directory.join(CREATED_FILE_NAME))?;
821    info!(
822        monitoring = monitoring_ip.as_str(),
823        binary = ?all_binary_ips,
824        "deployment complete"
825    );
826    Ok(())
827}