A Kubernetes operator to manage the externalIP
field on Kubernetes services.
Today, many k8s clusters are deployed in private networks behind NATs, with only Ingress services exposed through port forwards.
This poses a problem for applications that need to know the external IP address of such a service, such as external-dns
.
Within a private cluster, only the private network IPs will be available, requiring the use of workarounds to obtain the true publicly reachable IP address.
Kubernetes does have a built-in solution for this - the externalIP
field present on all services:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
externalIPs:
- 198.51.100.32
This field can inform applications about the true public/external IP of the service (for example, external-dns uses these IPs for its DNS records). However, there is a catch, as mentioned in the documentation:
Kubernetes does not manage allocation of externalIPs; these are the responsibility of the cluster administrator.
This is where externalip-manager
comes in.
The purpose of this operator is to automate the management of the externalIP
field in situations where manual assignment is unfeasible (such as dynamic IP addresses).
To do so, it uses a new resource type ClusterExternalIPSource
containing one or more solvers for determining the external IP addresses:
apiVersion: externalip.spacebird.dev/v1alpha1
kind: ClusterExternalIPSource
metadta:
name: public
spec:
ipv4:
solvers:
- dnsHostname:
host: "cluster-public-ip.example.com"
ipv6:
solvers:
- loadBalancerIngress: {}
Services can then select their externalIP
source through an annotation:
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
externalip.spacebird.dev/cluster-external-ip-source: public
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
externalip-manager
will then pick up this service and query the solvers in the ClusterExternalIPSource
until valid IP addresses are found.
It will then write them into the externalIP
field of the service and regularly check the sources for any changes.
From there, an Ingress controller can then pick up the externalIP
field and use it to advertise Ingress IP addresses for ExternalDNS.
In particular, ingress-nginx
uses both the externalIP
field the loadBalancer.ingress
status as provisioned by MetalLB, so your Ingress resources will have both public and internal IPs set.
You can then use 'net-filter' parameters for external-dns
to further restrict your published IPs, depending on your networking (Hairpin NAT or split-Horizon DNS).
The following solvers are currently available:
dnsHostname
: Perform a DNS query and use the IPs returned inA
/AAAA
records.- Use case: You have a firewall/NAT gateway that sets a DNS record with the public IP.
- Parameters:
host
: The host to resolve
ìpAPI
: Uses a "what-is-my-ip" style API to retrieve public addresses- Parameters:
provider
: Which API Provider to use. Currently, the only option ismyIp
- Parameters:
loadBalancerIngress
: Use the addresses specified in the.status.loadBalancer.ingress
field- Use case: You have MetalLB or a similar LoadBalancer providing you with some public addresses
- Parameters: None
You can optionally define multiple solvers for a single IP source:
piVersion: externalip.spacebird.dev/v1alpha1
kind: ClusterExternalIPSource
metadta:
name: public
spec:
ipv4:
queryMode: firstFound
solvers:
- dnsHostname:
host: "cluster-public-ip.example.com"
- loadBalancerIngress: {}
By default, the controller will attempt each solver in sequence until one returns a valid address (queryMode == firstFound
).
You can also have the controller query all solvers and return the combined set of addresses by setting queryMode
to all
.
To install this operator, use the Helm chart at spacebird-dev/charts.
This operator is built in Rust, using standard cargo
tooling.
You may want to install the just
command runner to run the recipes in the Justfile
.
cross
is used for cross-compilation.
When making changes to the CRDs, please run just crds
before committing any changes.
There is also a pre-commit hook that does this for you if you run pre-commit install