Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for loadbalancer interface #153

Merged
merged 13 commits into from May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 96 additions & 29 deletions lib/charms/layer/kubernetes_master.py
Expand Up @@ -15,7 +15,7 @@
from charmhelpers.core.templating import render
from charmhelpers.core import unitdata
from charmhelpers.fetch import apt_install
from charms.reactive import endpoint_from_flag, is_flag_set
from charms.reactive import endpoint_from_flag, endpoint_from_name, is_flag_set
from charms.layer import kubernetes_common


Expand All @@ -32,10 +32,9 @@
db = unitdata.kv()


def get_external_lb_endpoints():
def get_endpoints_from_config():
"""
Return a list of any external API load-balancer endpoints that have
been manually configured.
Return a list of any manually configured API endpoints.
"""
ha_connected = is_flag_set("ha.connected")
forced_lb_ips = hookenv.config("loadbalancer-ips").split()
Expand All @@ -54,41 +53,109 @@ def get_external_lb_endpoints():
return []


def get_lb_endpoints():
def get_internal_api_endpoints(relation=None):
"""
Return all load-balancer endpoints, whether from manual config or via
relation.
Determine the best API endpoints for an internal client to connect to.

If a relation is given, it will try to take that into account.

May return an empty list if an endpoint is expected but not yet available.
"""
external_lb_endpoints = get_external_lb_endpoints()
loadbalancer = endpoint_from_flag("loadbalancer.available")
try:
goal_state = hookenv.goal_state()
except NotImplementedError:
goal_state = {}
goal_state.setdefault("relations", {})

# Config takes precedence.
endpoints_from_config = get_endpoints_from_config()
if endpoints_from_config:
return endpoints_from_config

# If the internal LB relation is attached, use that or nothing. If it's
# not attached but the external LB relation is, use that or nothing.
for lb_type in ("internal", "external"):
lb_endpoint = "loadbalancer-" + lb_type
request_name = "api-server-" + lb_type
if lb_endpoint in goal_state["relations"]:
lb_provider = endpoint_from_name(lb_endpoint)
lb_response = lb_provider.get_response(request_name)
if not lb_response or lb_response.error:
return []
return [(lb_response.address, STANDARD_API_PORT)]

# Support the older loadbalancer relation (public-address interface).
if "loadbalancer" in goal_state["relations"]:
loadbalancer = endpoint_from_name("loadbalancer")
lb_addresses = loadbalancer.get_addresses_ports()
return [(host.get("public-address"), host.get("port")) for host in lb_addresses]

if external_lb_endpoints:
return external_lb_endpoints
elif loadbalancer:
# No LBs of any kind, so fall back to ingress-address.
if not relation:
kube_control = endpoint_from_name("kube-control")
if not kube_control.relations:
return []
relation = kube_control.relations[0]
ingress_address = hookenv.ingress_address(
relation.relation_id, hookenv.local_unit()
)
return [(ingress_address, STANDARD_API_PORT)]


def get_external_api_endpoints():
"""
Determine the best API endpoints for an external client to connect to.

May return an empty list if an endpoint is expected but not yet available.
"""
try:
goal_state = hookenv.goal_state()
except NotImplementedError:
goal_state = {}
goal_state.setdefault("relations", {})

# Config takes precedence.
endpoints_from_config = get_endpoints_from_config()
if endpoints_from_config:
return endpoints_from_config

# If the external LB relation is attached, use that or nothing. If it's
# not attached but the internal LB relation is, use that or nothing.
for lb_type in ("external", "internal"):
lb_endpoint = "loadbalancer-" + lb_type
lb_name = "api-server-" + lb_type
if lb_endpoint in goal_state["relations"]:
lb_provider = endpoint_from_name(lb_endpoint)
lb_response = lb_provider.get_response(lb_name)
if not lb_response or lb_response.error:
return []
return [(lb_response.address, STANDARD_API_PORT)]

# Support the older loadbalancer relation (public-address interface).
if "loadbalancer" in goal_state["relations"]:
loadbalancer = endpoint_from_flag("loadbalancer.available")
lb_addresses = loadbalancer.get_addresses_ports()
return [(host.get("public-address"), host.get("port")) for host in lb_addresses]
else:
return []

# No LBs of any kind, so fall back to public-address.
return [(hookenv.unit_public_ip(), STANDARD_API_PORT)]

def get_api_endpoint(relation=None):

def get_api_urls(endpoints):
"""
Convert a list of API server endpoints to URLs.
"""
Determine the best endpoint for a client to connect to.
return ["https://{0}:{1}".format(*endpoint) for endpoint in endpoints]

If a relation is given, it will take that into account when choosing an
endpoint.

def get_api_url(endpoints):
"""
endpoints = get_lb_endpoints()
if endpoints:
# select a single endpoint based on our local unit number
return endpoints[kubernetes_common.get_unit_number() % len(endpoints)]
elif relation:
ingress_address = hookenv.ingress_address(
relation.relation_id, hookenv.local_unit()
)
return (ingress_address, STANDARD_API_PORT)
else:
return (hookenv.unit_public_ip(), STANDARD_API_PORT)
Choose an API endpoint from the list and build a URL from it.
"""
if not endpoints:
return None
urls = get_api_urls(endpoints)
return urls[kubernetes_common.get_unit_number() % len(urls)]


def install_ceph_common():
Expand Down
17 changes: 17 additions & 0 deletions metadata.yaml
Expand Up @@ -29,6 +29,13 @@ peers:
interface: kube-masters
provides:
kube-api-endpoint:
# Use of this relation is strongly discouraged as the API endpoints will be
# provided via the kube-control relation. However, it can be used to
# override those endpoints if you need to inject a reverse proxy between
# the master and workers using a charm which only supports the old MITM
# style relations. Note, though, that since this reverse proxy will not be
# visible to the master, it will not be used in any of the client or
# component kube config files.
interface: http
cluster-dns:
# kube-dns is deprecated. Its functionality has been rolled into the
Expand All @@ -51,6 +58,8 @@ requires:
etcd:
interface: etcd
loadbalancer:
# Use of this relation is strongly discouraged in favor of the more
# explicit loadbalancer-internal / loadbalancer-external relations.
interface: public-address
ceph-storage:
interface: ceph-admin
Expand All @@ -70,6 +79,14 @@ requires:
interface: keystone-credentials
dns-provider:
interface: kube-dns
loadbalancer-internal:
# Indicates that the LB should not be public and should use internal
# networks if available. Intended for control plane and other internal use.
interface: loadbalancer
loadbalancer-external:
# Indicates that the LB should be public facing. Intended for clients which
# must reach the API server via external networks.
interface: loadbalancer
resources:
core:
type: file
Expand Down