chore: merge with master

This commit is contained in:
Arnaud Lefray 2023-05-04 15:23:10 +02:00
commit 56773c0430
No known key found for this signature in database
GPG Key ID: 1049DFCA2DCF465D
258 changed files with 20383 additions and 5403 deletions

View File

@ -9,3 +9,11 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker" # Keep Docker dependencies up to date
directory: "/"
schedule:
interval: "daily"

View File

@ -6,40 +6,38 @@ on:
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ^1.17
go-version: 1.19
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install CI
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Install additional CI for nektos/act
run: |
apt update
apt install -y make gcc libc-dev git
if: github.actor == 'nektos/act'
- name: Lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0
make lint
- name: Test
run: make test

View File

@ -26,15 +26,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install go version
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: '^1.16'
go-version: '^1.19'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -46,4 +46,4 @@ jobs:
make build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

47
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Release Docs
on:
push:
tags:
- "v*"
permissions: {}
jobs:
release_docs:
permissions:
contents: write # for mike to push
name: Release Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: "./docs/scripts/requirements.txt"
- uses: actions/setup-go@v4
with:
go-version: ^1.19
- run: |
pip install -r docs/scripts/requirements.txt
- name: setup
run: |
./docs/scripts/copy_docs.sh
go run ./docs/scripts/docs.go
- name: Configure Git user
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
- name: build and push
run: |
mike deploy ${{ github.ref_name }} latest --push --update-aliases
mike set-default --push latest

View File

@ -0,0 +1,41 @@
name: Approve GH Workflows
on:
pull_request_target:
types:
- labeled
- synchronize
branches:
- master
jobs:
approve:
name: Approve ok-to-test
if: contains(github.event.pull_request.labels.*.name, 'ok-to-test')
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Update PR
uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 # v6.3.3
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
debug: ${{ secrets.ACTIONS_RUNNER_DEBUG }}
script: |
const result = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
event: "pull_request",
status: "action_required",
head_sha: context.payload.pull_request.head.sha,
per_page: 100
});
for (var run of result.data.workflow_runs) {
await github.rest.actions.approveWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
});
}

View File

@ -0,0 +1,23 @@
name: json-yaml-validate
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull requests
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v1.2.0
with:
comment: "true" # enable comment mode
yaml_exclude_regex: "(charts/external-dns/templates.*|mkdocs.yml)"

View File

@ -9,24 +9,36 @@ jobs:
lint-test:
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: v3.6.3
- name: Run Artifact Hub lint
run: |
set -euo pipefail
curl -Lo ah_linux_amd64.tar.gz https://github.com/artifacthub/hub/releases/download/v1.9.0/ah_1.9.0_linux_amd64.tar.gz
tar -xzvf ah_linux_amd64.tar.gz ah
./ah lint --kind helm || exit 1
rm -f ./ah ./ah_linux_amd64.tar.gz
- name: Set up Python
uses: actions/setup-python@v2
- name: Set-up Helm
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78
with:
python-version: 3.7
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.1.0
- name: Set-up Python
uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5
with:
python-version: "3.x"
- name: Set-up chart-testing
uses: helm/chart-testing-action@afea100a513515fbd68b0e72a7bb0ae34cb62aec
- name: Run chart-testing (list-changed)
id: list-changed
@ -39,8 +51,8 @@ jobs:
- name: Run chart-testing (lint)
run: ct lint --check-version-increment=false
- name: Create Kind cluster
uses: helm/kind-action@v1.2.0
- name: Set-up Kind cluster
uses: helm/kind-action@d8ccf8fb623ce1bb360ae2f45f323d9d5c5e9f00
with:
wait: 120s
if: steps.list-changed.outputs.changed == 'true'

35
.github/workflows/lint.yaml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
make lint

View File

@ -7,28 +7,59 @@ on:
paths:
- "charts/external-dns/Chart.yaml"
permissions: {}
jobs:
release:
permissions:
contents: write # to push chart release and create a release (helm/chart-releaser-action)
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
with:
fetch-depth: 0
- name: Get chart version
id: chart_version
run: |
set -euo pipefail
chart_version="$(grep -Po "(?<=^version: ).+" charts/external-dns/Chart.yaml)"
echo "::set-output name=version::${chart_version}"
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@b97ce03a10d9bdbb07beb491c76a5a01d78cd3ef
with:
path: charts/external-dns/CHANGELOG.md
version: "v${{ steps.chart_version.outputs.version }}"
- name: Create release notes
run: |
set -euo pipefail
cat <<"EOF" > charts/external-dns/RELEASE.md
${{ steps.changelog_reader.outputs.changes }}
EOF
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v1
- name: Set-up Helm
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78
with:
version: v3.6.3
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.2.1
uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_RELEASE_NAME_TEMPLATE: "external-dns-helm-chart-{{ .Version }}"
CR_RELEASE_NOTES_FILE: RELEASE.md
CR_MAKE_RELEASE_LATEST: false

View File

@ -0,0 +1,37 @@
name: Build all images
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Install CI
run: |
go get -v -t -d ./...
- name: Test
run: make build.image/multiarch

View File

@ -1,13 +1,17 @@
name: trivy vulnerability scanner
on:
push:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build an image from Dockerfile
run: |
make build.docker

7
.gitignore vendored
View File

@ -54,3 +54,10 @@ profile.cov
# Helm charts
!/charts/external-dns/
docs/LICENSE.md
docs/code-of-conduct.md
docs/CONTRIBUTING.md
docs/index.md
docs/redirect
site

View File

@ -3,39 +3,38 @@ linters-settings:
default-signifies-exhaustive: false
goimports:
local-prefixes: sigs.k8s.io/external-dns
golint:
min-confidence: 0.9
maligned:
suggest-new: true
misspell:
locale: US
revive:
confusing-naming: false
ignore-generated-header: true
rules:
- name: confusing-naming
disabled: true
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- deadcode
- depguard
- dogsled
- gofmt
- goimports
- golint
- goprintffuncname
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
- revive
- unused
- gosimple
- staticcheck
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
@ -47,12 +46,10 @@ issues:
- dogsled
- gofmt
- goimports
- golint
- goprintffuncname
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- nolintlint
- rowserrcheck

View File

@ -14,7 +14,7 @@
# builder image
ARG ARCH
FROM golang:1.17 as builder
FROM golang:1.19 as builder
ARG ARCH
WORKDIR /sigs.k8s.io/external-dns
@ -24,10 +24,10 @@ COPY go.sum .
RUN go mod download
COPY . .
RUN make test build.$ARCH
# final image
FROM $ARCH/alpine:3.15
FROM alpine:3.17
RUN apk update && apk add "libcrypto3>=3.0.8-r1" "libssl3>=3.0.8-r1" && rm -rf /var/cache/apt/*
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM golang:1.17 as builder
FROM golang:1.19 as builder
WORKDIR /sigs.k8s.io/external-dns

View File

@ -19,7 +19,7 @@
cover:
go get github.com/wadey/gocovmerge
$(eval PKGS := $(shell go list ./... | grep -v /vendor/))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | sed -e 's/ /,/g'))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | tr / -'))
go list -f '{{if or (len .TestGoFiles) (len .XTestGoFiles)}}go test -test.v -test.timeout=120s -covermode=count -coverprofile={{.Name}}_{{len .Imports}}_{{len .Deps}}.coverprofile -coverpkg $(PKGS_DELIM) {{.ImportPath}}{{end}}' $(PKGS) | xargs -0 sh -c
gocovmerge `ls *.coverprofile` > cover.out
rm *.coverprofile
@ -90,8 +90,11 @@ IMAGE ?= us.gcr.io/k8s-artifacts-prod/external-dns/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty)
BUILD_FLAGS ?= -v
LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
ARCHS = amd64 arm64v8 arm32v7
SHELL = /bin/bash
ARCHS = amd64 arm64 arm/v7
ARCH ?= amd64
DEFAULT_ARCH = amd64
SHELL = /bin/bash
OUTPUT_TYPE ?= docker
build: build/$(BINARY)
@ -99,37 +102,65 @@ build: build/$(BINARY)
build/$(BINARY): $(SOURCES)
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.push/multiarch:
build.push/multiarch: $(addprefix build.push-,$(ARCHS))
arch_specific_tags=()
for arch in $(ARCHS); do \
image="$(IMAGE):$(VERSION)-$${arch}" ;\
# pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\
docker pull $${arch}/alpine:3.14 ;\
docker pull golang:1.17 ;\
DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\
docker push $${image} ;\
arch_specific_tags+=( "--amend $${image}" ) ;\
image="$(IMAGE):$(VERSION)-$$(echo $$arch | tr / -)" ;\
arch_specific_tags+=( " $${image}" ) ;\
done ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
for arch in $(ARCHS); do \
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate --arch $${arch} "$(IMAGE):$(VERSION)" "$(IMAGE):$(VERSION)-$${arch}" ;\
done;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(IMAGE):$(VERSION)" \
echo $${arch_specific_tags[@]} ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx imagetools create --tag "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
build.push: build.docker
docker push "$(IMAGE):$(VERSION)"
build.image/multiarch: $(addprefix build.image-,$(ARCHS))
build.arm64v8:
build.image:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=docker build.docker
build.image-amd64:
$(MAKE) ARCH=amd64 build.image
build.image-arm64:
$(MAKE) ARCH=arm64 build.image
build.image-arm/v7:
$(MAKE) ARCH=arm/v7 build.image
build.push:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=registry build.docker
build.push-amd64:
$(MAKE) ARCH=amd64 build.push
build.push-arm64:
$(MAKE) ARCH=arm64 build.push
build.push-arm/v7:
$(MAKE) ARCH=arm/v7 build.push
build.arm64:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.arm32v7:
build.arm/v7:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.docker:
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" .
build.setup:
docker buildx inspect img-builder > /dev/null || docker buildx create --name img-builder --use
build.docker: build.setup build.$(ARCH)
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="$(ARCH)" .
image="$(IMAGE):$(VERSION)-$(subst /,-,$(ARCH))"; \
docker buildx build \
--pull \
--provenance=false \
--sbom=false \
--output=type=$(OUTPUT_TYPE) \
--platform linux/$(ARCH) \
--build-arg ARCH="$(ARCH)" \
--build-arg VERSION="$(VERSION)" \
--tag $${image} .
build.mini:
docker build --rm --tag "$(IMAGE):$(VERSION)-mini" --build-arg VERSION="$(VERSION)" -f Dockerfile.mini .
@ -140,8 +171,8 @@ clean:
# Builds and push container images to the staging bucket.
.PHONY: release.staging
release.staging:
release.staging: test
IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch
release.prod:
release.prod: test
$(MAKE) build.push/multiarch

2
OWNERS
View File

@ -5,11 +5,13 @@ approvers:
- raffo
- njuettner
- seanmalloy
- szuecs
reviewers:
- njuettner
- raffo
- seanmalloy
- szuecs
emeritus_approvers:
- hjacobs

View File

@ -1,13 +1,17 @@
---
hide:
- toc
- navigation
---
<p align="center">
<img src="img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
</p>
# ExternalDNS
[![Build Status](https://github.com/kubernetes-sigs/external-dns/workflows/Go/badge.svg)](https://github.com/kubernetes-sigs/external-dns/actions)
[![Coverage Status](https://coveralls.io/repos/github/kubernetes-sigs/external-dns/badge.svg)](https://coveralls.io/github/kubernetes-sigs/external-dns)
[![GitHub release](https://img.shields.io/github/release/kubernetes-sigs/external-dns.svg)](https://github.com/kubernetes-sigs/external-dns/releases)
[![go-doc](https://godoc.org/github.com/kubernetes-sigs/external-dns?status.svg)](https://godoc.org/github.com/kubernetes-sigs/external-dns)
[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes-sigs/external-dns)](https://goreportcard.com/report/github.com/kubernetes-sigs/external-dns)
[![Build Status](https://github.com/kubernetes-sigs/external-dns/workflows/Go/badge.svg)](https://github.com/kubernetes-sigs/external-dns/actions) [![Coverage Status](https://coveralls.io/repos/github/kubernetes-sigs/external-dns/badge.svg)](https://coveralls.io/github/kubernetes-sigs/external-dns) [![GitHub release](https://img.shields.io/github/release/kubernetes-sigs/external-dns.svg)](https://github.com/kubernetes-sigs/external-dns/releases) [![go-doc](https://godoc.org/github.com/kubernetes-sigs/external-dns?status.svg)](https://godoc.org/github.com/kubernetes-sigs/external-dns) [![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes-sigs/external-dns)](https://goreportcard.com/report/github.com/kubernetes-sigs/external-dns) [![ExternalDNS docs](https://img.shields.io/badge/docs-external--dns-blue)](https://kubernetes-sigs.github.io/external-dns/)
ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
@ -23,16 +27,16 @@ To see ExternalDNS in action, have a look at this [video](https://www.youtube.co
## The Latest Release
ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` in various cloud providers:
ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` and nodes in various DNS providers:
* [Google Cloud DNS](https://cloud.google.com/dns/docs/)
* [AWS Route 53](https://aws.amazon.com/route53/)
* [AWS Cloud Map](https://docs.aws.amazon.com/cloud-map/)
* [AzureDNS](https://azure.microsoft.com/en-us/services/dns)
* [BlueCat](https://bluecatnetworks.com)
* [Civo](https://www.civo.com)
* [CloudFlare](https://www.cloudflare.com/dns)
* [RcodeZero](https://www.rcodezero.at/)
* [DigitalOcean](https://www.digitalocean.com/products/networking)
* [Hetzner](https://hetzner.com/)
* [DNSimple](https://dnsimple.com/)
* [Infoblox](https://www.infoblox.com/products/dns/)
* [Dyn](https://dyn.com/dns/)
@ -52,7 +56,12 @@ ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchroni
* [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html)
* [GoDaddy](https://www.godaddy.com)
* [Gandi](https://www.gandi.net)
* [UKFast SafeDNS](https://my.ukfast.co.uk/safedns/)
* [ANS Group SafeDNS](https://portal.ans.co.uk/safedns/)
* [IBM Cloud DNS](https://www.ibm.com/cloud/dns)
* [TencentCloud PrivateDNS](https://cloud.tencent.com/product/privatedns)
* [TencentCloud DNSPod](https://cloud.tencent.com/product/cns)
* [Plural](https://www.plural.sh/)
* [Pi-hole](https://pi-hole.net/)
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
@ -86,10 +95,10 @@ The following table clarifies the current status of the providers according to t
| Akamai Edge DNS | Beta | |
| AzureDNS | Beta | |
| BlueCat | Alpha | @seanmalloy @vinny-sabatini |
| Civo | Alpha | @alejandrojnm |
| CloudFlare | Beta | |
| RcodeZero | Alpha | |
| DigitalOcean | Alpha | |
| Hetzner | Alpha | @21h |
| DNSimple | Alpha | |
| Infoblox | Alpha | @saileshgiri |
| Dyn | Alpha | |
@ -111,6 +120,10 @@ The following table clarifies the current status of the providers according to t
| GoDaddy | Alpha | |
| Gandi | Alpha | @packi |
| SafeDNS | Alpha | @assureddt |
| IBMCloud | Alpha | @hughhuangzh |
| TencentCloud | Alpha | @Hyzhou |
| Plural | Alpha | @michaeljguarino |
| Pi-hole | Alpha | @tinyzimmer |
## Kubernetes version compatibility
@ -136,18 +149,18 @@ The following tutorials are provided:
* [Akamai Edge DNS](docs/tutorials/akamai-edgedns.md)
* [Alibaba Cloud](docs/tutorials/alibabacloud.md)
* AWS
* [ALB Ingress Controller](docs/tutorials/alb-ingress.md)
* [AWS Load Balancer Controller](docs/tutorials/aws-load-balancer-controller.md)
* [Route53](docs/tutorials/aws.md)
* [Same domain for public and private Route53 zones](docs/tutorials/public-private-route53.md)
* [Cloud Map](docs/tutorials/aws-sd.md)
* [Kube Ingress AWS Controller](docs/tutorials/kube-ingress-aws.md)
* [Azure DNS](docs/tutorials/azure.md)
* [Azure Private DNS](docs/tutorials/azure-private-dns.md)
* [Civo](docs/tutorials/civo.md)
* [Cloudflare](docs/tutorials/cloudflare.md)
* [BlueCat](docs/tutorials/bluecat.md)
* [CoreDNS](docs/tutorials/coredns.md)
* [DigitalOcean](docs/tutorials/digitalocean.md)
* [Hetzner](docs/tutorials/hetzner.md)
* [DNSimple](docs/tutorials/dnsimple.md)
* [Dyn](docs/tutorials/dyn.md)
* [Exoscale](docs/tutorials/exoscale.md)
@ -177,7 +190,12 @@ The following tutorials are provided:
* [UltraDNS](docs/tutorials/ultradns.md)
* [GoDaddy](docs/tutorials/godaddy.md)
* [Gandi](docs/tutorials/gandi.md)
* [SafeDNS](docs/tutorials/safedns.md)
* [SafeDNS](docs/tutorials/UKFast_SafeDNS.md)
* [IBM Cloud](docs/tutorials/ibmcloud.md)
* [Nodes as source](docs/tutorials/nodes.md)
* [TencentCloud](docs/tutorials/tencentcloud.md)
* [Plural](docs/tutorials/plural.md)
* [Pi-hole](docs/tutorials/pihole.md)
### Running Locally

View File

@ -0,0 +1,88 @@
# ExternalDNS Helm Chart Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [UNRELEASED]
### All Changes
## [v1.12.2] - UNRELEASED
### All Changes
- Added support for ServiceMonitor relabelling. ([#3366](https://github.com/kubernetes-sigs/external-dns/pull/3366)) [@jkroepke](https://github.com/jkroepke)
- Updated chart icon path. ([#3492](https://github.com/kubernetes-sigs/external-dns/pull/3494)) [kundan2707](https://github.com/kundan2707)
- Added RBAC for Gateway-API resources to ClusterRole. ([#3499](https://github.com/kubernetes-sigs/external-dns/pull/3499)) [@michaelvl](https://github.com/MichaelVL)
- Added RBAC for F5 VirtualServer to ClusterRole. ([#3503](https://github.com/kubernetes-sigs/external-dns/pull/3503)) [@mikejoh](https://github.com/mikejoh)
- Added support for running ExternalDNS with namespaced scope. ([#3403](https://github.com/kubernetes-sigs/external-dns/pull/3403)) [@jkroepke](https://github.com/jkroepke)
- Updated _ExternalDNS_ version to [v0.13.4](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.4). ([#3516](https://github.com/kubernetes-sigs/external-dns/pull/3516)) [@stevehipwell](https://github.com/stevehipwell)
## [v1.12.1] - 2023-02-06
### All Changes
- Updated _ExternalDNS_ version to [v0.13.2](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.2). ([#3371](https://github.com/kubernetes-sigs/external-dns/pull/3371)) [@stevehipwell](https://github.com/stevehipwell)
- Added `secretConfiguration.subPath` to mount specific files from secret as a sub-path. ([#3227](https://github.com/kubernetes-sigs/external-dns/pull/3227)) [@jkroepke](https://github.com/jkroepke)
- Changed to use `registry.k8s.io` instead of `k8s.gcr.io`. ([#3261](https://github.com/kubernetes-sigs/external-dns/pull/3261)) [@johngmyers](https://github.com/johngmyers)
## [v1.12.0] - 2022-11-29
### All Changes
- Added ability to provide ExternalDNS with secret configuration via `secretConfiguration`. ([#3144](https://github.com/kubernetes-sigs/external-dns/pull/3144)) [@jkroepke](https://github.com/jkroepke)
- Added the ability to template `provider` & `extraArgs`. ([#3144](https://github.com/kubernetes-sigs/external-dns/pull/3144)) [@jkroepke](https://github.com/jkroepke)
- Added the ability to customise the service account labels. ([#3145](https://github.com/kubernetes-sigs/external-dns/pull/3145)) [@jkroepke](https://github.com/jkroepke)
- Updated _ExternalDNS_ version to [v0.13.1](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.1). ([#3197](https://github.com/kubernetes-sigs/external-dns/pull/3197)) [@stevehipwell](https://github.com/stevehipwell)
## [v1.11.0] - 2022-08-10
### Added
- Added support to configure `dnsPolicy` on the Helm chart deployment. [@michelzanini](https://github.com/michelzanini)
- Added ability to customise the deployment strategy. [mac-chaffee](https://github.com/mac-chaffee)
### Changed
- Updated _ExternalDNS_ version to [v0.12.2](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.12.2). [@stevehipwell](https://github.com/stevehipwell)
- Changed default deployment strategy to `Recreate`. [mac-chaffee](https://github.com/mac-chaffee)
## [v1.10.1] - 2022-07-11
### Fixed
- Fixed incorrect addition of `namespace` to `ClusterRole` & `ClusterRoleBinding`. [@stevehipwell](https://github.com/stevehipwell)
## [v1.10.0] - 2022-07-08
### Added
- Added `commonLabels` value to allow the addition of labels to all resources. [@stevehipwell](https://github.com/stevehipwell)
- Added support for [Process Namespace Sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) via the `shareProcessNamespace`
value. ([#2715](https://github.com/kubernetes-sigs/external-dns/pull/2715)) [@wolffberg](https://github.com/wolffberg)
### Changed
- Update _ExternalDNS_ version to [v0.12.0](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.12.0). [@vojtechmares](https://github.com/vojtechmares)
- Set resource namespaces to `{{ .Release.Namespace }}` in the templates instead of waiting until apply time for inference. [@stevehipwell](https://github.com/stevehipwell)
- Fixed `rbac.additionalPermissions` default value.([#2796](https://github.com/kubernetes-sigs/external-dns/pull/2796)) [@tamalsaha](https://github.com/tamalsaha)
## [v1.9.0] - 2022-04-19
### Changed
- Update _ExternalDNS_ version to [v0.11.0](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.11.0). ([#2690](https://github.com/kubernetes-sigs/external-dns/pull/2690)) [@stevehipwell](https://github.com/stevehipwell)
## [v1.8.0] - 2022-04-13
### Added
- Add annotations to Deployment. ([#2477](https://github.com/kubernetes-sigs/external-dns/pull/2477)) [@beastob](https://github.com/beastob)
### Changed
- Fix RBAC for `istio-virtualservice` source when `istio-gateway` isn't also added. ([#2564](https://github.com/kubernetes-sigs/external-dns/pull/2564)) [@mcwarman](https://github.com/mcwarman)

View File

@ -2,14 +2,17 @@ apiVersion: v2
name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application
version: 1.7.1
appVersion: 0.10.2
version: 1.12.2
appVersion: 0.13.4
keywords:
- kubernetes
- externaldns
- external-dns
- dns
- service
- ingress
home: https://github.com/kubernetes-sigs/external-dns/
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/img/external-dns.png
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/docs/img/external-dns.png
sources:
- https://github.com/kubernetes-sigs/external-dns/
maintainers:
@ -18,8 +21,14 @@ maintainers:
annotations:
artifacthub.io/changes: |
- kind: added
description: "Allow custom ClusterRole rules to be specified for sources without defaults."
description: "Added support for ServiceMonitor relabelling."
- kind: changed
description: "Update ExternalDNS version to v0.10.2."
description: "Updated chart icon path."
- kind: added
description: "Added RBAC for Gateway-API resources to ClusterRole."
- kind: added
description: "Added RBAC for F5 VirtualServer to ClusterRole."
- kind: added
description: "Added support for running ExternalDNS with namespaced scope."
- kind: changed
description: "Set ClusterRole rules based more enabled sources."
description: "Updated _ExternalDNS_ version to [v0.13.4](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.4)."

View File

@ -13,58 +13,106 @@ helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
After you've installed the repo you can install the chart.
```shell
helm upgrade --install external-dns/external-dns
helm upgrade --install external-dns external-dns/external-dns
```
## Configuration
The following table lists the configurable parameters of the _ExternalDNS_ chart and their default values.
| Parameter | Description | Default |
|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| `image.repository` | Image repository. | `k8s.gcr.io/external-dns/external-dns` |
| `image.tag` | Image tag, will override the default tag derived from the chart app version. | `""` |
| `image.pullPolicy` | Image pull policy. | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets. | `[]` |
| `nameOverride` | Override the `name` of the chart. | `""` |
| `fullnameOverride` | Override the `fullname` of the chart. | `""` |
| `serviceAccount.create` | If `true`, create a new `serviceaccount`. | `true` |
| `serviceAccount.annotations` | Annotations to add to the service account. | `{}` |
| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` |
| `rbac.create` | If `true`, create the RBAC resources. | `true` |
| `rbac.additionalPermissions` | Additional permissions to be added to the cluster role. | `{}` |
| `deploymentAnnotations` | Annotations to add to the Deployment. | `{}` |
| `podLabels` | Labels to add to the pod. | `{}` |
| `podAnnotations` | Annotations to add to the pod. | `{}` |
| `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ |
| `securityContext` | Security context for the _external-dns_ container, this supports the full [SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#securitycontext-v1-core) API. | _see values.yaml_ |
| `priorityClassName` | Priority class name to use for the pod. | `""` |
| `terminationGracePeriodSeconds` | Termination grace period for the pod. | `null` |
| `serviceMonitor.enabled` | If `true`, create a _Prometheus_ service monitor. | `false` |
| `serviceMonitor.additionalLabels` | Additional labels to be set on the ServiceMonitor. | `{}` |
| `serviceMonitor.interval` | _Prometheus_ scrape frequency. | `1m` |
| `serviceMonitor.scrapeTimeout` | _Prometheus_ scrape timeout. | `10s` |
| `env` | [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the _external-dns_ container, this supports the full [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) API including secrets and configmaps. | `[]` |
| `livenessProbe` | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `readinessProbe` | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `service.annotations` | Annotations to add to the service. | `{}` |
| `service.port` | Port to expose via the service. | `7979` |
| `extraVolumes` | Additional volumes for the pod, this supports the full [VolumeDevice](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumedevice-v1-core) API. | `[]` |
| `extraVolumeMounts` | Additional volume mounts for the _external-dns_ container, this supports the full [VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumemount-v1-core) API. | `[]` |
| `resources` | Resource requests and limits for the _external-dns_ container, this supports the full [ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#resourcerequirements-v1-core) API. | `{}` |
| `nodeSelector` | Node labels for pod assignment. | `{}` |
| `tolerations` | Tolerations for pod assignment, this supports the full [Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#toleration-v1-core) API. | `[]` |
| `affinity` | Affinity settings for pod assignment, this supports the full [Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#affinity-v1-core) API. | `{}` |
| `topologySpreadConstraints` | TopologySpreadConstraint settings for pod assignment, this supports the full [TopologySpreadConstraints](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#topologyspreadconstraint-v1-core) API. | `[]` |
| `logLevel` | Verbosity of the logs, available values are: `panic`, `debug`, `info`, `warn`, `error`, `fatal`. | `info` |
| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
| `interval` | The interval for DNS updates. | `1m` |
| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` |
| `sources` | K8s resources type to be observed for new DNS entries. | See _values.yaml_ |
| `policy` | How DNS records are synchronized between sources and providers, available values are: `sync`, `upsert-only`. | `upsert-only` |
| `registry` | Registry Type, available types are: `txt`, `noop`. | `txt` |
| `txtOwnerId` | TXT registry identifier. | `""` |
| `txtPrefix` | Prefix to create a TXT record with a name following the pattern `prefix.<CNAME record>`. | `""` |
| `domainFilters` | Limit possible target zones by domain suffixes. | `[]` |
| `provider` | DNS provider where the DNS records will be created, for the available providers and how to configure them see the [README](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster). | `aws` |
| `extraArgs` | Extra arguments to pass to the _external-dns_ container, these are needed for provider specific arguments. | `[]` |
| Parameter | Description | Default |
|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|
| `image.repository` | Image repository. | `registry.k8s.io/external-dns/external-dns` |
| `image.tag` | Image tag, will override the default tag derived from the chart app version. | `""` |
| `image.pullPolicy` | Image pull policy. | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets. | `[]` |
| `nameOverride` | Override the `name` of the chart. | `""` |
| `fullnameOverride` | Override the `fullname` of the chart. | `""` |
| `serviceAccount.create` | If `true`, create a new `serviceaccount`. | `true` |
| `serviceAccount.annotations` | Annotations to add to the service account. | `{}` |
| `serviceAccount.labels` | Labels to add to the service account. | `{}` |
| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` |
| `rbac.create` | If `true`, create the RBAC resources. | `true` |
| `rbac.additionalPermissions` | Additional permissions to be added to the cluster role. | `{}` |
| `deploymentAnnotations` | Annotations to add to the Deployment. | `{}` |
| `podLabels` | Labels to add to the pod. | `{}` |
| `podAnnotations` | Annotations to add to the pod. | `{}` |
| `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ |
| `shareProcessNamespace` | If `true` enable [Process Namespace Sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) | `false` |
| `securityContext` | Security context for the _external-dns_ container, this supports the full [SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#securitycontext-v1-core) API. | _see values.yaml_ |
| `priorityClassName` | Priority class name to use for the pod. | `""` |
| `terminationGracePeriodSeconds` | Termination grace period for the pod. | `null` |
| `serviceMonitor.enabled` | If `true`, create a _Prometheus_ service monitor. | `false` |
| `serviceMonitor.namespace` | Forced namespace for ServiceMonitor. | `null` |
| `serviceMonitor.annotations` | Annotations to be set on the ServiceMonitor. | `{}` |
| `serviceMonitor.additionalLabels` | Additional labels to be set on the ServiceMonitor. | `{}` |
| `serviceMonitor.interval` | _Prometheus_ scrape frequency. | `null` |
| `serviceMonitor.scrapeTimeout` | _Prometheus_ scrape timeout. | `null` |
| `serviceMonitor.scheme` | _Prometheus_ scrape scheme. | `null` |
| `serviceMonitor.tlsConfig` | _Prometheus_ scrape tlsConfig. | `{}` |
| `serviceMonitor.metricRelabelings` | _Prometheus_ scrape metricRelabelings. | `[]` |
| `serviceMonitor.relabelings` | _Prometheus_ scrape relabelings. | `[]` |
| `serviceMonitor.targetLabels` | _Prometheus_ scrape targetLabels. | `[]` |
| `env` | [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the _external-dns_ container, this supports the full [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) API including secrets and configmaps. | `[]` |
| `livenessProbe` | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `readinessProbe` | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `service.annotations` | Annotations to add to the service. | `{}` |
| `service.port` | Port to expose via the service. | `7979` |
| `extraVolumes` | Additional volumes for the pod, this supports the full [VolumeDevice](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumedevice-v1-core) API. | `[]` |
| `extraVolumeMounts` | Additional volume mounts for the _external-dns_ container, this supports the full [VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumemount-v1-core) API. | `[]` |
| `resources` | Resource requests and limits for the _external-dns_ container, this supports the full [ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#resourcerequirements-v1-core) API. | `{}` |
| `nodeSelector` | Node labels for pod assignment. | `{}` |
| `tolerations` | Tolerations for pod assignment, this supports the full [Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#toleration-v1-core) API. | `[]` |
| `affinity` | Affinity settings for pod assignment, this supports the full [Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#affinity-v1-core) API. | `{}` |
| `topologySpreadConstraints` | TopologySpreadConstraint settings for pod assignment, this supports the full [TopologySpreadConstraints](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#topologyspreadconstraint-v1-core) API. | `[]` |
| `logLevel` | Verbosity of the logs, available values are: `panic`, `debug`, `info`, `warning`, `error`, `fatal`. | `info` |
| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
| `interval` | The interval for DNS updates. | `1m` |
| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` |
| `namespaced` | When enabled, external-dns runs on namespace scope. Additionally, Role and Rolebinding will be namespaced, too. | `false` |
| `sources` | K8s resources type to be observed for new DNS entries. | See _values.yaml_ |
| `policy` | How DNS records are synchronized between sources and providers, available values are: `sync`, `upsert-only`. | `upsert-only` |
| `registry` | Registry Type, available types are: `txt`, `noop`. | `txt` |
| `txtOwnerId` | TXT registry identifier. | `""` |
| `txtPrefix` | Prefix to create a TXT record with a name following the pattern `prefix.<CNAME record>`. | `""` |
| `domainFilters` | Limit possible target zones by domain suffixes. | `[]` |
| `provider` | DNS provider where the DNS records will be created, for the available providers and how to configure them see the [README](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster) (this can be templated). | `aws` |
| `extraArgs` | Extra arguments to pass to the _external-dns_ container, these are needed for provider specific arguments (these can be templated). | `[]` |
| `deploymentStrategy` | .spec.strategy of the external-dns Deployment. Defaults to 'Recreate' since multiple external-dns pods may conflict with each other. | `{type: Recreate}` |
| `secretConfiguration.enabled` | Enable additional secret configuration. | `false` |
| `secretConfiguration.mountPath` | Mount path of secret configuration secret (this can be templated). | `""` |
| `secretConfiguration.data` | Secret configuration secret data. Could be used to store DNS provider credentials. | `{}` |
| `secretConfiguration.subPath` | Sub-path of secret configuration secret (this can be templated). | `""` |
## Namespaced scoped installation
external-dns supports running on a namespaced only scope, too.
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
### Limited supported
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
namespaces as `external-dns`.
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
If `namespaced` is set to `true`, please ensure that `sources` my only contains supported sources (Default: `service,ingress`.
### Support matrix
| Source | Supported | Infos |
|------------------------|-----------|------------------------|
| `ingress` | ✅ | |
| `istio-gateway` | ✅ | |
| `istio-virtualservice` | ✅ | |
| `contour-ingressroute` | ✅ | |
| `crd` | ✅ | |
| `kong-tcpingress` | ✅ | |
| `openshift-route` | ✅ | |
| `skipper-routegroup` | ✅ | |
| `gloo-proxy` | ✅ | |
| `contour-httpproxy` | ✅ | |
| `service` | ⚠️️ | NodePort not supported |
| `node` | ❌ | |
| `pod` | ❌ | |

View File

@ -40,6 +40,9 @@ helm.sh/chart: {{ include "external-dns.chart" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}
{{/*

View File

@ -1,35 +1,31 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
metadata:
name: {{ template "external-dns.fullname" . }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
{{- if or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
{{- if and (not .Values.namespaced) (or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources)) }}
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
{{- end }}
{{- if or (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["services","endpoints"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "ingress" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) }}
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
@ -41,25 +37,21 @@ rules:
resources: ["virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "ambassador-host" .Values.sources }}
- apiGroups: ["getambassador.io"]
resources: ["hosts","ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "contour-httpproxy" .Values.sources }}
- apiGroups: ["projectcontour.io"]
resources: ["httpproxies"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "contour-ingressroute" .Values.sources }}
- apiGroups: ["contour.heptio.com"]
resources: ["ingressroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "crd" .Values.sources }}
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints"]
@ -68,25 +60,56 @@ rules:
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-grpcroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["grpcroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tlsroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tlsroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tcproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tcproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-udproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "kong-tcpingress" .Values.sources }}
- apiGroups: ["configuration.konghq.com"]
resources: ["tcpingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "openshift-route" .Values.sources }}
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "skipper-routegroup" .Values.sources }}
- apiGroups: ["zalando.org"]
resources: ["routegroups"]
@ -95,9 +118,12 @@ rules:
resources: ["routegroups/status"]
verbs: ["patch","update"]
{{- end }}
{{- if has "f5-virtualserver" .Values.sources }}
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers"]
verbs: ["get","watch","list"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}

View File

@ -1,13 +1,13 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
kind: {{ .Values.namespaced | ternary "RoleBinding" "ClusterRoleBinding" }}
metadata:
name: {{ printf "%s-viewer" (include "external-dns.fullname" .) }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
name: {{ template "external-dns.fullname" . }}
subjects:
- kind: ServiceAccount

View File

@ -2,6 +2,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.deploymentAnnotations }}
@ -13,6 +14,8 @@ spec:
selector:
matchLabels:
{{- include "external-dns.selectorLabels" . | nindent 6 }}
strategy:
{{- toYaml .Values.deploymentStrategy | nindent 4 }}
template:
metadata:
labels:
@ -20,9 +23,14 @@ spec:
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- if or .Values.secretConfiguration.enabled .Values.podAnnotations }}
annotations:
{{- if .Values.secretConfiguration.enabled }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
@ -30,6 +38,9 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "external-dns.serviceAccountName" . }}
{{- with .Values.shareProcessNamespace }}
shareProcessNamespace: {{ . }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
@ -40,6 +51,9 @@ spec:
{{- with .Values.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ . }}
{{- end }}
{{- with .Values.dnsPolicy }}
dnsPolicy: {{ . }}
{{- end }}
containers:
- name: external-dns
{{- with .Values.securityContext }}
@ -75,12 +89,15 @@ spec:
- --txt-suffix={{ .Values.txtSuffix }}
{{- end }}
{{- end }}
{{- if .Values.namespaced }}
- --namespace={{ .Release.Namespace }}
{{- end }}
{{- range .Values.domainFilters }}
- --domain-filter={{ . }}
{{- end }}
- --provider={{ .Values.provider }}
- --provider={{ tpl .Values.provider $ }}
{{- range .Values.extraArgs }}
- {{ . }}
- {{ tpl . $ }}
{{- end }}
ports:
- name: http
@ -90,17 +107,33 @@ spec:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
{{- with .Values.extraVolumeMounts }}
{{- if or .Values.secretConfiguration.enabled .Values.extraVolumeMounts }}
volumeMounts:
{{- if .Values.secretConfiguration.enabled }}
- name: secrets
mountPath: {{ tpl .Values.secretConfiguration.mountPath $ }}
{{- with .Values.secretConfiguration.subPath }}
subPath: {{ tpl . $ }}
{{- end }}
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- if or .Values.secretConfiguration.enabled .Values.extraVolumes }}
volumes:
{{- if .Values.secretConfiguration.enabled }}
- name: secrets
secret:
secretName: {{ include "external-dns.fullname" . }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:

View File

@ -0,0 +1,13 @@
{{- if .Values.secretConfiguration.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.secretConfiguration.data }}
{{ $key }}: {{ tpl $value $ | b64enc | quote }}
{{- end }}
{{- end }}

View File

@ -2,6 +2,7 @@ apiVersion: v1
kind: Service
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}

View File

@ -3,8 +3,12 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}

View File

@ -1,13 +1,18 @@
{{- if.Values.serviceMonitor.enabled -}}
{{- if .Values.serviceMonitor.enabled -}}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ default .Release.Namespace .Values.serviceMonitor.namespace }}
{{- with .Values.serviceMonitor.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.additionalLabels }}
{{- with .Values.serviceMonitor.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
spec:
jobLabel: {{ .Release.Name }}
namespaceSelector:
@ -22,7 +27,29 @@ spec:
{{- with .Values.serviceMonitor.interval }}
interval: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.scrapeTimeout }}
{{- with .Values.serviceMonitor.scheme }}
scheme: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.bearerTokenFile }}
bearerTokenFile: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.tlsConfig }}
tlsConfig:
{{- toYaml .| nindent 8 }}
{{- end }}
{{- with .Values.serviceMonitor.scrapeTimeout }}
scrapeTimeout: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.serviceMonitor.targetLabels }}
targetLabels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"provider": {
"type": "string"
},
"extraArgs": {
"type": "array",
"items": {
"type": "string"
}
},
"secretConfiguration": {
"type": "object",
"properties": {
"mountPath": {
"type": "string"
},
"data": {
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
}
}
}
}
}
}

View File

@ -3,20 +3,25 @@
# Declare variables to be passed into your templates.
image:
repository: k8s.gcr.io/external-dns/external-dns
repository: registry.k8s.io/external-dns/external-dns
# Overrides the image tag whose default is v{{ .Chart.AppVersion }}
tag: ""
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
commonLabels: {}
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# Labels to add to the service account
labels: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
@ -24,7 +29,7 @@ serviceAccount:
rbac:
# Specifies whether RBAC resources should be created
create: true
additionalPermissions: {}
additionalPermissions: []
# Annotations to add to the Deployment
deploymentAnnotations: {}
@ -34,6 +39,8 @@ podLabels: {}
# Annotations to add to the Pod
podAnnotations: {}
shareProcessNamespace: false
podSecurityContext:
fsGroup: 65534
@ -44,15 +51,59 @@ securityContext:
capabilities:
drop: ["ALL"]
# Defaults to `ClusterFirst`.
# Valid values are: `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`.
dnsPolicy:
priorityClassName: ""
terminationGracePeriodSeconds:
serviceMonitor:
enabled: false
# force namespace
# namespace: monitoring
# Fallback to the prometheus default unless specified
# interval: 10s
## scheme: HTTP scheme to use for scraping. Can be used with `tlsConfig` for example if using istio mTLS.
# scheme: ""
## tlsConfig: TLS configuration to use when scraping the endpoint. For example if using istio mTLS.
## Of type: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig
# tlsConfig: {}
# bearerTokenFile:
# Fallback to the prometheus default unless specified
# scrapeTimeout: 30s
## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
additionalLabels: {}
interval: 1m
scrapeTimeout: 10s
## Used to pass annotations that are used by the Prometheus installed in your cluster to select Service Monitors to work with
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
annotations: {}
## Metric relabel configs to apply to samples before ingestion.
## [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs)
metricRelabelings: []
# - action: keep
# regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
# sourceLabels: [__name__]
## Relabel configs to apply to samples before ingestion.
## [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
relabelings: []
# - sourceLabels: [__meta_kubernetes_pod_node_name]
# separator: ;
# regex: ^(.*)$
# targetLabel: nodename
# replacement: $1
# action: replace
targetLabels: []
env: []
@ -100,6 +151,8 @@ logFormat: text
interval: 1m
triggerLoopOnEvent: false
namespaced: false
sources:
- service
- ingress
@ -116,3 +169,16 @@ domainFilters: []
provider: aws
extraArgs: []
secretConfiguration:
enabled: false
mountPath: ""
subPath: ""
data: {}
# credentials: |
# [default]
# aws_access_key_id = $SECRET_ACCESS_KEY
# aws_secret_access_key = $SECRET_ACCESS_KEY
deploymentStrategy:
type: Recreate

View File

@ -2,15 +2,22 @@
timeout: 5000s
options:
substitution_option: ALLOW_LOOSE
machineType: 'N1_HIGHCPU_8'
steps:
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90"
entrypoint: make
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20230206-8160eea68e"
entrypoint: bash
env:
- DOCKER_CLI_EXPERIMENTAL=enabled
- VERSION=$_GIT_TAG
- PULL_BASE_REF=$_PULL_BASE_REF
- HOME=/root
args:
- release.staging
- -c
- |
gcloud auth configure-docker
/buildx-entrypoint version
apk add musl-dev gcc
make release.staging
substitutions:
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
# can be used as a substitution

View File

@ -102,6 +102,14 @@ var (
Help: "Number of Registry A records.",
},
)
registryAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "registry",
Name: "aaaa_records",
Help: "Number of Registry AAAA records.",
},
)
sourceARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -110,6 +118,14 @@ var (
Help: "Number of Source A records.",
},
)
sourceAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "source",
Name: "aaaa_records",
Help: "Number of Source AAAA records.",
},
)
verifiedARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -118,6 +134,14 @@ var (
Help: "Number of DNS A-records that exists both in source and registry.",
},
)
verifiedAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "controller",
Name: "verified_aaaa_records",
Help: "Number of DNS AAAA-records that exists both in source and registry.",
},
)
)
func init() {
@ -130,8 +154,11 @@ func init() {
prometheus.MustRegister(deprecatedSourceErrors)
prometheus.MustRegister(controllerNoChangesTotal)
prometheus.MustRegister(registryARecords)
prometheus.MustRegister(registryAAAARecords)
prometheus.MustRegister(sourceARecords)
prometheus.MustRegister(sourceAAAARecords)
prometheus.MustRegister(verifiedARecords)
prometheus.MustRegister(verifiedAAAARecords)
}
// Controller is responsible for orchestrating the different components.
@ -167,9 +194,13 @@ func (c *Controller) RunOnce(ctx context.Context) error {
deprecatedRegistryErrors.Inc()
return err
}
missingRecords := c.Registry.MissingRecords()
registryEndpointsTotal.Set(float64(len(records)))
regARecords := filterARecords(records)
registryARecords.Set(float64(len(regARecords)))
regARecords, regAAAARecords := countAddressRecords(records)
registryARecords.Set(float64(regARecords))
registryAAAARecords.Set(float64(regAAAARecords))
ctx = context.WithValue(ctx, provider.RecordsContextKey, records)
endpoints, err := c.Source.Endpoints(ctx)
@ -179,12 +210,37 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err
}
sourceEndpointsTotal.Set(float64(len(endpoints)))
srcARecords := filterARecords(endpoints)
sourceARecords.Set(float64(len(srcARecords)))
vRecords := fetchMatchingARecords(endpoints, records)
verifiedARecords.Set(float64(len(vRecords)))
srcARecords, srcAAAARecords := countAddressRecords(endpoints)
sourceARecords.Set(float64(srcARecords))
sourceAAAARecords.Set(float64(srcAAAARecords))
vARecords, vAAAARecords := countMatchingAddressRecords(endpoints, records)
verifiedARecords.Set(float64(vARecords))
verifiedAAAARecords.Set(float64(vAAAARecords))
endpoints = c.Registry.AdjustEndpoints(endpoints)
if len(missingRecords) > 0 {
// Add missing records before the actual plan is applied.
// This prevents the problems when the missing TXT record needs to be
// created and deleted/upserted in the same batch.
missingRecordsPlan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Missing: missingRecords,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, c.Registry.GetDomainFilter()},
PropertyComparator: c.Registry.PropertyValuesEqual,
ManagedRecords: c.ManagedRecordTypes,
}
missingRecordsPlan = missingRecordsPlan.Calculate()
if missingRecordsPlan.Changes.HasChanges() {
err = c.Registry.ApplyChanges(ctx, missingRecordsPlan.Changes)
if err != nil {
registryErrorsTotal.Inc()
deprecatedRegistryErrors.Inc()
return err
}
log.Info("All missing records are created")
}
}
plan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Current: records,
@ -212,37 +268,55 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return nil
}
// Checks and returns the intersection of A records in endpoint and registry.
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
aRecords := filterARecords(endpoints)
recordsMap := make(map[string]struct{})
// Counts the intersections of A and AAAA records in endpoint and registry.
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) (int, int) {
recordsMap := make(map[string]map[string]struct{})
for _, regRecord := range registryRecords {
recordsMap[regRecord.DNSName] = struct{}{}
if _, found := recordsMap[regRecord.DNSName]; !found {
recordsMap[regRecord.DNSName] = make(map[string]struct{})
}
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
}
var cm []string
for _, sourceRecord := range aRecords {
if _, found := recordsMap[sourceRecord]; found {
cm = append(cm, sourceRecord)
aCount := 0
aaaaCount := 0
for _, sourceRecord := range endpoints {
if _, found := recordsMap[sourceRecord.DNSName]; found {
if _, found := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; found {
switch sourceRecord.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
}
}
return cm
return aCount, aaaaCount
}
func filterARecords(endpoints []*endpoint.Endpoint) []string {
var aRecords []string
func countAddressRecords(endpoints []*endpoint.Endpoint) (int, int) {
aCount := 0
aaaaCount := 0
for _, endPoint := range endpoints {
if endPoint.RecordType == endpoint.RecordTypeA {
aRecords = append(aRecords, endPoint.DNSName)
switch endPoint.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
return aRecords
return aCount, aaaaCount
}
// ScheduleRunOnce makes sure execution happens at most once per interval.
func (c *Controller) ScheduleRunOnce(now time.Time) {
c.nextRunAtMux.Lock()
defer c.nextRunAtMux.Unlock()
c.nextRunAt = now.Add(c.MinEventSyncInterval)
// schedule only if a reconciliation is not already planned
// to happen in the following c.MinEventSyncInterval
if !c.nextRunAt.Before(now.Add(c.MinEventSyncInterval)) {
c.nextRunAt = now.Add(c.MinEventSyncInterval)
}
}
func (c *Controller) ShouldRunOnce(now time.Time) bool {

View File

@ -19,12 +19,14 @@ package controller
import (
"context"
"errors"
"github.com/prometheus/client_golang/prometheus"
"math"
"reflect"
"sort"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
@ -82,32 +84,20 @@ func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
// ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if len(changes.Create) != len(p.ExpectChanges.Create) {
return errors.New("number of created records is wrong")
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return err
}
for i := range changes.Create {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
return errors.New("created record is wrong")
}
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
return err
}
for i := range changes.UpdateNew {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
return err
}
for i := range changes.UpdateOld {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
return errors.New("delete record is wrong")
}
}
for i := range changes.Delete {
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
return err
}
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
@ -116,6 +106,21 @@ func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
return nil
}
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
if len(actual) != len(expected) {
return errors.New("number of records is wrong")
}
sort.Slice(actual, func(i, j int) bool {
return actual[i].DNSName < actual[j].DNSName
})
for i := range actual {
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
return errors.New("record is wrong")
}
}
return nil
}
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{
@ -131,7 +136,7 @@ func TestRunOnce(t *testing.T) {
// Fake some desired endpoints coming from our source.
source := new(testutils.MockSource)
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
@ -143,6 +148,16 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
{
DNSName: "create-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
}, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
@ -158,18 +173,32 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
{
DNSName: "delete-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::4"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
@ -192,6 +221,7 @@ func TestRunOnce(t *testing.T) {
source.AssertExpectations(t)
// check the verified records
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
}
func valueFromMetric(metric prometheus.Gauge) uint64 {
@ -236,12 +266,23 @@ func TestShouldRunOnce(t *testing.T) {
// But not two times
assert.False(t, ctrl.ShouldRunOnce(now))
// Multiple ingresses or services changes, closer than MinInterval from each other
firstChangeTime := now
secondChangeTime := firstChangeTime.Add(time.Second)
// First change
ctrl.ScheduleRunOnce(firstChangeTime)
// Second change
ctrl.ScheduleRunOnce(secondChangeTime)
// Should not postpone the reconciliation further than firstChangeTime + MinInterval
now = now.Add(ctrl.MinEventSyncInterval)
assert.True(t, ctrl.ShouldRunOnce(now))
}
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
@ -270,6 +311,51 @@ func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.
}
}
type noopRegistryWithMissing struct {
*registry.NoopRegistry
missingRecords []*endpoint.Endpoint
}
func (r *noopRegistryWithMissing) MissingRecords() []*endpoint.Endpoint {
return r.missingRecords
}
func testControllerFiltersDomainsWithMissing(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints, missingEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
noop, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
r := &noopRegistryWithMissing{
NoopRegistry: noop,
missingRecords: missingEndpoints,
}
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: domainFilter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
assert.NoError(t, ctrl.RunOnce(context.Background()))
assert.Equal(t, 1, provider.RecordsCallCount)
require.Len(t, provider.ApplyChangesCalls, len(expectedChanges))
for i, change := range expectedChanges {
assert.Equal(t, *change, *provider.ApplyChangesCalls[i])
}
}
func TestControllerSkipsEmptyChanges(t *testing.T) {
testControllerFiltersDomains(
t,
@ -469,6 +555,85 @@ func TestVerifyARecords(t *testing.T) {
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords))
}
func TestVerifyAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
},
[]*plan.Changes{},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
}},
)
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
}
func TestARecords(t *testing.T) {
@ -517,3 +682,104 @@ func TestARecords(t *testing.T) {
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryARecords))
}
// TestMissingRecordsApply validates that the missing records result in the dedicated plan apply.
func TestMissingRecordsApply(t *testing.T) {
testControllerFiltersDomainsWithMissing(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
[]*endpoint.Endpoint{
{
DNSName: "a-record1.used.tld",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
},
},
[]*plan.Changes{
// Missing record had its own plan applied.
{
Create: []*endpoint.Endpoint{
{
DNSName: "a-record1.used.tld",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
},
},
},
{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
})
}
func TestAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords))
}

View File

@ -3,3 +3,5 @@
## Chart Changes
When contributing chart changes please follow the same process as when contributing other content but also please **DON'T** modify _Chart.yaml_ in the PR as this would result in a chart release when merged and will mean that your PR will need modifying before it can be accepted. The chart version will be updated as part of the PR to release the chart.
Please **DO** add your changes to the _CHANGELOG.md_ file in the chart directory under the `## [UNRELEASED]` section, if there isn't an uncommented `## [UNRELEASED]` section please copy the commented out template and use that.

View File

@ -0,0 +1,18 @@
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplednsrecord
spec:
endpoints:
- dnsName: subdomain.foo.bar.com
providerSpecific:
- name: "aws/failover"
value: "PRIMARY"
- name: "aws/health-check-id"
value: "asdf1234-as12-as12-as12-asdf12345678"
- name: "aws/evaluate-target-health"
value: "true"
recordType: CNAME
setIdentifier: some-unique-id
targets:
- other-subdomain.foo.bar.com

View File

@ -1,7 +1,7 @@
# Quick Start
- [Git](https://git-scm.com/downloads)
- [Go 1.17+](https://golang.org/dl/)
- [Go 1.19+](https://golang.org/dl/)
- [Go modules](https://github.com/golang/go/wiki/Modules)
- [golangci-lint](https://github.com/golangci/golangci-lint)
- [Docker](https://docs.docker.com/install/)
@ -31,7 +31,7 @@ make build.docker
ExternalDNS's sources of DNS records live in package [source](../../source). They implement the `Source` interface that has a single method `Endpoints` which returns the represented source's objects converted to `Endpoints`. Endpoints are just a tuple of DNS name and target where target can be an IP or another hostname.
For example, the `ServiceSource` returns all Services converted to `Endpoints` where the hostname is the value of the `external-dns.alpha.kubernetes.io/hostname` annotation and the target is the IP of the load balancer or where the hostname is the value of the `external-dns.alpha.kubernetes.io/internal-hostname` annotation and the target is the IP of the service CLusterIP.
For example, the `ServiceSource` returns all Services converted to `Endpoints` where the hostname is the value of the `external-dns.alpha.kubernetes.io/hostname` annotation and the target is the IP of the load balancer or where the hostname is the value of the `external-dns.alpha.kubernetes.io/internal-hostname` annotation and the target is the IP of the service ClusterIP.
This list of endpoints is passed to the [Plan](../../plan) which determines the difference between the current DNS records and the desired list of `Endpoints`.

View File

@ -34,7 +34,7 @@ As stated in the README, we are currently looking for stable maintainers for tho
### Which Kubernetes objects are supported?
Services exposed via `type=LoadBalancer`, `type=ExternalName` and for the hostnames defined in Ingress objects as well as headless hostPort services. An initial effort to support type `NodePort` was started as of May 2018 and it is in progress at the time of writing.
Services exposed via `type=LoadBalancer`, `type=ExternalName`, `type=NodePort`, and for the hostnames defined in Ingress objects as well as [headless hostPort](tutorials/hostport.md) services.
### How do I specify a DNS name for my Kubernetes objects?
@ -88,7 +88,7 @@ ExternalDNS will allow you to opt into any Services and Ingresses that you want
### I'm afraid you will mess up my DNS records!
ExternalDNS since v0.3 implements the concept of owning DNS records. This means that ExternalDNS will keep track of which records it has control over, and will never modify any records over which it doesn't have control. This is a fundamental requirement to operate ExternalDNS safely when there might be other actors creating DNS records in the same target space.
Since v0.3, ExternalDNS can be configured to use an ownership registry. When this option is enabled, ExternalDNS will keep track of which records it has control over, and will never modify any records over which it doesn't have control. This is a fundamental requirement to operate ExternalDNS safely when there might be other actors creating DNS records in the same target space.
For now ExternalDNS uses TXT records to label owned records, and there might be other alternatives coming in the future releases.
@ -178,16 +178,18 @@ You can use the host label in the metric to figure out if the request was agains
Here is the full list of available metrics provided by ExternalDNS:
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------- | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge |
| | source & registry | |
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------------------ | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_aaaa_records | Number of DNS AAAA-records that exists both in source and registry | Gauge |
| external_dns_controller_verified_a_records | Number of DNS A-records that exists both in source and registry | Gauge |
| external_dns_registry_aaaa_records | Number of AAAA records in registry | Gauge |
| external_dns_registry_a_records | Number of A records in registry | Gauge |
| external_dns_source_aaaa_records | Number of AAAA records in source | Gauge |
| external_dns_source_a_records | Number of A records in source | Gauge |
### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects?
@ -205,7 +207,7 @@ $ docker run \
-e EXTERNAL_DNS_SOURCE=$'service\ningress' \
-e EXTERNAL_DNS_PROVIDER=google \
-e EXTERNAL_DNS_DOMAIN_FILTER=$'foo.com\nbar.com' \
k8s.gcr.io/external-dns/external-dns:v0.7.6
registry.k8s.io/external-dns/external-dns:v0.13.4
time="2017-08-08T14:10:26Z" level=info msg="config: &{APIServerURL: KubeConfig: Sources:[service ingress] Namespace: ...
```
@ -289,6 +291,9 @@ Conversely, to force the public IP: `external-dns.alpha.kubernetes.io/access=pub
If this annotation is not set, and the node has both public and private IP addresses, then the public IP will be used by default.
Some loadbalancer implementations assign multiple IP addresses as external addresses. You can filter the generated targets by their networks
using `--target-net-filter=10.0.0.0/8` or `--exclude-target-net=10.0.0.0/8`.
### Can external-dns manage(add/remove) records in a hosted zone which is setup in different AWS account?
Yes, give it the correct cross-account/assume-role permissions and use the `--aws-assume-role` flag https://github.com/kubernetes-sigs/external-dns/pull/524#issue-181256561
@ -303,7 +308,7 @@ Separate them by `,`.
When we tag a new release, we push a container image to the Kubernetes projects official container registry with the following name:
```
k8s.gcr.io/external-dns/external-dns
registry.k8s.io/external-dns/external-dns
```
As tags, you use the external-dns release of choice(i.e. `v0.7.6`). A `latest` tag is not provided in the container registry.

View File

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 251 KiB

View File

@ -32,8 +32,7 @@ The following presents two ways to implement the registry, and we are planning t
This implementation idea is borrowed from [Mate](https://github.com/linki/mate/)
Each record created by external-dns is accompanied by the TXT record, which internally stores the external-dns identifier. For example, if external dns with `owner-id="external-dns-1"` record to be created with dns name `foo.zone.org`, external-dns will create a TXT record with the same dns name `foo.zone.org` and injected value of `"external-dns-1"`. The transfer of ownership can be done by modifying the value of the TXT record. If no TXT record exists for the record or the value does not match its own `owner-id`, then external-dns will simply ignore it.
Each record created by external-dns is accompanied by the TXT record, which internally stores the external-dns identifier. For example, if external dns with `owner-id="external-dns-1"` record to be created with dns name `foo.zone.org`, external-dns will create a TXT record with the same dns name `<record_type>-foo.zone.org` and injected value of `"external-dns-1"`. The transfer of ownership can be done by modifying the value of the TXT record. If no TXT record exists for the record or the value does not match its own `owner-id`, then external-dns will simply ignore it.
#### Goods
1. Easy to guarantee cross-cluster ownership safety

15
docs/registry.md Normal file
View File

@ -0,0 +1,15 @@
### TXT Registry migration to a new format ###
In order to support more record types and be able to track ownership without TXT record name clash, a new TXT record is introduced.
It contains record type it manages, e.g.:
* A record foo.example.com will be tracked with classic foo.example.com TXT record
* At the same time a new TXT record will be created a-foo.example.com
Prefix and suffix are extended with %{record_type} template where the user can control how prefixed/suffixed records should look like.
In order to maintain compatibility, both records will be maintained for some time, in order to have downgrade possibility.
The controller will try to create the "new format" TXT records if they are not present to ease the migration from the versions < 0.12.0.
Later on, the old format will be dropped and only the new format will be kept (<record_type>-<endpoint_name>).
Cleanup will be done by controller itself.

View File

@ -28,9 +28,10 @@ You must be an official maintainer of the project to be able to do a release.
- The step above will trigger the Kubernetes based CI/CD system [Prow](https://prow.k8s.io/?repo=kubernetes-sigs%2Fexternal-dns). Verify that a new image was built and uploaded to `gcr.io/k8s-staging-external-dns/external-dns`.
- Create a PR in the [k8s.io repo](https://github.com/kubernetes/k8s.io) (see https://github.com/kubernetes/k8s.io/pull/540 for reference) by taking the current staging image using the sha256 digest. Once the PR is merged, the image will be live with the corresponding tag specified in the PR.
- Verify that the image is pullable with the given tag (i.e. `v0.7.5`).
- Branch out from the default branch and run `scripts/kustomize-version-udapter.sh` to update the image tag used in the kustomization.yaml.
- Branch out from the default branch and run `scripts/kustomize-version-updater.sh` to update the image tag used in the kustomization.yaml.
- Create an issue to release the corresponding Helm chart via the chart release process (below) assigned to a chart maintainer
- Create a PR with the kustomize change.
- Create a PR to replace all versions for docker images in the tutorials. A possible script to use is `sd registry.k8s.io/external-dns/external-dns:.* registry.k8s.io/external-dns/external-dns:v0.13.2 $(fd --type file)` which uses the `fd` and `sd` utilities.
- Once the PR is merged, all is done :-)
## How to release a new chart version

9
docs/scripts/copy_docs.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
set -eo pipefail
cp CONTRIBUTING.md code-of-conduct.md ./docs/
cp LICENSE ./docs/LICENSE.md
cp README.md ./docs/index.md

47
docs/scripts/docs.go Normal file
View File

@ -0,0 +1,47 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"log"
"os"
"strings"
)
func removeLinkPrefixInIndex() {
content, err := os.ReadFile("./docs/index.md")
if err != nil {
log.Fatalf("Could not read index.md file. Make sure to run copy_docs.sh script first. Original error: %s", err)
}
updatedContent := strings.ReplaceAll(string(content), "](./docs/", "](")
updatedContent = strings.ReplaceAll(updatedContent, "](docs/", "](")
f, err := os.OpenFile("./docs/index.md", os.O_RDWR, 0o644)
if err != nil {
log.Fatalf("Could not open index.md file to update content. Original error: %s", err)
}
defer f.Close()
if _, err := f.WriteString(updatedContent); err != nil {
log.Fatalf("Failed writing links update to index.md. Original error: %s", err)
}
}
func main() {
removeLinkPrefixInIndex()
}

View File

@ -0,0 +1,3 @@
<head>
<meta http-equiv="Refresh" content="0; url='/external-dns/{{.}}/'" />
</head>

View File

@ -0,0 +1,5 @@
mkdocs-git-revision-date-localized-plugin == 1.0.0
mkdocs == 1.3.0
mkdocs-material == 8.2.8
mkdocs-literate-nav == 0.4.0
mike == 1.1.2

View File

@ -36,8 +36,8 @@ Providers
=========
- [x] AWS (Route53)
- [ ] Azure
- [ ] Cloudflare
- [x] Azure
- [x] Cloudflare
- [x] DigitalOcean
- [x] DNSimple
- [x] Google
@ -58,6 +58,13 @@ When the `external-dns.alpha.kubernetes.io/ttl` annotation is not provided, the
The AWS Provider overrides the value to 300s when the TTL is 0.
This value is a constant in the provider code.
## Azure
TTL value should be between 1 and 2,147,483,647 seconds.
By default it will be 300s.
## CloudFlare Provider
CloudFlare overrides the value to "auto" when the TTL is 0.
### DigitalOcean Provider
The DigitalOcean Provider overrides the value to 300s when the TTL is 0.
This value is a constant in the provider code.
@ -82,5 +89,5 @@ The TransIP Provider minimal TTL is used when the TTL is 0. The minimal TTL is 6
### Vultr Provider
The Vultr provider minimal TTL is used when the TTL is 0. The default is 1 hour.
### UltraDNS
### UltraDNS
The UltraDNS provider minimal TTL is used when the TTL is not provided. The default TTL is account level default TTL, if defined, otherwise 24 hours.

View File

@ -1,4 +1,4 @@
# Setting up ExternalDNS for Services on UKFast's SafeDNS
# Setting up ExternalDNS for Services on ANS Group's SafeDNS
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using SafeDNS.
@ -9,14 +9,14 @@ Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial.
If you want to learn about how to use the SafeDNS service read the following tutorials:
To learn more about the use of SafeDNS in general, see the following page:
[UKFast's SafeDNS documentation](https://docs.ukfast.co.uk/domains/safedns/index.html).
[ANS Group's SafeDNS documentation](https://docs.ukfast.co.uk/domains/safedns/index.html).
## Creating SafeDNS credentials
Generate a fresh API token for use with ExternalDNS, following the instructions
at the UKFast developer [Getting-Started](https://developers.ukfast.io/getting-started)
at the ANS Group developer [Getting-Started](https://developers.ukfast.io/getting-started)
page. You will need to grant read/write access to the SafeDNS API. No access to
any other UKFast service is required.
any other ANS Group service is required.
The environment variable `SAFEDNS_TOKEN` must have a value of this token to run
ExternalDNS with SafeDNS integration.
@ -48,7 +48,7 @@ spec:
- name: external-dns
# You will need to check what the latest version is yourself:
# https://github.com/kubernetes-sigs/external-dns/releases
image: k8s.gcr.io/external-dns/external-dns:vX.Y.Z
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the
@ -114,7 +114,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.11.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the

View File

@ -57,7 +57,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # or ingress or both
- --provider=akamai
@ -143,7 +143,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # or ingress or both
- --provider=akamai

View File

@ -113,7 +113,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -187,7 +187,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -241,8 +241,11 @@ spec:
http:
paths:
- backend:
serviceName: foo
servicePort: 80
service:
name: foo
port:
number: 80
pathType: Prefix
```
## Verify ExternalDNS works (Service example)

View File

@ -1,10 +1,10 @@
# Using ExternalDNS with alb-ingress-controller
# Using ExternalDNS with aws-load-balancer-controller
This tutorial describes how to use ExternalDNS with the [aws-alb-ingress-controller][1].
This tutorial describes how to use ExternalDNS with the [aws-load-balancer-controller][1].
[1]: https://kubernetes-sigs.github.io/aws-load-balancer-controller
## Setting up ExternalDNS and aws-alb-ingress-controller
## Setting up ExternalDNS and aws-load-balancer-controller
Follow the [AWS tutorial](aws.md) to setup ExternalDNS for use in Kubernetes clusters
running in AWS. Specify the `source=ingress` argument so that ExternalDNS will look
@ -12,11 +12,11 @@ for hostnames in Ingress objects. In addition, you may wish to limit which Ingre
objects are used as an ExternalDNS source via the `ingress-class` argument, but
this is not required.
For help setting up the ALB Ingress Controller, follow the [Setup Guide][2].
For help setting up the AWS Load Balancer Controller, follow the [Setup Guide][2].
[2]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/installation/
Note that the ALB ingress controller uses the same tags for [subnet auto-discovery][3]
Note that the AWS Load Balancer Controller uses the same tags for [subnet auto-discovery][3]
as Kubernetes does with the AWS cloud provider.
[3]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/
@ -143,7 +143,7 @@ multiple aliases for the resulting ALB.
## Dualstack ALBs
AWS [supports][4] both IPv4 and "dualstack" (both IPv4 and IPv6) interfaces for ALBs.
The ALB ingress controller uses the `alb.ingress.kubernetes.io/ip-address-type`
The AWS Load Balancer Controller uses the `alb.ingress.kubernetes.io/ip-address-type`
annotation (which defaults to `ipv4`) to determine this. If this annotation is
set to `dualstack` then ExternalDNS will create two alias records (one A record
and one AAAA record) for each hostname associated with the Ingress object.

View File

@ -81,7 +81,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region
@ -148,7 +148,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region

File diff suppressed because it is too large Load Diff

View File

@ -3,60 +3,20 @@
This tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS.
It comprises of the following steps:
1) Install NGINX Ingress Controller
2) Provision Azure Private DNS
3) Configure service principal for managing the zone
4) Deploy ExternalDNS
1) Provision Azure Private DNS
2) Configure service principal for managing the zone
3) Deploy ExternalDNS
4) Expose an NGINX service with a LoadBalancer and annotate it with the desired DNS name
5) Install NGINX Ingress Controller (Optional)
6) Expose an nginx service with an ingress (Optional)
7) Verify the DNS records
Everything will be deployed on Kubernetes.
Everything will be deployed on Kubernetes.
Therefore, please see the subsequent prerequisites.
## Prerequisites
- Azure Kubernetes Service is deployed and ready
- [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and `kubectl` installed on the box to execute the subsequent steps
## Install NGINX Ingress Controller
Helm is used to deploy the ingress controller.
We employ the popular chart [stable/nginx-ingress](https://github.com/helm/charts/tree/HEAD/stable/nginx-ingress).
```
$ helm install stable/nginx-ingress \
--name nginx-ingress \
--set controller.publishService.enabled=true
```
The parameter `controller.publishService.enabled` needs to be set to `true.`
It will make the ingress controller update the endpoint records of ingress-resources to contain the external-ip of the loadbalancer serving the ingress-controller.
This is crucial as ExternalDNS reads those endpoints records when creating DNS-Records from ingress-resources.
In the subsequent parameter we will make use of this. If you don't want to work with ingress-resources in your later use, you can leave the parameter out.
Verify the correct propagation of the loadbalancer's ip by listing the ingresses.
```
$ kubectl get ingress
```
The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information.
```
NAME HOSTS ADDRESS PORTS AGE
nginx1 sample1.aks.com 52.167.195.110 80 6d22h
nginx2 sample2.aks.com 52.167.195.110 80 6d21h
```
If you do not want to deploy the ingress controller with Helm, ensure to pass the following cmdline-flags to it through the mechanism of your choice:
```
flags:
--publish-service=<namespace of ingress-controller >/<svcname of ingress-controller>
--update-status=true (default-value)
example:
./nginx-ingress-controller --publish-service=default/nginx-ingress-controller
```
- [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and `kubectl` installed on the box to execute the subsequent steps
## Provision Azure Private DNS
@ -71,7 +31,7 @@ $ az group create -n externaldns -l westeurope
Substitute a more suitable location for the resource group if desired.
As a prerequisite for Azure Private DNS to resolve records is to define links with VNETs.
As a prerequisite for Azure Private DNS to resolve records is to define links with VNETs.
Thus, first create a VNET.
```
@ -100,11 +60,11 @@ $ az network private-dns link vnet create -g externaldns -n mylink \
```
## Configure service principal for managing the zone
ExternalDNS needs permissions to make changes in Azure Private DNS.
ExternalDNS needs permissions to make changes in Azure Private DNS.
These permissions are roles assigned to the service principal used by ExternalDNS.
A service principal with a minimum access level of `Private DNS Zone Contributor` to the Private DNS zone(s) and `Reader` to the resource group containing the Azure Private DNS zone(s) is necessary.
More powerful role-assignments like `Owner` or assignments on subscription-level work too.
More powerful role-assignments like `Owner` or assignments on subscription-level work too.
Start off by **creating the service principal** without role-assignments.
```
@ -118,8 +78,7 @@ $ az ad sp create-for-rbac --skip-assignment -n http://externaldns-sp
```
> Note: Alternatively, you can issue `az account show --query "tenantId"` to retrieve the id of your AAD Tenant too.
Next, assign the roles to the service principal.
Next, assign the roles to the service principal.
But first **retrieve the ID's** of the objects to assign roles on.
```
@ -133,17 +92,17 @@ $ az network private-dns zone show --name example.com -g externaldns --query id
Now, **create role assignments**.
```
# 1. as a reader to the resource group
$ az role assignment create --role "Reader" --assignee <appId GUID> --scope <resource group resource id>
$ az role assignment create --role "Reader" --assignee <appId GUID> --scope <resource group resource id>
# 2. as a contributor to DNS Zone itself
$ az role assignment create --role "Private DNS Zone Contributor" --assignee <appId GUID> --scope <dns zone resource id>
$ az role assignment create --role "Private DNS Zone Contributor" --assignee <appId GUID> --scope <dns zone resource id>
```
## Deploy ExternalDNS
Configure `kubectl` to be able to communicate and authenticate with your cluster.
Configure `kubectl` to be able to communicate and authenticate with your cluster.
This is per default done through the file `~/.kube/config`.
For general background information on this see [kubernetes-docs](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/).
For general background information on this see [kubernetes-docs](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/).
Azure-CLI features functionality for automatically maintaining this file for AKS-Clusters. See [Azure-Docs](https://docs.microsoft.com/de-de/cli/azure/aks?view=azure-cli-latest#az-aks-get-credentials).
Follow the steps for [azure-dns provider](./azure.md#creating-configuration-file) to create a configuration file.
@ -171,7 +130,7 @@ spec:
spec:
containers:
- name: externaldns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -242,7 +201,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -313,7 +272,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -337,11 +296,12 @@ Create the deployment for ExternalDNS:
$ kubectl create -f externaldns.yaml
```
## Deploying sample service
## Create an nginx deployment
Create a service file called 'nginx.yaml' with the following contents:
This step creates a demo workload in your cluster. Apply the following manifest to create a deployment that we are going to expose later in this tutorial in multiple ways:
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -356,15 +316,92 @@ spec:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
- image: nginx
name: nginx
ports:
- containerPort: 80
```
## Expose the nginx deployment with a load balancer
Apply the following manifest to create a service of type `LoadBalancer`. This will create a public load balancer in Azure that will forward traffic to the nginx pods.
```yaml
---
apiVersion: v1
kind: Service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
external-dns.alpha.kubernetes.io/hostname: server.example.com
external-dns.alpha.kubernetes.io/internal-hostname: server-clusterip.example.com
metadata:
name: nginx-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
```
In the service we used multiple annptations. The annotation `service.beta.kubernetes.io/azure-load-balancer-internal` is used to create an internal load balancer. The annotation `external-dns.alpha.kubernetes.io/hostname` is used to create a DNS record for the load balancer that will point to the internal IP address in the VNET allocated by the internal load balancer. The annotation `external-dns.alpha.kubernetes.io/internal-hostname` is used to create a private DNS record for the load balancer that will point to the cluster IP.
## Install NGINX Ingress Controller (Optional)
Helm is used to deploy the ingress controller.
We employ the popular chart [ingress-nginx](https://github.com/kubernetes/ingress-nginx/tree/main/charts/ingress-nginx).
```
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update
$ helm install [RELEASE_NAME] ingress-nginx/ingress-nginx
--set controller.publishService.enabled=true
```
The parameter `controller.publishService.enabled` needs to be set to `true.`
It will make the ingress controller update the endpoint records of ingress-resources to contain the external-ip of the loadbalancer serving the ingress-controller.
This is crucial as ExternalDNS reads those endpoints records when creating DNS-Records from ingress-resources.
In the subsequent parameter we will make use of this. If you don't want to work with ingress-resources in your later use, you can leave the parameter out.
Verify the correct propagation of the loadbalancer's ip by listing the ingresses.
```
$ kubectl get ingress
```
The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information.
```
NAME HOSTS ADDRESS PORTS AGE
nginx1 sample1.aks.com 52.167.195.110 80 6d22h
nginx2 sample2.aks.com 52.167.195.110 80 6d21h
```
If you do not want to deploy the ingress controller with Helm, ensure to pass the following cmdline-flags to it through the mechanism of your choice:
```
flags:
--publish-service=<namespace of ingress-controller >/<svcname of ingress-controller>
--update-status=true (default-value)
example:
./nginx-ingress-controller --publish-service=default/nginx-ingress-controller
```
## Expose the nginx deployment with the ingress (Optional)
Apply the following manifest to create an ingress resource that will expose the nginx deployment. The ingress resource backend points to a `ClusterIP` service that is needed to select the pods that will receive the traffic.
```yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
name: nginx-svc-clusterip
spec:
ports:
- port: 80
@ -373,7 +410,7 @@ spec:
selector:
app: nginx
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
@ -387,9 +424,11 @@ spec:
http:
paths:
- backend:
serviceName: nginx-svc
servicePort: 80
path: /
service:
name: nginx-svc-clusterip
port:
number: 80
pathType: Prefix
```
When using ExternalDNS with ingress objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the externaldns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
@ -400,7 +439,7 @@ Create the deployment, service and ingress object:
$ kubectl create -f nginx.yaml
```
Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
## Verify created records

View File

@ -1,9 +1,9 @@
# Setting up ExternalDNS for Services on Azure
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Azure.
This tutorial describes how to setup ExternalDNS for [Azure DNS](https://azure.microsoft.com/services/dns/) with [Azure Kubernetes Service](https://docs.microsoft.com/azure/aks/).
Make sure to use **>=0.5.7** version of ExternalDNS for this tutorial.
Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial.
This tutorial uses [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for all
Azure commands and assumes that the Kubernetes cluster was created via Azure Container Services and `kubectl` commands
@ -11,36 +11,29 @@ are being run on an orchestration node.
## Creating an Azure DNS zone
The Azure provider for ExternalDNS will find suitable zones for domains it manages; it will
not automatically create zones.
The Azure provider for ExternalDNS will find suitable zones for domains it manages; it will not automatically create zones.
For this tutorial, we will create a Azure resource group named 'externaldns' that can easily be deleted later:
For this tutorial, we will create a Azure resource group named `MyDnsResourceGroup` that can easily be deleted later:
```
$ az group create -n externaldns -l eastus
```bash
$ az group create --name "MyDnsResourceGroup" --location "eastus"
```
Substitute a more suitable location for the resource group if desired.
Next, create a Azure DNS zone for "example.com":
Next, create a Azure DNS zone for `example.com`:
```
$ az network dns zone create -g externaldns -n example.com
```bash
$ az network dns zone create --resource-group "MyDnsResourceGroup" --name "example.com"
```
Substitute a domain you own for "example.com" if desired.
Substitute a domain you own for `example.com` if desired.
If using your own domain that was registered with a third-party domain registrar, you should point your domain's
name servers to the values in the `nameServers` field from the JSON data returned by the `az network dns zone create` command.
Please consult your registrar's documentation on how to do that.
If using your own domain that was registered with a third-party domain registrar, you should point your domain's name servers to the values in the `nameServers` field from the JSON data returned by the `az network dns zone create` command. Please consult your registrar's documentation on how to do that.
## Permissions to modify DNS zone
External-DNS needs permissions to make changes in the Azure DNS server. These permissions are defined in a Service Principal that should be made available to External-DNS as a configuration file.
## Configuration file
The Azure DNS provider expects, by default, that the configuration file is at `/etc/kubernetes/azure.json`. This can be overridden with the `--azure-config-file` option when starting ExternalDNS.
### Creating configuration file
The preferred way to inject the configuration file is by using a Kubernetes secret. The secret should contain an object named azure.json with content similar to this:
The azure provider will reference a configuration file called `azure.json`. The preferred way to inject the configuration file is by using a Kubernetes secret. The secret should contain an object named `azure.json` with content similar to this:
```json
{
@ -52,93 +45,93 @@ The preferred way to inject the configuration file is by using a Kubernetes secr
}
```
You can find the `tenantId` by running `az account show --query "tenantId"` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties.
The following fields are used:
You can find the `subscriptionId` by running `az account show --query "id"` or by selecting Subscriptions in the Azure Portal.
* `tenantId` (**required**) - run `az account show --query "tenantId"` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties.
* `subscriptionId` (**required**) - run `az account show --query "id"` or by selecting Subscriptions in the Azure Portal.
* `resourceGroup` (**required**) is the Resource Group created in a previous step that contains the Azure DNS Zone.
* `aadClientID` and `aaClientSecret` are associated with the Service Principal. This is only used with Service Principal method documented in the next section.
* `useManagedIdentityExtension` - this is set to `true` if you use either AKS Kubelet Identity or AAD Pod Identities methods documented in the next section.
* `userAssignedIdentityID` - this contains the client id from the Managed identitty when using the AAD Pod Identities method documented in the next setion.
The `resourceGroup` is the Resource Group created in a previous step.
The Azure DNS provider expects, by default, that the configuration file is at `/etc/kubernetes/azure.json`. This can be overridden with the `--azure-config-file` option when starting ExternalDNS.
The `aadClientID` and `aaClientSecret` are associated with the Service Principal, that you need to create next.
## Permissions to modify DNS zone
### Creating service principal
A Service Principal with a minimum access level of `DNS Zone Contributor` or `Contributor` to the DNS zone(s) and `Reader` to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. However, other more permissive access levels will work too (e.g. `Contributor` to the resource group or the whole subscription).
ExternalDNS needs permissions to make changes to the Azure DNS zone. There are three ways configure the access needed:
This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps.
- [Service Principal](#service-principal)
- [Managed Identity Using AKS Kubelet Identity](#managed-identity-using-aks-kubelet-identity)
- [Managed Identity Using AAD Pod Identities](#managed-identity-using-aad-pod-identities)
``` bash
> az login
### Service Principal
These permissions are defined in a Service Principal that should be made available to ExternalDNS as a configuration file `azure.json`.
#### Creating a service principal
A Service Principal with a minimum access level of `DNS Zone Contributor` or `Contributor` to the DNS zone(s) and `Reader` to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. However, other more permissive access levels will work too (e.g. `Contributor` to the resource group or the whole subscription).
This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps (requires `azure-cli` and `jq`)
```bash
$ EXTERNALDNS_NEW_SP_NAME="ExternalDnsServicePrincipal" # name of the service principal
$ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # name of resource group where dns zone is hosted
$ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
# Create the service principal
$ DNS_SP=$(az ad sp create-for-rbac --name $EXTERNALDNS_NEW_SP_NAME)
$ EXTERNALDNS_SP_APP_ID=$(echo $DNS_SP | jq -r '.appId')
$ EXTERNALDNS_SP_PASSWORD=$(echo $DNS_SP | jq -r '.password')
```
Find the relevant subscription and make sure it is selected (the same subscriptionId should be set into azure.json)
#### Assign the rights for the service principal
``` bash
> az account list
{
"cloudName": "AzureCloud",
"id": "<subscriptionId GUID>",
"isDefault": false,
"name": "My Subscription",
"state": "Enabled",
"tenantId": "AzureAD tenant ID",
"user": {
"name": "name",
"type": "user"
}
Grant access to Azure DNS zone for the service principal.
# select the subscription
> az account set -s <subscriptionId GUID>
...
```
Create the service principal
``` bash
> az ad sp create-for-rbac -n ExternalDnsServicePrincipal
{
"appId": "appId GUID", --> aadClientId value
...
"password": "password", --> aadClientSecret value
"tenant": "AzureAD Tenant Id" --> tenantId value
}
```
Assign the rights for the service principal
```
# find out the resource ids of the resource group where the dns zone is deployed, and the dns zone itself
> az group show --name externaldns
{
"id": "/subscriptions/id/resourceGroups/externaldns",
...
}
> az network dns zone show --name example.com -g externaldns
{
"id": "/subscriptions/.../resourceGroups/externaldns/providers/Microsoft.Network/dnszones/example.com",
...
}
```
```
# assign the rights to the created service principal, using the resource ids from previous step
```bash
# fetch DNS id used to grant access to the service principal
DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE \
--resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv)
# 1. as a reader to the resource group
> az role assignment create --role "Reader" --assignee <appId GUID> --scope <resource group resource id>
$ az role assignment create --role "Reader" --assignee $EXTERNALDNS_SP_APP_ID --scope $DNS_ID
# 2. as a contributor to DNS Zone itself
> az role assignment create --role "Contributor" --assignee <appId GUID> --scope <dns zone resource id>
$ az role assignment create --role "Contributor" --assignee $EXTERNALDNS_SP_APP_ID --scope $DNS_ID
```
Now you can create a file named 'azure.json' with values gathered above and with the structure of the example above. Use this file to create a Kubernetes secret:
#### Creating a configuration file for the service principal
```
$ kubectl create secret generic azure-config-file --from-file=/local/path/to/azure.json
Create the file `azure.json` with values gather from previous steps.
```bash
cat <<-EOF > /local/path/to/azure.json
{
"tenantId": "$(az account show --query tenantId -o tsv)",
"subscriptionId": "$(az account show --query id -o tsv)",
"resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
"aadClientId": "$EXTERNALDNS_SP_APP_ID",
"aadClientSecret": "$EXTERNALDNS_SP_PASSWORD"
}
EOF
```
### Azure Managed Service Identity (MSI)
Use this file to create a Kubernetes secret:
If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. Note that when granting access the kubeletidentity must be used, not the MSI used for the cluster (it usually has a name in the format <Clustername>-<agentpool>).
```bash
$ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
```
The contents of `azure.json` should be similar to this:
### Managed identity using AKS Kubelet identity
The [managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) that is assigned to the underlying node pool in the AKS cluster can be given permissions to access Azure DNS. Managed identities are essentially a service principal whose lifecycle is managed, such as deleting the AKS cluster will also delete the service principals associated with the AKS cluster. The managed identity assigned Kuberetes node pool, or specifically the [VMSS](https://docs.microsoft.com/azure/virtual-machine-scale-sets/overview), is called the Kubelet identity.
The managed identites were previously called MSI (Managed Service Identity) and are enabled by default when creating an AKS cluster.
Note that permissions granted to this identity will be accessible to all containers running inside the Kubernetes cluster, not just the ExternalDNS container(s).
For the managed identity, the contents of `azure.json` should be similar to this:
```json
{
@ -149,15 +142,184 @@ The contents of `azure.json` should be similar to this:
}
```
If you have all the information necessary: create a file called azure.json containing the json structure above and substitute the values. Otherwise create a service principal as previously shown before creating the Kubernetes secret.
#### Fetching the Kubelet identity
Then add the secret to the Kubernetes cluster before continuing:
```
kubectl create secret generic azure-config-file --from-file=azure.json
For this process, you will need to get the kublet identity:
```bash
$ PRINCIPAL_ID=$(az aks show --resource-group $CLUSTER_GROUP --name $CLUSTERNAME \
--query "identityProfile.kubeletidentity.objectId" --output tsv)
```
#### Assign rights for the Kubelet identity
## Deploy ExternalDNS
Grant access to Azure DNS zone for the kublet identity.
```bash
$ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
$ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # resource group where DNS zone is hosted
# fetch DNS id used to grant access to the kublet identity
$ DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE \
--resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv)
$ az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $DNS_ID
```
#### Creating a configuration file for the kubelet identity
Create the file `azure.json` with values gather from previous steps.
```bash
cat <<-EOF > /local/path/to/azure.json
{
"tenantId": "$(az account show --query tenantId -o tsv)",
"subscriptionId": "$(az account show --query id -o tsv)",
"resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
"useManagedIdentityExtension": true
}
EOF
```
Use the `azure.json` file to create a Kubernetes secret:
```bash
$ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
```
### Managed identity using AAD Pod Identities
For this process, we will create a [managed identity](https://docs.microsoft.com//azure/active-directory/managed-identities-azure-resources/overview) that will be explicitly used by the ExternalDNS container. This process is similar to Kubelet identity except that this managed identity is not associated with the Kubernetes node pool, but rather associated with explicit ExternalDNS containers.
#### Enable the AAD Pod Identities feature
For this solution, [AAD Pod Identities](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity) preview feature can be enabled. The commands below should do the trick to enable this feature:
```bash
$ az feature register --name EnablePodIdentityPreview --namespace Microsoft.ContainerService
$ az feature register --name AutoUpgradePreview --namespace Microsoft.ContainerService
$ az extension add --name aks-preview
$ az extension update --name aks-preview
$ az provider register --namespace Microsoft.ContainerService
```
#### Deploy the AAD Pod Identities service
Once enabled, you can update your cluster and install needed services for the [AAD Pod Identities](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity) feature.
```bash
$ AZURE_AKS_RESOURCE_GROUP="my-aks-cluster-group" # name of resource group where aks cluster was created
$ AZURE_AKS_CLUSTER_NAME="my-aks-cluster" # name of aks cluster previously created
$ az aks update --resource-group ${AZURE_AKS_RESOURCE_GROUP} --name ${AZURE_AKS_CLUSTER_NAME} --enable-pod-identity
```
Note that, if you use the default network plugin `kubenet`, then you need to add the command line option `--enable-pod-identity-with-kubenet` to the above command.
#### Creating the managed identity
After this process is finished, create a managed identity.
```bash
$ IDENTITY_RESOURCE_GROUP=$AZURE_AKS_RESOURCE_GROUP # custom group or reuse AKS group
$ IDENTITY_NAME="example-com-identity"
# create a managed identity
$ az identity create --resource-group "${IDENTITY_RESOURCE_GROUP}" --name "${IDENTITY_NAME}"
```
#### Assign rights for the managed identity
Grant access to Azure DNS zone for the managed identity.
```bash
$ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # name of resource group where dns zone is hosted
$ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
# fetch identity client id from managed identity created earlier
$ IDENTITY_CLIENT_ID=$(az identity show --resource-group "${IDENTITY_RESOURCE_GROUP}" \
--name "${IDENTITY_NAME}" --query "clientId" --output tsv)
# fetch DNS id used to grant access to the managed identity
$ DNS_ID=$(az network dns zone show --name "${AZURE_DNS_ZONE}" \
--resource-group "${AZURE_DNS_ZONE_RESOURCE_GROUP}" --query "id" --output tsv)
$ az role assignment create --role "DNS Zone Contributor" \
--assignee "${IDENTITY_CLIENT_ID}" --scope "${DNS_ID}"
```
#### Creating a configuration file for the managed identity
Create the file `azure.json` with the values from previous steps:
```bash
cat <<-EOF > /local/path/to/azure.json
{
"tenantId": "$(az account show --query tenantId -o tsv)",
"subscriptionId": "$(az account show --query id -o tsv)",
"resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
"useManagedIdentityExtension": true,
"userAssignedIdentityID": "$IDENTITY_CLIENT_ID"
}
EOF
```
Use the `azure.json` file to create a Kubernetes secret:
```bash
$ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
```
#### Creating an Azure identity binding
A binding between the managed identity and the ExternalDNS pods needs to be setup by creating `AzureIdentity` and `AzureIdentityBinding` resources. This will allow appropriately labeled ExternalDNS pods to authenticate using the managed identity. When AAD Pod Identity feature is enabled from previous steps above, the `az aks pod-identity add` can be used to create these resources:
```bash
$ IDENTITY_RESOURCE_ID=$(az identity show --resource-group ${IDENTITY_RESOURCE_GROUP} \
--name ${IDENTITY_NAME} --query id --output tsv)
$ az aks pod-identity add --resource-group ${AZURE_AKS_RESOURCE_GROUP} \
--cluster-name ${AZURE_AKS_CLUSTER_NAME} --namespace "default" \
--name "external-dns" --identity-resource-id ${IDENTITY_RESOURCE_ID}
```
This will add something similar to the following resouces:
```yaml
apiVersion: aadpodidentity.k8s.io/v1
kind: AzureIdentity
metadata:
labels:
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.azure.com/managedby: aks
name: external-dns
spec:
clientID: $IDENTITY_CLIENT_ID
resourceID: $IDENTITY_RESOURCE_ID
type: 0
---
apiVersion: aadpodidentity.k8s.io/v1
kind: AzureIdentityBinding
metadata:
annotations:
labels:
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.azure.com/managedby: aks
name: external-dns-binding
spec:
azureIdentity: external-dns
selector: external-dns
```
#### Update ExternalDNS labels
When deploying ExternalDNS, you want to make sure that deployed pod(s) will have the label `aadpodidbinding: external-dns` to enable AAD Pod Identities. You can patch an existing deployment of ExternalDNS with this command:
```bash
kubectl patch deployment external-dns --namespace "default" --patch \
'{"spec": {"template": {"metadata": {"labels": {"aadpodidbinding": "external-dns"}}}}}'
```
## Ingress used with ExternalDNS
This deployment assumes that you will be using nginx-ingress. When using nginx-ingress do not deploy it as a Daemon Set. This causes nginx-ingress to write the Cluster IP of the backend pods in the ingress status.loadbalancer.ip property which then has external-dns write the Cluster IP(s) in DNS vs. the nginx-ingress service external IP.
@ -169,8 +331,11 @@ Ensure that your nginx-ingress deployment has the following arg: added to it:
For more details see here: [nginx-ingress external-dns](https://github.com/kubernetes-sigs/external-dns/blob/HEAD/docs/faq.md#why-is-externaldns-only-adding-a-single-ip-address-in-route-53-on-aws-when-using-the-nginx-ingress-controller-how-do-i-get-it-to-use-the-fqdn-of-the-elb-assigned-to-my-nginx-ingress-controller-service-instead)
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with. Then apply one of the following manifests file to deploy ExternalDNS.
The deployment assumes that ExternalDNS will be installed into the `default` namespace. If this namespace is different, the `ClusterRoleBinding` will need to be updated to reflect the desired alternative namespace, such as `external-dns`, `kube-addons`, etc.
### Manifest (for clusters without RBAC enabled)
```yaml
@ -191,13 +356,13 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
- --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
@ -206,12 +371,10 @@ spec:
- name: azure-config-file
secret:
secretName: azure-config-file
items:
- key: externaldns-config.json
path: azure.json
```
### Manifest (for clusters with RBAC enabled, cluster access)
```yaml
apiVersion: v1
kind: ServiceAccount
@ -223,15 +386,12 @@ kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
- apiGroups: [""]
resources: ["services","endpoints","pods", "nodes"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
@ -242,9 +402,9 @@ roleRef:
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
@ -263,26 +423,23 @@ spec:
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
- --txt-prefix=externaldns-
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
- --txt-prefix=externaldns-
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file
items:
- key: externaldns-config.json
path: azure.json
- name: azure-config-file
secret:
secretName: azure-config-file
```
### Manifest (for clusters with RBAC enabled, namespace access)
@ -301,12 +458,12 @@ kind: Role
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@ -317,8 +474,8 @@ roleRef:
kind: Role
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
- kind: ServiceAccount
name: external-dns
---
apiVersion: apps/v1
kind: Deployment
@ -337,36 +494,33 @@ spec:
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file
items:
- key: externaldns-config.json
path: azure.json
- name: azure-config-file
secret:
secretName: azure-config-file
```
Create the deployment for ExternalDNS:
```
$ kubectl create -f externaldns.yaml
```bash
$ kubectl create --namespace "default" --filename externaldns.yaml
```
## Deploying an Nginx Service
## Ingress Option: Expose an nginx service with an ingress
Create a service file called 'nginx.yaml' with the following contents:
Create a file called `nginx.yaml` with the following contents:
```yaml
apiVersion: apps/v1
@ -383,10 +537,10 @@ spec:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
@ -394,9 +548,9 @@ metadata:
name: nginx-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: ClusterIP
@ -406,35 +560,80 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:
paths:
- backend:
serviceName: nginx-svc
servicePort: 80
path: /
- host: server.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-svc
port:
number: 80
```
When using external-dns with ingress objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the external-dns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
When using ExternalDNS with `ingress` objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the external-dns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
Create the deployment, service and ingress object:
```
$ kubectl create -f nginx.yaml
```bash
$ kubectl create --namespace "default" --filename nginx.yaml
```
Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
## Azure Load Balancer option: Expose an nginx service with a load balancer
Create a file called `nginx.yaml` with the following contents:
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
annotations:
external-dns.alpha.kubernetes.io/hostname: server.example.com
metadata:
name: nginx-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
```
The annotation `external-dns.alpha.kubernetes.io/hostname` is used to specify the DNS name that should be created for the service. The annotation value is a comma separated list of host names.
## Verifying Azure DNS records
Run the following command to view the A records for your Azure DNS zone:
```
$ az network dns record-set a list -g externaldns -z example.com
```bash
$ az network dns record-set a list --resource-group "MyDnsResourceGroup" --zone-name example.com
```
Substitute the zone for the one created above if a different domain was used.
@ -446,6 +645,6 @@ This should show the external IP address of the service as the A record for your
Now that we have verified that ExternalDNS will automatically manage Azure DNS records, we can delete the tutorial's
resource group:
```
$ az group delete -n externaldns
```bash
$ az group delete --name "MyDnsResourceGroup"
```

View File

@ -7,8 +7,9 @@ Install the BlueCat Gateway product and deploy the [community gateway workflows]
## Configuration Options
There are two ways to pass configuration options to the Bluecat Provider JSON configuration file and command line flags. The JSON configuration file option
is deprecated and will eventually be removed.
There are two ways to pass configuration options to the Bluecat Provider JSON configuration file and command line flags. Currently if a valid configuration file is used all
BlueCat provider configurations will be taken from the configuration file. If a configuraiton file is not provided or cannot be read then all BlueCat provider configurations will
be taken from the command line flags. In the future an enhancement will be made to merge configuration options from the configuration file and command line flags if both are provided.
BlueCat provider supports getting the proxy URL from the environment variables. The format is the one specified by golang's [http.ProxyFromEnvironment](https://pkg.go.dev/net/http#ProxyFromEnvironment).
@ -45,7 +46,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --log-level=debug
- --source=service
@ -71,7 +72,7 @@ kubectl apply -f ~/bluecat.yml -n bluecat-example
```
### Using JSON Configuration file (DEPRECATED)
### Using JSON Configuration File
The options for configuring the Bluecat Provider are available through the JSON file provided to External-DNS via the flag `--bluecat-config-file`.
| Key | Required |
@ -135,7 +136,7 @@ spec:
secretName: bluecatconfig
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
volumeMounts:
- name: bluecatconfig
mountPath: "/etc/external-dns/"

View File

@ -1,22 +1,20 @@
# Setting up ExternalDNS for Services on Hetzner DNS
# Setting up ExternalDNS for Services on Civo
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Hetzner DNS.
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Civo DNS Manager.
Make sure to use **>=0.7.6** version of ExternalDNS for this tutorial.
Make sure to use **>0.12.2** version of ExternalDNS for this tutorial.
## Creating a Hetzner DNS zone
## Managing DNS with Civo
If you want to learn about how to use Hetzner's DNS service read the following tutorial series:
If you want to learn about how to use Civo DNS Manager read the following tutorials:
[An Introduction to Managing DNS](https://wiki.hetzner.de/index.php/DNS_Overview), and [Add a new DNS zone](https://wiki.hetzner.de/index.php/Getting_started).
[An Introduction to Managing DNS](https://www.civo.com/learn/configure-dns)
Create a new DNS zone where you want to create your records in. Let's use `example.com` as an example here.
## Get Civo Token
## Creating Hetzner Credentials
Copy the token in the settings fo your account
Generate a new personal token by going to [the API settings](https://dns.hetzner.com/settings/api-token) or follow [Generating an API access token](https://wiki.hetzner.de/index.php/API_access_token) if you need more information. Give the token a name and choose read and write access. The token needs to be passed to ExternalDNS so make a note of it for later use.
The environment variable `HETZNER_TOKEN` will be needed to run ExternalDNS with Hetzner.
The environment variable `CIVO_TOKEN` will be needed to run ExternalDNS with Civo.
## Deploy ExternalDNS
@ -24,18 +22,18 @@ Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
strategy:
type: Recreate
template:
metadata:
labels:
@ -43,24 +41,25 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=hetzner
- --provider=civo
env:
- name: HETZNER_TOKEN
value: "YOUR_HETZNER_DNS_API_KEY"
- name: CIVO_TOKEN
value: "YOUR_CIVO_API_TOKEN"
```
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
@ -69,13 +68,13 @@ rules:
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
@ -93,12 +92,11 @@ kind: Deployment
metadata:
name: external-dns
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
strategy:
type: Recreate
template:
metadata:
labels:
@ -107,17 +105,16 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=hetzner
- --provider=civo
env:
- name: HETZNER_TOKEN
value: "YOUR_HETZNER_DNS_API_KEY"
- name: CIVO_TOKEN
value: "YOUR_CIVO_API_TOKEN"
```
## Deploying an Nginx Service
Create a service file called 'nginx.yaml' with the following contents:
@ -128,7 +125,6 @@ kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
@ -159,7 +155,7 @@ spec:
targetPort: 80
```
Note the annotation on the service; use the same hostname as the Hetzner DNS zone created above.
Note the annotation on the service; use the same hostname as the Civo DNS zone created above.
ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation will cause ExternalDNS to remove the corresponding DNS records.
@ -171,11 +167,11 @@ $ kubectl create -f nginx.yaml
Depending where you run your service it can take a little while for your cloud provider to create an external IP for the service.
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize the Hetzner DNS records.
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize the Civo DNS records.
## Verifying Hetzner DNS records
## Verifying Civo DNS records
Check your [Hetzner DNS UI](https://dns.hetzner.com/) to view the records for your Hetzner DNS zone.
Check your [Civo UI](https://www.civo.com/account/dns) to view the records for your Civo DNS zone.
Click on the zone for the one created above if a different domain was used.
@ -183,9 +179,9 @@ This should show the external IP address of the service as the A record for your
## Cleanup
Now that we have verified that ExternalDNS will automatically manage Hetzner DNS records, we can delete the tutorial's example:
Now that we have verified that ExternalDNS will automatically manage Civo DNS records, we can delete the tutorial's example:
```
$ kubectl delete service -f nginx.yaml
$ kubectl delete service -f externaldns.yaml
```
```

View File

@ -18,13 +18,19 @@ Snippet from [Cloudflare - Getting Started](https://api.cloudflare.com/#getting-
>The Cloudflare API is a RESTful API based on HTTPS requests and JSON responses. If you are registered with Cloudflare, you can obtain your API key from the bottom of the "My Account" page, found here: [Go to My account](https://dash.cloudflare.com/profile).
API Token will be preferred for authentication if `CF_API_TOKEN` environment variable is set.
API Token will be preferred for authentication if `CF_API_TOKEN` environment variable is set.
Otherwise `CF_API_KEY` and `CF_API_EMAIL` should be set to run ExternalDNS with Cloudflare.
You may provide the Cloudflare API token through a file by setting the
`CF_API_TOKEN="file:/path/to/token"`.
When using API Token authentication, the token should be granted Zone `Read`, DNS `Edit` privileges, and access to `All zones`.
If you would like to further restrict the API permissions to a specific zone (or zones), you also need to use the `--zone-id-filter` so that the underlying API requests only access the zones that you explicitly specify, as opposed to accessing all zones.
## Throttling
Cloudflare API has a [global rate limit of 1,200 requests per five minutes](https://developers.cloudflare.com/fundamentals/api/reference/limits/). Running several fast polling ExternalDNS instances in a given account can easily hit that limit. The AWS Provider [docs](./aws.md#throttling) has some recommendations that can be followed here too, but in particular, consider passing `--cloudflare-dns-records-per-page` with a high value (maximum is 5,000).
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
@ -50,13 +56,14 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --zone-id-filter=023e105f4ecef8ad9ca31a8372d0c353 # (optional) limit to a specific zone.
- --provider=cloudflare
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
env:
- name: CF_API_KEY
value: "YOUR_CLOUDFLARE_API_KEY"
@ -81,7 +88,7 @@ rules:
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
@ -118,13 +125,14 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --zone-id-filter=023e105f4ecef8ad9ca31a8372d0c353 # (optional) limit to a specific zone.
- --provider=cloudflare
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
env:
- name: CF_API_KEY
value: "YOUR_CLOUDFLARE_API_KEY"

View File

@ -26,7 +26,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -102,7 +102,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress

View File

@ -108,7 +108,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --provider=coredns
@ -175,7 +175,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --provider=coredns

View File

@ -59,7 +59,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -136,7 +136,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -35,7 +35,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.
@ -100,7 +100,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --txt-prefix=_d
@ -122,9 +122,11 @@ spec:
http:
paths:
- backend:
serviceName: my-awesome-service
servicePort: 8080
service:
name: my-awesome-service
port:
number: 8080
pathType: Prefix
```
As the DNS name `test-ingress.example.com` matches the filter, external-dns will create two records:

View File

@ -41,7 +41,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress # or service or both
- --provider=exoscale
@ -117,8 +117,11 @@ spec:
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
service:
name: "nginx"
port:
number: 80
pathType: Prefix
---

View File

@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --log-level=debug
- --source=service

View File

@ -0,0 +1,33 @@
# Configuring ExternalDNS to use the F5 Networks VirtualServer Source
This tutorial describes how to configure ExternalDNS to use the F5 Networks VirtualServer Source. It is meant to supplement the other provider-specific setup tutorials.
The F5 Networks VirtualServer CRD is part of [this](https://github.com/F5Networks/k8s-bigip-ctlr) project. See more in-depth info regarding the VirtualServer CRD [here](https://github.com/F5Networks/k8s-bigip-ctlr/blob/master/docs/config_examples/customResource/CustomResource.md#virtualserver).
## Start with ExternalDNS with the F5 Networks VirtualServer source
1. Make sure that you have the `k8s-bigip-ctlr` installed in your cluster. The needed CRDs are bundled within the controller.
2. In your Helm `values.yaml` add:
```
sources:
- ...
- f5-virtualserver
- ...
```
or add it in your `Deployment` if you aren't installing `external-dns` via Helm:
```
args:
- --source=f5-virtualserver
```
Note that, in case you're not installing via Helm, you'll need the following in the `ClusterRole` bound to the service account of `external-dns`:
```
- apiGroups:
- cis.f5.com
resources:
- virtualservers
verbs:
- get
- list
- watch
```

View File

@ -39,7 +39,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.7
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -103,7 +103,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.7
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -0,0 +1,96 @@
# Configuring ExternalDNS to use Gateway API Route Sources
This describes how to configure ExternalDNS to use Gateway API Route sources.
It is meant to supplement the other provider-specific setup tutorials.
## Supported API Versions
As the Gateway API is still in an experimental phase, ExternalDNS makes no backwards
compatibilty guarantees regarding its support. However, it currently supports a mixture of
v1alpha2 and v1beta1 APIs. Gateways and HTTPRoutes are supported using the v1beta1 API.
GRPCRoutes, TLSRoutes, TCPRoutes, and UDPRoutes are supported using the v1alpha2 API.
## Hostnames
HTTPRoute and TLSRoute specs, along with their associated Gateway Listeners, contain hostnames that
will be used by ExternalDNS. However, no such hostnames may be specified in TCPRoute or UDPRoute
specs. For TCPRoutes and UDPRoutes, the `external-dns.alpha.kubernetes.io/hostname` annotation
is the recommended way to provide their hostnames to ExternalDNS. This annotation is also supported
for HTTPRoutes and TLSRoutes by ExternalDNS, but it's _strongly_ recommended that they use their
specs to provide all intended hostnames, since the Gateway that ultimately routes their
requests/connections won't recognize additional hostnames from the annotation.
## Manifest with RBAC
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways","httproutes","grpcroutes","tlsroutes","tcproutes","udproutes"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: default
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
# Add desired Gateway API Route sources.
- --source=gateway-httproute
- --source=gateway-grpcroute
- --source=gateway-tlsroute
- --source=gateway-tcproute
- --source=gateway-udproute
# Optionally, limit Routes to those in the given namespace.
- --namespace=my-route-namespace
# Optionally, limit Routes to those matching the given label selector.
- --label-filter=my-route-label==my-route-value
# Optionally, limit Route endpoints to those Gateways in the given namespace.
- --gateway-namespace=my-gateway-namespace
# Optionally, limit Route endpoints to those Gateways matching the given label selector.
- --gateway-label-filter=my-gateway-label==my-gateway-value
# Add provider-specific flags...
- --domain-filter=external-dns-test.my-org.com
- --provider=google
- --registry=txt
- --txt-owner-id=my-identifier
```

View File

@ -1,66 +1,264 @@
# Setting up ExternalDNS on Google Kubernetes Engine
This tutorial describes how to setup ExternalDNS for usage within a GKE cluster. Make sure to use **>=0.4** version of ExternalDNS for this tutorial
This tutorial describes how to setup ExternalDNS for usage within a [GKE](https://cloud.google.com/kubernetes-engine) ([Google Kuberentes Engine](https://cloud.google.com/kubernetes-engine)) cluster. Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial
## Set up your environment
Setup your environment to work with Google Cloud Platform. Fill in your values as needed, e.g. target project.
```console
$ gcloud config set project "zalando-external-dns-test"
$ gcloud config set compute/region "europe-west1"
$ gcloud config set compute/zone "europe-west1-d"
```
## GKE Node Scopes
## Single project test scenario using access scopes
*If you prefer to try-out ExternalDNS in one of the existing environments you can skip this step*
The following instructions use instance scopes to provide ExternalDNS with the
permissions it needs to manage DNS records. Note that since these permissions
are associated with the instance, all pods in the cluster will also have these
permissions. As such, this approach is not suitable for anything but testing
environments.
The following instructions use [access scopes](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam) to provide ExternalDNS with the permissions it needs to manage DNS records within a single [project](https://cloud.google.com/docs/overview#projects), the organizing entity to allocate resources.
Create a GKE cluster.
Note that since these permissions are associated with the instance, all pods in the cluster will also have these permissions. As such, this approach is not suitable for anything but testing environments.
```console
$ gcloud container clusters create "external-dns" \
--num-nodes 1 \
--scopes "https://www.googleapis.com/auth/ndev.clouddns.readwrite"
This solution will only work when both CloudDNS and GKE are provisioned in the same project. If the CloudDNS zone is in a different project, this solution will not work.
### Configure Project Environment
Setup your environment to work with Google Cloud Platform. Fill in your variables as needed, e.g. target project.
```bash
# set variables to the appropriate desired values
PROJECT_ID="my-external-dns-test"
REGION="europe-west1"
ZONE="europe-west1-d"
ClOUD_BILLING_ACCOUNT="<my-cloud-billing-account>"
# set default settings for project
gcloud config set project $PROJECT_ID
gcloud config set compute/region $REGION
gcloud config set compute/zone $ZONE
# enable billing and APIs if not done already
gcloud beta billing projects link $PROJECT_ID \
--billing-account $BILLING_ACCOUNT
gcloud services enable "dns.googleapis.com"
gcloud services enable "container.googleapis.com"
```
Create a DNS zone which will contain the managed DNS records.
### Create GKE Cluster
```console
$ gcloud dns managed-zones create "external-dns-test-gcp-zalan-do" \
--dns-name "external-dns-test.gcp.zalan.do." \
--description "Automatically managed zone by kubernetes.io/external-dns"
```bash
gcloud container clusters create $GKE_CLUSTER_NAME \
--num-nodes 1 \
--scopes "https://www.googleapis.com/auth/ndev.clouddns.readwrite"
```
**WARNING**: Note that this cluster will use the default [compute engine GSA](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account) that contians the overly permissive project editor (`roles/editor`) role. So essentially, anything on the cluster could potentially grant escalated privileges. Also, as mentioned earlier, the access scope `ndev.clouddns.readwrite` will allow anything running on the cluster to have read/write permissions on all Cloud DNS zones within the same project.
### Cloud DNS Zone
Create a DNS zone which will contain the managed DNS records. If using your own domain that was registered with a third-party domain registrar, you should point your domain's name servers to the values under the `nameServers` key. Please consult your registrar's documentation on how to do that. This tutorial will use example domain of `example.com`.
```bash
gcloud dns managed-zones create "example-com" --dns-name "example.com." \
--description "Automatically managed zone by kubernetes.io/external-dns"
```
Make a note of the nameservers that were assigned to your new zone.
```console
$ gcloud dns record-sets list \
--zone "external-dns-test-gcp-zalan-do" \
--name "external-dns-test.gcp.zalan.do." \
--type NS
NAME TYPE TTL DATA
external-dns-test.gcp.zalan.do. NS 21600 ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
```bash
gcloud dns record-sets list \
--zone "example-com" --name "example.com." --type NS
```
Outputs:
```
NAME TYPE TTL DATA
example.com. NS 21600 ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
```
In this case it's `ns-cloud-{e1-e4}.googledomains.com.` but your's could slightly differ, e.g. `{a1-a4}`, `{b1-b4}` etc.
Tell the parent zone where to find the DNS records for this zone by adding the corresponding NS records there. Assuming the parent zone is "gcp-zalan-do" and the domain is "gcp.zalan.do" and that it's also hosted at Google we would do the following.
## Cross project access scenario using Google Service Account
```console
$ gcloud dns record-sets transaction start --zone "gcp-zalan-do"
$ gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \
--name "external-dns-test.gcp.zalan.do." --ttl 300 --type NS --zone "gcp-zalan-do"
$ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
More often, following best practices in regards to security and operations, Cloud DNS zones will be managed in a separate project from the Kubernetes cluster. This section shows how setup ExternalDNS to access Cloud DNS from a different project. These steps will also work for single project scenarios as well.
ExternalDNS will need permissions to make changes to the Cloud DNS zone. There are three ways to configure the access needed:
* [Worker Node Service Account](#worker-node-service-account)
* [Static Credentials](#static-credentials)
* [Work Load Identity](#work-load-identity)
### Setup Cloud DNS and GKE
Below are examples on how you can configure Cloud DNS and GKE in separate projects, and then use one of the three methods to grant access to ExternalDNS. Replace the environment variables to values that make sense in your environment.
#### Configure Projects
For this process, create projects with the appropriate APIs enabled.
```bash
# set variables to appropriate desired values
GKE_PROJECT_ID="my-workload-project"
DNS_PROJECT_ID="my-cloud-dns-project"
ClOUD_BILLING_ACCOUNT="<my-cloud-billing-account>"
# enable billing and APIs for DNS project if not done already
gcloud config set project $DNS_PROJECT_ID
gcloud beta billing projects link $CLOUD_DNS_PROJECT \
--billing-account $ClOUD_BILLING_ACCOUNT
gcloud services enable "dns.googleapis.com"
# enable billing and APIs for GKE project if not done already
gcloud config set project $GKE_PROJECT_ID
gcloud beta billing projects link $CLOUD_DNS_PROJECT \
--billing-account $ClOUD_BILLING_ACCOUNT
gcloud services enable "container.googleapis.com"
```
### Deploy ExternalDNS
#### Provisioning Cloud DNS
Create a Cloud DNS zone in the designated DNS project.
```bash
gcloud dns managed-zones create "example-com" --project $DNS_PROJECT_ID \
--description "example.com" --dns-name="example.com." --visibility=public
```
If using your own domain that was registered with a third-party domain registrar, you should point your domain's name servers to the values under the `nameServers` key. Please consult your registrar's documentation on how to do that. The example domain of `example.com` will be used for this tutorial.
#### Provisioning a GKE cluster for cross project access
Create a GSA (Google Service Account) and grant it the [minimal set of privileges required](https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa) for GKE nodes:
```bash
GKE_CLUSTER_NAME="my-external-dns-cluster"
GKE_REGION="us-central1"
GKE_SA_NAME="worker-nodes-sa"
GKE_SA_EMAIL="$GKE_SA_NAME@${GKE_PROJECT_ID}.iam.gserviceaccount.com"
ROLES=(
roles/logging.logWriter
roles/monitoring.metricWriter
roles/monitoring.viewer
roles/stackdriver.resourceMetadata.writer
)
gcloud iam service-accounts create $GKE_SA_NAME \
--display-name $GKE_SA_NAME --project $GKE_PROJECT_ID
# assign google service account to roles in GKE project
for ROLE in ${ROLES[*]}; do
gcloud projects add-iam-policy-binding $GKE_PROJECT_ID \
--member "serviceAccount:$GKE_SA_EMAIL" \
--role $ROLE
done
```
Create a cluster using this service account and enable [workload identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity):
```bash
gcloud container clusters create $GKE_CLUSTER_NAME \
--project $GKE_PROJECT_ID --region $GKE_REGION --num-nodes 1 \
--service-account "$GKE_SA_EMAIL" \
--workload-pool "$GKE_PROJECT_ID.svc.id.goog"
```
### Worker Node Service Account method
In this method, the GSA (Google Service Account) that is associated with GKE worker nodes will be configured to have access to Cloud DNS.
**WARNING**: This will grant access to modify the Cloud DNS zone records for all containers running on cluster, not just ExternalDNS, so use this option with caution. This is not recommended for production environments.
```bash
GKE_SA_EMAIL="$GKE_SA_NAME@${GKE_PROJECT_ID}.iam.gserviceaccount.com"
# assign google service account to dns.admin role in the cloud dns project
gcloud projects add-iam-policy-binding $DNS_PROJECT_ID \
--member serviceAccount:$GKE_SA_EMAIL \
--role roles/dns.admin
```
After this, follow the steps in [Deploy ExternalDNS](#deploy-externaldns). Make sure to set the `--google-project` flag to match the Cloud DNS project name.
### Static Credentials
In this scenario, a new GSA (Google Service Account) is created that has access to the CloudDNS zone. The credentials for this GSA are saved and installed as a Kubernetes secret that will be used by ExternalDNS.
This allows only containers that have access to the secret, such as ExternalDNS to update records on the Cloud DNS Zone.
#### Create GSA for use with static credentials
```bash
DNS_SA_NAME="external-dns-sa"
DNS_SA_EMAIL="$DNS_SA_NAME@${GKE_PROJECT_ID}.iam.gserviceaccount.com"
# create GSA used to access the Cloud DNS zone
gcloud iam service-accounts create $DNS_SA_NAME --display-name $DNS_SA_NAME
# assign google service account to dns.admin role in cloud-dns project
gcloud projects add-iam-policy-binding $DNS_PROJECT_ID \
--member serviceAccount:$DNS_SA_EMAIL --role "roles/dns.admin"
```
#### Create Kubernetes secret using static credentials
Generate static credentials from the ExternalDNS GSA.
```bash
# download static credentials
gcloud iam service-accounts keys create /local/path/to/credentials.json \
--iam-account $DNS_SA_EMAIL
```
Create a Kubernetes secret with the credentials in the same namespace of ExternalDNS.
```bash
kubectl create secret generic "external-dns" --namespace ${EXTERNALDNS_NS:-"default"} \
--from-file /local/path/to/credentials.json
```
After this, follow the steps in [Deploy ExternalDNS](#deploy-externaldns). Make sure to set the `--google-project` flag to match Cloud DNS project name. Make sure to uncomment out the section that mounts the secret to the ExternalDNS pods.
### Workload Identity
[Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) allows workloads in your GKE cluster to impersonate GSA (Google Service Accounts) using KSA (Kubernetes Service Accounts) configured during deployemnt. These are the steps to use this feature with ExternalDNS.
#### Create GSA for use with Workload Identity
```bash
DNS_SA_NAME="external-dns-sa"
DNS_SA_EMAIL="$DNS_SA_NAME@${GKE_PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create $DNS_SA_NAME --display-name $DNS_SA_NAME
gcloud projects add-iam-policy-binding $DNS_PROJECT_ID \
--member serviceAccount:$DNS_SA_EMAIL --role "roles/dns.admin"
```
#### Link KSA to GSA
Add an IAM policy binding bewtween the workload identity GSA and ExternalDNS GSA. This will link the ExternalDNS KSA to ExternalDNS GSA.
```bash
gcloud iam service-accounts add-iam-policy-binding $DNS_SA_EMAIL \
--role "roles/iam.workloadIdentityUser" \
--member "serviceAccount:$GKE_PROJECT_ID.svc.id.goog[${EXTERNALDNS_NS:-"default"}/external-dns]"
```
#### Deploy External DNS
Deploy ExternalDNS with the following steps below, documented under [Deploy ExternalDNS](#deploy-externaldns). Set the `--google-project` flag to the Cloud DNS project name.
#### Link KSA to GSA in Kubernetes
Add the proper workload identity annotation to the ExternalDNS KSA.
```bash
kubectl annotate serviceaccount "external-dns" \
--namespace ${EXTERNALDNS_NS:-"default"} \
"iam.gke.io/gcp-service-account=$DNS_SA_EMAIL"
```
#### Update ExternalDNS pods
Update the Pod spec to schedule the workloads on nodes that use Workload Identity and to use the annotated Kubernetes service account.
```bash
kubectl patch deployment "external-dns" \
--namespace ${EXTERNALDNS_NS:-"default"} \
--patch \
'{"spec": {"template": {"spec": {"nodeSelector": {"iam.gke.io/gke-metadata-server-enabled": "true"}}}}}'
```
After all of these steps you may see several messages with `googleapi: Error 403: Forbidden, forbidden`. After several minutes when the token is refreshed, these error messages will go away, and you should see info messages, such as: `All records are already up to date`.
## Deploy ExternalDNS
Then apply the following manifests file to deploy ExternalDNS.
@ -69,71 +267,96 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
labels:
app.kubernetes.io/name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
labels:
app.kubernetes.io/name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["services","endpoints","pods","nodes"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
labels:
app.kubernetes.io/name: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
- kind: ServiceAccount
name: external-dns
namespace: default # change if namespace is not 'default'
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
labels:
app.kubernetes.io/name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
app.kubernetes.io/name: external-dns
template:
metadata:
labels:
app: external-dns
app.kubernetes.io/name: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
args:
- --source=service
- --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=google
# - --google-project=zalando-external-dns-test # Use this to specify a project different from the one external-dns is running inside
- --google-zone-visibility=private # Use this to filter to only zones with this visibility. Set to either 'public' or 'private'. Omitting will match public and private zones
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --registry=txt
- --txt-owner-id=my-identifier
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=google
- --log-format=json # google cloud logs parses severity of the "text" log format incorrectly
# - --google-project=my-cloud-dns-project # Use this to specify a project different from the one external-dns is running inside
- --google-zone-visibility=public # Use this to filter to only zones with this visibility. Set to either 'public' or 'private'. Omitting will match public and private zones
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --registry=txt
- --txt-owner-id=my-identifier
# # uncomment below if static credentials are used
# env:
# - name: GOOGLE_APPLICATION_CREDENTIALS
# value: /etc/secrets/service-account/credentials.json
# volumeMounts:
# - name: google-service-account
# mountPath: /etc/secrets/service-account/
# volumes:
# - name: google-service-account
# secret:
# secretName: external-dns
```
Use `--dry-run` if you want to be extra careful on the first run. Note, that you will not see any records created when you are running in dry-run mode. You can, however, inspect the logs and watch what would have been done.
Create the deployment for ExternalDNS:
### Verify ExternalDNS works
```bash
kubectl create --namespace "default" --filename externaldns.yaml
```
Create the following sample application to test that ExternalDNS works.
## Verify ExternalDNS works
The following will deploy a small nginx server that will be used to demonstrate that ExternalDNS is working.
### Verify using an external load balancer
Create the following sample application to test that ExternalDNS works. This example will provision a L4 load balancer.
```yaml
apiVersion: v1
@ -141,17 +364,16 @@ kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.gcp.zalan.do.
# change nginx.example.com to match an appropriate value
external-dns.alpha.kubernetes.io/hostname: nginx.example.com
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
- port: 80
targetPort: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -166,49 +388,49 @@ spec:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
- image: nginx
name: nginx
ports:
- containerPort: 80
```
Create the deployment and service objects:
```bash
kubectl create --namespace "default" --filename nginx.yaml
```
After roughly two minutes check that a corresponding DNS record for your service was created.
```console
$ gcloud dns record-sets list \
--zone "external-dns-test-gcp-zalan-do" \
--name "nginx.external-dns-test.gcp.zalan.do."
NAME TYPE TTL DATA
nginx.external-dns-test.gcp.zalan.do. A 300 104.155.60.49
nginx.external-dns-test.gcp.zalan.do. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
```bash
gcloud dns record-sets list --zone "example-com" --name "nginx.example.com."
```
Note created TXT record alongside A record. TXT record signifies that the corresponding A record is managed by ExternalDNS. This makes ExternalDNS safe for running in environments where there are other records managed via other means.
Example output:
```
NAME TYPE TTL DATA
nginx.example.com. A 300 104.155.60.49
nginx.example.com. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
```
Note created `TXT` record alongside `A` record. `TXT` record signifies that the corresponding `A` record is managed by ExternalDNS. This makes ExternalDNS safe for running in environments where there are other records managed via other means.
Let's check that we can resolve this DNS name. We'll ask the nameservers assigned to your zone first.
```console
$ dig +short @ns-cloud-e1.googledomains.com. nginx.external-dns-test.gcp.zalan.do.
```bash
dig +short @ns-cloud-e1.googledomains.com. nginx.example.com.
104.155.60.49
```
Given you hooked up your DNS zone with its parent zone you can use `curl` to access your site.
```console
$ curl nginx.external-dns-test.gcp.zalan.do
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```bash
curl nginx.example.com
```
### Verify using an ingress
Let's check that Ingress works as well. Create the following Ingress.
```yaml
@ -218,362 +440,66 @@ metadata:
name: nginx
spec:
rules:
- host: via-ingress.external-dns-test.gcp.zalan.do
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
- host: server.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
```
Again, after roughly two minutes check that a corresponding DNS record for your Ingress was created.
Create the ingress objects with:
```console
$ gcloud dns record-sets list \
--zone "external-dns-test-gcp-zalan-do" \
--name "via-ingress.external-dns-test.gcp.zalan.do." \
```bash
kubectl create --namespace "default" --filename ingress.yaml
```
NAME TYPE TTL DATA
via-ingress.external-dns-test.gcp.zalan.do. A 300 130.211.46.224
via-ingress.external-dns-test.gcp.zalan.do. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
Note that this will ingress object will use the default ingress controller that comes with GKE to create a L7 load balancer in addition to the L4 load balancer previously with the service object. To use only the L7 load balancer, update the service manafest to change the Service type to `NodePort` and remove the ExternalDNS annotation.
After roughly two minutes check that a corresponding DNS record for your Ingress was created.
```bash
gcloud dns record-sets list \
--zone "example-com" \
--name "server.example.com." \
```
Output:
```
NAME TYPE TTL DATA
server.example.com. A 300 130.211.46.224
server.example.com. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
```
Let's check that we can resolve this DNS name as well.
```console
dig +short @ns-cloud-e1.googledomains.com. via-ingress.external-dns-test.gcp.zalan.do.
```bash
dig +short @ns-cloud-e1.googledomains.com. server.example.com.
130.211.46.224
```
Try with `curl` as well.
```console
$ curl via-ingress.external-dns-test.gcp.zalan.do
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```bash
curl server.example.com
```
### Clean up
Make sure to delete all Service and Ingress objects before terminating the cluster so all load balancers get cleaned up correctly.
```console
$ kubectl delete service nginx
$ kubectl delete ingress nginx
```bash
kubectl delete service nginx
kubectl delete ingress nginx
```
Give ExternalDNS some time to clean up the DNS records for you. Then delete the managed zone and cluster.
```console
$ gcloud dns managed-zones delete "external-dns-test-gcp-zalan-do"
$ gcloud container clusters delete "external-dns"
```
Also delete the NS records for your removed zone from the parent zone.
```console
$ gcloud dns record-sets transaction start --zone "gcp-zalan-do"
$ gcloud dns record-sets transaction remove ns-cloud-e{1..4}.googledomains.com. \
--name "external-dns-test.gcp.zalan.do." --ttl 300 --type NS --zone "gcp-zalan-do"
$ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
```
## GKE with Workload Identity
The following instructions use [GKE workload
identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
to provide ExternalDNS with the permissions it needs to manage DNS records.
Workload identity is the Google-recommended way to provide GKE workloads access
to GCP APIs.
Create a GKE cluster with workload identity enabled.
```console
$ gcloud container clusters create external-dns \
--workload-metadata-from-node=GKE_METADATA_SERVER \
--identity-namespace=zalando-external-dns-test.svc.id.goog
```
Create a GCP service account (GSA) for ExternalDNS and save its email address.
```console
$ sa_name="Kubernetes external-dns"
$ gcloud iam service-accounts create sa-edns --display-name="$sa_name"
$ sa_email=$(gcloud iam service-accounts list --format='value(email)' \
--filter="displayName:$sa_name")
```
Bind the ExternalDNS GSA to the DNS admin role.
```console
$ gcloud projects add-iam-policy-binding zalando-external-dns-test \
--member="serviceAccount:$sa_email" --role=roles/dns.admin
```
Link the ExternalDNS GSA to the Kubernetes service account (KSA) that
external-dns will run under, i.e., the external-dns KSA in the external-dns
namespaces.
```console
$ gcloud iam service-accounts add-iam-policy-binding "$sa_email" \
--member="serviceAccount:zalando-external-dns-test.svc.id.goog[external-dns/external-dns]" \
--role=roles/iam.workloadIdentityUser
```
Create a DNS zone which will contain the managed DNS records.
```console
$ gcloud dns managed-zones create external-dns-test-gcp-zalan-do \
--dns-name=external-dns-test.gcp.zalan.do. \
--description="Automatically managed zone by ExternalDNS"
```
Make a note of the nameservers that were assigned to your new zone.
```console
$ gcloud dns record-sets list \
--zone=external-dns-test-gcp-zalan-do \
--name=external-dns-test.gcp.zalan.do. \
--type NS
NAME TYPE TTL DATA
external-dns-test.gcp.zalan.do. NS 21600 ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
```
In this case it's `ns-cloud-{e1-e4}.googledomains.com.` but your's could
slightly differ, e.g. `{a1-a4}`, `{b1-b4}` etc.
Tell the parent zone where to find the DNS records for this zone by adding the
corresponding NS records there. Assuming the parent zone is "gcp-zalan-do" and
the domain is "gcp.zalan.do" and that it's also hosted at Google we would do the
following.
```console
$ gcloud dns record-sets transaction start --zone=gcp-zalan-do
$ gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \
--name=external-dns-test.gcp.zalan.do. --ttl 300 --type NS --zone=gcp-zalan-do
$ gcloud dns record-sets transaction execute --zone=gcp-zalan-do
```
Connect your `kubectl` client to the cluster you just created and bind your GCP
user to the cluster admin role in Kubernetes.
```console
$ gcloud container clusters get-credentials external-dns
$ kubectl create clusterrolebinding cluster-admin-me \
--clusterrole=cluster-admin --user="$(gcloud config get-value account)"
```
### Deploy ExternalDNS
Apply the following manifest file to deploy external-dns.
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: external-dns
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "pods"]
verbs: ["get", "watch", "list"]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- args:
- --source=ingress
- --source=service
- --domain-filter=external-dns-test.gcp.zalan.do
- --provider=google
- --google-project=zalando-external-dns-test
- --registry=txt
- --txt-owner-id=my-identifier
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
name: external-dns
securityContext:
fsGroup: 65534
runAsUser: 65534
serviceAccountName: external-dns
```
Then add the proper workload identity annotation to the cert-manager service
account.
```bash
$ kubectl annotate serviceaccount --namespace=external-dns external-dns \
"iam.gke.io/gcp-service-account=$sa_email"
gcloud dns managed-zones delete "example-com"
gcloud container clusters delete "external-dns"
```
### Deploy a sample application
Create the following sample application to test that ExternalDNS works.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
spec:
rules:
- host: via-ingress.external-dns-test.gcp.zalan.do
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.gcp.zalan.do.
name: nginx
spec:
ports:
- port: 80
targetPort: 80
selector:
app: nginx
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
```
After roughly two minutes check that a corresponding DNS records for your
service and ingress were created.
```console
$ gcloud dns record-sets list \
--zone "external-dns-test-gcp-zalan-do" \
--name "via-ingress.external-dns-test.gcp.zalan.do." \
--type A
NAME TYPE TTL DATA
nginx.external-dns-test.gcp.zalan.do. A 300 104.155.60.49
nginx.external-dns-test.gcp.zalan.do. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
via-ingress.external-dns-test.gcp.zalan.do. TXT 300 "heritage=external-dns,external-dns/owner=my-identifier"
via-ingress.external-dns-test.gcp.zalan.do. A 300 35.187.1.246
```
Let's check that we can resolve this DNS name as well.
```console
$ dig +short @ns-cloud-e1.googledomains.com. via-ingress.external-dns-test.gcp.zalan.do.
35.187.1.246
```
Try with `curl` as well.
```console
$ curl via-ingress.external-dns-test.gcp.zalan.do
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```
### Clean up
Make sure to delete all service and ingress objects before terminating the
cluster so all load balancers and DNS entries get cleaned up correctly.
```console
$ kubectl delete ingress nginx
$ kubectl delete service nginx
```
Give ExternalDNS some time to clean up the DNS records for you. Then delete the
managed zone and cluster.
```console
$ gcloud dns managed-zones delete external-dns-test-gcp-zalan-do
$ gcloud container clusters delete external-dns
```
Also delete the NS records for your removed zone from the parent zone.
```console
$ gcloud dns record-sets transaction start --zone gcp-zalan-do
$ gcloud dns record-sets transaction remove ns-cloud-e{1..4}.googledomains.com. \
--name=external-dns-test.gcp.zalan.do. --ttl 300 --type NS --zone=gcp-zalan-do
$ gcloud dns record-sets transaction execute --zone=gcp-zalan-do
```
## User Demo How-To Blogs and Examples
* A full demo on GKE Kubernetes + CloudDNS + SA-Permissions [How-to Kubernetes with DNS management (ssl-manager pre-req)](https://medium.com/@jpantjsoha/how-to-kubernetes-with-dns-management-for-gitops-31239ea75d8d)
* Run external-dns on GKE with workload identity. See [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/)

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)
@ -90,7 +90,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.7
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -115,7 +115,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.7
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -31,7 +31,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --log-level=debug
- --source=service
@ -96,7 +96,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --log-level=debug
- --source=service
@ -198,3 +198,34 @@ kafka-1.ksvc.example.org
kafka-2.ksvc.example.org
```
#### Using pods' HostIPs as targets
Add the following annotation to your `Service`:
```yaml
external-dns.alpha.kubernetes.io/endpoints-type: HostIP
```
external-dns will now publish the value of the `.status.hostIP` field of the pods backing your `Service`.
#### Using node external IPs as targets
Add the following annotation to your `Service`:
```yaml
external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP
```
external-dns will now publish the node external IP (`.status.addresses` entries of with `type: NodeExternalIP`) of the nodes on which the pods backing your `Service` are running.
#### Using pod annotations to specify target IPs
Add the following annotation to the **pods** backing your `Service`:
```yaml
external-dns.alpha.kubernetes.io/target: "1.2.3.4"
```
external-dns will publish the IP specified in the annotation of each pod instead of using the podIP advertised by Kubernetes.
This can be useful e.g. if you are NATing public IPs onto your pod IPs and want to publish these in DNS.

262
docs/tutorials/ibmcloud.md Normal file
View File

@ -0,0 +1,262 @@
# Setting up ExternalDNS for Services on IBMCloud
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using IBMCloud DNS.
This tutorial uses [IBMCloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started) for all
IBM Cloud commands and assumes that the Kubernetes cluster was created via IBM Cloud Kubernetes Service and `kubectl` commands
are being run on an orchestration node.
## Creating a IBMCloud DNS zone
The IBMCloud provider for ExternalDNS will find suitable zones for domains it manages; it will
not automatically create zones.
For public zone, This tutorial assume that the [IBMCloud Internet Services](https://cloud.ibm.com/catalog/services/internet-services) was provisioned and the [cis cli plugin](https://cloud.ibm.com/docs/cis?topic=cis-cli-plugin-cis-cli) was installed with IBMCloud CLI
For private zone, This tutorial assume that the [IBMCloud DNS Services](https://cloud.ibm.com/catalog/services/dns-services) was provisioned and the [dns cli plugin](https://cloud.ibm.com/docs/dns-svcs?topic=dns-svcs-cli-plugin-dns-services-cli-commands) was installed with IBMCloud CLI
### Public Zone
For this tutorial, we create public zone named `example.com` on IBMCloud Internet Services instance `external-dns-public`
```
$ ibmcloud cis domain-add example.com -i external-dns-public
```
Follow [step](https://cloud.ibm.com/docs/cis?topic=cis-getting-started#configure-your-name-servers-with-the-registrar-or-existing-dns-provider) to active your zone
### Private Zone
For this tutorial, we create private zone named `example.com` on IBMCloud DNS Services instance `external-dns-private`
```
$ ibmcloud dns zone-create example.com -i external-dns-private
```
## Creating configuration file
The preferred way to inject the configuration file is by using a Kubernetes secret. The secret should contain an object named azure.json with content similar to this:
```
{
"apiKey": "1234567890abcdefghijklmnopqrstuvwxyz",
"instanceCrn": "crn:v1:bluemix:public:internet-svcs:global:a/bcf1865e99742d38d2d5fc3fb80a5496:b950da8a-5be6-4691-810e-36388c77b0a3::"
}
```
You can create or find the `apiKey` in your ibmcloud IAM --> [API Keys page](https://cloud.ibm.com/iam/apikeys)
You can find the `instanceCrn` in your service instance details
Now you can create a file named 'ibmcloud.json' with values gathered above and with the structure of the example above. Use this file to create a Kubernetes secret:
```
$ kubectl create secret generic ibmcloud-config-file --from-file=/local/path/to/ibmcloud.json
```
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=ibmcloud
- --ibmcloud-proxied # (optional) enable the proxy feature of IBMCloud
volumeMounts:
- name: ibmcloud-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: ibmcloud-config-file
secret:
secretName: ibmcloud-config-file
items:
- key: externaldns-config.json
path: ibmcloud.json
```
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=ibmcloud
- --ibmcloud-proxied # (optional) enable the proxy feature of IBMCloud public zone
volumeMounts:
- name: ibmcloud-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: ibmcloud-config-file
secret:
secretName: ibmcloud-config-file
items:
- key: externaldns-config.json
path: ibmcloud.json
```
## Deploying an Nginx Service
Create a service file called `nginx.yaml` with the following contents:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: www.example.com
external-dns.alpha.kubernetes.io/ttl: "120" #optional
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
```
Note the annotation on the service; use the hostname as the IBMCloud DNS zone created above. The annotation may also be a subdomain
of the DNS zone (e.g. 'www.example.com').
By setting the TTL annotation on the service, you have to pass a valid TTL, which must be 120 or above.
This annotation is optional, if you won't set it, it will be 1 (automatic) which is 300.
ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation
will cause ExternalDNS to remove the corresponding DNS records.
Create the deployment and service:
```
$ kubectl create -f nginx.yaml
```
Depending where you run your service it can take a little while for your cloud provider to create an external IP for the service.
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize
the IBMCloud DNS records.
## Verifying IBMCloud DNS records
Run the following command to view the A records:
### Public Zone
```
# Get the domain ID with below command on IBMCloud Internet Services instance `external-dns-public`
$ ibmcloud cis domains -i external-dns-public
# Get the records with domain ID
$ ibmcloud cis dns-records DOMAIN_ID -i external-dns-public
```
### Private Zone
```
# Get the domain ID with below command on IBMCloud DNS Services instance `external-dns-private`
$ ibmcloud dns zones -i external-dns-private
# Get the records with domain ID
$ ibmcloud dns resource-records ZONE_ID -i external-dns-public
```
This should show the external IP address of the service as the A record for your domain.
## Cleanup
Now that we have verified that ExternalDNS will automatically manage IBMCloud DNS records, we can delete the tutorial's example:
```
$ kubectl delete -f nginx.yaml
$ kubectl delete -f externaldns.yaml
```
## Setting proxied records on public zone
Using the `external-dns.alpha.kubernetes.io/ibmcloud-proxied: "true"` annotation on your ingress or service, you can specify if the proxy feature of IBMCloud public DNS should be enabled for that record. This setting will override the global `--ibmcloud-proxied` setting.
## Active priviate zone with VPC allocated
By default, IBMCloud DNS Services don't active your private zone with new zone added, with externale DNS, you can use `external-dns.alpha.kubernetes.io/ibmcloud-vpc: "crn:v1:bluemix:public:is:us-south:a/bcf1865e99742d38d2d5fc3fb80a5496::vpc:r006-74353823-a60d-42e4-97c5-5e2551278435"` annotation on your ingress or service, it will active your private zone with in specific VPC for that record created in. this setting won't work if the private zone was active already.
Note: the annotaion value is the VPC CRN, every IBM Cloud service have a valid CRN.

View File

@ -69,7 +69,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.
@ -150,7 +150,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.
@ -271,6 +271,14 @@ There is also the ability to filter results from the Infoblox zone_auth service
--infoblox-fqdn-regex=^staging.*test.com$
```
## Ability to filter A, Host, CNAME and TXT records from the by name using a regular expression
Infoblox supports filtering records by name using a regular expression. See the [Infoblox API document](https://www.infoblox.com/wp-content/uploads/infoblox-deployment-infoblox-rest-api.pdf) for examples. To use this feature, set the parameter infoblox-name-regex for external-dns to a regular expression that makes sense for you. For instance, if all your dns records end with `cluster1.example.com`, you can fetch records matching this style by setting the following:
```
--infoblox-name-regex=cluster1.example.com
```
## Infoblox PTR record support
There is an option to enable PTR records support for infoblox provider. PTR records allow to do reverse dns search. To enable PTR records support, add following into arguments for external-dns:

View File

@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -98,7 +98,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.9.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=kong-tcpingress
- --provider=aws
@ -86,7 +86,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.9.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=kong-tcpingress
- --provider=aws

View File

@ -41,7 +41,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -273,7 +273,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do
@ -570,7 +570,7 @@ spec:
- --google-project=zalando-external-dns-test
- --registry=txt
- --txt-owner-id=my-identifier
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
name: external-dns
securityContext:
fsGroup: 65534

114
docs/tutorials/nodes.md Normal file
View File

@ -0,0 +1,114 @@
# Configuring ExternalDNS to use Cluster Nodes as Source
This tutorial describes how to configure ExternalDNS to use the cluster nodes as source.
Using nodes (`--source=node`) as source is possible to synchronize a DNS zone with the nodes of a cluster.
The node source adds an `A` record per each node `externalIP` (if not found, node's `internalIP` is used).
The TTL record can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
## Manifest (for cluster without RBAC enabled)
```
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=node # will use nodes as source
- --provider=aws
- --zone-name-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --domain-filter=external-dns-test.my-org.com
- --aws-zone-type=public
- --registry=txt
- --fqdn-template={{.Name}}.external-dns-test.my-org.com
- --txt-owner-id=my-identifier
- --policy=sync
- --log-level=debug
```
## Manifest (for cluster with RBAC enabled)
```
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=node # will use nodes as source
- --provider=aws
- --zone-name-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --domain-filter=external-dns-test.my-org.com
- --aws-zone-type=public
- --registry=txt
- --fqdn-template={{.Name}}.external-dns-test.my-org.com
- --txt-owner-id=my-identifier
- --policy=sync
- --log-level=debug
```

View File

@ -61,7 +61,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -125,7 +125,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -4,46 +4,34 @@ It is meant to supplement the other provider-specific setup tutorials.
### For OCP 4.x
In OCP 4.x, if you have multiple ingress controllers then you must specify an ingress controller name or a router name(you can get it from the route's Status.Ingress.RouterName field).
If you don't specify an ingress controller's or router name when you have multiple ingresscontrollers in your environment then the route gets populated with multiple entries of router canonical hostnames which causes external dns to create a CNAME record with multiple router canonical hostnames pointing to the route host which is a violation of RFC 1912 and is not allowed by Cloud Providers which leads to failure of record creation.
Once you specify the ingresscontroller or router name then that will be matched by the external-dns and the router canonical hostname corresponding to this routerName(which is present in route's Status.Ingress.RouterName field) is selected and a CNAME record of this route host pointing to this router canonical hostname is created.
Your externaldns CR shall be created as per the following example.
Replace names in the domain section and zone ID as per your environment.
This is example is for AWS environment.
In OCP 4.x, if you have multiple [OpenShift ingress controllers](https://docs.openshift.com/container-platform/4.9/networking/ingress-operator.html) then you must specify an ingress controller name (also called router name), you can get it from the route's `status.ingress[*].routerName` field.
If you don't specify a router name when you have multiple ingress controllers in your cluster then the first router from the route's `status.ingress` will be used. Note that the router must have admitted the route in order to be selected.
Once the router is known, ExternalDNS will use this router's canonical hostname as the target for the CNAME record.
Starting from OCP 4.10 you can use [ExternalDNS Operator](https://github.com/openshift/external-dns-operator) to manage ExternalDNS instances. Example of its custom resource for AWS provider:
```yaml
apiVersion: externaldns.olm.openshift.io/v1alpha1
kind: ExternalDNS
metadata:
name: sample1
name: sample
spec:
domains:
- filterType: Include
matchType: Exact
names: apps.miheer.externaldns
provider:
type: AWS
source:
hostnameAnnotation: Allow
openshiftRouteOptions:
routerName: default
type: OpenShiftRoute
zones:
- Z05387772BD5723IZFRX3
```
This will create an externaldns pod with the following container args under spec in the external-dns namespace where `- --source=openshift-route` and `- --openshift-router-name=default` is added by the external-dns-operator.
This will create an ExternalDNS POD with the following container args in `external-dns` namespace:
```
spec:
containers:
- args:
- --domain-filter=apps.misalunk.externaldns
- --metrics-address=127.0.0.1:7979
- --txt-owner-id=external-dns-sample1
- --txt-owner-id=external-dns-sample
- --provider=aws
- --source=openshift-route
- --policy=sync
@ -52,7 +40,6 @@ spec:
- --zone-id-filter=Z05387772BD5723IZFRX3
- --openshift-router-name=default
- --txt-prefix=external-dns-
```
### For OCP 3.11 environment
@ -79,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
@ -146,7 +133,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones

View File

@ -6,16 +6,25 @@ Make sure to use the latest version of ExternalDNS for this tutorial.
## Creating an OCI DNS Zone
Create a DNS zone which will contain the managed DNS records. Let's use `example.com` as an reference here.
Create a DNS zone which will contain the managed DNS records. Let's use
`example.com` as a reference here. Make note of the OCID of the compartment
in which you created the zone; you'll need to provide that later.
For more information about OCI DNS see the documentation [here][1].
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
The OCI provider supports two authentication options: key-based and instance
principals.
### Key-based
We first need to create a config file containing the information needed to connect with the OCI API.
Create a new file (oci.yaml) and modify the contents to match the example below. Be sure to adjust the values to match your own credentials:
Create a new file (oci.yaml) and modify the contents to match the example
below. Be sure to adjust the values to match your own credentials, and the OCID
of the compartment containing the zone:
```yaml
auth:
@ -37,7 +46,29 @@ Create a secret using the config file above:
$ kubectl create secret generic external-dns-config --from-file=oci.yaml
```
### Manifest (for clusters with RBAC enabled)
### OCI IAM Instance Principal
If you're running ExternalDNS within OCI, you can use OCI IAM instance
principals to authenticate with OCI. This obviates the need to create the
secret with your credentials. You'll need to ensure an OCI IAM policy exists
with a statement granting the `manage dns` permission on zones and records in
the target compartment to the dynamic group covering your instance running
ExternalDNS.
E.g.:
```
Allow dynamic-group <dynamic-group-name> to manage dns in compartment id <target-compartment-OCID>
```
You'll also need to add the `--oci-auth-instance-principal` flag to enable
this type of authentication. Finally, you'll need to add the
`--oci-compartment-ocid=ocid1.compartment.oc1...` flag to provide the OCID of
the compartment containing the zone to be managed.
For more information about OCI IAM instance principals, see the documentation [here][2].
For more information about OCI IAM policy details for the DNS service, see the documentation [here][3].
## Manifest (for clusters with RBAC enabled)
Apply the following manifest to deploy ExternalDNS.
@ -93,7 +124,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -159,3 +190,6 @@ $ kubectl apply -f nginx.yaml
```
[1]: https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm
[2]: https://docs.cloud.oracle.com/iaas/Content/Identity/Reference/dnspolicyreference.htm
[3]: https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm

View File

@ -86,7 +86,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -160,7 +160,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -42,7 +42,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # or ingress or both
- --provider=pdns

188
docs/tutorials/pihole.md Normal file
View File

@ -0,0 +1,188 @@
# Setting up ExternalDNS for Pi-hole
This tutorial describes how to setup ExternalDNS to sync records with Pi-hole's Custom DNS.
Pi-hole has an internal list it checks last when resolving requests. This list can contain any number of arbitrary A or CNAME records.
There is a pseudo-API exposed that ExternalDNS is able to use to manage these records.
## Deploy ExternalDNS
You can skip to the [manifest](#externaldns-manifest) if authentication is disabled on your Pi-hole instance or you don't want to use secrets.
If your Pi-hole server's admin dashboard is protected by a password, you'll likely want to create a secret first containing its value.
This is optional since you _do_ retain the option to pass it as a flag with `--pihole-password`.
You can create the secret with:
```bash
kubectl create secret generic pihole-password \
--from-literal EXTERNAL_DNS_PIHOLE_PASSWORD=supersecret
```
Replacing **"supersecret"** with the actual password to your Pi-hole server.
### ExternalDNS Manifest
Apply the following manifest to deploy ExternalDNS, editing values for your environment accordingly.
Be sure to change the namespace in the `ClusterRoleBinding` if you are using a namespace other than **default**.
```yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
# If authentication is disabled and/or you didn't create
# a secret, you can remove this block.
envFrom:
- secretRef:
# Change this if you gave the secret a different name
name: pihole-password
args:
- --source=service
- --source=ingress
# Pihole only supports A/CNAME records so there is no mechanism to track ownership.
# You don't need to set this flag, but if you leave it unset, you will receive warning
# logs when ExternalDNS attempts to create TXT records.
- --registry=noop
# IMPORTANT: If you have records that you manage manually in Pi-hole, set
# the policy to upsert-only so they do not get deleted.
- --policy=upsert-only
- --provider=pihole
# Change this to the actual address of your Pi-hole web server
- --pihole-server=http://pihole-web.pihole.svc.cluster.local
securityContext:
fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes token files
```
### Arguments
- `--pihole-server (env: EXTERNAL_DNS_PIHOLE_SERVER)` - The address of the Pi-hole web server
- `--pihole-password (env: EXTERNAL_DNS_PIHOLE_PASSWORD)` - The password to the Pi-hole web server (if enabled)
- `--pihole-tls-skip-verify (env: EXTERNAL_DNS_PIHOLE_TLS_SKIP_VERIFY)` - Skip verification of any TLS certificates served by the Pi-hole web server.
## Verify ExternalDNS Works
### Ingress Example
Create an Ingress resource. ExternalDNS will use the hostname specified in the Ingress object.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo
spec:
ingressClassName: nginx
rules:
- host: foo.bar.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: foo
port:
number: 80
```
### Service Example
The below sample application can be used to verify Services work.
For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
```yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.homelab.com
spec:
type: LoadBalancer
ports:
- port: 80
name: http
targetPort: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
```
You can then query your Pi-hole to see if the record was created.
_Change `@192.168.100.2` to the actual address of your DNS server_
```bash
$ dig +short @192.168.100.2 nginx.external-dns-test.homelab.com
192.168.100.129
```

197
docs/tutorials/plural.md Normal file
View File

@ -0,0 +1,197 @@
# Setting up ExternalDNS for Services on Plural
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Plural DNS.
Make sure to use **>=0.12.3** version of ExternalDNS for this tutorial.
## Creating Plural Credentials
A secret containing the a Plural access token is needed for this provider. You can get a token for your user [here](https://app.plural.sh/profile/tokens).
To create the secret you can run `kubectl create secret generic plural-env --from-literal=PLURAL_ACCESS_TOKEN=<replace-with-your-access-token>`.
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=plural
- --plural-cluster=example-plural-cluster
- --plural-provider=aws # gcp, azure, equinix and kind are also possible
env:
- name: PLURAL_ACCESS_TOKEN
valueFrom:
secretKeyRef:
key: PLURAL_ACCESS_TOKEN
name: plural-env
- name: PLURAL_ENDPOINT # (optional) use an alternative endpoint for Plural; defaults to https://app.plural.sh
value: https://app.plural.sh
```
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=plural
- --plural-cluster=example-plural-cluster
- --plural-provider=aws # gcp, azure, equinix and kind are also possible
env:
- name: PLURAL_ACCESS_TOKEN
valueFrom:
secretKeyRef:
key: PLURAL_ACCESS_TOKEN
name: plural-env
- name: PLURAL_ENDPOINT # (optional) use an alternative endpoint for Plural; defaults to https://app.plural.sh
value: https://app.plural.sh
```
## Deploying an Nginx Service
Create a service file called 'nginx.yaml' with the following contents:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: example.com
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
```
Note the annotation on the service; use the same hostname as the Plural DNS zone created above. The annotation may also be a subdomain
of the DNS zone (e.g. 'www.example.com').
By setting the TTL annotation on the service, you have to pass a valid TTL, which must be 120 or above.
This annotation is optional, if you won't set it, it will be 1 (automatic) which is 300.
ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation
will cause ExternalDNS to remove the corresponding DNS records.
Create the deployment and service:
```
$ kubectl create -f nginx.yaml
```
Depending where you run your service it can take a little while for your cloud provider to create an external IP for the service.
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize
the Plural DNS records.
## Verifying Plural DNS records
Check your [Plural domain overview](https://app.plural.sh/account/domains) to view the domains associated with your Plural account. There you can view the records for each domain.
The records should show the external IP address of the service as the A record for your domain.
## Cleanup
Now that we have verified that ExternalDNS will automatically manage Plural DNS records, we can delete the tutorial's example:
```
$ kubectl delete -f nginx.yaml
$ kubectl delete -f externaldns.yaml

View File

@ -245,7 +245,7 @@ spec:
# ... or, if you use annotations for ingress classes
# - --annotation-filter=kubernetes.io/ingress.class in (external-ingress)
- --aws-zone-type=public
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
name: external-dns-public
```
@ -285,7 +285,7 @@ spec:
# ... or, if you use annotations for ingress classes
# - --annotation-filter=kubernetes.io/ingress.class in (internal-ingress)
- --aws-zone-type=private
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
name: external-dns-private
```
@ -313,8 +313,11 @@ spec:
http:
paths:
- backend:
serviceName: app
servicePort: 80
service:
name: app
port:
number: 80
pathType: Prefix
```
Then create private Ingress definition (again, make sure to un-comment either the `annotations` or `ingressClassName` lines):
@ -337,8 +340,11 @@ spec:
http:
paths:
- backend:
serviceName: app
servicePort: 80
service:
name: app
port:
number: 80
pathType: Prefix
```
Additionally, you may leverage [cert-manager](https://github.com/jetstack/cert-manager) to automatically issue SSL certificates from [Let's Encrypt](https://letsencrypt.org/). To do that, request a certificate in public service definition:
@ -362,8 +368,11 @@ spec:
http:
paths:
- backend:
serviceName: app
servicePort: 80
service:
name: app
port:
number: 80
pathType: Prefix
tls:
- hosts:
- app.domain.com
@ -386,8 +395,11 @@ spec:
http:
paths:
- backend:
serviceName: app
servicePort: 80
service:
name: app
port:
number: 80
pathType: Prefix
tls:
- hosts:
- app.domain.com

View File

@ -53,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -120,7 +120,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -54,7 +54,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --provider=rdns
@ -123,7 +123,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=ingress
- --provider=rdns

View File

@ -218,7 +218,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --registry=txt
- --txt-prefix=external-dns-
@ -260,7 +260,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --registry=txt
- --txt-prefix=external-dns-

View File

@ -24,6 +24,7 @@ Note that you will also need to the Organization ID, which can be retrieve on th
Three environment variables are needed to run ExternalDNS with Scaleway DNS:
- `SCW_ACCESS_KEY` which is the Access Key.
- `SCW_SECRET_KEY` which is the Secret Key.
- `SCW_DEFAULT_ORGANIZATION_ID` which is the Default Organization ID.
## Deploy ExternalDNS
@ -52,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -62,6 +63,8 @@ spec:
value: "<your access key>"
- name: SCW_SECRET_KEY
value: "<your secret key>"
- name: SCW_DEFAULT_ORGANIZATION_ID
value: "<your default organization ID>"
```
### Manifest (for clusters with RBAC enabled)
@ -118,7 +121,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -128,6 +131,8 @@ spec:
value: "<your access key>"
- name: SCW_SECRET_KEY
value: "<your secret key>"
- name: SCW_DEFAULT_ORGANIZATION_ID
value: "<your default organization ID>"
```

View File

@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- ... # your arguments here
securityContext:

View File

@ -0,0 +1,214 @@
# Setting up ExternalDNS for Tencent Cloud
## External Dns Version
* Make sure to use **>=0.13.1** version of ExternalDNS for this tutorial
## Set up PrivateDns or DNSPod
Tencent Cloud DNSPod Service is the domain name resolution and management service for public access.
Tencent Cloud PrivateDNS Service is the domain name resolution and management service for VPC internal access.
* If you want to use internal dns service in Tencent Cloud.
1. Set up the args `--tencent-cloud-zone-type=private`
2. Create a DNS domain in PrivateDNS console. DNS domain which will contain the managed DNS records.
* If you want to use public dns service in Tencent Cloud.
1. Set up the args `--tencent-cloud-zone-type=public`
2. Create a Domain in DnsPod console. DNS domain which will contain the managed DNS records.
## Set up CAM for API Key
In Tencent CAM Console. you may get the secretId and secretKey pair. make sure the key pair has those Policy.
```json
{
"version": "2.0",
"statement": [
{
"effect": "allow",
"action": [
"dnspod:ModifyRecord",
"dnspod:DeleteRecord",
"dnspod:CreateRecord",
"dnspod:DescribeRecordList",
"dnspod:DescribeDomainList"
],
"resource": [
"*"
]
},
{
"effect": "allow",
"action": [
"privatedns:DescribePrivateZoneList",
"privatedns:DescribePrivateZoneRecordList",
"privatedns:CreatePrivateZoneRecord",
"privatedns:DeletePrivateZoneRecord",
"privatedns:ModifyPrivateZoneRecord"
],
"resource": [
"*"
]
}
]
}
```
# Deploy ExternalDNS
## Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: v1
kind: ConfigMap
metadata:
name: external-dns
data:
tencent-cloud.json: |
{
"regionId": "ap-shanghai",
"secretId": "******",
"secretKey": "******",
"vpcId": "vpc-******",
"internetEndpoint": false # Default: false. Access the Tencent API through the intranet. If you need to deploy on the public network, you need to change to true
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- args:
- --source=service
- --source=ingress
- --domain-filter=external-dns-test.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=tencentcloud
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records
- --tencent-cloud-zone-type=private # only look at private hosted zones. set `public` to use the public dns service.
- --tencent-cloud-config-file=/etc/kubernetes/tencent-cloud.json
image: registry.k8s.io/external-dns/external-dns:v0.13.4
imagePullPolicy: Always
name: external-dns
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/kubernetes
name: config-volume
readOnly: true
dnsPolicy: ClusterFirst
hostAliases:
- hostnames:
- privatedns.internal.tencentcloudapi.com
- dnspod.internal.tencentcloudapi.com
ip: 169.254.0.95
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: external-dns
serviceAccountName: external-dns
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
items:
- key: tencent-cloud.json
path: tencent-cloud.json
name: external-dns
name: config-volume
```
# Example
## Service
```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.com
external-dns.alpha.kubernetes.io/internal-hostname: nginx-internal.external-dns-test.com
external-dns.alpha.kubernetes.io/ttl: "600"
spec:
type: LoadBalancer
ports:
- port: 80
name: http
targetPort: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
```
`nginx.external-dns-test.com` will record to the Loadbalancer VIP.
`nginx-internal.external-dns-test.com` will record to the ClusterIP.
all of the DNS Record ttl will be 600.
# Attention
This makes ExternalDNS safe for running in environments where there are other records managed via other means.

View File

@ -36,7 +36,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress # ingress is also possible
@ -116,7 +116,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service
- --source=ingress
@ -251,6 +251,7 @@ spec:
- http:
paths:
- path: /apple
pathType: Prefix
backend:
service:
name: example-service

View File

@ -66,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --provider=vinyldns
- --source=service
@ -137,7 +137,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --provider=vinyldns
- --source=service

View File

@ -42,7 +42,7 @@ spec:
spec:
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -106,7 +106,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
image: registry.k8s.io/external-dns/external-dns:v0.13.4
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -73,9 +73,11 @@ type DomainFilter struct {
// prepareFilters provides consistent trimming for filters/exclude params
func prepareFilters(filters []string) []string {
fs := make([]string, len(filters))
for i, domain := range filters {
fs[i] = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(domain), "."))
var fs []string
for _, filter := range filters {
if domain := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(filter), ".")); domain != "" {
fs = append(fs, domain)
}
}
return fs
}
@ -98,7 +100,7 @@ func NewRegexDomainFilter(regexDomainFilter *regexp.Regexp, regexDomainExclusion
// Match checks whether a domain can be found in the DomainFilter.
// RegexFilter takes precedence over Filters
func (df DomainFilter) Match(domain string) bool {
if df.regex != nil && df.regex.String() != "" {
if df.regex != nil && df.regex.String() != "" || df.regexExclusion != nil && df.regexExclusion.String() != "" {
return matchRegex(df.regex, df.regexExclusion, domain)
}
@ -113,12 +115,13 @@ func matchFilter(filters []string, domain string, emptyval bool) bool {
return emptyval
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
for _, filter := range filters {
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
if filter == "" {
return emptyval
} else if strings.HasPrefix(filter, ".") && strings.HasSuffix(strippedDomain, filter) {
continue
}
if strings.HasPrefix(filter, ".") && strings.HasSuffix(strippedDomain, filter) {
return true
} else if strings.Count(strippedDomain, ".") == strings.Count(filter, ".") {
if strippedDomain == filter {
@ -146,35 +149,32 @@ func matchRegex(regex *regexp.Regexp, negativeRegex *regexp.Regexp, domain strin
// MatchParent checks wether DomainFilter matches a given parent domain.
func (df DomainFilter) MatchParent(domain string) bool {
if !df.IsConfigured() {
if matchFilter(df.exclude, domain, false) {
return false
}
if len(df.Filters) == 0 {
return true
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
for _, filter := range df.Filters {
if strings.HasPrefix(filter, ".") {
if filter == "" || strings.HasPrefix(filter, ".") {
// We don't check parents if the filter is prefixed with "."
continue
}
if filter == "" {
return true
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
if strings.HasSuffix(filter, "."+strippedDomain) && !matchFilter(df.exclude, domain, false) {
if strings.HasSuffix(filter, "."+strippedDomain) {
return true
}
}
return false
}
// IsConfigured returns true if DomainFilter is configured, false otherwise
// IsConfigured returns true if any inclusion or exclusion rules have been specified.
func (df DomainFilter) IsConfigured() bool {
if df.regex != nil && df.regex.String() != "" {
return true
} else if len(df.Filters) == 1 {
return df.Filters[0] != ""
} else if df.regexExclusion != nil && df.regexExclusion.String() != "" {
return true
}
return len(df.Filters) > 0
return len(df.Filters) > 0 || len(df.exclude) > 0
}

View File

@ -379,15 +379,15 @@ func TestPrepareFiltersStripsWhitespaceAndDotSuffix(t *testing.T) {
}{
{
[]string{},
[]string{},
nil,
},
{
[]string{""},
[]string{""},
nil,
},
{
[]string{" ", " ", ""},
[]string{"", "", ""},
nil,
},
{
[]string{" foo ", " bar. ", "baz."},
@ -429,7 +429,7 @@ func TestDomainFilterIsConfigured(t *testing.T) {
{
[]string{"", ""},
[]string{""},
true,
false,
},
{
[]string{" . "},
@ -446,6 +446,11 @@ func TestDomainFilterIsConfigured(t *testing.T) {
[]string{" thisdoesntmatter.com "},
true,
},
{
[]string{""},
[]string{" thisdoesntmatter.com "},
true,
},
} {
t.Run("test IsConfigured", func(t *testing.T) {
df := NewDomainFilterWithExclusions(tt.filters, tt.exclude)

Some files were not shown because too many files have changed in this diff Show More