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