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 #[clap(long, env = "RESTARTED_IN_PLACE", hide = true)]
110 restarted_in_place: bool,
111
112 #[clap(
116 long,
117 env = "LISTENERS_CONFIG_PATH",
118 value_name = "PATH",
119 action = ArgAction::Set,
120 )]
121 listeners_config_path: PathBuf,
122 #[clap(
124 long,
125 env = "EXTERNAL_LOGIN_PASSWORD_MZ_SYSTEM",
126 action = ArgAction::Set,
127 )]
128 external_login_password_mz_system: Option<Password>,
129 #[clap(
135 long,
136 value_name = "HOST:PORT",
137 env = "INTERNAL_PERSIST_PUBSUB_LISTEN_ADDR",
138 default_value = "127.0.0.1:6879",
139 action = ArgAction::Set,
140 )]
141 internal_persist_pubsub_listen_addr: SocketAddr,
142 #[structopt(long, env = "CORS_ALLOWED_ORIGIN")]
151 cors_allowed_origin: Vec<HeaderValue>,
152 #[clap(
155 long,
156 env = "ANNOUNCE_EGRESS_ADDRESS",
157 action = ArgAction::Append,
158 use_value_delimiter = true
159 )]
160 announce_egress_address: Vec<IpNet>,
161 #[clap(long, env = "HTTP_HOST_NAME")]
170 http_host_name: Option<String>,
171 #[clap(long, env = "INTERNAL_CONSOLE_REDIRECT_URL")]
174 internal_console_redirect_url: Option<String>,
175 #[clap(flatten)]
177 tls: TlsCliArgs,
178 #[clap(flatten)]
180 frontegg: FronteggCliArgs,
181 #[structopt(long, value_enum, env = "ORCHESTRATOR")]
184 orchestrator: OrchestratorKind,
185 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SCHEDULER_NAME")]
187 orchestrator_kubernetes_scheduler_name: Option<String>,
188 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_PRIORITY_CLASS_NAME")]
190 orchestrator_kubernetes_priority_class_name: Option<String>,
191 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_ANNOTATION")]
194 orchestrator_kubernetes_service_annotation: Vec<KeyValueArg<String, String>>,
195 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_LABEL")]
198 orchestrator_kubernetes_service_label: Vec<KeyValueArg<String, String>>,
199 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_NODE_SELECTOR")]
202 orchestrator_kubernetes_service_node_selector: Vec<KeyValueArg<String, String>>,
203 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_AFFINITY")]
206 orchestrator_kubernetes_service_affinity: Option<String>,
207 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_TOLERATIONS")]
210 orchestrator_kubernetes_service_tolerations: Option<String>,
211 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_ACCOUNT")]
214 orchestrator_kubernetes_service_account: Option<String>,
215 #[structopt(
220 long,
221 env = "ORCHESTRATOR_KUBERNETES_CONTEXT",
222 default_value = "minikube"
223 )]
224 orchestrator_kubernetes_context: String,
225 #[structopt(
228 long,
229 env = "ORCHESTRATOR_KUBERNETES_IMAGE_PULL_POLICY",
230 default_value = "always",
231 value_enum
232 )]
233 orchestrator_kubernetes_image_pull_policy: KubernetesImagePullPolicy,
234 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_INIT_CONTAINER_IMAGE")]
236 orchestrator_kubernetes_init_container_image: Option<String>,
237 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_EPHEMERAL_VOLUME_CLASS")]
243 orchestrator_kubernetes_ephemeral_volume_class: Option<String>,
244 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_FS_GROUP")]
246 orchestrator_kubernetes_service_fs_group: Option<i64>,
247 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_NAME_PREFIX")]
249 orchestrator_kubernetes_name_prefix: Option<String>,
250 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_DISABLE_POD_METRICS_COLLECTION")]
255 orchestrator_kubernetes_disable_pod_metrics_collection: bool,
256 #[clap(
258 long,
259 env = "ORCHESTRATOR_KUBERNETES_ENABLE_PROMETHEUS_SCRAPE_ANNOTATIONS"
260 )]
261 orchestrator_kubernetes_enable_prometheus_scrape_annotations: bool,
262 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_ENABLE_USER_NAMESPACES")]
265 orchestrator_kubernetes_enable_user_namespaces: bool,
266 #[clap(long, env = "ORCHESTRATOR_PROCESS_WRAPPER")]
267 orchestrator_process_wrapper: Option<String>,
268 #[clap(
270 long,
271 env = "ORCHESTRATOR_PROCESS_SECRETS_DIRECTORY",
272 value_name = "PATH",
273 required_if_eq("orchestrator", "process")
274 )]
275 orchestrator_process_secrets_directory: Option<PathBuf>,
276 #[clap(long, env = "ORCHESTRATOR_PROCESS_PROPAGATE_CRASHES")]
279 orchestrator_process_propagate_crashes: bool,
280 #[clap(long, env = "ORCHESTRATOR_PROCESS_TCP_PROXY_LISTEN_ADDR")]
292 orchestrator_process_tcp_proxy_listen_addr: Option<IpAddr>,
293 #[clap(
306 long,
307 env = "ORCHESTRATOR_PROCESS_PROMETHEUS_SERVICE_DISCOVERY_DIRECTORY"
308 )]
309 orchestrator_process_prometheus_service_discovery_directory: Option<PathBuf>,
310 #[clap(
312 long,
313 env = "ORCHESTRATOR_PROCESS_SCRATCH_DIRECTORY",
314 value_name = "PATH"
315 )]
316 orchestrator_process_scratch_directory: Option<PathBuf>,
317 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_COVERAGE")]
320 orchestrator_kubernetes_coverage: bool,
321 #[structopt(
323 long,
324 value_enum,
325 env = "SECRETS_CONTROLLER",
326 default_value_ifs([
327 ("orchestrator", "kubernetes", Some("kubernetes")),
328 ("orchestrator", "process", Some("local-file"))
329 ]),
330 default_value("kubernetes"), )]
332 secrets_controller: SecretsControllerKind,
333 #[clap(
336 long,
337 env = "AWS_SECRETS_CONTROLLER_TAGS",
338 action = ArgAction::Append,
339 value_delimiter = ';',
340 required_if_eq("secrets_controller", "aws-secrets-manager")
341 )]
342 aws_secrets_controller_tags: Vec<KeyValueArg<String, String>>,
343 #[structopt(
345 long,
346 env = "CLUSTERD_IMAGE",
347 required_if_eq("orchestrator", "kubernetes"),
348 default_value_if("orchestrator", "process", Some("clusterd"))
349 )]
350 clusterd_image: Option<String>,
351 #[clap(long, env = "DEPLOY_GENERATION", default_value = "0")]
356 deploy_generation: u64,
357
358 #[clap(
361 long,
362 env = "METADATA_BACKEND_URL",
363 conflicts_with_all = &[
364 "persist_consensus_url",
365 "timestamp_oracle_url",
366 ],
367 )]
368 metadata_backend_url: Option<SensitiveUrl>,
369
370 #[clap(long, env = "HELM_CHART_VERSION")]
375 helm_chart_version: Option<String>,
376
377 #[clap(long, env = "PERSIST_BLOB_URL")]
380 persist_blob_url: SensitiveUrl,
381 #[clap(long, env = "PERSIST_CONSENSUS_URL")]
383 persist_consensus_url: Option<SensitiveUrl>,
384 #[clap(
388 long,
389 env = "PERSIST_PUBSUB_URL",
390 default_value = "http://localhost:6879"
391 )]
392 persist_pubsub_url: String,
393 #[clap(long, env = "PERSIST_ISOLATED_RUNTIME_THREADS")]
397 persist_isolated_runtime_threads: Option<isize>,
398 #[clap(
400 long,
401 env = "STORAGE_USAGE_COLLECTION_INTERVAL",
402 value_parser = humantime::parse_duration,
403 default_value = "3600s"
404 )]
405 storage_usage_collection_interval_sec: Duration,
406 #[clap(long, env = "STORAGE_USAGE_RETENTION_PERIOD", value_parser = humantime::parse_duration)]
410 storage_usage_retention_period: Option<Duration>,
411
412 #[clap(long, env = "TIMESTAMP_ORACLE_URL", value_name = "POSTGRES_URL")]
415 timestamp_oracle_url: Option<SensitiveUrl>,
416 #[clap(long, env = "AVAILABILITY_ZONE", use_value_delimiter = true)]
419 availability_zone: Vec<String>,
420 #[clap(
422 long,
423 env = "CLUSTER_REPLICA_SIZES",
424 requires = "bootstrap_default_cluster_replica_size"
425 )]
426 cluster_replica_sizes: String,
427 #[clap(long, env = "SEGMENT_API_KEY")]
429 segment_api_key: Option<String>,
430 #[clap(long, env = "SEGMENT_CLIENT_SIDE")]
436 segment_client_side: bool,
437 #[clap(long, env = "TEST_ONLY_DUMMY_SEGMENT_CLIENT")]
440 test_only_dummy_segment_client: bool,
441 #[clap(long, env = "LAUNCHDARKLY_SDK_KEY")]
447 launchdarkly_sdk_key: Option<String>,
448 #[clap(long, env = "LAUNCHDARKLY_BASE_URI", value_name = "URL")]
452 launchdarkly_base_uri: Option<String>,
453 #[clap(
460 long,
461 env = "LAUNCHDARKLY_KEY_MAP",
462 action = ArgAction::Append,
463 value_delimiter = ';'
464 )]
465 launchdarkly_key_map: Vec<KeyValueArg<String, String>>,
466 #[clap(
468 long,
469 env = "CONFIG_SYNC_TIMEOUT",
470 value_parser = humantime::parse_duration,
471 default_value = "30s"
472 )]
473 config_sync_timeout: Duration,
474 #[clap(
480 long,
481 env = "CONFIG_SYNC_LOOP_INTERVAL",
482 value_parser = humantime::parse_duration,
483 )]
484 config_sync_loop_interval: Option<Duration>,
485 #[clap(long, env = "CONFIG_SYNC_FILE_PATH", value_name = "PATH")]
488 config_sync_file_path: Option<PathBuf>,
489
490 #[clap(
492 long,
493 env = "ENVIRONMENT_ID",
494 value_name = "<CLOUD>-<REGION>-<ORG-ID>-<ORDINAL>"
495 )]
496 environment_id: EnvironmentId,
497 #[clap(long, env = "BOOTSTRAP_ROLE")]
506 bootstrap_role: Option<String>,
507 #[clap(
509 long,
510 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICA_SIZE",
511 default_value = "scale=1,workers=1"
512 )]
513 bootstrap_default_cluster_replica_size: String,
514 #[clap(
516 long,
517 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICA_SIZE",
518 default_value = "scale=1,workers=1"
519 )]
520 bootstrap_builtin_system_cluster_replica_size: String,
521 #[clap(
523 long,
524 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICA_SIZE",
525 default_value = "scale=1,workers=1"
526 )]
527 bootstrap_builtin_catalog_server_cluster_replica_size: String,
528 #[clap(
530 long,
531 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICA_SIZE",
532 default_value = "scale=1,workers=1"
533 )]
534 bootstrap_builtin_probe_cluster_replica_size: String,
535 #[clap(
537 long,
538 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICA_SIZE",
539 default_value = "scale=1,workers=1"
540 )]
541 bootstrap_builtin_support_cluster_replica_size: String,
542 #[clap(
544 long,
545 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICA_SIZE",
546 default_value = "scale=1,workers=1"
547 )]
548 bootstrap_builtin_analytics_cluster_replica_size: String,
549 #[clap(
550 long,
551 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICATION_FACTOR",
552 default_value = DEFAULT_REPLICATION_FACTOR.to_string(),
553 value_parser = clap::value_parser!(u32).range(0..=2)
554 )]
555 bootstrap_default_cluster_replication_factor: u32,
556 #[clap(
558 long,
559 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICATION_FACTOR",
560 default_value = SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
561 value_parser = clap::value_parser!(u32).range(0..=2)
562 )]
563 bootstrap_builtin_system_cluster_replication_factor: u32,
564 #[clap(
566 long,
567 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICATION_FACTOR",
568 default_value = CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
569 value_parser = clap::value_parser!(u32).range(0..=2)
570 )]
571 bootstrap_builtin_catalog_server_cluster_replication_factor: u32,
572 #[clap(
574 long,
575 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICATION_FACTOR",
576 default_value = PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
577 value_parser = clap::value_parser!(u32).range(0..=2)
578 )]
579 bootstrap_builtin_probe_cluster_replication_factor: u32,
580 #[clap(
582 long,
583 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICATION_FACTOR",
584 default_value = SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
585 value_parser = clap::value_parser!(u32).range(0..=2)
586 )]
587 bootstrap_builtin_support_cluster_replication_factor: u32,
588 #[clap(
590 long,
591 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICATION_FACTOR",
592 default_value = ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
593 value_parser = clap::value_parser!(u32).range(0..=2)
594 )]
595 bootstrap_builtin_analytics_cluster_replication_factor: u32,
596 #[clap(
599 long,
600 env = "SYSTEM_PARAMETER_DEFAULT",
601 action = ArgAction::Append,
602 value_delimiter = ';'
603 )]
604 system_parameter_default: Vec<KeyValueArg<String, String>>,
605 #[clap(long, env = "LICENSE_KEY")]
607 license_key: Option<String>,
608
609 #[clap(long, env = "AWS_ACCOUNT_ID")]
613 aws_account_id: Option<String>,
614 #[clap(long, env = "AWS_CONNECTION_ROLE_ARN")]
617 aws_connection_role_arn: Option<String>,
618 #[clap(long, env = "AWS_EXTERNAL_ID_PREFIX", value_name = "ID", value_parser = AwsExternalIdPrefix::new_from_cli_argument_or_environment_variable)]
622 aws_external_id_prefix: Option<AwsExternalIdPrefix>,
623 #[clap(
626 long,
627 env = "AWS_PRIVATELINK_AVAILABILITY_ZONES",
628 action = ArgAction::Append,
629 use_value_delimiter = true
630 )]
631 aws_privatelink_availability_zones: Option<Vec<String>>,
632
633 #[clap(flatten)]
635 tracing: TracingCliArgs,
636
637 #[clap(long, value_enum, requires = "unsafe_mode")]
644 unsafe_force_builtin_schema_migration: Option<String>,
645}
646
647#[derive(ValueEnum, Debug, Clone)]
648enum OrchestratorKind {
649 Kubernetes,
650 Process,
651}
652
653fn aws_secrets_controller_prefix(env_id: &EnvironmentId) -> String {
656 format!("/user-managed/{}/", env_id)
657}
658fn aws_secrets_controller_key_alias(env_id: &EnvironmentId) -> String {
659 format!("alias/customer_key_{}", env_id)
662}
663
664pub fn main() {
665 let args = cli::parse_args(CliConfig {
666 env_prefix: Some("MZ_"),
667 enable_version_flag: true,
668 });
669 if let Err(err) = run(args) {
670 panic!("environmentd: fatal: {}", err.display_with_causes());
671 }
672}
673
674fn run(mut args: Args) -> Result<(), anyhow::Error> {
675 mz_ore::panic::install_enhanced_handler();
676
677 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
684
685 let envd_start = Instant::now();
686
687 sys::enable_sigusr2_coverage_dump()?;
690 sys::enable_termination_signal_cleanup()?;
691
692 let license_key = if let Some(license_key_file) = args.license_key {
693 let license_key_text = std::fs::read_to_string(&license_key_file)
694 .context("failed to open license key file")?;
695 let license_key = mz_license_keys::validate(license_key_text.trim())
696 .context("failed to validate license key file")?;
697 if license_key.expired {
698 let message = format!(
699 "The license key provided at {license_key_file} is expired! Please contact Materialize for assistance."
700 );
701 match license_key.expiration_behavior {
702 ExpirationBehavior::Warn | ExpirationBehavior::DisableClusterCreation => {
703 warn!("{message}");
704 }
705 ExpirationBehavior::Disable => bail!("{message}"),
706 }
707 }
708 license_key
709 } else if matches!(args.orchestrator, OrchestratorKind::Kubernetes) {
710 bail!("--license-key is required when running in Kubernetes");
711 } else {
712 ValidatedLicenseKey::disabled()
714 };
715
716 let force_builtin_schema_migration = args
718 .unsafe_force_builtin_schema_migration
719 .inspect(|_mechanism| assert!(args.unsafe_mode));
720
721 let ncpus_useful = usize::max(1, cmp::min(num_cpus::get(), num_cpus::get_physical()));
724 let runtime = Arc::new(
725 tokio::runtime::Builder::new_multi_thread()
726 .worker_threads(ncpus_useful)
727 .thread_stack_size(3 * 1024 * 1024) .thread_name_fn(|| {
731 static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
732 let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
733 format!("tokio:work-{}", id)
734 })
735 .enable_all()
736 .build()?,
737 );
738
739 args.tracing.log_prefix = if matches!(args.orchestrator, OrchestratorKind::Process) {
743 Some("environmentd".to_string())
744 } else {
745 None
746 };
747
748 let metrics_registry = MetricsRegistry::new();
749 let tracing_handle = runtime.block_on(args.tracing.configure_tracing(
750 StaticTracingConfig {
751 service_name: "environmentd",
752 build_info: BUILD_INFO,
753 },
754 metrics_registry.clone(),
755 ))?;
756 register_runtime_metrics("main", runtime.metrics(), &metrics_registry);
757
758 let span = tracing::info_span!("environmentd::run").entered();
759
760 info!("startup: envd init: beginning");
761 info!("startup: envd init: preamble beginning");
762
763 let metrics = Metrics::register_into(&metrics_registry, BUILD_INFO);
764
765 runtime.block_on(mz_alloc::register_metrics_into(&metrics_registry));
766 runtime.block_on(mz_metrics::register_metrics_into(
767 &metrics_registry,
768 mz_dyncfgs::all_dyncfgs(),
769 None,
771 ));
772
773 let _failpoint_scenario = FailScenario::setup();
775
776 let tls = args.tls.into_config()?;
778 let frontegg_oauth_issuer_url = args.frontegg.oauth_issuer_url().map(str::to_string);
779 let frontegg = FronteggAuthenticator::from_args(args.frontegg, &metrics_registry)?;
780 let listeners_config: ListenersConfig = {
781 let f = File::open(args.listeners_config_path)?;
782 serde_json::from_reader(f)?
783 };
784
785 for (_, listener) in &listeners_config.sql {
786 listener
787 .validate()
788 .map_err(|e| anyhow::anyhow!("invalid SQL listener: {}", e))?;
789 }
790
791 for (_, listener) in &listeners_config.http {
792 listener
793 .validate()
794 .map_err(|e| anyhow::anyhow!("invalid HTTP listener: {}", e))?;
795 }
796
797 let allowed_origins = if !args.cors_allowed_origin.is_empty() {
799 args.cors_allowed_origin
800 } else {
801 let mut allowed_origins = Vec::with_capacity(listeners_config.http.len() * 6);
802 for (_, listener) in &listeners_config.http {
803 let port = listener.addr().port();
804 allowed_origins.extend([
805 HeaderValue::from_str(&format!("http://localhost:{}", port)).unwrap(),
806 HeaderValue::from_str(&format!("http://127.0.0.1:{}", port)).unwrap(),
807 HeaderValue::from_str(&format!("http://[::1]:{}", port)).unwrap(),
808 HeaderValue::from_str(&format!("https://localhost:{}", port)).unwrap(),
809 HeaderValue::from_str(&format!("https://127.0.0.1:{}", port)).unwrap(),
810 HeaderValue::from_str(&format!("https://[::1]:{}", port)).unwrap(),
811 ])
812 }
813 allowed_origins
814 };
815 let cors_allowed_origin = mz_http_util::build_cors_allowed_origin(&allowed_origins);
816 let cors_allowed_origin_list = allowed_origins.clone();
817
818 let entered = info_span!("environmentd::configure_controller").entered();
820 let (orchestrator, secrets_controller, cloud_resource_controller): (
821 Arc<dyn Orchestrator>,
822 Arc<dyn SecretsController>,
823 Option<Arc<dyn CloudResourceController>>,
824 ) = match args.orchestrator {
825 OrchestratorKind::Kubernetes => {
826 if args.orchestrator_process_scratch_directory.is_some() {
827 bail!(
828 "--orchestrator-process-scratch-directory is \
829 not currently usable with the kubernetes orchestrator"
830 );
831 }
832
833 let orchestrator = Arc::new(
834 runtime
835 .block_on(KubernetesOrchestrator::new(
836 KubernetesOrchestratorConfig {
837 context: args.orchestrator_kubernetes_context.clone(),
838 scheduler_name: args.orchestrator_kubernetes_scheduler_name,
839 priority_class_name: args.orchestrator_kubernetes_priority_class_name,
840 service_annotations: args
841 .orchestrator_kubernetes_service_annotation
842 .into_iter()
843 .map(|l| (l.key, l.value))
844 .collect(),
845 service_labels: args
846 .orchestrator_kubernetes_service_label
847 .into_iter()
848 .map(|l| (l.key, l.value))
849 .collect(),
850 service_node_selector: args
851 .orchestrator_kubernetes_service_node_selector
852 .into_iter()
853 .map(|l| (l.key, l.value))
854 .collect(),
855 service_affinity: args.orchestrator_kubernetes_service_affinity,
856 service_tolerations: args.orchestrator_kubernetes_service_tolerations,
857 service_account: args.orchestrator_kubernetes_service_account,
858 image_pull_policy: args.orchestrator_kubernetes_image_pull_policy,
859 aws_external_id_prefix: args.aws_external_id_prefix.clone(),
860 coverage: args.orchestrator_kubernetes_coverage,
861 ephemeral_volume_storage_class: args
862 .orchestrator_kubernetes_ephemeral_volume_class
863 .clone(),
864 service_fs_group: args.orchestrator_kubernetes_service_fs_group.clone(),
865 name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
866 collect_pod_metrics: !args
867 .orchestrator_kubernetes_disable_pod_metrics_collection,
868 enable_prometheus_scrape_annotations: args
869 .orchestrator_kubernetes_enable_prometheus_scrape_annotations,
870 enable_user_namespaces: args
871 .orchestrator_kubernetes_enable_user_namespaces,
872 },
873 &metrics_registry,
874 ))
875 .context("creating kubernetes orchestrator")?,
876 );
877 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
878 SecretsControllerKind::Kubernetes => {
879 let sc = Arc::clone(&orchestrator);
880 let sc: Arc<dyn SecretsController> = sc;
881 sc
882 }
883 SecretsControllerKind::AwsSecretsManager => {
884 Arc::new(
885 runtime.block_on(AwsSecretsController::new(
886 &aws_secrets_controller_prefix(&args.environment_id),
889 &aws_secrets_controller_key_alias(&args.environment_id),
890 args.aws_secrets_controller_tags
891 .into_iter()
892 .map(|tag| (tag.key, tag.value))
893 .collect(),
894 )),
895 )
896 }
897 SecretsControllerKind::LocalFile => bail!(
898 "SecretsControllerKind::LocalFile is not compatible with Orchestrator::Kubernetes."
899 ),
900 };
901 let cloud_resource_controller = Arc::clone(&orchestrator);
902 (
903 orchestrator,
904 secrets_controller,
905 Some(cloud_resource_controller),
906 )
907 }
908 OrchestratorKind::Process => {
909 if args
910 .orchestrator_kubernetes_ephemeral_volume_class
911 .is_some()
912 {
913 bail!(
914 "--orchestrator-kubernetes-ephemeral-volume-class is \
915 not usable with the process orchestrator"
916 );
917 }
918 let orchestrator = Arc::new(
919 runtime
920 .block_on(ProcessOrchestrator::new(ProcessOrchestratorConfig {
921 image_dir: env::current_exe()?.parent().unwrap().to_path_buf(),
927 suppress_output: false,
928 environment_id: args.environment_id.to_string(),
929 secrets_dir: args
930 .orchestrator_process_secrets_directory
931 .clone()
932 .expect("clap enforced"),
933 command_wrapper: args
934 .orchestrator_process_wrapper
935 .map_or(Ok(vec![]), |s| shell_words::split(&s))?,
936 propagate_crashes: args.orchestrator_process_propagate_crashes,
937 tcp_proxy: args.orchestrator_process_tcp_proxy_listen_addr.map(
938 |listen_addr| ProcessOrchestratorTcpProxyConfig {
939 listen_addr,
940 prometheus_service_discovery_dir: args
941 .orchestrator_process_prometheus_service_discovery_directory,
942 },
943 ),
944 scratch_directory: args
945 .orchestrator_process_scratch_directory
946 .expect("process orchestrator requires scratch directory"),
947 }))
948 .context("creating process orchestrator")?,
949 );
950 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
951 SecretsControllerKind::Kubernetes => bail!(
952 "SecretsControllerKind::Kubernetes is not compatible with Orchestrator::Process."
953 ),
954 SecretsControllerKind::AwsSecretsManager => Arc::new(
955 runtime.block_on(AwsSecretsController::new(
956 &aws_secrets_controller_prefix(&args.environment_id),
957 &aws_secrets_controller_key_alias(&args.environment_id),
958 args.aws_secrets_controller_tags
959 .into_iter()
960 .map(|tag| (tag.key, tag.value))
961 .collect(),
962 )),
963 ),
964 SecretsControllerKind::LocalFile => {
965 let sc = Arc::clone(&orchestrator);
966 let sc: Arc<dyn SecretsController> = sc;
967 sc
968 }
969 };
970 (orchestrator, secrets_controller, None)
971 }
972 };
973 drop(entered);
974 let cloud_resource_reader = cloud_resource_controller.as_ref().map(|c| c.reader());
975 let secrets_reader = secrets_controller.reader();
976 let now = SYSTEM_TIME.clone();
977
978 let mut persist_config =
979 PersistConfig::new(&BUILD_INFO, now.clone(), mz_dyncfgs::all_dyncfgs());
980 persist_config.disable_compaction();
982
983 let persist_pubsub_server = PersistGrpcPubSubServer::new(&persist_config, &metrics_registry);
984 let persist_pubsub_client = persist_pubsub_server.new_same_process_connection();
985
986 match args.persist_isolated_runtime_threads {
987 None | Some(0) => (),
989 Some(x @ ..=-1) => {
990 let threads = num_cpus::get().saturating_add_signed(x).max(1);
991 persist_config.isolated_runtime_worker_threads = threads;
992 }
993 Some(x @ 1..) => {
994 let threads = usize::try_from(x).expect("pattern matched a positive value");
995 persist_config.isolated_runtime_worker_threads = threads;
996 }
997 };
998
999 let _server = runtime.spawn_named(
1000 || "persist::rpc::server",
1001 async move {
1002 info!(
1003 "listening for Persist PubSub connections on {}",
1004 args.internal_persist_pubsub_listen_addr
1005 );
1006 let res = persist_pubsub_server
1009 .serve(args.internal_persist_pubsub_listen_addr)
1010 .await;
1011 error!("Persist Pubsub server exited {:?}", res);
1012 }
1013 .instrument(tracing::info_span!("persist::rpc::server")),
1014 );
1015
1016 let persist_clients = {
1017 let _tokio_guard = runtime.enter();
1019 PersistClientCache::new(persist_config, &metrics_registry, |cfg, metrics| {
1020 let sender: Arc<dyn PubSubSender> = Arc::new(MetricsSameProcessPubSubSender::new(
1021 cfg,
1022 persist_pubsub_client.sender,
1023 metrics,
1024 ));
1025 PubSubClientConnection::new(sender, persist_pubsub_client.receiver)
1026 })
1027 };
1028
1029 let consensus_uri = args.persist_consensus_url.unwrap_or_else(|| {
1030 args.metadata_backend_url
1031 .as_ref()
1032 .map(|metadata_backend_url| {
1033 SensitiveUrl(
1034 Url::parse_with_params(
1035 metadata_backend_url.0.as_ref(),
1036 &[("options", "--search_path=consensus")],
1037 )
1038 .unwrap(),
1039 )
1040 })
1041 .expect("either --persist-consensus-url or --metadata-backend-url must be provided")
1042 });
1043 let timestamp_oracle_url = args.timestamp_oracle_url.or_else(|| {
1044 args.metadata_backend_url
1045 .as_ref()
1046 .map(|metadata_backend_url| {
1047 SensitiveUrl(
1048 Url::parse_with_params(
1049 metadata_backend_url.0.as_ref(),
1050 &[("options", "--search_path=tsoracle")],
1051 )
1052 .unwrap(),
1053 )
1054 })
1055 });
1056
1057 let persist_clients = Arc::new(persist_clients);
1058 let system_dyncfgs = Arc::clone(&persist_clients.cfg().configs);
1059 let connection_context = ConnectionContext::from_cli_args(
1060 args.environment_id.to_string(),
1061 &args.tracing.startup_log_filter,
1062 args.aws_external_id_prefix,
1063 args.aws_connection_role_arn,
1064 secrets_reader,
1065 cloud_resource_reader,
1066 );
1067 let orchestrator = Arc::new(TracingOrchestrator::new(orchestrator, args.tracing.clone()));
1068 let replica_http_locator = Arc::new(ReplicaHttpLocator::default());
1069 let controller = ControllerConfig {
1070 build_info: &BUILD_INFO,
1071 orchestrator,
1072 persist_location: PersistLocation {
1073 blob_uri: args.persist_blob_url,
1074 consensus_uri,
1075 },
1076 persist_clients: Arc::clone(&persist_clients),
1077 clusterd_image: args.clusterd_image.expect("clap enforced"),
1078 init_container_image: args.orchestrator_kubernetes_init_container_image,
1079 deploy_generation: args.deploy_generation,
1080 now: SYSTEM_TIME.clone(),
1081 metrics_registry: metrics_registry.clone(),
1082 persist_pubsub_url: args.persist_pubsub_url,
1083 connection_context,
1084 secrets_args: SecretsReaderCliArgs {
1087 secrets_reader: args.secrets_controller,
1088 secrets_reader_local_file_dir: args.orchestrator_process_secrets_directory,
1089 secrets_reader_kubernetes_context: Some(args.orchestrator_kubernetes_context),
1090 secrets_reader_aws_prefix: Some(aws_secrets_controller_prefix(&args.environment_id)),
1091 secrets_reader_name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
1092 },
1093 replica_http_locator: Arc::clone(&replica_http_locator),
1094 };
1095
1096 let cluster_replica_sizes = ClusterReplicaSizeMap::parse_from_str(
1097 &args.cluster_replica_sizes,
1098 !license_key.allow_credit_consumption_override,
1099 )
1100 .context("parsing replica size map")?;
1101
1102 emit_boot_diagnostics!(&BUILD_INFO);
1103 sys::adjust_rlimits();
1104
1105 info!(
1106 "startup: envd init: preamble complete in {:?}",
1107 envd_start.elapsed()
1108 );
1109
1110 let serve_start = Instant::now();
1111 info!("startup: envd init: serving beginning");
1112 let server = runtime.block_on(async {
1113 let listeners = Listeners::bind(listeners_config)?;
1114 let catalog_config = CatalogConfig {
1115 persist_clients,
1116 metrics: Arc::new(mz_catalog::durable::Metrics::new(&metrics_registry)),
1117 };
1118 let server = listeners
1119 .serve(crate::Config {
1120 unsafe_mode: args.unsafe_mode,
1122 all_features: args.all_features,
1123 restarted_in_place: args.restarted_in_place,
1124 tls,
1126 tls_reload_certs: mz_server_core::default_cert_reload_ticker(),
1127 external_login_password_mz_system: args.external_login_password_mz_system,
1128 frontegg,
1129 frontegg_oauth_issuer_url,
1130 cors_allowed_origin,
1131 cors_allowed_origin_list,
1132 egress_addresses: args.announce_egress_address,
1133 http_host_name: args.http_host_name,
1134 internal_console_redirect_url: args.internal_console_redirect_url,
1135 controller,
1137 secrets_controller,
1138 cloud_resource_controller,
1139 system_dyncfgs,
1140 storage_usage_collection_interval: args.storage_usage_collection_interval_sec,
1142 storage_usage_retention_period: args.storage_usage_retention_period,
1143 catalog_config,
1145 availability_zones: args.availability_zone,
1146 cluster_replica_sizes,
1147 timestamp_oracle_url,
1148 segment_api_key: args.segment_api_key,
1149 segment_client_side: args.segment_client_side,
1150 test_only_dummy_segment_client: args.test_only_dummy_segment_client,
1151 launchdarkly_sdk_key: args.launchdarkly_sdk_key,
1152 launchdarkly_base_uri: args.launchdarkly_base_uri,
1153 launchdarkly_key_map: args
1154 .launchdarkly_key_map
1155 .into_iter()
1156 .map(|kv| (kv.key, kv.value))
1157 .collect(),
1158 config_sync_timeout: args.config_sync_timeout,
1159 config_sync_loop_interval: args.config_sync_loop_interval,
1160 config_sync_file_path: args.config_sync_file_path,
1161
1162 environment_id: args.environment_id,
1164 bootstrap_role: args.bootstrap_role,
1165 bootstrap_default_cluster_replica_size: args.bootstrap_default_cluster_replica_size,
1166 bootstrap_default_cluster_replication_factor: args
1167 .bootstrap_default_cluster_replication_factor,
1168 bootstrap_builtin_system_cluster_config: BootstrapBuiltinClusterConfig {
1169 size: args.bootstrap_builtin_system_cluster_replica_size,
1170 replication_factor: args.bootstrap_builtin_system_cluster_replication_factor,
1171 },
1172 bootstrap_builtin_catalog_server_cluster_config: BootstrapBuiltinClusterConfig {
1173 size: args.bootstrap_builtin_catalog_server_cluster_replica_size,
1174 replication_factor: args
1175 .bootstrap_builtin_catalog_server_cluster_replication_factor,
1176 },
1177 bootstrap_builtin_probe_cluster_config: BootstrapBuiltinClusterConfig {
1178 size: args.bootstrap_builtin_probe_cluster_replica_size,
1179 replication_factor: args.bootstrap_builtin_probe_cluster_replication_factor,
1180 },
1181 bootstrap_builtin_support_cluster_config: BootstrapBuiltinClusterConfig {
1182 size: args.bootstrap_builtin_support_cluster_replica_size,
1183 replication_factor: args.bootstrap_builtin_support_cluster_replication_factor,
1184 },
1185 bootstrap_builtin_analytics_cluster_config: BootstrapBuiltinClusterConfig {
1186 size: args.bootstrap_builtin_analytics_cluster_replica_size,
1187 replication_factor: args.bootstrap_builtin_analytics_cluster_replication_factor,
1188 },
1189 system_parameter_defaults: args
1190 .system_parameter_default
1191 .into_iter()
1192 .map(|kv| (kv.key, kv.value))
1193 .collect(),
1194 helm_chart_version: args.helm_chart_version.clone(),
1195 license_key,
1196 aws_account_id: args.aws_account_id,
1198 aws_privatelink_availability_zones: args.aws_privatelink_availability_zones,
1199 metrics_registry,
1201 tracing_handle,
1202 now,
1204 force_builtin_schema_migration,
1205 })
1206 .await
1207 .maybe_terminate("booting server")?;
1208 Ok::<_, anyhow::Error>(server)
1209 })?;
1210 info!(
1211 "startup: envd init: serving complete in {:?}",
1212 serve_start.elapsed()
1213 );
1214
1215 let start_duration = envd_start.elapsed();
1216 metrics
1217 .start_time_environmentd
1218 .set(start_duration.as_millis().try_into().expect("must fit"));
1219 let span = span.exit();
1220 let id = span.context().span().span_context().trace_id();
1221 drop(span);
1222
1223 info!("startup: envd init: complete in {start_duration:?}");
1224
1225 println!(
1226 "environmentd {} listening...",
1227 BUILD_INFO.human_version(args.helm_chart_version)
1228 );
1229 for (name, handle) in &server.sql_listener_handles {
1230 println!("{} SQL address: {}", name, handle.local_addr);
1231 }
1232 for (name, handle) in &server.http_listener_handles {
1233 println!("{} HTTP address: {}", name, handle.local_addr);
1234 }
1235 println!(
1237 " Internal Persist PubSub address: {}",
1238 args.internal_persist_pubsub_listen_addr
1239 );
1240
1241 println!(" Root trace ID: {id}");
1242
1243 loop {
1245 thread::park();
1246 }
1247}
1248
1249fn build_info() -> Vec<String> {
1250 let openssl_version =
1251 unsafe { CStr::from_ptr(openssl_sys::OpenSSL_version(openssl_sys::OPENSSL_VERSION)) };
1252 let rdkafka_version = unsafe { CStr::from_ptr(rdkafka_sys::bindings::rd_kafka_version_str()) };
1253 vec![
1254 openssl_version.to_string_lossy().into_owned(),
1255 format!("librdkafka v{}", rdkafka_version.to_string_lossy()),
1256 ]
1257}
1258
1259#[derive(Debug, Clone)]
1260struct Metrics {
1261 pub start_time_environmentd: IntGauge,
1262}
1263
1264impl Metrics {
1265 pub fn register_into(registry: &MetricsRegistry, build_info: BuildInfo) -> Metrics {
1266 Metrics {
1267 start_time_environmentd: registry.register(metric!(
1268 name: "mz_start_time_environmentd",
1269 help: "Time in milliseconds from environmentd start until the adapter is ready.",
1270 const_labels: {
1271 "version" => build_info.version,
1272 "build_type" => if cfg!(release) { "release" } else { "debug" }
1273 },
1274 )),
1275 }
1276 }
1277}