1use std::ffi::CStr;
16use std::fs::File;
17use std::net::{IpAddr, SocketAddr};
18use std::path::PathBuf;
19use std::sync::Arc;
20use std::sync::LazyLock;
21use std::sync::atomic::{AtomicUsize, Ordering};
22use std::time::{Duration, Instant};
23use std::{cmp, env, iter, thread};
24
25use anyhow::{Context, bail};
26use clap::{ArgAction, Parser, ValueEnum};
27use fail::FailScenario;
28use http::header::HeaderValue;
29use ipnet::IpNet;
30use itertools::Itertools;
31use mz_adapter::ResultExt;
32use mz_adapter_types::bootstrap_builtin_cluster_config::{
33 ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR, BootstrapBuiltinClusterConfig,
34 CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR, DEFAULT_REPLICATION_FACTOR,
35 PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR, SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR,
36 SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR,
37};
38use mz_auth::password::Password;
39use mz_aws_secrets_controller::AwsSecretsController;
40use mz_build_info::BuildInfo;
41use mz_catalog::config::ClusterReplicaSizeMap;
42use mz_cloud_resources::{AwsExternalIdPrefix, CloudResourceController};
43use mz_controller::{ControllerConfig, ReplicaHttpLocator};
44use mz_frontegg_auth::{Authenticator as FronteggAuthenticator, FronteggCliArgs};
45use mz_license_keys::{ExpirationBehavior, ValidatedLicenseKey};
46use mz_orchestrator::Orchestrator;
47use mz_orchestrator_kubernetes::{
48 KubernetesImagePullPolicy, KubernetesOrchestrator, KubernetesOrchestratorConfig,
49};
50use mz_orchestrator_process::{
51 ProcessOrchestrator, ProcessOrchestratorConfig, ProcessOrchestratorTcpProxyConfig,
52};
53use mz_orchestrator_tracing::{StaticTracingConfig, TracingCliArgs, TracingOrchestrator};
54use mz_ore::cli::{self, CliConfig, KeyValueArg};
55use mz_ore::error::ErrorExt;
56use mz_ore::metric;
57use mz_ore::metrics::{MetricsRegistry, register_runtime_metrics};
58use mz_ore::now::SYSTEM_TIME;
59use mz_ore::task::RuntimeExt;
60use mz_ore::url::SensitiveUrl;
61use mz_persist_client::PersistLocation;
62use mz_persist_client::cache::PersistClientCache;
63use mz_persist_client::cfg::PersistConfig;
64use mz_persist_client::rpc::{
65 MetricsSameProcessPubSubSender, PersistGrpcPubSubServer, PubSubClientConnection, PubSubSender,
66};
67use mz_secrets::SecretsController;
68use mz_secrets_cli::{SecretsControllerKind, SecretsReaderCliArgs};
69use mz_server_core::TlsCliArgs;
70use mz_service::emit_boot_diagnostics;
71use mz_sql::catalog::EnvironmentId;
72use mz_storage_types::connections::ConnectionContext;
73use opentelemetry::trace::TraceContextExt;
74use prometheus::IntGauge;
75use tracing::{Instrument, error, info, info_span, warn};
76use tracing_opentelemetry::OpenTelemetrySpanExt;
77use url::Url;
78
79use crate::environmentd::sys;
80use crate::{BUILD_INFO, CatalogConfig, ListenerConfig, Listeners, ListenersConfig};
81
82static VERSION: LazyLock<String> = LazyLock::new(|| BUILD_INFO.human_version(None));
83static LONG_VERSION: LazyLock<String> = LazyLock::new(|| {
84 iter::once(BUILD_INFO.human_version(None))
85 .chain(build_info())
86 .join("\n")
87});
88
89#[derive(Parser, Debug)]
91#[clap(
92 name = "environmentd",
93 next_line_help = true,
94 version = VERSION.as_str(),
95 long_version = LONG_VERSION.as_str(),
96)]
97pub struct Args {
98 #[clap(long, env = "UNSAFE_MODE")]
102 unsafe_mode: bool,
103 #[clap(long, env = "ALL_FEATURES")]
106 all_features: bool,
107
108 #[clap(
112 long,
113 env = "LISTENERS_CONFIG_PATH",
114 value_name = "PATH",
115 action = ArgAction::Set,
116 )]
117 listeners_config_path: PathBuf,
118 #[clap(
120 long,
121 env = "EXTERNAL_LOGIN_PASSWORD_MZ_SYSTEM",
122 action = ArgAction::Set,
123 )]
124 external_login_password_mz_system: Option<Password>,
125 #[clap(
131 long,
132 value_name = "HOST:PORT",
133 env = "INTERNAL_PERSIST_PUBSUB_LISTEN_ADDR",
134 default_value = "127.0.0.1:6879",
135 action = ArgAction::Set,
136 )]
137 internal_persist_pubsub_listen_addr: SocketAddr,
138 #[structopt(long, env = "CORS_ALLOWED_ORIGIN")]
147 cors_allowed_origin: Vec<HeaderValue>,
148 #[clap(
151 long,
152 env = "ANNOUNCE_EGRESS_ADDRESS",
153 action = ArgAction::Append,
154 use_value_delimiter = true
155 )]
156 announce_egress_address: Vec<IpNet>,
157 #[clap(long, env = "HTTP_HOST_NAME")]
166 http_host_name: Option<String>,
167 #[clap(long, env = "INTERNAL_CONSOLE_REDIRECT_URL")]
170 internal_console_redirect_url: Option<String>,
171 #[clap(flatten)]
173 tls: TlsCliArgs,
174 #[clap(flatten)]
176 frontegg: FronteggCliArgs,
177 #[structopt(long, value_enum, env = "ORCHESTRATOR")]
180 orchestrator: OrchestratorKind,
181 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SCHEDULER_NAME")]
183 orchestrator_kubernetes_scheduler_name: Option<String>,
184 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_PRIORITY_CLASS_NAME")]
186 orchestrator_kubernetes_priority_class_name: Option<String>,
187 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_ANNOTATION")]
190 orchestrator_kubernetes_service_annotation: Vec<KeyValueArg<String, String>>,
191 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_LABEL")]
194 orchestrator_kubernetes_service_label: Vec<KeyValueArg<String, String>>,
195 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_NODE_SELECTOR")]
198 orchestrator_kubernetes_service_node_selector: Vec<KeyValueArg<String, String>>,
199 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_AFFINITY")]
202 orchestrator_kubernetes_service_affinity: Option<String>,
203 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_TOLERATIONS")]
206 orchestrator_kubernetes_service_tolerations: Option<String>,
207 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_ACCOUNT")]
210 orchestrator_kubernetes_service_account: Option<String>,
211 #[structopt(
216 long,
217 env = "ORCHESTRATOR_KUBERNETES_CONTEXT",
218 default_value = "minikube"
219 )]
220 orchestrator_kubernetes_context: String,
221 #[structopt(
224 long,
225 env = "ORCHESTRATOR_KUBERNETES_IMAGE_PULL_POLICY",
226 default_value = "always",
227 value_enum
228 )]
229 orchestrator_kubernetes_image_pull_policy: KubernetesImagePullPolicy,
230 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_INIT_CONTAINER_IMAGE")]
232 orchestrator_kubernetes_init_container_image: Option<String>,
233 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_EPHEMERAL_VOLUME_CLASS")]
239 orchestrator_kubernetes_ephemeral_volume_class: Option<String>,
240 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_FS_GROUP")]
242 orchestrator_kubernetes_service_fs_group: Option<i64>,
243 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_NAME_PREFIX")]
245 orchestrator_kubernetes_name_prefix: Option<String>,
246 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_DISABLE_POD_METRICS_COLLECTION")]
251 orchestrator_kubernetes_disable_pod_metrics_collection: bool,
252 #[clap(
254 long,
255 env = "ORCHESTRATOR_KUBERNETES_ENABLE_PROMETHEUS_SCRAPE_ANNOTATIONS"
256 )]
257 orchestrator_kubernetes_enable_prometheus_scrape_annotations: bool,
258 #[clap(long, env = "ORCHESTRATOR_PROCESS_WRAPPER")]
259 orchestrator_process_wrapper: Option<String>,
260 #[clap(
262 long,
263 env = "ORCHESTRATOR_PROCESS_SECRETS_DIRECTORY",
264 value_name = "PATH",
265 required_if_eq("orchestrator", "process")
266 )]
267 orchestrator_process_secrets_directory: Option<PathBuf>,
268 #[clap(long, env = "ORCHESTRATOR_PROCESS_PROPAGATE_CRASHES")]
271 orchestrator_process_propagate_crashes: bool,
272 #[clap(long, env = "ORCHESTRATOR_PROCESS_TCP_PROXY_LISTEN_ADDR")]
284 orchestrator_process_tcp_proxy_listen_addr: Option<IpAddr>,
285 #[clap(
298 long,
299 env = "ORCHESTRATOR_PROCESS_PROMETHEUS_SERVICE_DISCOVERY_DIRECTORY"
300 )]
301 orchestrator_process_prometheus_service_discovery_directory: Option<PathBuf>,
302 #[clap(
304 long,
305 env = "ORCHESTRATOR_PROCESS_SCRATCH_DIRECTORY",
306 value_name = "PATH"
307 )]
308 orchestrator_process_scratch_directory: Option<PathBuf>,
309 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_COVERAGE")]
312 orchestrator_kubernetes_coverage: bool,
313 #[structopt(
315 long,
316 value_enum,
317 env = "SECRETS_CONTROLLER",
318 default_value_ifs([
319 ("orchestrator", "kubernetes", Some("kubernetes")),
320 ("orchestrator", "process", Some("local-file"))
321 ]),
322 default_value("kubernetes"), )]
324 secrets_controller: SecretsControllerKind,
325 #[clap(
328 long,
329 env = "AWS_SECRETS_CONTROLLER_TAGS",
330 action = ArgAction::Append,
331 value_delimiter = ';',
332 required_if_eq("secrets_controller", "aws-secrets-manager")
333 )]
334 aws_secrets_controller_tags: Vec<KeyValueArg<String, String>>,
335 #[structopt(
337 long,
338 env = "CLUSTERD_IMAGE",
339 required_if_eq("orchestrator", "kubernetes"),
340 default_value_if("orchestrator", "process", Some("clusterd"))
341 )]
342 clusterd_image: Option<String>,
343 #[clap(long, env = "DEPLOY_GENERATION", default_value = "0")]
348 deploy_generation: u64,
349
350 #[clap(
353 long,
354 env = "METADATA_BACKEND_URL",
355 conflicts_with_all = &[
356 "persist_consensus_url",
357 "timestamp_oracle_url",
358 ],
359 )]
360 metadata_backend_url: Option<SensitiveUrl>,
361
362 #[clap(long, env = "HELM_CHART_VERSION")]
367 helm_chart_version: Option<String>,
368
369 #[clap(long, env = "PERSIST_BLOB_URL")]
372 persist_blob_url: SensitiveUrl,
373 #[clap(long, env = "PERSIST_CONSENSUS_URL")]
375 persist_consensus_url: Option<SensitiveUrl>,
376 #[clap(
380 long,
381 env = "PERSIST_PUBSUB_URL",
382 default_value = "http://localhost:6879"
383 )]
384 persist_pubsub_url: String,
385 #[clap(long, env = "PERSIST_ISOLATED_RUNTIME_THREADS")]
389 persist_isolated_runtime_threads: Option<isize>,
390 #[clap(
392 long,
393 env = "STORAGE_USAGE_COLLECTION_INTERVAL",
394 value_parser = humantime::parse_duration,
395 default_value = "3600s"
396 )]
397 storage_usage_collection_interval_sec: Duration,
398 #[clap(long, env = "STORAGE_USAGE_RETENTION_PERIOD", value_parser = humantime::parse_duration)]
402 storage_usage_retention_period: Option<Duration>,
403
404 #[clap(long, env = "TIMESTAMP_ORACLE_URL", value_name = "POSTGRES_URL")]
407 timestamp_oracle_url: Option<SensitiveUrl>,
408 #[clap(long, env = "AVAILABILITY_ZONE", use_value_delimiter = true)]
411 availability_zone: Vec<String>,
412 #[clap(
414 long,
415 env = "CLUSTER_REPLICA_SIZES",
416 requires = "bootstrap_default_cluster_replica_size"
417 )]
418 cluster_replica_sizes: String,
419 #[clap(long, env = "SEGMENT_API_KEY")]
421 segment_api_key: Option<String>,
422 #[clap(long, env = "SEGMENT_CLIENT_SIDE")]
428 segment_client_side: bool,
429 #[clap(long, env = "TEST_ONLY_DUMMY_SEGMENT_CLIENT")]
432 test_only_dummy_segment_client: bool,
433 #[clap(long, env = "LAUNCHDARKLY_SDK_KEY")]
439 launchdarkly_sdk_key: Option<String>,
440 #[clap(long, env = "LAUNCHDARKLY_BASE_URI", value_name = "URL")]
444 launchdarkly_base_uri: Option<String>,
445 #[clap(
452 long,
453 env = "LAUNCHDARKLY_KEY_MAP",
454 action = ArgAction::Append,
455 value_delimiter = ';'
456 )]
457 launchdarkly_key_map: Vec<KeyValueArg<String, String>>,
458 #[clap(
460 long,
461 env = "CONFIG_SYNC_TIMEOUT",
462 value_parser = humantime::parse_duration,
463 default_value = "30s"
464 )]
465 config_sync_timeout: Duration,
466 #[clap(
472 long,
473 env = "CONFIG_SYNC_LOOP_INTERVAL",
474 value_parser = humantime::parse_duration,
475 )]
476 config_sync_loop_interval: Option<Duration>,
477 #[clap(long, env = "CONFIG_SYNC_FILE_PATH", value_name = "PATH")]
480 config_sync_file_path: Option<PathBuf>,
481
482 #[clap(
484 long,
485 env = "ENVIRONMENT_ID",
486 value_name = "<CLOUD>-<REGION>-<ORG-ID>-<ORDINAL>"
487 )]
488 environment_id: EnvironmentId,
489 #[clap(long, env = "BOOTSTRAP_ROLE")]
498 bootstrap_role: Option<String>,
499 #[clap(
501 long,
502 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICA_SIZE",
503 default_value = "scale=1,workers=1"
504 )]
505 bootstrap_default_cluster_replica_size: String,
506 #[clap(
508 long,
509 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICA_SIZE",
510 default_value = "scale=1,workers=1"
511 )]
512 bootstrap_builtin_system_cluster_replica_size: String,
513 #[clap(
515 long,
516 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICA_SIZE",
517 default_value = "scale=1,workers=1"
518 )]
519 bootstrap_builtin_catalog_server_cluster_replica_size: String,
520 #[clap(
522 long,
523 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICA_SIZE",
524 default_value = "scale=1,workers=1"
525 )]
526 bootstrap_builtin_probe_cluster_replica_size: String,
527 #[clap(
529 long,
530 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICA_SIZE",
531 default_value = "scale=1,workers=1"
532 )]
533 bootstrap_builtin_support_cluster_replica_size: String,
534 #[clap(
536 long,
537 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICA_SIZE",
538 default_value = "scale=1,workers=1"
539 )]
540 bootstrap_builtin_analytics_cluster_replica_size: String,
541 #[clap(
542 long,
543 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICATION_FACTOR",
544 default_value = DEFAULT_REPLICATION_FACTOR.to_string(),
545 value_parser = clap::value_parser!(u32).range(0..=2)
546 )]
547 bootstrap_default_cluster_replication_factor: u32,
548 #[clap(
550 long,
551 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICATION_FACTOR",
552 default_value = SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
553 value_parser = clap::value_parser!(u32).range(0..=2)
554 )]
555 bootstrap_builtin_system_cluster_replication_factor: u32,
556 #[clap(
558 long,
559 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICATION_FACTOR",
560 default_value = CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
561 value_parser = clap::value_parser!(u32).range(0..=2)
562 )]
563 bootstrap_builtin_catalog_server_cluster_replication_factor: u32,
564 #[clap(
566 long,
567 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICATION_FACTOR",
568 default_value = PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
569 value_parser = clap::value_parser!(u32).range(0..=2)
570 )]
571 bootstrap_builtin_probe_cluster_replication_factor: u32,
572 #[clap(
574 long,
575 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICATION_FACTOR",
576 default_value = SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
577 value_parser = clap::value_parser!(u32).range(0..=2)
578 )]
579 bootstrap_builtin_support_cluster_replication_factor: u32,
580 #[clap(
582 long,
583 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICATION_FACTOR",
584 default_value = ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
585 value_parser = clap::value_parser!(u32).range(0..=2)
586 )]
587 bootstrap_builtin_analytics_cluster_replication_factor: u32,
588 #[clap(
591 long,
592 env = "SYSTEM_PARAMETER_DEFAULT",
593 action = ArgAction::Append,
594 value_delimiter = ';'
595 )]
596 system_parameter_default: Vec<KeyValueArg<String, String>>,
597 #[clap(long, env = "LICENSE_KEY")]
599 license_key: Option<String>,
600
601 #[clap(long, env = "AWS_ACCOUNT_ID")]
605 aws_account_id: Option<String>,
606 #[clap(long, env = "AWS_CONNECTION_ROLE_ARN")]
609 aws_connection_role_arn: Option<String>,
610 #[clap(long, env = "AWS_EXTERNAL_ID_PREFIX", value_name = "ID", value_parser = AwsExternalIdPrefix::new_from_cli_argument_or_environment_variable)]
614 aws_external_id_prefix: Option<AwsExternalIdPrefix>,
615 #[clap(
618 long,
619 env = "AWS_PRIVATELINK_AVAILABILITY_ZONES",
620 action = ArgAction::Append,
621 use_value_delimiter = true
622 )]
623 aws_privatelink_availability_zones: Option<Vec<String>>,
624
625 #[clap(flatten)]
627 tracing: TracingCliArgs,
628
629 #[clap(long, value_enum, requires = "unsafe_mode")]
636 unsafe_force_builtin_schema_migration: Option<String>,
637}
638
639#[derive(ValueEnum, Debug, Clone)]
640enum OrchestratorKind {
641 Kubernetes,
642 Process,
643}
644
645fn aws_secrets_controller_prefix(env_id: &EnvironmentId) -> String {
648 format!("/user-managed/{}/", env_id)
649}
650fn aws_secrets_controller_key_alias(env_id: &EnvironmentId) -> String {
651 format!("alias/customer_key_{}", env_id)
654}
655
656pub fn main() {
657 let args = cli::parse_args(CliConfig {
658 env_prefix: Some("MZ_"),
659 enable_version_flag: true,
660 });
661 if let Err(err) = run(args) {
662 panic!("environmentd: fatal: {}", err.display_with_causes());
663 }
664}
665
666fn run(mut args: Args) -> Result<(), anyhow::Error> {
667 mz_ore::panic::install_enhanced_handler();
668
669 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
676
677 let envd_start = Instant::now();
678
679 sys::enable_sigusr2_coverage_dump()?;
682 sys::enable_termination_signal_cleanup()?;
683
684 let license_key = if let Some(license_key_file) = args.license_key {
685 let license_key_text = std::fs::read_to_string(&license_key_file)
686 .context("failed to open license key file")?;
687 let license_key = mz_license_keys::validate(license_key_text.trim())
688 .context("failed to validate license key file")?;
689 if license_key.expired {
690 let message = format!(
691 "The license key provided at {license_key_file} is expired! Please contact Materialize for assistance."
692 );
693 match license_key.expiration_behavior {
694 ExpirationBehavior::Warn | ExpirationBehavior::DisableClusterCreation => {
695 warn!("{message}");
696 }
697 ExpirationBehavior::Disable => bail!("{message}"),
698 }
699 }
700 license_key
701 } else if matches!(args.orchestrator, OrchestratorKind::Kubernetes) {
702 bail!("--license-key is required when running in Kubernetes");
703 } else {
704 ValidatedLicenseKey::disabled()
706 };
707
708 let force_builtin_schema_migration = args
710 .unsafe_force_builtin_schema_migration
711 .inspect(|_mechanism| assert!(args.unsafe_mode));
712
713 let ncpus_useful = usize::max(1, cmp::min(num_cpus::get(), num_cpus::get_physical()));
716 let runtime = Arc::new(
717 tokio::runtime::Builder::new_multi_thread()
718 .worker_threads(ncpus_useful)
719 .thread_stack_size(3 * 1024 * 1024) .thread_name_fn(|| {
723 static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
724 let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
725 format!("tokio:work-{}", id)
726 })
727 .enable_all()
728 .build()?,
729 );
730
731 args.tracing.log_prefix = if matches!(args.orchestrator, OrchestratorKind::Process) {
735 Some("environmentd".to_string())
736 } else {
737 None
738 };
739
740 let metrics_registry = MetricsRegistry::new();
741 let tracing_handle = runtime.block_on(args.tracing.configure_tracing(
742 StaticTracingConfig {
743 service_name: "environmentd",
744 build_info: BUILD_INFO,
745 },
746 metrics_registry.clone(),
747 ))?;
748 register_runtime_metrics("main", runtime.metrics(), &metrics_registry);
749
750 let span = tracing::info_span!("environmentd::run").entered();
751
752 info!("startup: envd init: beginning");
753 info!("startup: envd init: preamble beginning");
754
755 let metrics = Metrics::register_into(&metrics_registry, BUILD_INFO);
756
757 runtime.block_on(mz_alloc::register_metrics_into(&metrics_registry));
758 runtime.block_on(mz_metrics::register_metrics_into(
759 &metrics_registry,
760 mz_dyncfgs::all_dyncfgs(),
761 None,
763 ));
764
765 let _failpoint_scenario = FailScenario::setup();
767
768 let tls = args.tls.into_config()?;
770 let frontegg_oauth_issuer_url = args.frontegg.oauth_issuer_url().map(str::to_string);
771 let frontegg = FronteggAuthenticator::from_args(args.frontegg, &metrics_registry)?;
772 let listeners_config: ListenersConfig = {
773 let f = File::open(args.listeners_config_path)?;
774 serde_json::from_reader(f)?
775 };
776
777 for (_, listener) in &listeners_config.sql {
778 listener
779 .validate()
780 .map_err(|e| anyhow::anyhow!("invalid SQL listener: {}", e))?;
781 }
782
783 for (_, listener) in &listeners_config.http {
784 listener
785 .validate()
786 .map_err(|e| anyhow::anyhow!("invalid HTTP listener: {}", e))?;
787 }
788
789 let allowed_origins = if !args.cors_allowed_origin.is_empty() {
791 args.cors_allowed_origin
792 } else {
793 let mut allowed_origins = Vec::with_capacity(listeners_config.http.len() * 6);
794 for (_, listener) in &listeners_config.http {
795 let port = listener.addr().port();
796 allowed_origins.extend([
797 HeaderValue::from_str(&format!("http://localhost:{}", port)).unwrap(),
798 HeaderValue::from_str(&format!("http://127.0.0.1:{}", port)).unwrap(),
799 HeaderValue::from_str(&format!("http://[::1]:{}", port)).unwrap(),
800 HeaderValue::from_str(&format!("https://localhost:{}", port)).unwrap(),
801 HeaderValue::from_str(&format!("https://127.0.0.1:{}", port)).unwrap(),
802 HeaderValue::from_str(&format!("https://[::1]:{}", port)).unwrap(),
803 ])
804 }
805 allowed_origins
806 };
807 let cors_allowed_origin = mz_http_util::build_cors_allowed_origin(&allowed_origins);
808 let cors_allowed_origin_list = allowed_origins.clone();
809
810 let entered = info_span!("environmentd::configure_controller").entered();
812 let (orchestrator, secrets_controller, cloud_resource_controller): (
813 Arc<dyn Orchestrator>,
814 Arc<dyn SecretsController>,
815 Option<Arc<dyn CloudResourceController>>,
816 ) = match args.orchestrator {
817 OrchestratorKind::Kubernetes => {
818 if args.orchestrator_process_scratch_directory.is_some() {
819 bail!(
820 "--orchestrator-process-scratch-directory is \
821 not currently usable with the kubernetes orchestrator"
822 );
823 }
824
825 let orchestrator = Arc::new(
826 runtime
827 .block_on(KubernetesOrchestrator::new(
828 KubernetesOrchestratorConfig {
829 context: args.orchestrator_kubernetes_context.clone(),
830 scheduler_name: args.orchestrator_kubernetes_scheduler_name,
831 priority_class_name: args.orchestrator_kubernetes_priority_class_name,
832 service_annotations: args
833 .orchestrator_kubernetes_service_annotation
834 .into_iter()
835 .map(|l| (l.key, l.value))
836 .collect(),
837 service_labels: args
838 .orchestrator_kubernetes_service_label
839 .into_iter()
840 .map(|l| (l.key, l.value))
841 .collect(),
842 service_node_selector: args
843 .orchestrator_kubernetes_service_node_selector
844 .into_iter()
845 .map(|l| (l.key, l.value))
846 .collect(),
847 service_affinity: args.orchestrator_kubernetes_service_affinity,
848 service_tolerations: args.orchestrator_kubernetes_service_tolerations,
849 service_account: args.orchestrator_kubernetes_service_account,
850 image_pull_policy: args.orchestrator_kubernetes_image_pull_policy,
851 aws_external_id_prefix: args.aws_external_id_prefix.clone(),
852 coverage: args.orchestrator_kubernetes_coverage,
853 ephemeral_volume_storage_class: args
854 .orchestrator_kubernetes_ephemeral_volume_class
855 .clone(),
856 service_fs_group: args.orchestrator_kubernetes_service_fs_group.clone(),
857 name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
858 collect_pod_metrics: !args
859 .orchestrator_kubernetes_disable_pod_metrics_collection,
860 enable_prometheus_scrape_annotations: args
861 .orchestrator_kubernetes_enable_prometheus_scrape_annotations,
862 },
863 &metrics_registry,
864 ))
865 .context("creating kubernetes orchestrator")?,
866 );
867 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
868 SecretsControllerKind::Kubernetes => {
869 let sc = Arc::clone(&orchestrator);
870 let sc: Arc<dyn SecretsController> = sc;
871 sc
872 }
873 SecretsControllerKind::AwsSecretsManager => {
874 Arc::new(
875 runtime.block_on(AwsSecretsController::new(
876 &aws_secrets_controller_prefix(&args.environment_id),
879 &aws_secrets_controller_key_alias(&args.environment_id),
880 args.aws_secrets_controller_tags
881 .into_iter()
882 .map(|tag| (tag.key, tag.value))
883 .collect(),
884 )),
885 )
886 }
887 SecretsControllerKind::LocalFile => bail!(
888 "SecretsControllerKind::LocalFile is not compatible with Orchestrator::Kubernetes."
889 ),
890 };
891 let cloud_resource_controller = Arc::clone(&orchestrator);
892 (
893 orchestrator,
894 secrets_controller,
895 Some(cloud_resource_controller),
896 )
897 }
898 OrchestratorKind::Process => {
899 if args
900 .orchestrator_kubernetes_ephemeral_volume_class
901 .is_some()
902 {
903 bail!(
904 "--orchestrator-kubernetes-ephemeral-volume-class is \
905 not usable with the process orchestrator"
906 );
907 }
908 let orchestrator = Arc::new(
909 runtime
910 .block_on(ProcessOrchestrator::new(ProcessOrchestratorConfig {
911 image_dir: env::current_exe()?.parent().unwrap().to_path_buf(),
917 suppress_output: false,
918 environment_id: args.environment_id.to_string(),
919 secrets_dir: args
920 .orchestrator_process_secrets_directory
921 .clone()
922 .expect("clap enforced"),
923 command_wrapper: args
924 .orchestrator_process_wrapper
925 .map_or(Ok(vec![]), |s| shell_words::split(&s))?,
926 propagate_crashes: args.orchestrator_process_propagate_crashes,
927 tcp_proxy: args.orchestrator_process_tcp_proxy_listen_addr.map(
928 |listen_addr| ProcessOrchestratorTcpProxyConfig {
929 listen_addr,
930 prometheus_service_discovery_dir: args
931 .orchestrator_process_prometheus_service_discovery_directory,
932 },
933 ),
934 scratch_directory: args
935 .orchestrator_process_scratch_directory
936 .expect("process orchestrator requires scratch directory"),
937 }))
938 .context("creating process orchestrator")?,
939 );
940 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
941 SecretsControllerKind::Kubernetes => bail!(
942 "SecretsControllerKind::Kubernetes is not compatible with Orchestrator::Process."
943 ),
944 SecretsControllerKind::AwsSecretsManager => Arc::new(
945 runtime.block_on(AwsSecretsController::new(
946 &aws_secrets_controller_prefix(&args.environment_id),
947 &aws_secrets_controller_key_alias(&args.environment_id),
948 args.aws_secrets_controller_tags
949 .into_iter()
950 .map(|tag| (tag.key, tag.value))
951 .collect(),
952 )),
953 ),
954 SecretsControllerKind::LocalFile => {
955 let sc = Arc::clone(&orchestrator);
956 let sc: Arc<dyn SecretsController> = sc;
957 sc
958 }
959 };
960 (orchestrator, secrets_controller, None)
961 }
962 };
963 drop(entered);
964 let cloud_resource_reader = cloud_resource_controller.as_ref().map(|c| c.reader());
965 let secrets_reader = secrets_controller.reader();
966 let now = SYSTEM_TIME.clone();
967
968 let mut persist_config =
969 PersistConfig::new(&BUILD_INFO, now.clone(), mz_dyncfgs::all_dyncfgs());
970 persist_config.disable_compaction();
972
973 let persist_pubsub_server = PersistGrpcPubSubServer::new(&persist_config, &metrics_registry);
974 let persist_pubsub_client = persist_pubsub_server.new_same_process_connection();
975
976 match args.persist_isolated_runtime_threads {
977 None | Some(0) => (),
979 Some(x @ ..=-1) => {
980 let threads = num_cpus::get().saturating_add_signed(x).max(1);
981 persist_config.isolated_runtime_worker_threads = threads;
982 }
983 Some(x @ 1..) => {
984 let threads = usize::try_from(x).expect("pattern matched a positive value");
985 persist_config.isolated_runtime_worker_threads = threads;
986 }
987 };
988
989 let _server = runtime.spawn_named(
990 || "persist::rpc::server",
991 async move {
992 info!(
993 "listening for Persist PubSub connections on {}",
994 args.internal_persist_pubsub_listen_addr
995 );
996 let res = persist_pubsub_server
999 .serve(args.internal_persist_pubsub_listen_addr)
1000 .await;
1001 error!("Persist Pubsub server exited {:?}", res);
1002 }
1003 .instrument(tracing::info_span!("persist::rpc::server")),
1004 );
1005
1006 let persist_clients = {
1007 let _tokio_guard = runtime.enter();
1009 PersistClientCache::new(persist_config, &metrics_registry, |cfg, metrics| {
1010 let sender: Arc<dyn PubSubSender> = Arc::new(MetricsSameProcessPubSubSender::new(
1011 cfg,
1012 persist_pubsub_client.sender,
1013 metrics,
1014 ));
1015 PubSubClientConnection::new(sender, persist_pubsub_client.receiver)
1016 })
1017 };
1018
1019 let consensus_uri = args.persist_consensus_url.unwrap_or_else(|| {
1020 args.metadata_backend_url
1021 .as_ref()
1022 .map(|metadata_backend_url| {
1023 SensitiveUrl(
1024 Url::parse_with_params(
1025 metadata_backend_url.0.as_ref(),
1026 &[("options", "--search_path=consensus")],
1027 )
1028 .unwrap(),
1029 )
1030 })
1031 .expect("either --persist-consensus-url or --metadata-backend-url must be provided")
1032 });
1033 let timestamp_oracle_url = args.timestamp_oracle_url.or_else(|| {
1034 args.metadata_backend_url
1035 .as_ref()
1036 .map(|metadata_backend_url| {
1037 SensitiveUrl(
1038 Url::parse_with_params(
1039 metadata_backend_url.0.as_ref(),
1040 &[("options", "--search_path=tsoracle")],
1041 )
1042 .unwrap(),
1043 )
1044 })
1045 });
1046
1047 let persist_clients = Arc::new(persist_clients);
1048 let system_dyncfgs = Arc::clone(&persist_clients.cfg().configs);
1049 let connection_context = ConnectionContext::from_cli_args(
1050 args.environment_id.to_string(),
1051 &args.tracing.startup_log_filter,
1052 args.aws_external_id_prefix,
1053 args.aws_connection_role_arn,
1054 secrets_reader,
1055 cloud_resource_reader,
1056 );
1057 let orchestrator = Arc::new(TracingOrchestrator::new(orchestrator, args.tracing.clone()));
1058 let replica_http_locator = Arc::new(ReplicaHttpLocator::default());
1059 let controller = ControllerConfig {
1060 build_info: &BUILD_INFO,
1061 orchestrator,
1062 persist_location: PersistLocation {
1063 blob_uri: args.persist_blob_url,
1064 consensus_uri,
1065 },
1066 persist_clients: Arc::clone(&persist_clients),
1067 clusterd_image: args.clusterd_image.expect("clap enforced"),
1068 init_container_image: args.orchestrator_kubernetes_init_container_image,
1069 deploy_generation: args.deploy_generation,
1070 now: SYSTEM_TIME.clone(),
1071 metrics_registry: metrics_registry.clone(),
1072 persist_pubsub_url: args.persist_pubsub_url,
1073 connection_context,
1074 secrets_args: SecretsReaderCliArgs {
1077 secrets_reader: args.secrets_controller,
1078 secrets_reader_local_file_dir: args.orchestrator_process_secrets_directory,
1079 secrets_reader_kubernetes_context: Some(args.orchestrator_kubernetes_context),
1080 secrets_reader_aws_prefix: Some(aws_secrets_controller_prefix(&args.environment_id)),
1081 secrets_reader_name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
1082 },
1083 replica_http_locator: Arc::clone(&replica_http_locator),
1084 };
1085
1086 let cluster_replica_sizes = ClusterReplicaSizeMap::parse_from_str(
1087 &args.cluster_replica_sizes,
1088 !license_key.allow_credit_consumption_override,
1089 )
1090 .context("parsing replica size map")?;
1091
1092 emit_boot_diagnostics!(&BUILD_INFO);
1093 sys::adjust_rlimits();
1094
1095 info!(
1096 "startup: envd init: preamble complete in {:?}",
1097 envd_start.elapsed()
1098 );
1099
1100 let serve_start = Instant::now();
1101 info!("startup: envd init: serving beginning");
1102 let server = runtime.block_on(async {
1103 let listeners = Listeners::bind(listeners_config).await?;
1104 let catalog_config = CatalogConfig {
1105 persist_clients,
1106 metrics: Arc::new(mz_catalog::durable::Metrics::new(&metrics_registry)),
1107 };
1108 let server = listeners
1109 .serve(crate::Config {
1110 unsafe_mode: args.unsafe_mode,
1112 all_features: args.all_features,
1113 tls,
1115 tls_reload_certs: mz_server_core::default_cert_reload_ticker(),
1116 external_login_password_mz_system: args.external_login_password_mz_system,
1117 frontegg,
1118 frontegg_oauth_issuer_url,
1119 cors_allowed_origin,
1120 cors_allowed_origin_list,
1121 egress_addresses: args.announce_egress_address,
1122 http_host_name: args.http_host_name,
1123 internal_console_redirect_url: args.internal_console_redirect_url,
1124 controller,
1126 secrets_controller,
1127 cloud_resource_controller,
1128 system_dyncfgs,
1129 storage_usage_collection_interval: args.storage_usage_collection_interval_sec,
1131 storage_usage_retention_period: args.storage_usage_retention_period,
1132 catalog_config,
1134 availability_zones: args.availability_zone,
1135 cluster_replica_sizes,
1136 timestamp_oracle_url,
1137 segment_api_key: args.segment_api_key,
1138 segment_client_side: args.segment_client_side,
1139 test_only_dummy_segment_client: args.test_only_dummy_segment_client,
1140 launchdarkly_sdk_key: args.launchdarkly_sdk_key,
1141 launchdarkly_base_uri: args.launchdarkly_base_uri,
1142 launchdarkly_key_map: args
1143 .launchdarkly_key_map
1144 .into_iter()
1145 .map(|kv| (kv.key, kv.value))
1146 .collect(),
1147 config_sync_timeout: args.config_sync_timeout,
1148 config_sync_loop_interval: args.config_sync_loop_interval,
1149 config_sync_file_path: args.config_sync_file_path,
1150
1151 environment_id: args.environment_id,
1153 bootstrap_role: args.bootstrap_role,
1154 bootstrap_default_cluster_replica_size: args.bootstrap_default_cluster_replica_size,
1155 bootstrap_default_cluster_replication_factor: args
1156 .bootstrap_default_cluster_replication_factor,
1157 bootstrap_builtin_system_cluster_config: BootstrapBuiltinClusterConfig {
1158 size: args.bootstrap_builtin_system_cluster_replica_size,
1159 replication_factor: args.bootstrap_builtin_system_cluster_replication_factor,
1160 },
1161 bootstrap_builtin_catalog_server_cluster_config: BootstrapBuiltinClusterConfig {
1162 size: args.bootstrap_builtin_catalog_server_cluster_replica_size,
1163 replication_factor: args
1164 .bootstrap_builtin_catalog_server_cluster_replication_factor,
1165 },
1166 bootstrap_builtin_probe_cluster_config: BootstrapBuiltinClusterConfig {
1167 size: args.bootstrap_builtin_probe_cluster_replica_size,
1168 replication_factor: args.bootstrap_builtin_probe_cluster_replication_factor,
1169 },
1170 bootstrap_builtin_support_cluster_config: BootstrapBuiltinClusterConfig {
1171 size: args.bootstrap_builtin_support_cluster_replica_size,
1172 replication_factor: args.bootstrap_builtin_support_cluster_replication_factor,
1173 },
1174 bootstrap_builtin_analytics_cluster_config: BootstrapBuiltinClusterConfig {
1175 size: args.bootstrap_builtin_analytics_cluster_replica_size,
1176 replication_factor: args.bootstrap_builtin_analytics_cluster_replication_factor,
1177 },
1178 system_parameter_defaults: args
1179 .system_parameter_default
1180 .into_iter()
1181 .map(|kv| (kv.key, kv.value))
1182 .collect(),
1183 helm_chart_version: args.helm_chart_version.clone(),
1184 license_key,
1185 aws_account_id: args.aws_account_id,
1187 aws_privatelink_availability_zones: args.aws_privatelink_availability_zones,
1188 metrics_registry,
1190 tracing_handle,
1191 now,
1193 force_builtin_schema_migration,
1194 })
1195 .await
1196 .maybe_terminate("booting server")?;
1197 Ok::<_, anyhow::Error>(server)
1198 })?;
1199 info!(
1200 "startup: envd init: serving complete in {:?}",
1201 serve_start.elapsed()
1202 );
1203
1204 let start_duration = envd_start.elapsed();
1205 metrics
1206 .start_time_environmentd
1207 .set(start_duration.as_millis().try_into().expect("must fit"));
1208 let span = span.exit();
1209 let id = span.context().span().span_context().trace_id();
1210 drop(span);
1211
1212 info!("startup: envd init: complete in {start_duration:?}");
1213
1214 println!(
1215 "environmentd {} listening...",
1216 BUILD_INFO.human_version(args.helm_chart_version)
1217 );
1218 for (name, handle) in &server.sql_listener_handles {
1219 println!("{} SQL address: {}", name, handle.local_addr);
1220 }
1221 for (name, handle) in &server.http_listener_handles {
1222 println!("{} HTTP address: {}", name, handle.local_addr);
1223 }
1224 println!(
1226 " Internal Persist PubSub address: {}",
1227 args.internal_persist_pubsub_listen_addr
1228 );
1229
1230 println!(" Root trace ID: {id}");
1231
1232 loop {
1234 thread::park();
1235 }
1236}
1237
1238fn build_info() -> Vec<String> {
1239 let openssl_version =
1240 unsafe { CStr::from_ptr(openssl_sys::OpenSSL_version(openssl_sys::OPENSSL_VERSION)) };
1241 let rdkafka_version = unsafe { CStr::from_ptr(rdkafka_sys::bindings::rd_kafka_version_str()) };
1242 vec![
1243 openssl_version.to_string_lossy().into_owned(),
1244 format!("librdkafka v{}", rdkafka_version.to_string_lossy()),
1245 ]
1246}
1247
1248#[derive(Debug, Clone)]
1249struct Metrics {
1250 pub start_time_environmentd: IntGauge,
1251}
1252
1253impl Metrics {
1254 pub fn register_into(registry: &MetricsRegistry, build_info: BuildInfo) -> Metrics {
1255 Metrics {
1256 start_time_environmentd: registry.register(metric!(
1257 name: "mz_start_time_environmentd",
1258 help: "Time in milliseconds from environmentd start until the adapter is ready.",
1259 const_labels: {
1260 "version" => build_info.version,
1261 "build_type" => if cfg!(release) { "release" } else { "debug" }
1262 },
1263 )),
1264 }
1265 }
1266}