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_server_core::TlsCliArgs;
69use mz_service::emit_boot_diagnostics;
70use mz_service::secrets::{SecretsControllerKind, SecretsReaderCliArgs};
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_SERVICE_ANNOTATION")]
187 orchestrator_kubernetes_service_annotation: Vec<KeyValueArg<String, String>>,
188 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_LABEL")]
191 orchestrator_kubernetes_service_label: Vec<KeyValueArg<String, String>>,
192 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_NODE_SELECTOR")]
195 orchestrator_kubernetes_service_node_selector: Vec<KeyValueArg<String, String>>,
196 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_AFFINITY")]
199 orchestrator_kubernetes_service_affinity: Option<String>,
200 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_TOLERATIONS")]
203 orchestrator_kubernetes_service_tolerations: Option<String>,
204 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_ACCOUNT")]
207 orchestrator_kubernetes_service_account: Option<String>,
208 #[structopt(
213 long,
214 env = "ORCHESTRATOR_KUBERNETES_CONTEXT",
215 default_value = "minikube"
216 )]
217 orchestrator_kubernetes_context: String,
218 #[structopt(
221 long,
222 env = "ORCHESTRATOR_KUBERNETES_IMAGE_PULL_POLICY",
223 default_value = "always",
224 value_enum
225 )]
226 orchestrator_kubernetes_image_pull_policy: KubernetesImagePullPolicy,
227 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_INIT_CONTAINER_IMAGE")]
229 orchestrator_kubernetes_init_container_image: Option<String>,
230 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_EPHEMERAL_VOLUME_CLASS")]
236 orchestrator_kubernetes_ephemeral_volume_class: Option<String>,
237 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_SERVICE_FS_GROUP")]
239 orchestrator_kubernetes_service_fs_group: Option<i64>,
240 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_NAME_PREFIX")]
242 orchestrator_kubernetes_name_prefix: Option<String>,
243 #[clap(long, env = "ORCHESTRATOR_KUBERNETES_DISABLE_POD_METRICS_COLLECTION")]
248 orchestrator_kubernetes_disable_pod_metrics_collection: bool,
249 #[clap(
251 long,
252 env = "ORCHESTRATOR_KUBERNETES_ENABLE_PROMETHEUS_SCRAPE_ANNOTATIONS"
253 )]
254 orchestrator_kubernetes_enable_prometheus_scrape_annotations: bool,
255 #[clap(long, env = "ORCHESTRATOR_PROCESS_WRAPPER")]
256 orchestrator_process_wrapper: Option<String>,
257 #[clap(
259 long,
260 env = "ORCHESTRATOR_PROCESS_SECRETS_DIRECTORY",
261 value_name = "PATH",
262 required_if_eq("orchestrator", "process")
263 )]
264 orchestrator_process_secrets_directory: Option<PathBuf>,
265 #[clap(long, env = "ORCHESTRATOR_PROCESS_PROPAGATE_CRASHES")]
268 orchestrator_process_propagate_crashes: bool,
269 #[clap(long, env = "ORCHESTRATOR_PROCESS_TCP_PROXY_LISTEN_ADDR")]
281 orchestrator_process_tcp_proxy_listen_addr: Option<IpAddr>,
282 #[clap(
295 long,
296 env = "ORCHESTRATOR_PROCESS_PROMETHEUS_SERVICE_DISCOVERY_DIRECTORY"
297 )]
298 orchestrator_process_prometheus_service_discovery_directory: Option<PathBuf>,
299 #[clap(
301 long,
302 env = "ORCHESTRATOR_PROCESS_SCRATCH_DIRECTORY",
303 value_name = "PATH"
304 )]
305 orchestrator_process_scratch_directory: Option<PathBuf>,
306 #[structopt(long, env = "ORCHESTRATOR_KUBERNETES_COVERAGE")]
309 orchestrator_kubernetes_coverage: bool,
310 #[structopt(
312 long,
313 value_enum,
314 env = "SECRETS_CONTROLLER",
315 default_value_ifs([
316 ("orchestrator", "kubernetes", Some("kubernetes")),
317 ("orchestrator", "process", Some("local-file"))
318 ]),
319 default_value("kubernetes"), )]
321 secrets_controller: SecretsControllerKind,
322 #[clap(
325 long,
326 env = "AWS_SECRETS_CONTROLLER_TAGS",
327 action = ArgAction::Append,
328 value_delimiter = ';',
329 required_if_eq("secrets_controller", "aws-secrets-manager")
330 )]
331 aws_secrets_controller_tags: Vec<KeyValueArg<String, String>>,
332 #[structopt(
334 long,
335 env = "CLUSTERD_IMAGE",
336 required_if_eq("orchestrator", "kubernetes"),
337 default_value_if("orchestrator", "process", Some("clusterd"))
338 )]
339 clusterd_image: Option<String>,
340 #[clap(long, env = "DEPLOY_GENERATION", default_value = "0")]
345 deploy_generation: u64,
346
347 #[clap(
350 long,
351 env = "METADATA_BACKEND_URL",
352 conflicts_with_all = &[
353 "persist_consensus_url",
354 "timestamp_oracle_url",
355 ],
356 )]
357 metadata_backend_url: Option<SensitiveUrl>,
358
359 #[clap(long, env = "HELM_CHART_VERSION")]
364 helm_chart_version: Option<String>,
365
366 #[clap(long, env = "PERSIST_BLOB_URL")]
369 persist_blob_url: SensitiveUrl,
370 #[clap(long, env = "PERSIST_CONSENSUS_URL")]
372 persist_consensus_url: Option<SensitiveUrl>,
373 #[clap(
377 long,
378 env = "PERSIST_PUBSUB_URL",
379 default_value = "http://localhost:6879"
380 )]
381 persist_pubsub_url: String,
382 #[clap(long, env = "PERSIST_ISOLATED_RUNTIME_THREADS")]
386 persist_isolated_runtime_threads: Option<isize>,
387 #[clap(
389 long,
390 env = "STORAGE_USAGE_COLLECTION_INTERVAL",
391 value_parser = humantime::parse_duration,
392 default_value = "3600s"
393 )]
394 storage_usage_collection_interval_sec: Duration,
395 #[clap(long, env = "STORAGE_USAGE_RETENTION_PERIOD", value_parser = humantime::parse_duration)]
399 storage_usage_retention_period: Option<Duration>,
400
401 #[clap(long, env = "TIMESTAMP_ORACLE_URL", value_name = "POSTGRES_URL")]
404 timestamp_oracle_url: Option<SensitiveUrl>,
405 #[clap(long, env = "AVAILABILITY_ZONE", use_value_delimiter = true)]
408 availability_zone: Vec<String>,
409 #[clap(
411 long,
412 env = "CLUSTER_REPLICA_SIZES",
413 requires = "bootstrap_default_cluster_replica_size"
414 )]
415 cluster_replica_sizes: String,
416 #[clap(long, env = "SEGMENT_API_KEY")]
418 segment_api_key: Option<String>,
419 #[clap(long, env = "SEGMENT_CLIENT_SIDE")]
425 segment_client_side: bool,
426 #[clap(long, env = "TEST_ONLY_DUMMY_SEGMENT_CLIENT")]
429 test_only_dummy_segment_client: bool,
430 #[clap(long, env = "LAUNCHDARKLY_SDK_KEY")]
436 launchdarkly_sdk_key: Option<String>,
437 #[clap(long, env = "LAUNCHDARKLY_BASE_URI", value_name = "URL")]
441 launchdarkly_base_uri: Option<String>,
442 #[clap(
449 long,
450 env = "LAUNCHDARKLY_KEY_MAP",
451 action = ArgAction::Append,
452 value_delimiter = ';'
453 )]
454 launchdarkly_key_map: Vec<KeyValueArg<String, String>>,
455 #[clap(
457 long,
458 env = "CONFIG_SYNC_TIMEOUT",
459 value_parser = humantime::parse_duration,
460 default_value = "30s"
461 )]
462 config_sync_timeout: Duration,
463 #[clap(
469 long,
470 env = "CONFIG_SYNC_LOOP_INTERVAL",
471 value_parser = humantime::parse_duration,
472 )]
473 config_sync_loop_interval: Option<Duration>,
474 #[clap(long, env = "CONFIG_SYNC_FILE_PATH", value_name = "PATH")]
477 config_sync_file_path: Option<PathBuf>,
478
479 #[clap(
481 long,
482 env = "ENVIRONMENT_ID",
483 value_name = "<CLOUD>-<REGION>-<ORG-ID>-<ORDINAL>"
484 )]
485 environment_id: EnvironmentId,
486 #[clap(long, env = "BOOTSTRAP_ROLE")]
495 bootstrap_role: Option<String>,
496 #[clap(
498 long,
499 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICA_SIZE",
500 default_value = "scale=1,workers=1"
501 )]
502 bootstrap_default_cluster_replica_size: String,
503 #[clap(
505 long,
506 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICA_SIZE",
507 default_value = "scale=1,workers=1"
508 )]
509 bootstrap_builtin_system_cluster_replica_size: String,
510 #[clap(
512 long,
513 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICA_SIZE",
514 default_value = "scale=1,workers=1"
515 )]
516 bootstrap_builtin_catalog_server_cluster_replica_size: String,
517 #[clap(
519 long,
520 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICA_SIZE",
521 default_value = "scale=1,workers=1"
522 )]
523 bootstrap_builtin_probe_cluster_replica_size: String,
524 #[clap(
526 long,
527 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICA_SIZE",
528 default_value = "scale=1,workers=1"
529 )]
530 bootstrap_builtin_support_cluster_replica_size: String,
531 #[clap(
533 long,
534 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICA_SIZE",
535 default_value = "scale=1,workers=1"
536 )]
537 bootstrap_builtin_analytics_cluster_replica_size: String,
538 #[clap(
539 long,
540 env = "BOOTSTRAP_DEFAULT_CLUSTER_REPLICATION_FACTOR",
541 default_value = DEFAULT_REPLICATION_FACTOR.to_string(),
542 value_parser = clap::value_parser!(u32).range(0..=2)
543 )]
544 bootstrap_default_cluster_replication_factor: u32,
545 #[clap(
547 long,
548 env = "BOOTSTRAP_BUILTIN_SYSTEM_CLUSTER_REPLICATION_FACTOR",
549 default_value = SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
550 value_parser = clap::value_parser!(u32).range(0..=2)
551 )]
552 bootstrap_builtin_system_cluster_replication_factor: u32,
553 #[clap(
555 long,
556 env = "BOOTSTRAP_BUILTIN_CATALOG_SERVER_CLUSTER_REPLICATION_FACTOR",
557 default_value = CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
558 value_parser = clap::value_parser!(u32).range(0..=2)
559 )]
560 bootstrap_builtin_catalog_server_cluster_replication_factor: u32,
561 #[clap(
563 long,
564 env = "BOOTSTRAP_BUILTIN_PROBE_CLUSTER_REPLICATION_FACTOR",
565 default_value = PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
566 value_parser = clap::value_parser!(u32).range(0..=2)
567 )]
568 bootstrap_builtin_probe_cluster_replication_factor: u32,
569 #[clap(
571 long,
572 env = "BOOTSTRAP_BUILTIN_SUPPORT_CLUSTER_REPLICATION_FACTOR",
573 default_value = SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
574 value_parser = clap::value_parser!(u32).range(0..=2)
575 )]
576 bootstrap_builtin_support_cluster_replication_factor: u32,
577 #[clap(
579 long,
580 env = "BOOTSTRAP_BUILTIN_ANALYTICS_CLUSTER_REPLICATION_FACTOR",
581 default_value = ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR.to_string(),
582 value_parser = clap::value_parser!(u32).range(0..=2)
583 )]
584 bootstrap_builtin_analytics_cluster_replication_factor: u32,
585 #[clap(
588 long,
589 env = "SYSTEM_PARAMETER_DEFAULT",
590 action = ArgAction::Append,
591 value_delimiter = ';'
592 )]
593 system_parameter_default: Vec<KeyValueArg<String, String>>,
594 #[clap(long, env = "LICENSE_KEY")]
596 license_key: Option<String>,
597
598 #[clap(long, env = "AWS_ACCOUNT_ID")]
602 aws_account_id: Option<String>,
603 #[clap(long, env = "AWS_CONNECTION_ROLE_ARN")]
606 aws_connection_role_arn: Option<String>,
607 #[clap(long, env = "AWS_EXTERNAL_ID_PREFIX", value_name = "ID", value_parser = AwsExternalIdPrefix::new_from_cli_argument_or_environment_variable)]
611 aws_external_id_prefix: Option<AwsExternalIdPrefix>,
612 #[clap(
615 long,
616 env = "AWS_PRIVATELINK_AVAILABILITY_ZONES",
617 action = ArgAction::Append,
618 use_value_delimiter = true
619 )]
620 aws_privatelink_availability_zones: Option<Vec<String>>,
621
622 #[clap(flatten)]
624 tracing: TracingCliArgs,
625
626 #[clap(long, value_enum, requires = "unsafe_mode")]
633 unsafe_force_builtin_schema_migration: Option<String>,
634}
635
636#[derive(ValueEnum, Debug, Clone)]
637enum OrchestratorKind {
638 Kubernetes,
639 Process,
640}
641
642fn aws_secrets_controller_prefix(env_id: &EnvironmentId) -> String {
645 format!("/user-managed/{}/", env_id)
646}
647fn aws_secrets_controller_key_alias(env_id: &EnvironmentId) -> String {
648 format!("alias/customer_key_{}", env_id)
651}
652
653pub fn main() {
654 let args = cli::parse_args(CliConfig {
655 env_prefix: Some("MZ_"),
656 enable_version_flag: true,
657 });
658 if let Err(err) = run(args) {
659 panic!("environmentd: fatal: {}", err.display_with_causes());
660 }
661}
662
663fn run(mut args: Args) -> Result<(), anyhow::Error> {
664 mz_ore::panic::install_enhanced_handler();
665
666 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
673
674 let envd_start = Instant::now();
675
676 sys::enable_sigusr2_coverage_dump()?;
679 sys::enable_termination_signal_cleanup()?;
680
681 let license_key = if let Some(license_key_file) = args.license_key {
682 let license_key_text = std::fs::read_to_string(&license_key_file)
683 .context("failed to open license key file")?;
684 let license_key = mz_license_keys::validate(license_key_text.trim())
685 .context("failed to validate license key file")?;
686 if license_key.expired {
687 let message = format!(
688 "The license key provided at {license_key_file} is expired! Please contact Materialize for assistance."
689 );
690 match license_key.expiration_behavior {
691 ExpirationBehavior::Warn | ExpirationBehavior::DisableClusterCreation => {
692 warn!("{message}");
693 }
694 ExpirationBehavior::Disable => bail!("{message}"),
695 }
696 }
697 license_key
698 } else if matches!(args.orchestrator, OrchestratorKind::Kubernetes) {
699 bail!("--license-key is required when running in Kubernetes");
700 } else {
701 ValidatedLicenseKey::disabled()
703 };
704
705 let force_builtin_schema_migration = args
707 .unsafe_force_builtin_schema_migration
708 .inspect(|_mechanism| assert!(args.unsafe_mode));
709
710 let ncpus_useful = usize::max(1, cmp::min(num_cpus::get(), num_cpus::get_physical()));
713 let runtime = Arc::new(
714 tokio::runtime::Builder::new_multi_thread()
715 .worker_threads(ncpus_useful)
716 .thread_stack_size(3 * 1024 * 1024) .thread_name_fn(|| {
720 static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
721 let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
722 format!("tokio:work-{}", id)
723 })
724 .enable_all()
725 .build()?,
726 );
727
728 args.tracing.log_prefix = if matches!(args.orchestrator, OrchestratorKind::Process) {
732 Some("environmentd".to_string())
733 } else {
734 None
735 };
736
737 let metrics_registry = MetricsRegistry::new();
738 let tracing_handle = runtime.block_on(args.tracing.configure_tracing(
739 StaticTracingConfig {
740 service_name: "environmentd",
741 build_info: BUILD_INFO,
742 },
743 metrics_registry.clone(),
744 ))?;
745 register_runtime_metrics("main", runtime.metrics(), &metrics_registry);
746
747 let span = tracing::info_span!("environmentd::run").entered();
748
749 info!("startup: envd init: beginning");
750 info!("startup: envd init: preamble beginning");
751
752 let metrics = Metrics::register_into(&metrics_registry, BUILD_INFO);
753
754 runtime.block_on(mz_alloc::register_metrics_into(&metrics_registry));
755 runtime.block_on(mz_metrics::register_metrics_into(
756 &metrics_registry,
757 mz_dyncfgs::all_dyncfgs(),
758 ));
759
760 let _failpoint_scenario = FailScenario::setup();
762
763 let tls = args.tls.into_config()?;
765 let frontegg_oauth_issuer_url = args.frontegg.oauth_issuer_url().map(str::to_string);
766 let frontegg = FronteggAuthenticator::from_args(args.frontegg, &metrics_registry)?;
767 let listeners_config: ListenersConfig = {
768 let f = File::open(args.listeners_config_path)?;
769 serde_json::from_reader(f)?
770 };
771
772 for (_, listener) in &listeners_config.sql {
773 listener
774 .validate()
775 .map_err(|e| anyhow::anyhow!("invalid SQL listener: {}", e))?;
776 }
777
778 for (_, listener) in &listeners_config.http {
779 listener
780 .validate()
781 .map_err(|e| anyhow::anyhow!("invalid HTTP listener: {}", e))?;
782 }
783
784 let allowed_origins = if !args.cors_allowed_origin.is_empty() {
786 args.cors_allowed_origin
787 } else {
788 let mut allowed_origins = Vec::with_capacity(listeners_config.http.len() * 6);
789 for (_, listener) in &listeners_config.http {
790 let port = listener.addr().port();
791 allowed_origins.extend([
792 HeaderValue::from_str(&format!("http://localhost:{}", port)).unwrap(),
793 HeaderValue::from_str(&format!("http://127.0.0.1:{}", port)).unwrap(),
794 HeaderValue::from_str(&format!("http://[::1]:{}", port)).unwrap(),
795 HeaderValue::from_str(&format!("https://localhost:{}", port)).unwrap(),
796 HeaderValue::from_str(&format!("https://127.0.0.1:{}", port)).unwrap(),
797 HeaderValue::from_str(&format!("https://[::1]:{}", port)).unwrap(),
798 ])
799 }
800 allowed_origins
801 };
802 let cors_allowed_origin = mz_http_util::build_cors_allowed_origin(&allowed_origins);
803 let cors_allowed_origin_list = allowed_origins.clone();
804
805 let entered = info_span!("environmentd::configure_controller").entered();
807 let (orchestrator, secrets_controller, cloud_resource_controller): (
808 Arc<dyn Orchestrator>,
809 Arc<dyn SecretsController>,
810 Option<Arc<dyn CloudResourceController>>,
811 ) = match args.orchestrator {
812 OrchestratorKind::Kubernetes => {
813 if args.orchestrator_process_scratch_directory.is_some() {
814 bail!(
815 "--orchestrator-process-scratch-directory is \
816 not currently usable with the kubernetes orchestrator"
817 );
818 }
819
820 let orchestrator = Arc::new(
821 runtime
822 .block_on(KubernetesOrchestrator::new(KubernetesOrchestratorConfig {
823 context: args.orchestrator_kubernetes_context.clone(),
824 scheduler_name: args.orchestrator_kubernetes_scheduler_name,
825 service_annotations: args
826 .orchestrator_kubernetes_service_annotation
827 .into_iter()
828 .map(|l| (l.key, l.value))
829 .collect(),
830 service_labels: args
831 .orchestrator_kubernetes_service_label
832 .into_iter()
833 .map(|l| (l.key, l.value))
834 .collect(),
835 service_node_selector: args
836 .orchestrator_kubernetes_service_node_selector
837 .into_iter()
838 .map(|l| (l.key, l.value))
839 .collect(),
840 service_affinity: args.orchestrator_kubernetes_service_affinity,
841 service_tolerations: args.orchestrator_kubernetes_service_tolerations,
842 service_account: args.orchestrator_kubernetes_service_account,
843 image_pull_policy: args.orchestrator_kubernetes_image_pull_policy,
844 aws_external_id_prefix: args.aws_external_id_prefix.clone(),
845 coverage: args.orchestrator_kubernetes_coverage,
846 ephemeral_volume_storage_class: args
847 .orchestrator_kubernetes_ephemeral_volume_class
848 .clone(),
849 service_fs_group: args.orchestrator_kubernetes_service_fs_group.clone(),
850 name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
851 collect_pod_metrics: !args
852 .orchestrator_kubernetes_disable_pod_metrics_collection,
853 enable_prometheus_scrape_annotations: args
854 .orchestrator_kubernetes_enable_prometheus_scrape_annotations,
855 }))
856 .context("creating kubernetes orchestrator")?,
857 );
858 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
859 SecretsControllerKind::Kubernetes => {
860 let sc = Arc::clone(&orchestrator);
861 let sc: Arc<dyn SecretsController> = sc;
862 sc
863 }
864 SecretsControllerKind::AwsSecretsManager => {
865 Arc::new(
866 runtime.block_on(AwsSecretsController::new(
867 &aws_secrets_controller_prefix(&args.environment_id),
870 &aws_secrets_controller_key_alias(&args.environment_id),
871 args.aws_secrets_controller_tags
872 .into_iter()
873 .map(|tag| (tag.key, tag.value))
874 .collect(),
875 )),
876 )
877 }
878 SecretsControllerKind::LocalFile => bail!(
879 "SecretsControllerKind::LocalFile is not compatible with Orchestrator::Kubernetes."
880 ),
881 };
882 let cloud_resource_controller = Arc::clone(&orchestrator);
883 (
884 orchestrator,
885 secrets_controller,
886 Some(cloud_resource_controller),
887 )
888 }
889 OrchestratorKind::Process => {
890 if args
891 .orchestrator_kubernetes_ephemeral_volume_class
892 .is_some()
893 {
894 bail!(
895 "--orchestrator-kubernetes-ephemeral-volume-class is \
896 not usable with the process orchestrator"
897 );
898 }
899 let orchestrator = Arc::new(
900 runtime
901 .block_on(ProcessOrchestrator::new(ProcessOrchestratorConfig {
902 image_dir: env::current_exe()?.parent().unwrap().to_path_buf(),
908 suppress_output: false,
909 environment_id: args.environment_id.to_string(),
910 secrets_dir: args
911 .orchestrator_process_secrets_directory
912 .clone()
913 .expect("clap enforced"),
914 command_wrapper: args
915 .orchestrator_process_wrapper
916 .map_or(Ok(vec![]), |s| shell_words::split(&s))?,
917 propagate_crashes: args.orchestrator_process_propagate_crashes,
918 tcp_proxy: args.orchestrator_process_tcp_proxy_listen_addr.map(
919 |listen_addr| ProcessOrchestratorTcpProxyConfig {
920 listen_addr,
921 prometheus_service_discovery_dir: args
922 .orchestrator_process_prometheus_service_discovery_directory,
923 },
924 ),
925 scratch_directory: args
926 .orchestrator_process_scratch_directory
927 .expect("process orchestrator requires scratch directory"),
928 }))
929 .context("creating process orchestrator")?,
930 );
931 let secrets_controller: Arc<dyn SecretsController> = match args.secrets_controller {
932 SecretsControllerKind::Kubernetes => bail!(
933 "SecretsControllerKind::Kubernetes is not compatible with Orchestrator::Process."
934 ),
935 SecretsControllerKind::AwsSecretsManager => Arc::new(
936 runtime.block_on(AwsSecretsController::new(
937 &aws_secrets_controller_prefix(&args.environment_id),
938 &aws_secrets_controller_key_alias(&args.environment_id),
939 args.aws_secrets_controller_tags
940 .into_iter()
941 .map(|tag| (tag.key, tag.value))
942 .collect(),
943 )),
944 ),
945 SecretsControllerKind::LocalFile => {
946 let sc = Arc::clone(&orchestrator);
947 let sc: Arc<dyn SecretsController> = sc;
948 sc
949 }
950 };
951 (orchestrator, secrets_controller, None)
952 }
953 };
954 drop(entered);
955 let cloud_resource_reader = cloud_resource_controller.as_ref().map(|c| c.reader());
956 let secrets_reader = secrets_controller.reader();
957 let now = SYSTEM_TIME.clone();
958
959 let mut persist_config =
960 PersistConfig::new(&BUILD_INFO, now.clone(), mz_dyncfgs::all_dyncfgs());
961 persist_config.disable_compaction();
963
964 let persist_pubsub_server = PersistGrpcPubSubServer::new(&persist_config, &metrics_registry);
965 let persist_pubsub_client = persist_pubsub_server.new_same_process_connection();
966
967 match args.persist_isolated_runtime_threads {
968 None | Some(0) => (),
970 Some(x @ ..=-1) => {
971 let threads = num_cpus::get().saturating_add_signed(x).max(1);
972 persist_config.isolated_runtime_worker_threads = threads;
973 }
974 Some(x @ 1..) => {
975 let threads = usize::try_from(x).expect("pattern matched a positive value");
976 persist_config.isolated_runtime_worker_threads = threads;
977 }
978 };
979
980 let _server = runtime.spawn_named(
981 || "persist::rpc::server",
982 async move {
983 info!(
984 "listening for Persist PubSub connections on {}",
985 args.internal_persist_pubsub_listen_addr
986 );
987 let res = persist_pubsub_server
990 .serve(args.internal_persist_pubsub_listen_addr)
991 .await;
992 error!("Persist Pubsub server exited {:?}", res);
993 }
994 .instrument(tracing::info_span!("persist::rpc::server")),
995 );
996
997 let persist_clients = {
998 let _tokio_guard = runtime.enter();
1000 PersistClientCache::new(persist_config, &metrics_registry, |cfg, metrics| {
1001 let sender: Arc<dyn PubSubSender> = Arc::new(MetricsSameProcessPubSubSender::new(
1002 cfg,
1003 persist_pubsub_client.sender,
1004 metrics,
1005 ));
1006 PubSubClientConnection::new(sender, persist_pubsub_client.receiver)
1007 })
1008 };
1009
1010 let consensus_uri = args.persist_consensus_url.unwrap_or_else(|| {
1011 args.metadata_backend_url
1012 .as_ref()
1013 .map(|metadata_backend_url| {
1014 SensitiveUrl(
1015 Url::parse_with_params(
1016 metadata_backend_url.0.as_ref(),
1017 &[("options", "--search_path=consensus")],
1018 )
1019 .unwrap(),
1020 )
1021 })
1022 .expect("either --persist-consensus-url or --metadata-backend-url must be provided")
1023 });
1024 let timestamp_oracle_url = args.timestamp_oracle_url.or_else(|| {
1025 args.metadata_backend_url
1026 .as_ref()
1027 .map(|metadata_backend_url| {
1028 SensitiveUrl(
1029 Url::parse_with_params(
1030 metadata_backend_url.0.as_ref(),
1031 &[("options", "--search_path=tsoracle")],
1032 )
1033 .unwrap(),
1034 )
1035 })
1036 });
1037
1038 let persist_clients = Arc::new(persist_clients);
1039 let system_dyncfgs = Arc::clone(&persist_clients.cfg().configs);
1040 let connection_context = ConnectionContext::from_cli_args(
1041 args.environment_id.to_string(),
1042 &args.tracing.startup_log_filter,
1043 args.aws_external_id_prefix,
1044 args.aws_connection_role_arn,
1045 secrets_reader,
1046 cloud_resource_reader,
1047 );
1048 let orchestrator = Arc::new(TracingOrchestrator::new(orchestrator, args.tracing.clone()));
1049 let replica_http_locator = Arc::new(ReplicaHttpLocator::default());
1050 let controller = ControllerConfig {
1051 build_info: &BUILD_INFO,
1052 orchestrator,
1053 persist_location: PersistLocation {
1054 blob_uri: args.persist_blob_url,
1055 consensus_uri,
1056 },
1057 persist_clients: Arc::clone(&persist_clients),
1058 clusterd_image: args.clusterd_image.expect("clap enforced"),
1059 init_container_image: args.orchestrator_kubernetes_init_container_image,
1060 deploy_generation: args.deploy_generation,
1061 now: SYSTEM_TIME.clone(),
1062 metrics_registry: metrics_registry.clone(),
1063 persist_pubsub_url: args.persist_pubsub_url,
1064 connection_context,
1065 secrets_args: SecretsReaderCliArgs {
1068 secrets_reader: args.secrets_controller,
1069 secrets_reader_local_file_dir: args.orchestrator_process_secrets_directory,
1070 secrets_reader_kubernetes_context: Some(args.orchestrator_kubernetes_context),
1071 secrets_reader_aws_prefix: Some(aws_secrets_controller_prefix(&args.environment_id)),
1072 secrets_reader_name_prefix: args.orchestrator_kubernetes_name_prefix.clone(),
1073 },
1074 replica_http_locator: Arc::clone(&replica_http_locator),
1075 };
1076
1077 let cluster_replica_sizes = ClusterReplicaSizeMap::parse_from_str(
1078 &args.cluster_replica_sizes,
1079 !license_key.allow_credit_consumption_override,
1080 )
1081 .context("parsing replica size map")?;
1082
1083 emit_boot_diagnostics!(&BUILD_INFO);
1084 sys::adjust_rlimits();
1085
1086 info!(
1087 "startup: envd init: preamble complete in {:?}",
1088 envd_start.elapsed()
1089 );
1090
1091 let serve_start = Instant::now();
1092 info!("startup: envd init: serving beginning");
1093 let server = runtime.block_on(async {
1094 let listeners = Listeners::bind(listeners_config).await?;
1095 let catalog_config = CatalogConfig {
1096 persist_clients,
1097 metrics: Arc::new(mz_catalog::durable::Metrics::new(&metrics_registry)),
1098 };
1099 let server = listeners
1100 .serve(crate::Config {
1101 unsafe_mode: args.unsafe_mode,
1103 all_features: args.all_features,
1104 tls,
1106 tls_reload_certs: mz_server_core::default_cert_reload_ticker(),
1107 external_login_password_mz_system: args.external_login_password_mz_system,
1108 frontegg,
1109 frontegg_oauth_issuer_url,
1110 cors_allowed_origin,
1111 cors_allowed_origin_list,
1112 egress_addresses: args.announce_egress_address,
1113 http_host_name: args.http_host_name,
1114 internal_console_redirect_url: args.internal_console_redirect_url,
1115 controller,
1117 secrets_controller,
1118 cloud_resource_controller,
1119 system_dyncfgs,
1120 storage_usage_collection_interval: args.storage_usage_collection_interval_sec,
1122 storage_usage_retention_period: args.storage_usage_retention_period,
1123 catalog_config,
1125 availability_zones: args.availability_zone,
1126 cluster_replica_sizes,
1127 timestamp_oracle_url,
1128 segment_api_key: args.segment_api_key,
1129 segment_client_side: args.segment_client_side,
1130 test_only_dummy_segment_client: args.test_only_dummy_segment_client,
1131 launchdarkly_sdk_key: args.launchdarkly_sdk_key,
1132 launchdarkly_base_uri: args.launchdarkly_base_uri,
1133 launchdarkly_key_map: args
1134 .launchdarkly_key_map
1135 .into_iter()
1136 .map(|kv| (kv.key, kv.value))
1137 .collect(),
1138 config_sync_timeout: args.config_sync_timeout,
1139 config_sync_loop_interval: args.config_sync_loop_interval,
1140 config_sync_file_path: args.config_sync_file_path,
1141
1142 environment_id: args.environment_id,
1144 bootstrap_role: args.bootstrap_role,
1145 bootstrap_default_cluster_replica_size: args.bootstrap_default_cluster_replica_size,
1146 bootstrap_default_cluster_replication_factor: args
1147 .bootstrap_default_cluster_replication_factor,
1148 bootstrap_builtin_system_cluster_config: BootstrapBuiltinClusterConfig {
1149 size: args.bootstrap_builtin_system_cluster_replica_size,
1150 replication_factor: args.bootstrap_builtin_system_cluster_replication_factor,
1151 },
1152 bootstrap_builtin_catalog_server_cluster_config: BootstrapBuiltinClusterConfig {
1153 size: args.bootstrap_builtin_catalog_server_cluster_replica_size,
1154 replication_factor: args
1155 .bootstrap_builtin_catalog_server_cluster_replication_factor,
1156 },
1157 bootstrap_builtin_probe_cluster_config: BootstrapBuiltinClusterConfig {
1158 size: args.bootstrap_builtin_probe_cluster_replica_size,
1159 replication_factor: args.bootstrap_builtin_probe_cluster_replication_factor,
1160 },
1161 bootstrap_builtin_support_cluster_config: BootstrapBuiltinClusterConfig {
1162 size: args.bootstrap_builtin_support_cluster_replica_size,
1163 replication_factor: args.bootstrap_builtin_support_cluster_replication_factor,
1164 },
1165 bootstrap_builtin_analytics_cluster_config: BootstrapBuiltinClusterConfig {
1166 size: args.bootstrap_builtin_analytics_cluster_replica_size,
1167 replication_factor: args.bootstrap_builtin_analytics_cluster_replication_factor,
1168 },
1169 system_parameter_defaults: args
1170 .system_parameter_default
1171 .into_iter()
1172 .map(|kv| (kv.key, kv.value))
1173 .collect(),
1174 helm_chart_version: args.helm_chart_version.clone(),
1175 license_key,
1176 aws_account_id: args.aws_account_id,
1178 aws_privatelink_availability_zones: args.aws_privatelink_availability_zones,
1179 metrics_registry,
1181 tracing_handle,
1182 now,
1184 force_builtin_schema_migration,
1185 })
1186 .await
1187 .maybe_terminate("booting server")?;
1188 Ok::<_, anyhow::Error>(server)
1189 })?;
1190 info!(
1191 "startup: envd init: serving complete in {:?}",
1192 serve_start.elapsed()
1193 );
1194
1195 let start_duration = envd_start.elapsed();
1196 metrics
1197 .start_time_environmentd
1198 .set(start_duration.as_millis().try_into().expect("must fit"));
1199 let span = span.exit();
1200 let id = span.context().span().span_context().trace_id();
1201 drop(span);
1202
1203 info!("startup: envd init: complete in {start_duration:?}");
1204
1205 println!(
1206 "environmentd {} listening...",
1207 BUILD_INFO.human_version(args.helm_chart_version)
1208 );
1209 for (name, handle) in &server.sql_listener_handles {
1210 println!("{} SQL address: {}", name, handle.local_addr);
1211 }
1212 for (name, handle) in &server.http_listener_handles {
1213 println!("{} HTTP address: {}", name, handle.local_addr);
1214 }
1215 println!(
1217 " Internal Persist PubSub address: {}",
1218 args.internal_persist_pubsub_listen_addr
1219 );
1220
1221 println!(" Root trace ID: {id}");
1222
1223 loop {
1225 thread::park();
1226 }
1227}
1228
1229fn build_info() -> Vec<String> {
1230 let openssl_version =
1231 unsafe { CStr::from_ptr(openssl_sys::OpenSSL_version(openssl_sys::OPENSSL_VERSION)) };
1232 let rdkafka_version = unsafe { CStr::from_ptr(rdkafka_sys::bindings::rd_kafka_version_str()) };
1233 vec![
1234 openssl_version.to_string_lossy().into_owned(),
1235 format!("librdkafka v{}", rdkafka_version.to_string_lossy()),
1236 ]
1237}
1238
1239#[derive(Debug, Clone)]
1240struct Metrics {
1241 pub start_time_environmentd: IntGauge,
1242}
1243
1244impl Metrics {
1245 pub fn register_into(registry: &MetricsRegistry, build_info: BuildInfo) -> Metrics {
1246 Metrics {
1247 start_time_environmentd: registry.register(metric!(
1248 name: "mz_start_time_environmentd",
1249 help: "Time in milliseconds from environmentd start until the adapter is ready.",
1250 const_labels: {
1251 "version" => build_info.version,
1252 "build_type" => if cfg!(release) { "release" } else { "debug" }
1253 },
1254 )),
1255 }
1256 }
1257}