diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml new file mode 100644 index 0000000000..b768cd2079 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml @@ -0,0 +1,16 @@ +# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: cname-example + namespace: default + annotations: + dns.why/type: aws-localstack-tutorial +spec: + endpoints: + - dnsName: www.example.com + recordTTL: 600 + recordType: CNAME + targets: + - example.com diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml new file mode 100644 index 0000000000..07c97180a5 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml @@ -0,0 +1,28 @@ +# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: simple-example + namespace: default + annotations: + dns.why/type: aws-localstack-tutorial +spec: + endpoints: + - dnsName: dnsendpoint-a.example.com + recordTTL: 300 + recordType: A + targets: + - 192.168.1.100 + - dnsName: dnsendpoint-a-lb.example.com + recordTTL: 200 + recordType: A + targets: + - 10.0.1.1 + - 10.0.1.2 + - 10.0.1.3 + - dnsName: dnsendpoint-aaaa.example.com + recordTTL: 600 + recordType: AAAA + targets: + - 2001:0db8:85a3:0000:0000:8a2e:0370:7334 diff --git a/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml b/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml new file mode 100644 index 0000000000..ec738aed71 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml @@ -0,0 +1,19 @@ +# ref: docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: txt-example + namespace: default +spec: + endpoints: + - dnsName: _acme-challenge.example.com + recordTTL: 300 + recordType: TXT + targets: + - "validation-token-12345" + - dnsName: example.com + recordTTL: 3600 + recordType: TXT + targets: + - "v=spf1 include:_spf.google.com ~all" diff --git a/docs/snippets/tutorials/aws-localstack/fetch-records.sh b/docs/snippets/tutorials/aws-localstack/fetch-records.sh new file mode 100755 index 0000000000..b128359220 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/fetch-records.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +# run docs/snippets/tutorials/aws-localstack/check-records.sh + +export AWS_REGION=eu-west-1 +export AWS_ACCESS_KEY_ID=foo +export AWS_SECRET_ACCESS_KEY=bar +export AWS_ENDPOINT_URL=http://127.0.0.1:32379 + +MATCH="${1:-}" # optional positional argument to filter records by name + +zones=$(aws route53 list-hosted-zones-by-name --query "HostedZones[].Id" --output json) + +echo "$zones" | jq -r '.[]' | while IFS= read -r hosted_zone_id; do + zone=${hosted_zone_id#"/hostedzone/"} + echo "Checking records for zone: $zone" + + if [ -z "$MATCH" ]; then + # default behaviour (unchanged) + aws route53 list-resource-record-sets \ + --hosted-zone-id "$zone" \ + --query "ResourceRecordSets[].{Name:Name, Type:Type, Value:ResourceRecords[*].Value, TTL:TTL}" \ + --output json + else + # filtered behaviour + aws route53 list-resource-record-sets \ + --hosted-zone-id "$zone" \ + --query "ResourceRecordSets[?contains(Name, \`${MATCH}\`)].{Name:Name, Type:Type, Value:ResourceRecords[*].Value, TTL:TTL}" \ + --output json + fi +done diff --git a/docs/snippets/tutorials/aws-localstack/foo-app.yml b/docs/snippets/tutorials/aws-localstack/foo-app.yml new file mode 100644 index 0000000000..b0ecde7787 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/foo-app.yml @@ -0,0 +1,47 @@ +# ref: docs/snippets/tutorials/aws-localstack/foo-app.yml +--- +apiVersion: v1 +kind: Service +metadata: + name: foo-app + annotations: + external-dns.alpha.kubernetes.io/hostname: foo-app.example.com + dns.why/type: aws-localstack-tutorial +spec: + type: ClusterIP + clusterIP: None + ports: + - port: 80 + targetPort: 80 + protocol: TCP + selector: + app: foo +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo-app + annotations: + dns.why/type: aws-localstack-tutorial +spec: + replicas: 3 + selector: + matchLabels: + app: foo + template: + metadata: + labels: + app: foo + spec: + containers: + - name: foo + image: nginx:latest + ports: + - containerPort: 80 + resources: + requests: + memory: "5Mi" + cpu: "25m" + limits: + memory: "5Mi" + cpu: "25m" diff --git a/docs/snippets/tutorials/aws-localstack/kind.yaml b/docs/snippets/tutorials/aws-localstack/kind.yaml new file mode 100644 index 0000000000..b959597f79 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/kind.yaml @@ -0,0 +1,37 @@ +# ref: https://kind.sigs.k8s.io/docs/user/quick-start/ +# https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings + +# kind create cluster --config=docs/snippets/tutorials/aws-localstack/kind.yaml +# kind delete cluster --name aws-localstack +# kubectl cluster-info --context kind-aws-localstack +# kubectl get nodes -o wide +--- +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: aws-localstack +networking: + apiServerAddress: 127.0.0.1 + apiServerPort: 6443 + ipFamily: dual +nodes: +- role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 80 + hostPort: 8080 + listenAddress: "0.0.0.0" + protocol: TCP + - containerPort: 43 + hostPort: 4443 + listenAddress: "0.0.0.0" + protocol: TCP + - containerPort: 32379 # inside kind node + hostPort: 32379 # exposed on host + listenAddress: "0.0.0.0" + protocol: TCP +- role: worker diff --git a/docs/snippets/tutorials/aws-localstack/service-lb.yml b/docs/snippets/tutorials/aws-localstack/service-lb.yml new file mode 100644 index 0000000000..30a85f96d8 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/service-lb.yml @@ -0,0 +1,18 @@ +# ref: docs/snippets/tutorials/aws-localstack/service-lb.yml +--- +apiVersion: v1 +kind: Service +metadata: + name: loadbalancer-service + annotations: + external-dns.alpha.kubernetes.io/hostname: my-loadbalancer.example.com + dns.why/type: aws-localstack-tutorial + namespace: default +spec: + type: LoadBalancer + ports: + - port: 80 + name: http + targetPort: 80 + selector: + app: test-app diff --git a/docs/snippets/tutorials/aws-localstack/values-extdns.yml b/docs/snippets/tutorials/aws-localstack/values-extdns.yml new file mode 100644 index 0000000000..8e31ec2a3b --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/values-extdns.yml @@ -0,0 +1,29 @@ +# ref: https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/values.yaml +logLevel: info # debug, info, warn, error +policy: sync + +provider: + name: aws + +txtOwnerId: aws-localstack + +domainFilters: + - example.com + - local.tld + +sources: + - service + - ingress + - crd + +env: + - name: AWS_REGION + value: eu-west-1 + - name: AWS_ACCESS_KEY_ID + value: foo-fake + - name: AWS_SECRET_ACCESS_KEY + value: bar-fake + - name: AWS_ENDPOINT_URL + value: http://localstack.localstack.svc.cluster.local:4566 + +interval: 1m diff --git a/docs/snippets/tutorials/aws-localstack/values-localstack.yml b/docs/snippets/tutorials/aws-localstack/values-localstack.yml new file mode 100644 index 0000000000..ec689102d0 --- /dev/null +++ b/docs/snippets/tutorials/aws-localstack/values-localstack.yml @@ -0,0 +1,50 @@ +# ref: https://github.com/localstack/helm-charts/blob/main/charts/localstack/values.yaml + +debug: false + +extraLabels: + app: localstack + +extraEnvVars: + - name: SERVICES + value: "route53" + +# -- Set a fixed port for LocalStack edge service -- +service: + type: NodePort + edgeService: + name: edge + targetPort: 4566 + nodePort: 32379 + +enableStartupScripts: true +startupScriptContent: | + #!/bin/bash + create_zone_if_missing() { + ZONE_NAME="$1" + COMMENT="$2" + + EXISTING_ZONE_ID=$( + awslocal route53 list-hosted-zones-by-name \ + --dns-name "${ZONE_NAME}." \ + --query "HostedZones[?Name=='${ZONE_NAME}.'].Id | [0]" \ + --output text + ) + + if [ "$EXISTING_ZONE_ID" != "None" ]; then + echo "Route53 zone '${ZONE_NAME}' already exists (${EXISTING_ZONE_ID})" + return 0 + fi + + echo "Creating Route53 zone '${ZONE_NAME}'" + awslocal route53 create-hosted-zone \ + --name "$ZONE_NAME" \ + --caller-reference "$(date +%s)" \ + --hosted-zone-config Comment="$COMMENT" + } + + create_zone_if_missing "local.tld" "external-dns" + create_zone_if_missing "example.com" "external-dns" + +lambda: + executor: "kubernetes" diff --git a/docs/tutorials/aws-localstack.md b/docs/tutorials/aws-localstack.md new file mode 100644 index 0000000000..6ef5b475d5 --- /dev/null +++ b/docs/tutorials/aws-localstack.md @@ -0,0 +1,484 @@ +--- +tags: ["tutorial", "kind", "localstack", "aws"] +--- + +# AWS and LocalStack + +## Overview + +This tutorial demonstrates how to configure ExternalDNS to manage DNS records in LocalStack's Route53 service using a local Kind (Kubernetes in Docker) cluster. + +### TL;DR + +After completing this lab, you will have a Kubernetes environment running as containers in your local development machine with localstack and external-dns. + +## Prerequisite + +Before you start, ensure you have: + +- A running kubernetes cluster. + - In this tutorial we are going to use [kind](https://kind.sigs.k8s.io/) +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) and [`helm`](https://helm.sh/) +- `external-dns` source code or [helm chart](https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns) +- `Localstack` how to [documenation](https://docs.localstack.cloud/) +- Optional + - `AWS` [cli](https://aws.amazon.com/cli/) + - `Localstack` [cli](https://docs.localstack.cloud/aws/getting-started/installation/) + +## Architecture Overview + +In this setup: + +- `Kind` provides a local `Kubernetes` cluster +- `LocalStack` simulates AWS services (specifically `Route53`) +- `ExternalDNS` automatically creates DNS records in `LocalStack` based on `Kubernetes` resources + +## Bootstrap Environment + +### 1. Create cluster + +```sh +kind create cluster --config=docs/snippets/tutorials/aws-localstack/kind.yaml + +Creating cluster "aws-localstack" ... + ✓ Preparing nodes đŸ“Ļ đŸ“Ļ + ✓ Writing configuration 📜 + ✓ Starting control-plane đŸ•šī¸ + ✓ Installing CNI 🔌 + ✓ Installing StorageClass 💾 + ✓ Joining worker nodes 🚜 +Set kubectl context to "kind-aws-localstack" +You can now use your cluster with: + +kubectl cluster-info --context kind-aws-localstack +``` + +Verify the cluster is running: + +```bash +kubectl cluster-info --context kind-aws-localstack +kubectl get nodes +``` + +### 2. Deploy Localstack + +There are multiple options to configure etcd + +1. With custom manifest. +2. Localstack [helm](https://docs.localstack.cloud/aws/integrations/containers/kubernetes/) + +In this tutorial, we'll use the second option. + +```sh +helm repo add localstack https://localstack.github.io/helm-charts +helm upgrade localstack localstack-charts/localstack \ + -n localstack \ + --create-namespace \ + --install \ + --atomic \ + --wait \ + -f docs/snippets/tutorials/aws-localstack/values-localstack.yml + +❯❯ Release "localstack" does not exist. Installing it now. +``` + +Verify LocalStack is running + +```sh +kubectl get pods -n localstack +kubectl logs deploy/localstack -n localstack +``` + +### 3: Create a Hosted Zone in LocalStack + +Test if we could reach `Localstack`, `Route53` service is available and verify `Route53` zones created in localstack. + +```sh +curl http://127.0.0.1:$NODE_PORT/_localstack/health | jq +docs/snippets/tutorials/aws-localstack/fetch-records.sh +``` + +Create extra hosted zones in localstack when required + +```sh +export AWS_ACCESS_KEY_ID=test +export AWS_SECRET_ACCESS_KEY=test +export AWS_DEFAULT_REGION=us-east-1 +export AWS_ENDPOINT_URL=http://127.0.0.1:32379 + +aws route53 create-hosted-zone \ + --name test.com \ + --caller-reference $(date +%s) +``` + +### 4. Configure ExternalDNS + +Deploy with helm and minimal configuration. + +Add the `external-dns` helm repository and check available versions + +```sh +helm repo add --force-update external-dns https://kubernetes-sigs.github.io/external-dns/ +helm search repo external-dns --versions +``` + +Install with required configuration + +```sh +helm upgrade --install external-dns external-dns/external-dns \ + -n default \ + -f docs/snippets/tutorials/aws-localstack/values-extdns.yml + +❯❯ Release "external-dns" does not exist. Installing it now. +``` + +Validate pod status and view logs + +```sh +kubectl get pods -l app.kubernetes.io/name=external-dns +kubectl logs deploy/external-dns -n default +``` + +Or run it on the host from sources + +```sh +# required to access localstack +export AWS_REGION=eu-west-1 +export AWS_ACCESS_KEY_ID=foo +export AWS_SECRET_ACCESS_KEY=bar +export AWS_ENDPOINT_URL=http://127.0.0.1:32379 + +go run main.go \ + --provider=aws \ + --source=service \ + --source=ingress \ + --source=crd \ + --txt-owner-id=aws-localstack \ + --domain-filter=example.com \ + --domain-filter=local.tld \ + --log-level=info +``` + +## 5. Test with a Sample Service + +Create a test service so that ExternalDNS to create records + +```yaml +[[% include 'tutorials/aws-localstack/foo-app.yml' %]] +``` + +Deploy the service + +```sh +kubectl apply -f docs/snippets/tutorials/aws-localstack/foo-app.yml +``` + +Validate `route53` records created + +```sh +docs/snippets/tutorials/aws-localstack/fetch-records.sh "foo-app" + +❯❯ [ + { + "Name": "a-foo-app.example.com.", + "Type": "TXT", + }, + { + "Name": "foo-app.example.com.", + "Type": "A", + "Value": [ + "10.244.1.18", + ], + "TTL": 300 + } +] +``` + +## 6. Using DNSEndpoint CRD (Advanced) + +The DNSEndpoint Custom Resource Definition (CRD) provides direct control over DNS records, independent of Services or Ingresses. This is useful for: + +- Creating DNS records that don't correspond to Kubernetes services +- Managing complex DNS configurations (multiple targets, weighted routing) +- Integrating with external systems or custom controllers + +Verify the CRD is installed + +```sh +kubectl get crd dnsendpoints.externaldns.k8s.io +``` + +### Example 1: Multiple Records + +Create a simple A record pointing to a specific IP + +```yaml +[[% include 'tutorials/aws-localstack/dnsendpoint-multi.yml' %]] +``` + +Apply and verify + +```sh +kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-multi.yml +# Check the DNSEndpoint status +kubectl get dnsendpoint simple-example -o yaml +# validate +docs/snippets/tutorials/aws-localstack/fetch-records.sh "dnsendpoint-a" +docs/snippets/tutorials/aws-localstack/fetch-records.sh "dnsendpoint-aaaa" + +❯❯ [ + { + "Name": "a-dnsendpoint-a.example.com.", + "Type": "TXT", + "Value": [ + "heritage=external-dns,external-dns/owner=aws-localstack" + ], + "TTL": 300 + }, + { + "Name": "dnsendpoint-a.example.com.", + "Type": "A", + "Value": [ + "192.168.1.100" + ], + "TTL": 300 + }, + { + "Name": "dnsendpoint-aaaa.example.com.", + "Type": "AAAA", + "Value": [ + "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + ], + "TTL": 600 + }, +] +``` + +### Example 2: CNAME Record + +Create a CNAME record pointing to another domain: + +```yaml +[[% include 'tutorials/aws-localstack/dnsendpoint-cname.yml' %]] +``` + +Apply and verify + +```sh +kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-cname.yml +# Check the DNSEndpoint status +kubectl get dnsendpoint cname-example -o yaml +# validate +docs/snippets/tutorials/aws-localstack/fetch-records.sh "www.example" + +❯❯ [ + { + "Name": "a-www.example.com.", + "Type": "TXT", + "Value": [ + "\"heritage=external-dns,external-dns/owner=aws-localstack,external-dns/resource=crd/default/cname-example\"" + ], + "TTL": 300 + }, + { + "Name": "www.example.com.", + "Type": "A", + "Value": [ + "example.com" + ], + "TTL": 600 + } +] +``` + +### Example 4: TXT Records + +Create TXT records (useful for domain verification, SPF, DKIM, etc.) + +```yaml +[[% include 'tutorials/aws-localstack/dnsendpoint-txt.yml' %]] +``` + +Apply and verify + +```sh +kubectl apply -f docs/snippets/tutorials/aws-localstack/dnsendpoint-txt.yml +# Check the DNSEndpoint status +kubectl get dnsendpoint txt-example -o yaml +# validate +docs/snippets/tutorials/aws-localstack/fetch-records.sh +``` + +## 7. Test with Service LoadBalancer (Advanced) + +With Kind, LoadBalancer services won't get external IPs automatically. You can: + +- Use [MetalLB](https://metallb.io/) for LoadBalancer support in Kind +- Install and run [Cloud Provider KInd](https://kind.sigs.k8s.io/docs/user/loadbalancer/) +- Patch services, to manually assign an Ingress IPs. It just makes the Service appear like a real LoadBalancer for tools/tests. + +```yaml +[[% include 'tutorials/aws-localstack/service-lb.yml' %]] +``` + +Apply, patch and verify + +```sh +kubectl apply -f docs/snippets/tutorials/aws-localstack/service-lb.yml +# patch +kubectl patch svc loadbalancer-service --type=merge \ + -p '{"status":{"loadBalancer":{"ingress":[{"ip":"172.18.0.2"}]}}}' --subresource=status +❯❯ service/loadbalancer-service +# validate +docs/snippets/tutorials/aws-localstack/fetch-records.sh "my-loadbalancer" +``` + +### Cleanup + +Remove all resources: + +```sh +kind delete cluster --name aws-localstack +``` + +## Diagrams + +### System Architecture + +**Description:** This diagram illustrates the complete setup where ExternalDNS runs inside the Kind cluster, watches Kubernetes Service and Ingress resources, and automatically creates corresponding DNS records in LocalStack's Route53 service. +Both the Kind cluster and LocalStack container run on the same Docker network, enabling communication between them. + +```mermaid +graph TB + subgraph "Host Machine" + kubectl[kubectl CLI] + awscli[AWS CLI] + end + + subgraph "Docker Network: kind" + subgraph "Kind Cluster" + subgraph "Control Plane" + api[API Server] + end + + subgraph "Namespace: external-dns" + ed[ExternalDNS Pod] + end + + subgraph "Namespace: default" + nginx[Nginx Pod] + svc[Service
nginx.example.com] + ing[Ingress
nginx-ingress.example.com] + end + end + + ls[LocalStack Container
Route53 Mock] + end + + kubectl -->|manages| api + awscli -->|configures DNS| ls + ed -->|watches| svc + ed -->|watches| ing + ed -->|creates/updates
DNS records| ls + api -->|provides resources| ed + svc -->|routes to| nginx + ing -->|routes to| svc + + style ed fill:#326ce5,color:#fff + style ls fill:#ff9900,color:#fff + style kubectl fill:#326ce5,color:#fff + style awscli fill:#ff9900,color:#fff +``` + +### DNS Record Creation Flow + +**Description:** This sequence diagram demonstrates the automated DNS lifecycle management. When you create a Service with an ExternalDNS annotation, ExternalDNS detects the new resource, extracts the hostname, and creates corresponding DNS records in LocalStack. +It also creates TXT records for ownership tracking. When the Service is deleted, ExternalDNS automatically cleans up the DNS records. + +```mermaid +sequenceDiagram + participant User + participant K8s as Kubernetes API + participant ED as ExternalDNS + participant LS as LocalStack Route53 + + User->>K8s: kubectl apply -f service.yaml + K8s->>K8s: Service created + + Note over ED: Watches for Service changes + K8s->>ED: Service event detected + ED->>ED: Parse annotation:
nginx.example.com + ED->>LS: Check existing records + LS-->>ED: No record exists + ED->>LS: Create A record
nginx.example.com → LoadBalancer IP + LS->>LS: Record created + LS-->>ED: Success + ED->>LS: Create TXT record
"heritage=external-dns,..." + LS-->>ED: Success + + Note over ED: Continues watching for changes + + User->>K8s: kubectl delete service nginx + K8s->>ED: Service deletion event + ED->>LS: Delete A record + ED->>LS: Delete TXT record + LS-->>ED: Records deleted +``` + +### ExternalDNS Decision Flow + +**Description:** This flowchart illustrates ExternalDNS's decision-making process. +It checks for DNS annotations, validates the domain filter, ensures IP addresses are available, and uses TXT records to track ownership. +This prevents conflicts when multiple DNS controllers or manual DNS entries exist. +The ownership mechanism ensures ExternalDNS only modifies records it created. + +```mermaid +flowchart TD + Start([ExternalDNS detects
Kubernetes resource]) + + Start --> CheckAnnotation{Has external-dns
annotation?} + CheckAnnotation -->|No| Skip[Skip - No DNS needed] + CheckAnnotation -->|Yes| ExtractHost[Extract hostname from
annotation] + + ExtractHost --> CheckDomain{Hostname matches
domain-filter?} + CheckDomain -->|No| Skip2[Skip - Outside managed domain] + CheckDomain -->|Yes| GetIP[Get LoadBalancer IP or
Ingress address] + + GetIP --> CheckIP{IP/Address
available?} + CheckIP -->|No| Wait[Wait for IP assignment] + CheckIP -->|Yes| QueryRoute53[Query LocalStack Route53
for existing record] + + QueryRoute53 --> CheckExists{Record
exists?} + CheckExists -->|No| Create[Create new A record
+ TXT ownership record] + CheckExists -->|Yes| CheckOwner{Check TXT record
owner ID} + + CheckOwner -->|Not owned by us| Skip3[Skip - Managed by
another controller] + CheckOwner -->|Owned by us| CheckIP2{IP changed?} + + CheckIP2 -->|No| NoAction[No action needed] + CheckIP2 -->|Yes| Update[Update A record
with new IP] + + Create --> Success([DNS record created]) + Update --> Success + NoAction --> Success + Skip --> End([End]) + Skip2 --> End + Skip3 --> End + Wait --> End + Success --> End + + style Start fill:#90EE90,color:#000 + style Success fill:#90EE90,color:#000 + style Create fill:#ADD8E6,color:#000 + style Update fill:#ADD8E6,color:#000 + style Skip fill:#FFB6C1,color:#000 + style Skip2 fill:#FFB6C1,color:#000 + style Skip3 fill:#FFB6C1,color:#000 +``` + +## Additional Resources + +- [ExternalDNS Documentation](https://github.com/kubernetes-sigs/external-dns) +- [Kind Documentation](https://kind.sigs.k8s.io/) +- [LocalStack Documentation](https://docs.localstack.cloud/) +- [AWS Route53 API Reference](https://docs.aws.amazon.com/route53/) diff --git a/docs/tutorials/coredns-etcd.md b/docs/tutorials/coredns-etcd.md index 8f8ee0db99..be58d9d42a 100644 --- a/docs/tutorials/coredns-etcd.md +++ b/docs/tutorials/coredns-etcd.md @@ -1,3 +1,7 @@ +--- +tags: ["tutorial", "coredns", "etcd", "kind"] +--- + # CoreDNS with etcd backend ## Overview @@ -27,7 +31,7 @@ Before you start, ensure you have: - `CoreDNS` [helm chart](https://github.com/coredns/helm) - Optional - `dnstools` container for testing - - `etcdctl` to interat with [etcd](https://etcd.io/docs/v3.4/dev-guide/interacting_v3/) + - `etcdctl` to interact with [etcd](https://etcd.io/docs/v3.4/dev-guide/interacting_v3/) ## Bootstrap Environment diff --git a/mkdocs.yml b/mkdocs.yml index c25db22bed..9777e2cca5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,6 +58,7 @@ theme: - navigation.instant - navigation.tabs - navigation.tabs.sticky + - tags extra: version: