diff --git a/README.md b/README.md index fb3736ddfcf..0988c11d002 100644 --- a/README.md +++ b/README.md @@ -347,6 +347,9 @@ The following settings can be optionally set to customize the node labels and ta special = "true:NoSchedule" ``` +The following settings are optional and allow you to further configure your cluster. +* `settings.kubernetes.cluster-domain`: The DNS domain for this cluster, allowing all Kubernetes-run containers to search this domain before the host's search domains. Defaults to `cluster.local`. + The following settings are set for you automatically by [pluto](sources/api/) based on runtime instance information, but you can override them if you know what you're doing! * `settings.kubernetes.max-pods`: The maximum number of pods that can be scheduled on this node (limited by number of available IPv4 addresses) * `settings.kubernetes.cluster-dns-ip`: The CIDR block of the primary network interface. diff --git a/packages/kubernetes-1.15/kubelet-config b/packages/kubernetes-1.15/kubelet-config index 91fa75637ef..a54df906902 100644 --- a/packages/kubernetes-1.15/kubelet-config +++ b/packages/kubernetes-1.15/kubelet-config @@ -15,7 +15,7 @@ authorization: webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s -clusterDomain: cluster.local +clusterDomain: {{settings.kubernetes.cluster-domain}} clusterDNS: - {{settings.kubernetes.cluster-dns-ip}} resolvConf: "/etc/resolv.conf" diff --git a/packages/kubernetes-1.16/kubelet-config b/packages/kubernetes-1.16/kubelet-config index 91fa75637ef..a54df906902 100644 --- a/packages/kubernetes-1.16/kubelet-config +++ b/packages/kubernetes-1.16/kubelet-config @@ -15,7 +15,7 @@ authorization: webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s -clusterDomain: cluster.local +clusterDomain: {{settings.kubernetes.cluster-domain}} clusterDNS: - {{settings.kubernetes.cluster-dns-ip}} resolvConf: "/etc/resolv.conf" diff --git a/packages/kubernetes-1.17/kubelet-config b/packages/kubernetes-1.17/kubelet-config index 6bcc262a777..9c8ef0d4156 100644 --- a/packages/kubernetes-1.17/kubelet-config +++ b/packages/kubernetes-1.17/kubelet-config @@ -15,7 +15,7 @@ authorization: webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s -clusterDomain: cluster.local +clusterDomain: {{settings.kubernetes.cluster-domain}} clusterDNS: - {{settings.kubernetes.cluster-dns-ip}} resolvConf: "/etc/resolv.conf" diff --git a/sources/models/src/aws-k8s-1.15/override-defaults.toml b/sources/models/src/aws-k8s-1.15/override-defaults.toml index 48fd3ed0325..0635fc46afd 100644 --- a/sources/models/src/aws-k8s-1.15/override-defaults.toml +++ b/sources/models/src/aws-k8s-1.15/override-defaults.toml @@ -33,3 +33,6 @@ affected-services = ["kubernetes"] [metadata.settings.kubernetes.pod-infra-container-image] setting-generator = "pluto pod-infra-container-image" affected-services = ["kubernetes", "containerd"] + +[settings.kubernetes] +cluster-domain = "cluster.local" diff --git a/sources/models/src/lib.rs b/sources/models/src/lib.rs index 7a71c6718f9..18789c63bdc 100644 --- a/sources/models/src/lib.rs +++ b/sources/models/src/lib.rs @@ -76,7 +76,7 @@ use std::collections::HashMap; use std::net::Ipv4Addr; use crate::modeled_types::{ - FriendlyVersion, KubernetesClusterName, KubernetesLabelKey, KubernetesLabelValue, + DNSDomain, FriendlyVersion, KubernetesClusterName, KubernetesLabelKey, KubernetesLabelValue, KubernetesTaintValue, SingleLineString, Url, ValidBase64, }; @@ -94,6 +94,7 @@ struct KubernetesSettings { // Dynamic settings. max_pods: u32, cluster_dns_ip: Ipv4Addr, + cluster_domain: DNSDomain, node_ip: Ipv4Addr, pod_infra_container_image: SingleLineString, } diff --git a/sources/models/src/modeled_types/mod.rs b/sources/models/src/modeled_types/mod.rs index 4e0cf8b3a9a..86f43d56c5b 100644 --- a/sources/models/src/modeled_types/mod.rs +++ b/sources/models/src/modeled_types/mod.rs @@ -44,6 +44,9 @@ pub mod error { #[snafu(display("Given invalid cluster name '{}': {}", name, msg))] InvalidClusterName { name: String, msg: String }, + + #[snafu(display("Invalid domain name '{}': {}", input, msg))] + InvalidDomainName { input: String, msg: String }, } } diff --git a/sources/models/src/modeled_types/shared.rs b/sources/models/src/modeled_types/shared.rs index e3e100aa5e7..a64c34c8442 100644 --- a/sources/models/src/modeled_types/shared.rs +++ b/sources/models/src/modeled_types/shared.rs @@ -9,6 +9,7 @@ use std::convert::TryFrom; use std::fmt; use std::ops::Deref; use std::str::FromStr; +use url::Host; /// ValidBase64 can only be created by deserializing from valid base64 text. It stores the /// original text, not the decoded form. Its purpose is input validation, namely being used as a @@ -349,3 +350,73 @@ mod test_version { } } } + +// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= + +/// DNSDomain represents a string that is a valid DNS domain. It stores the +/// original string and makes it accessible through standard traits. Its purpose +/// is input validation, for example validating the kubelet's "clusterDomain" +/// config value. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct DNSDomain { + inner: String, +} + +impl TryFrom<&str> for DNSDomain { + type Error = error::Error; + + fn try_from(input: &str) -> Result { + ensure!( + !input.starts_with('.'), + error::InvalidDomainName { + input: input, + msg: "must not start with '.'", + } + ); + + let host = Host::parse(input).or_else(|e| { + error::InvalidDomainName { + input: input, + msg: e.to_string(), + } + .fail() + })?; + match host { + Host::Ipv4(_) | Host::Ipv6(_) => error::InvalidDomainName { + input: input, + msg: "IP address is not a valid domain name", + } + .fail(), + Host::Domain(_) => Ok(Self { + inner: input.to_string(), + }), + } + } +} + +string_impls_for!(DNSDomain, "DNSDomain"); + +#[cfg(test)] +mod test_dns_domain { + use super::DNSDomain; + use std::convert::TryFrom; + + #[test] + fn valid_dns_domain() { + for ok in &["cluster.local", "dev.eks", "stage.eks", "prod.eks"] { + assert!(DNSDomain::try_from(*ok).is_ok()); + } + } + + #[test] + fn invalid_dns_domain() { + for err in &[ + "foo/com", + ".a", + "123.123.123.123", + "[2001:db8::ff00:42:8329]", + ] { + assert!(DNSDomain::try_from(*err).is_err()); + } + } +}