Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions hack/create-kind-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}"
KUBECTL_CONTEXT="kind-${KIND_CLUSTER_NAME}"
reg_name="kind-registry"
reg_port="${KIND_REGISTRY_PORT:-5001}"
IPV6_DNS_UPSTREAM="${IPV6_DNS_UPSTREAM:-2001:4860:4860::8888 2001:4860:4860::8844}"

if [[ $# -gt 0 ]]; then
case "$1" in
Expand All @@ -31,6 +32,8 @@ if [[ $# -gt 0 ]]; then
echo "Configured through the environment:"
echo " KIND_CLUSTER_NAME Name of the cluster to create (default: kind)."
echo " IP_FAMILY Address families for pods and Services: ipv4, ipv6 or dual (default: ipv4)."
echo " IPV6_DNS_UPSTREAM Space-separated IPv6 resolvers CoreDNS forwards to when IP_FAMILY=ipv6"
echo " (default: Google Public DNS). Override where those are unreachable."
exit 0
;;
esac
Expand Down Expand Up @@ -196,6 +199,100 @@ if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}
docker network connect "kind" "${reg_name}"
fi

# 4.5. Give CoreDNS an IPv6 forwarder and a registry entry
#
# CoreDNS runs dnsPolicy: Default, inheriting the node's IPv4 resolver, which
# no pod here can reach, so external lookups SERVFAIL. Step 3's registry
# wiring is node-side, so it misses atelet too: that pull runs in atelet's own
# netns, where "kind-registry" NXDOMAINs.
if [[ "${IP_FAMILY}" == "ipv6" ]]; then
echo "Repointing CoreDNS at an IPv6 resolver and teaching it '${reg_name}'..."
reg_v6="$(docker inspect "${reg_name}" \
--format '{{.NetworkSettings.Networks.kind.GlobalIPv6Address}}')"
if [[ -z "${reg_v6}" ]]; then
echo "error: '${reg_name}' has no IPv6 address on the 'kind' network" >&2
exit 1
fi

corefile="$(kubectl --context="${KUBECTL_CONTEXT}" -n kube-system get cm coredns \
-o jsonpath='{.data.Corefile}')"
# fallthrough is load-bearing: without it every name but the registry
# NXDOMAINs, cluster.local included -- one outage traded for a worse one.
search="forward . /etc/resolv.conf"
replace="hosts {
${reg_v6} ${reg_name}
fallthrough
}
forward . ${IPV6_DNS_UPSTREAM}"
# Unquoted on purpose: bash 3.2 splices the quotes in literally. First match
# only, and the emitted block inherits the matched line's indentation.
patched="${corefile/$search/$replace}"
if [[ "${patched}" == "${corefile}" ]]; then
echo "error: '${search}' not found in the CoreDNS Corefile" >&2
echo " the Corefile layout changed upstream; update this block" >&2
exit 1
fi

# A YAML patch file avoids escaping the Corefile's newlines into JSON.
{ printf 'data:\n Corefile: |\n'; printf '%s\n' "${patched}" | sed 's/^/ /'; } \
> "${ROOT}/bin/coredns-patch.yaml"
kubectl --context="${KUBECTL_CONTEXT}" -n kube-system patch cm coredns \
--type=merge --patch-file "${ROOT}/bin/coredns-patch.yaml"
kubectl --context="${KUBECTL_CONTEXT}" -n kube-system rollout restart deploy/coredns
kubectl --context="${KUBECTL_CONTEXT}" -n kube-system rollout status deploy/coredns \
--timeout=120s

# Probe from a pod, not the node: the node is dual-stack and passes either
# way. The registry leg fetches rather than resolves -- the hosts entry is
# AAAA-only, which fails nslookup's A query but satisfies getaddrinfo.
echo "Verifying DNS from a pod..."
# Markers, not the exit status: --attach returns only the last leg's.
# PROBE_RAN separates a failed leg from a pod that never ran -- a different fix.
# Retry the whole pod, not just the query. Asking once right after the rollout
# fails on a brand-new cluster, and retrying inside the pod does not rescue it:
# measured here, a pod that fails keeps failing for 36s while a fresh pod 10s
# later resolves first try. So the outer loop is the one that matters; the
# inner ones only absorb a slow answer. busybox nslookup has no timeout flag,
# so every budget below is attempts, not seconds.
probe=""
for probe_attempt in 1 2 3 4; do
probe="$(kubectl --context="${KUBECTL_CONTEXT}" run "coredns-probe-$$-${probe_attempt}" \
--rm --attach --quiet --restart=Never --image=busybox:1.36 --command -- \
sh -c "echo PROBE_RAN
for i in 1 2 3; do
out=\$(nslookup storage.googleapis.com 2>&1) && { echo RESOLVE_OK; break; }
echo \"resolve attempt failed: \$(echo \"\$out\" | tail -2 | tr '\n' ' ')\"
sleep 3
done
for i in 1 2 3; do
wget -q -T10 -O/dev/null http://${reg_name}:5000/v2/ && { echo REGISTRY_OK; break; }
sleep 3
done")" || true
[[ "${probe}" == *RESOLVE_OK* && "${probe}" == *REGISTRY_OK* ]] && break
if [[ "${probe_attempt}" != 4 ]]; then
echo " the cluster is not resolving yet; re-probing (${probe_attempt}/4)..."
sleep 10
fi
done
if [[ "${probe}" != *PROBE_RAN* ]]; then
echo "error: the probe pod never ran, so CoreDNS is unverified" >&2
echo " check that it scheduled and that 'busybox:1.36' pulled" >&2
exit 1
fi
if [[ "${probe}" != *RESOLVE_OK* ]]; then
echo "error: a pod cannot resolve an external name" >&2
echo " IPV6_DNS_UPSTREAM is '${IPV6_DNS_UPSTREAM}'; set it to a reachable resolver" >&2
echo " probe output was:" >&2
while IFS= read -r line; do echo " ${line}" >&2; done <<<"${probe}"
exit 1
fi
if [[ "${probe}" != *REGISTRY_OK* ]]; then
echo "error: DNS works but a pod cannot reach '${reg_name}' at [${reg_v6}]:5000" >&2
echo " check the registry container is up and on the 'kind' network" >&2
exit 1
fi
fi

# 5. Document the local registry in kube-public ConfigMap
echo "Documenting local registry in cluster..."
cat <<EOF | kubectl --context="${KUBECTL_CONTEXT}" apply -f -
Expand Down
Loading