Skip to content
22 changes: 22 additions & 0 deletions k8s/deployment/templates/dns-endpoint.yaml.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: k-8-s-{{ .scope.slug }}-{{ .scope.id }}-dns
namespace: {{ .k8s_namespace }}
labels:
nullplatform: "true"
account: {{ .account.slug }}
account_id: "{{ .account.id }}"
namespace: {{ .namespace.slug }}
namespace_id: "{{ .namespace.id }}"
application: {{ .application.slug }}
application_id: "{{ .application.id }}"
scope: {{ .scope.slug }}
scope_id: "{{ .scope.id }}"
spec:
endpoints:
- dnsName: {{ .scope.domain }}
recordTTL: 60
recordType: A
targets:
- "{{ .gateway_ip }}"
3 changes: 2 additions & 1 deletion k8s/deployment/workflows/initial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ steps:
type: script
file: "$SERVICE_PATH/deployment/wait_deployment_active"
configuration:
TIMEOUT: DEPLOYMENT_MAX_WAIT_IN_SECONDS
TIMEOUT: DEPLOYMENT_MAX_WAIT_IN_SECONDS

6 changes: 4 additions & 2 deletions k8s/scope/build_context
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ export SCOPE_DOMAIN

if [ "$SCOPE_VISIBILITY" = "public" ]; then
export INGRESS_VISIBILITY="internet-facing"
export GATEWAY_NAME="${PUBLIC_GATEWAY_NAME:-gateway-public}"
GATEWAY_DEFAULT="${PUBLIC_GATEWAY_NAME:-gateway-public}"
export GATEWAY_NAME=$(echo "$CONTEXT" | jq -r --arg default "$GATEWAY_DEFAULT" '.providers["container-orchestration"].gateway.public_name // $default')
else
export INGRESS_VISIBILITY="internal"
export GATEWAY_NAME="${PRIVATE_GATEWAY_NAME:-gateway-internal}"
GATEWAY_DEFAULT="${PRIVATE_GATEWAY_NAME:-gateway-internal}"
export GATEWAY_NAME=$(echo "$CONTEXT" | jq -r --arg default "$GATEWAY_DEFAULT" '.providers["container-orchestration"].gateway.private_name // $default')
fi

K8S_MODIFIERS="${K8S_MODIFIERS:-"{}"}"
Expand Down
4 changes: 4 additions & 0 deletions k8s/scope/networking/dns/build_dns_context
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ case "$DNS_TYPE" in
echo "PUBLIC_GATEWAY_NAME: $PUBLIC_GATEWAY_NAME"
echo "PRIVATE_GATEWAY_NAME: $PRIVATE_GATEWAY_NAME"
;;
external_dns)
echo "external_dns context ready"
echo "DNS records will be managed automatically by External DNS operator"
;;
*)
echo "Error: Unsupported DNS type '$DNS_TYPE'"
exit 1
Expand Down
59 changes: 59 additions & 0 deletions k8s/scope/networking/dns/external_dns/manage_route
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash

set -euo pipefail

if [ "$ACTION" = "CREATE" ]; then
echo "Building DNSEndpoint manifest for ExternalDNS..."

echo "Getting IP for gateway: $GATEWAY_NAME"

GATEWAY_IP=$(kubectl get gateway "$GATEWAY_NAME" -n gateways \
-o jsonpath='{.status.addresses[?(@.type=="IPAddress")].value}' 2>/dev/null)

if [ -z "$GATEWAY_IP" ]; then
echo "Warning: Could not get gateway IP for $GATEWAY_NAME"

GATEWAY_IP=$(kubectl get service "$GATEWAY_NAME" -n gateways \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null)
fi

if [ -z "$GATEWAY_IP" ]; then
echo "Warning: Could not determine gateway IP address yet, DNSEndpoint will be created later"
exit 0
fi

echo "Gateway IP: $GATEWAY_IP"

DNS_ENDPOINT_TEMPLATE="${DNS_ENDPOINT_TEMPLATE:-$SERVICE_PATH/deployment/templates/dns-endpoint.yaml.tpl}"

if [ -f "$DNS_ENDPOINT_TEMPLATE" ]; then
DNS_ENDPOINT_FILE="$OUTPUT_DIR/dns-endpoint-$SCOPE_ID.yaml"
CONTEXT_PATH="$OUTPUT_DIR/context-$SCOPE_ID-dns.json"

echo "$CONTEXT" | jq --arg gateway_ip "$GATEWAY_IP" '. + {gateway_ip: $gateway_ip}' > "$CONTEXT_PATH"

echo "Building DNSEndpoint Template: $DNS_ENDPOINT_TEMPLATE to $DNS_ENDPOINT_FILE"

gomplate -c .="$CONTEXT_PATH" \
--file "$DNS_ENDPOINT_TEMPLATE" \
--out "$DNS_ENDPOINT_FILE"

echo "DNSEndpoint manifest created at: $DNS_ENDPOINT_FILE"

rm "$CONTEXT_PATH"

else
echo "Error: DNSEndpoint template not found at $DNS_ENDPOINT_TEMPLATE"
exit 1
fi

elif [ "$ACTION" = "DELETE" ]; then
echo "Deleting DNSEndpoint for external_dns..."

SCOPE_SLUG=$(echo "$CONTEXT" | jq -r '.scope.slug')
DNS_ENDPOINT_NAME="k-8-s-${SCOPE_SLUG}-${SCOPE_ID}-dns"
echo "Attempting to delete DNSEndpoint by name: $DNS_ENDPOINT_NAME"
kubectl delete dnsendpoint "$DNS_ENDPOINT_NAME" -n "$K8S_NAMESPACE" || echo "DNSEndpoint may already be deleted"

echo "DNSEndpoint deletion completed"
fi
7 changes: 7 additions & 0 deletions k8s/scope/networking/dns/manage_dns
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ case "$DNS_TYPE" in
--hosted-zone-name="$HOSTED_ZONE_NAME" \
--hosted-zone-rg="$HOSTED_ZONE_RG"
;;
external_dns)
echo "Using external_dns provider"
source "$SERVICE_PATH/scope/networking/dns/external_dns/manage_route" || {
echo "ERROR: External DNS management failed"
exit 1
}
;;
*)
echo "Error: Unsupported dns type '$DNS_TYPE'"
exit 1
Expand Down
1 change: 1 addition & 0 deletions k8s/scope/networking/gateway/build_gateway
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ gomplate -c .="$CONTEXT_PATH" \
--file "$TEMPLATE" \
--out "$INGRESS_FILE"


rm "$CONTEXT_PATH"
59 changes: 59 additions & 0 deletions k8s/scope/wait_on_balancer
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash

echo "Waiting for balancer/DNS setup to complete..."

MAX_ITERATIONS=${MAX_ITERATIONS:-30}
iteration=0

case "$DNS_TYPE" in
external_dns)
SCOPE_DOMAIN=$(echo "$CONTEXT" | jq -r '.scope.domain')
SCOPE_SLUG=$(echo "$CONTEXT" | jq -r '.scope.slug')
SCOPE_ID=$(echo "$CONTEXT" | jq -r '.scope.id')

echo "Checking ExternalDNS record creation for domain: $SCOPE_DOMAIN"

while true; do
iteration=$((iteration + 1))
if [ $iteration -gt $MAX_ITERATIONS ]; then
echo "⚠️ DNS record creation timeout after $((MAX_ITERATIONS * 10))s"
echo "ExternalDNS may still be processing the DNSEndpoint resource"
echo "You can check manually with: kubectl get dnsendpoint -A"
exit 1
fi

echo "Checking DNS resolution for $SCOPE_DOMAIN (attempt $iteration/$MAX_ITERATIONS)"

DNS_ENDPOINT_NAME="k-8-s-${SCOPE_SLUG}-${SCOPE_ID}-dns"
echo "Checking DNSEndpoint status: $DNS_ENDPOINT_NAME"

DNS_STATUS=$(kubectl get dnsendpoint "$DNS_ENDPOINT_NAME" -n "$K8S_NAMESPACE" -o jsonpath='{.status}' 2>/dev/null || echo "not found")

if [ "$DNS_STATUS" != "not found" ] && [ -n "$DNS_STATUS" ]; then
echo "DNSEndpoint status: $DNS_STATUS"
fi

if nslookup "$SCOPE_DOMAIN" 8.8.8.8 >/dev/null 2>&1; then
echo "✓ DNS record for $SCOPE_DOMAIN is now resolvable"

RESOLVED_IP=$(nslookup "$SCOPE_DOMAIN" 8.8.8.8 | grep -A1 "Name:" | tail -1 | awk '{print $2}' 2>/dev/null || echo "unknown")
echo "✓ Domain $SCOPE_DOMAIN resolves to: $RESOLVED_IP"

break
fi

echo "DNS record not yet available, waiting 10s..."
sleep 10
done

echo "✓ ExternalDNS setup completed successfully"
;;
route53|azure)
echo "DNS Type $DNS_TYPE - DNS should already be configured"
echo "Skipping DNS wait check"
;;
*)
echo "Unknown DNS type: $DNS_TYPE"
echo "Skipping DNS wait check"
;;
esac
13 changes: 13 additions & 0 deletions k8s/scope/workflows/create.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ steps:
type: environment
- name: HOSTED_PRIVATE_ZONE_ID
type: environment
post:
name: apply_and_wait
type: workflow
steps:
- name: apply dns templates
type: script
file: "$SERVICE_PATH/apply_templates"
configuration:
ACTION: apply
DRY_RUN: false
- name: wait on balancer
type: script
file: "$SERVICE_PATH/scope/wait_on_balancer"
2 changes: 1 addition & 1 deletion k8s/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ configuration:
K8S_NAMESPACE: nullplatform
DOMAIN: nullapps.io
USE_ACCOUNT_SLUG: false
DNS_TYPE: route53
DNS_TYPE: route53 # Available values route53 | azure | external_dns
DEPLOYMENT_MAX_WAIT_IN_SECONDS: 600
DEPLOYMENT_TEMPLATE: "$SERVICE_PATH/deployment/templates/deployment.yaml.tpl"
SECRET_TEMPLATE: "$SERVICE_PATH/deployment/templates/secret.yaml.tpl"
Expand Down