diff --git a/CHANGELOG.md b/CHANGELOG.md index 56bd839..2f0eb81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file. - Support objectOverrides using `.spec.objectOverrides`. See [objectOverrides concepts page](https://docs.stackable.tech/home/nightly/concepts/overrides/#object-overrides) for details ([#93]). - Enable the [restart-controller](https://docs.stackable.tech/home/nightly/commons-operator/restarter/), so that the Pods are automatically restarted on config changes ([#97]). +- Configure OpenSearch to publish the fully-qualified domain names of the nodes instead of the IP + addresses, so that TLS certificates can be verified ([#100]). ### Changed @@ -23,6 +25,7 @@ All notable changes to this project will be documented in this file. [#91]: https://github.com/stackabletech/opensearch-operator/pull/91 [#93]: https://github.com/stackabletech/opensearch-operator/pull/93 [#97]: https://github.com/stackabletech/opensearch-operator/pull/97 +[#100]: https://github.com/stackabletech/opensearch-operator/pull/100 ## [25.11.0] - 2025-11-07 diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 8f631da..da9c75a 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -10,7 +10,10 @@ use build::build; use snafu::{ResultExt, Snafu}; use stackable_operator::{ cluster_resources::ClusterResourceApplyStrategy, - commons::{affinity::StackableAffinity, product_image_selection::ResolvedProductImage}, + commons::{ + affinity::StackableAffinity, networking::DomainName, + product_image_selection::ResolvedProductImage, + }, crd::listener::v1alpha1::Listener, k8s_openapi::api::{ apps::v1::StatefulSet, @@ -56,6 +59,7 @@ pub struct ContextNames { pub product_name: ProductName, pub operator_name: OperatorName, pub controller_name: ControllerName, + pub cluster_domain_name: DomainName, } /// The controller context @@ -66,19 +70,22 @@ pub struct Context { impl Context { pub fn new(client: stackable_operator::client::Client, operator_name: OperatorName) -> Self { + let cluster_domain_name = client.kubernetes_cluster_info.cluster_domain.clone(); + Context { client, - names: Self::context_names(operator_name), + names: Self::context_names(operator_name, cluster_domain_name), } } - fn context_names(operator_name: OperatorName) -> ContextNames { + fn context_names(operator_name: OperatorName, cluster_domain_name: DomainName) -> ContextNames { ContextNames { product_name: ProductName::from_str("opensearch") .expect("should be a valid product name"), operator_name, controller_name: ControllerName::from_str("opensearchcluster") .expect("should be a valid controller name"), + cluster_domain_name, } } @@ -379,7 +386,10 @@ mod tests { }; use stackable_operator::{ - commons::{affinity::StackableAffinity, product_image_selection::ResolvedProductImage}, + commons::{ + affinity::StackableAffinity, networking::DomainName, + product_image_selection::ResolvedProductImage, + }, k8s_openapi::api::core::v1::PodTemplateSpec, kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, @@ -406,7 +416,10 @@ mod tests { #[test] fn test_context_names() { // Test that the function does not panic - Context::context_names(OperatorName::from_str_unsafe("my-operator")); + Context::context_names( + OperatorName::from_str_unsafe("my-operator"), + DomainName::from_str("cluster.local").expect("should be a valid domain name"), + ); } #[test] diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index b0f84c5..b93b2b1 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -62,7 +62,10 @@ mod tests { }; use stackable_operator::{ - commons::{affinity::StackableAffinity, product_image_selection::ResolvedProductImage}, + commons::{ + affinity::StackableAffinity, networking::DomainName, + product_image_selection::ResolvedProductImage, + }, k8s_openapi::api::core::v1::PodTemplateSpec, kube::Resource, kvp::LabelValue, @@ -158,6 +161,8 @@ mod tests { product_name: ProductName::from_str_unsafe("opensearch"), operator_name: OperatorName::from_str_unsafe("opensearch.stackable.tech"), controller_name: ControllerName::from_str_unsafe("opensearchcluster"), + cluster_domain_name: DomainName::from_str("cluster.local") + .expect("should be a valid domain name"), } } diff --git a/rust/operator-binary/src/controller/build/node_config.rs b/rust/operator-binary/src/controller/build/node_config.rs index b58dcd6..9862140 100644 --- a/rust/operator-binary/src/controller/build/node_config.rs +++ b/rust/operator-binary/src/controller/build/node_config.rs @@ -3,7 +3,9 @@ use std::str::FromStr; use serde_json::{Value, json}; -use stackable_operator::builder::pod::container::FieldPathEnvVar; +use stackable_operator::{ + builder::pod::container::FieldPathEnvVar, commons::networking::DomainName, +}; use super::ValidatedCluster; use crate::{ @@ -32,6 +34,11 @@ pub const CONFIG_OPTION_DISCOVERY_SEED_HOSTS: &str = "discovery.seed_hosts"; /// Type: string pub const CONFIG_OPTION_DISCOVERY_TYPE: &str = "discovery.type"; +/// Specifies an address or addresses that an OpenSearch node publishes to other nodes for HTTP +/// communication. +/// Type: (comma-separated) list of strings +pub const CONFIG_OPTION_HTTP_PUBLISH_HOST: &str = "http.publish_host"; + /// A list of cluster-manager-eligible nodes used to bootstrap the cluster. /// Type: (comma-separated) list of strings pub const CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES: &str = @@ -41,6 +48,11 @@ pub const CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES: &str = /// Type: string pub const CONFIG_OPTION_NETWORK_HOST: &str = "network.host"; +/// Specifies an address or addresses that an OpenSearch node publishes to other nodes in the +/// cluster so that they can connect to it. +/// Type: (comma-separated) list of strings +pub const CONFIG_OPTION_NETWORK_PUBLISH_HOST: &str = "network.publish_host"; + /// The custom node attribute "role-group" /// Type: string pub const CONFIG_OPTION_NODE_ATTR_ROLE_GROUP: &str = "node.attr.role-group"; @@ -97,6 +109,11 @@ pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH: &str = pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH: &str = "plugins.security.ssl.transport.pemtrustedcas_filepath"; +/// Specifies an address or addresses that an OpenSearch node publishes to other nodes for +/// transport communication. +/// Type: (comma-separated) list of strings +pub const CONFIG_OPTION_TRANSPORT_PUBLISH_HOST: &str = "transport.publish_host"; + const DEFAULT_OPENSEARCH_HOME: &str = "/stackable/opensearch"; /// Configuration of an OpenSearch node based on the cluster and role-group configuration @@ -105,6 +122,8 @@ pub struct NodeConfig { role_group_name: RoleGroupName, role_group_config: OpenSearchRoleGroupConfig, pub discovery_service_name: ServiceName, + cluster_domain_name: DomainName, + headless_service_name: ServiceName, } // Most functions are public because their configuration values could also be used in environment @@ -115,12 +134,16 @@ impl NodeConfig { role_group_name: RoleGroupName, role_group_config: OpenSearchRoleGroupConfig, discovery_service_name: ServiceName, + cluster_domain_name: DomainName, + headless_service_name: ServiceName, ) -> Self { Self { cluster, role_group_name, role_group_config, discovery_service_name, + cluster_domain_name, + headless_service_name, } } @@ -258,13 +281,36 @@ impl NodeConfig { /// The environment variables should only contain node-specific configuration options. /// Cluster-wide options should be added to the configuration file. pub fn environment_variables(&self) -> EnvVarSet { + let fqdn = format!( + "$(_POD_NAME).{}.{}.svc.{}", + self.headless_service_name, self.cluster.namespace, self.cluster_domain_name + ); + EnvVarSet::new() + .with_field_path( + // Prefix with an underscore, so that it occurs before the other environment + // variables which depend on it. + &EnvVarName::from_str_unsafe("_POD_NAME"), + FieldPathEnvVar::Name, + ) // Set the OpenSearch node name to the Pod name. // The node name is used e.g. for INITIAL_CLUSTER_MANAGER_NODES. .with_field_path( &EnvVarName::from_str_unsafe(CONFIG_OPTION_NODE_NAME), FieldPathEnvVar::Name, ) + .with_value( + &EnvVarName::from_str_unsafe(CONFIG_OPTION_NETWORK_PUBLISH_HOST), + &fqdn, + ) + .with_value( + &EnvVarName::from_str_unsafe(CONFIG_OPTION_TRANSPORT_PUBLISH_HOST), + &fqdn, + ) + .with_value( + &EnvVarName::from_str_unsafe(CONFIG_OPTION_HTTP_PUBLISH_HOST), + &fqdn, + ) .with_value( &EnvVarName::from_str_unsafe(CONFIG_OPTION_DISCOVERY_SEED_HOSTS), &self.discovery_service_name, @@ -510,6 +556,8 @@ mod tests { role_group_name, role_group_config, ServiceName::from_str_unsafe("my-opensearch-cluster-manager"), + DomainName::from_str("cluster.local").expect("should be a valid domain name"), + ServiceName::from_str_unsafe("my-opensearch-cluster-default-headless"), ) } @@ -609,6 +657,10 @@ mod tests { assert_eq!( EnvVarSet::new() .with_value(&EnvVarName::from_str_unsafe("TEST"), "value") + .with_field_path( + &EnvVarName::from_str_unsafe("_POD_NAME"), + FieldPathEnvVar::Name + ) .with_value( &EnvVarName::from_str_unsafe("cluster.initial_cluster_manager_nodes"), "my-opensearch-cluster-nodes-default-0,my-opensearch-cluster-nodes-default-1", @@ -617,6 +669,14 @@ mod tests { &EnvVarName::from_str_unsafe("discovery.seed_hosts"), "my-opensearch-cluster-manager", ) + .with_value( + &EnvVarName::from_str_unsafe("http.publish_host"), + "$(_POD_NAME).my-opensearch-cluster-default-headless.default.svc.cluster.local", + ) + .with_value( + &EnvVarName::from_str_unsafe("network.publish_host"), + "$(_POD_NAME).my-opensearch-cluster-default-headless.default.svc.cluster.local", + ) .with_field_path( &EnvVarName::from_str_unsafe("node.name"), FieldPathEnvVar::Name @@ -624,6 +684,10 @@ mod tests { .with_value( &EnvVarName::from_str_unsafe("node.roles"), "cluster_manager,data,ingest,remote_cluster_client" + ) + .with_value( + &EnvVarName::from_str_unsafe("transport.publish_host"), + "$(_POD_NAME).my-opensearch-cluster-default-headless.default.svc.cluster.local", ), node_config.environment_variables() ); diff --git a/rust/operator-binary/src/controller/build/role_builder.rs b/rust/operator-binary/src/controller/build/role_builder.rs index c2b8376..0ecbbd1 100644 --- a/rust/operator-binary/src/controller/build/role_builder.rs +++ b/rust/operator-binary/src/controller/build/role_builder.rs @@ -223,6 +223,7 @@ mod tests { use stackable_operator::{ commons::{ affinity::StackableAffinity, + networking::DomainName, product_image_selection::{ProductImage, ResolvedProductImage}, resources::Resources, }, @@ -259,6 +260,8 @@ mod tests { product_name: ProductName::from_str_unsafe("opensearch"), operator_name: OperatorName::from_str_unsafe("opensearch.stackable.tech"), controller_name: ControllerName::from_str_unsafe("opensearchcluster"), + cluster_domain_name: DomainName::from_str("cluster.local") + .expect("should be a valid domain name"), } } diff --git a/rust/operator-binary/src/controller/build/role_group_builder.rs b/rust/operator-binary/src/controller/build/role_group_builder.rs index 5ade469..1de9a61 100644 --- a/rust/operator-binary/src/controller/build/role_group_builder.rs +++ b/rust/operator-binary/src/controller/build/role_group_builder.rs @@ -116,6 +116,11 @@ impl<'a> RoleGroupBuilder<'a> { context_names: &'a ContextNames, discovery_service_name: ServiceName, ) -> RoleGroupBuilder<'a> { + let resource_names = ResourceNames { + cluster_name: cluster.name.clone(), + role_name: ValidatedCluster::role_name(), + role_group_name: role_group_name.clone(), + }; RoleGroupBuilder { service_account_name, cluster: cluster.clone(), @@ -124,15 +129,13 @@ impl<'a> RoleGroupBuilder<'a> { role_group_name.clone(), role_group_config.clone(), discovery_service_name, + context_names.cluster_domain_name.clone(), + resource_names.headless_service_name(), ), role_group_name: role_group_name.clone(), role_group_config, context_names, - resource_names: ResourceNames { - cluster_name: cluster.name.clone(), - role_name: ValidatedCluster::role_name(), - role_group_name, - }, + resource_names, } } @@ -810,8 +813,8 @@ mod tests { use serde_json::json; use stackable_operator::{ commons::{ - affinity::StackableAffinity, product_image_selection::ResolvedProductImage, - resources::Resources, + affinity::StackableAffinity, networking::DomainName, + product_image_selection::ResolvedProductImage, resources::Resources, }, k8s_openapi::api::core::v1::PodTemplateSpec, kvp::LabelValue, @@ -864,6 +867,8 @@ mod tests { product_name: ProductName::from_str_unsafe("opensearch"), operator_name: OperatorName::from_str_unsafe("opensearch.stackable.tech"), controller_name: ControllerName::from_str_unsafe("opensearchcluster"), + cluster_domain_name: DomainName::from_str("cluster.local") + .expect("should be a valid domain name"), } } @@ -1132,6 +1137,14 @@ mod tests { "-c" ], "env": [ + { + "name": "_POD_NAME", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.name" + } + } + }, { "name": "cluster.initial_cluster_manager_nodes", "value": "" @@ -1140,6 +1153,14 @@ mod tests { "name": "discovery.seed_hosts", "value": "my-opensearch-cluster" }, + { + "name": "http.publish_host", + "value": "$(_POD_NAME).my-opensearch-cluster-nodes-default-headless.default.svc.cluster.local" + }, + { + "name": "network.publish_host", + "value": "$(_POD_NAME).my-opensearch-cluster-nodes-default-headless.default.svc.cluster.local" + }, { "name": "node.name", "valueFrom": { @@ -1151,7 +1172,11 @@ mod tests { { "name": "node.roles", "value": "cluster_manager,data,ingest,remote_cluster_client" - } + }, + { + "name": "transport.publish_host", + "value": "$(_POD_NAME).my-opensearch-cluster-nodes-default-headless.default.svc.cluster.local" + }, ], "image": "oci.stackable.tech/sdp/opensearch:3.1.0-stackable0.0.0-dev", "imagePullPolicy": "Always", diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 2079558..ccacd25 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -263,6 +263,7 @@ mod tests { commons::{ affinity::StackableAffinity, cluster_operation::ClusterOperation, + networking::DomainName, product_image_selection::ResolvedProductImage, resources::{CpuLimits, MemoryLimits, PvcConfig, Resources}, }, @@ -689,6 +690,8 @@ mod tests { product_name: ProductName::from_str_unsafe("opensearch"), operator_name: OperatorName::from_str_unsafe("opensearch.stackable.tech"), controller_name: ControllerName::from_str_unsafe("opensearchcluster"), + cluster_domain_name: DomainName::from_str("cluster.local") + .expect("should be a valid domain name"), } } diff --git a/tests/templates/kuttl/smoke/11-assert.yaml.j2 b/tests/templates/kuttl/smoke/11-assert.yaml.j2 index deb1db6..9e1ce93 100644 --- a/tests/templates/kuttl/smoke/11-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/11-assert.yaml.j2 @@ -114,10 +114,19 @@ spec: value: "true" - name: OPENSEARCH_HOME value: {{ test_scenario['values']['opensearch_home'] }} + - name: _POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name - name: cluster.initial_cluster_manager_nodes value: opensearch-nodes-cluster-manager-0,opensearch-nodes-cluster-manager-1,opensearch-nodes-cluster-manager-2 - name: discovery.seed_hosts value: opensearch + - name: http.publish_host + # value: $(_POD_NAME).opensearch-nodes-cluster-manager-headless.$NAMESPACE.svc.cluster.local + - name: network.publish_host + # value: $(_POD_NAME).opensearch-nodes-cluster-manager-headless.$NAMESPACE.svc.cluster.local - name: node.name valueFrom: fieldRef: @@ -125,6 +134,8 @@ spec: fieldPath: metadata.name - name: node.roles value: cluster_manager + - name: transport.publish_host + # value: $(_POD_NAME).opensearch-nodes-cluster-manager-headless.$NAMESPACE.svc.cluster.local imagePullPolicy: IfNotPresent name: opensearch ports: @@ -450,9 +461,18 @@ spec: value: "true" - name: OPENSEARCH_HOME value: {{ test_scenario['values']['opensearch_home'] }} + - name: _POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name - name: cluster.initial_cluster_manager_nodes - name: discovery.seed_hosts value: opensearch + - name: http.publish_host + # value: $(_POD_NAME).opensearch-nodes-data-headless.$NAMESPACE.svc.cluster.local + - name: network.publish_host + # value: $(_POD_NAME).opensearch-nodes-data-headless.$NAMESPACE.svc.cluster.local - name: node.name valueFrom: fieldRef: @@ -460,6 +480,8 @@ spec: fieldPath: metadata.name - name: node.roles value: ingest,data,remote_cluster_client + - name: transport.publish_host + # value: $(_POD_NAME).opensearch-nodes-data-headless.$NAMESPACE.svc.cluster.local imagePullPolicy: IfNotPresent name: opensearch ports: