Skip to content

Commit

Permalink
Merge 2c9210b into 432d069
Browse files Browse the repository at this point in the history
  • Loading branch information
spohner committed Jan 27, 2020
2 parents 432d069 + 2c9210b commit b07c7b0
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 93 deletions.
148 changes: 71 additions & 77 deletions docs/tutorials/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,13 @@ Hosted Zone IDs.
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": ["arn:aws:route53:::hostedzone/*"]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": [
"*"
]
"Action": ["route53:ListHostedZones", "route53:ListResourceRecordSets"],
"Resource": ["*"]
}
]
}
Expand Down Expand Up @@ -85,7 +76,7 @@ instance metadata service (169.254.169.254). This is allowed by default.

## Set up a hosted zone

*If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step*
_If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step_

Create a DNS zone which will contain the managed DNS records.

Expand Down Expand Up @@ -119,6 +110,7 @@ Then apply one of the following manifests file to deploy ExternalDNS. You can ch
For clusters with RBAC enabled, be sure to choose the correct `namespace`.

### Manifest (for clusters without RBAC enabled)

```yaml
apiVersion: apps/v1
kind: Deployment
Expand All @@ -140,17 +132,17 @@ spec:
iam.amazonaws.com/role: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME
spec:
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --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
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-hostedzone-identifier
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --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
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-hostedzone-identifier
```

### Manifest (for clusters with RBAC enabled)
Expand All @@ -171,18 +163,18 @@ kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
Expand All @@ -193,9 +185,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
Expand All @@ -218,17 +210,17 @@ spec:
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --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
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-hostedzone-identifier
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --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
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-hostedzone-identifier
securityContext:
fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files
```
Expand All @@ -241,6 +233,10 @@ This list is not the full list, but a few arguments that where chosen.

`aws-zone-type` allows filtering for private and public zones

### subdomainFilter

`subdomainFilter` allows only domains matched by the subdomainfilter to be created.

## Annotations

Annotations which are specific to AWS.
Expand All @@ -264,12 +260,12 @@ metadata:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
rules:
- host: foo.bar.com
http:
paths:
- backend:
serviceName: foo
servicePort: 80
- host: foo.bar.com
http:
paths:
- backend:
serviceName: foo
servicePort: 80
```

## Verify ExternalDNS works (Service example)
Expand All @@ -290,14 +286,13 @@ metadata:
spec:
type: LoadBalancer
ports:
- port: 80
name: http
targetPort: 80
- port: 80
name: http
targetPort: 80
selector:
app: nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
Expand All @@ -312,11 +307,11 @@ spec:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
```

After roughly two minutes check that a corresponding DNS record for your service was created.
Expand Down Expand Up @@ -387,8 +382,7 @@ metadata:
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
external-dns.alpha.kubernetes.io/ttl: 60
spec:
...
spec: ...
```

This will set the DNS record's TTL to 60 seconds.
Expand All @@ -397,18 +391,18 @@ This will set the DNS record's TTL to 60 seconds.

Route53 offers [different routing policies](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html). The routing policy for a record can be controlled with the following annotations:

* `external-dns.alpha.kubernetes.io/set-identifier`: this **needs** to be set to use any of the following routing policies
- `external-dns.alpha.kubernetes.io/set-identifier`: this **needs** to be set to use any of the following routing policies

For any given DNS name, only **one** of the following routing policies can be used:

* Weighted records: `external-dns.alpha.kubernetes.io/aws-weight`
* Latency-based routing: `external-dns.alpha.kubernetes.io/aws-region`
* Failover:`external-dns.alpha.kubernetes.io/aws-failover`
* Geolocation-based routing:
* `external-dns.alpha.kubernetes.io/aws-geolocation-continent-code`
* `external-dns.alpha.kubernetes.io/aws-geolocation-country-code`
* `external-dns.alpha.kubernetes.io/aws-geolocation-subdivision-code`
* Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer`
- Weighted records: `external-dns.alpha.kubernetes.io/aws-weight`
- Latency-based routing: `external-dns.alpha.kubernetes.io/aws-region`
- Failover:`external-dns.alpha.kubernetes.io/aws-failover`
- Geolocation-based routing:
- `external-dns.alpha.kubernetes.io/aws-geolocation-continent-code`
- `external-dns.alpha.kubernetes.io/aws-geolocation-country-code`
- `external-dns.alpha.kubernetes.io/aws-geolocation-subdivision-code`
- Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer`

## Clean up

Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func main() {
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
subdomainFilter := provider.NewDomainFilter(cfg.AWSSubdomainFilter)

var p provider.Provider
switch cfg.Provider {
Expand All @@ -132,6 +133,7 @@ func main() {
ZoneIDFilter: zoneIDFilter,
ZoneTypeFilter: zoneTypeFilter,
ZoneTagFilter: zoneTagFilter,
SubdomainFilter: subdomainFilter,
BatchChangeSize: cfg.AWSBatchChangeSize,
BatchChangeInterval: cfg.AWSBatchChangeInterval,
EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Config struct {
AlibabaCloudZoneType string
AWSZoneType string
AWSZoneTagFilter []string
AWSSubdomainFilter []string
AWSAssumeRole string
AWSBatchChangeSize int
AWSBatchChangeInterval time.Duration
Expand Down Expand Up @@ -160,6 +161,7 @@ var defaultConfig = &Config{
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{},
AWSSubdomainFilter: []string{},
AWSAssumeRole: "",
AWSBatchChangeSize: 1000,
AWSBatchChangeInterval: time.Second,
Expand Down Expand Up @@ -300,6 +302,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
app.Flag("subdomain-filter", "Allow only changes to specific subdomain").Default("").StringsVar(&cfg.AWSSubdomainFilter)
app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize)
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var (
GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{""},
ExcludeDomains: []string{""},
AWSSubdomainFilter: []string{""},
ZoneIDFilter: []string{""},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
Expand Down Expand Up @@ -112,6 +113,7 @@ var (
GoogleBatchChangeSize: 100,
GoogleBatchChangeInterval: time.Second * 2,
DomainFilter: []string{"example.org", "company.com"},
AWSSubdomainFilter: []string{"foo.example.org"},
ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"},
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
Expand Down Expand Up @@ -241,6 +243,7 @@ func TestParseFlags(t *testing.T) {
"--exclude-domains=xapi.company.com",
"--zone-id-filter=/hostedzone/ZTST1",
"--zone-id-filter=/hostedzone/ZTST2",
"--subdomain-filter=foo.example.org",
"--aws-zone-type=private",
"--aws-zone-tags=tag=foo",
"--aws-assume-role=some-other-role",
Expand Down Expand Up @@ -314,6 +317,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_INMEMORY_ZONE": "example.org\ncompany.com",
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
"EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.com",
"EXTERNAL_DNS_SUBDOMAIN_FILTER": "foo.example.org",
"EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081",
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
"EXTERNAL_DNS_PDNS_TLS_ENABLED": "1",
Expand Down
22 changes: 20 additions & 2 deletions provider/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ type AWSProvider struct {
zoneTypeFilter ZoneTypeFilter
// filter hosted zones by tags
zoneTagFilter ZoneTagFilter
preferCNAME bool
// only allow changes to specified subdomain and its subdomains
subdomainFilter DomainFilter
preferCNAME bool
}

// AWSConfig contains configuration to create a new AWS provider.
Expand All @@ -133,6 +135,7 @@ type AWSConfig struct {
ZoneIDFilter ZoneIDFilter
ZoneTypeFilter ZoneTypeFilter
ZoneTagFilter ZoneTagFilter
SubdomainFilter DomainFilter
BatchChangeSize int
BatchChangeInterval time.Duration
EvaluateTargetHealth bool
Expand Down Expand Up @@ -174,6 +177,7 @@ func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) {
zoneIDFilter: awsConfig.ZoneIDFilter,
zoneTypeFilter: awsConfig.ZoneTypeFilter,
zoneTagFilter: awsConfig.ZoneTagFilter,
subdomainFilter: awsConfig.SubdomainFilter,
batchChangeSize: awsConfig.BatchChangeSize,
batchChangeInterval: awsConfig.BatchChangeInterval,
evaluateTargetHealth: awsConfig.EvaluateTargetHealth,
Expand Down Expand Up @@ -401,8 +405,10 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan
return nil
}

filteredChangesBySubdomains := filteredChangesBySubdomains(changes, p)

// separate into per-zone change sets to be passed to the API.
changesByZone := changesByZone(zones, changes)
changesByZone := changesByZone(zones, filteredChangesBySubdomains)
if len(changesByZone) == 0 {
log.Info("All records are already up to date, there are no changes for the matching hosted zones")
}
Expand Down Expand Up @@ -651,6 +657,18 @@ func sortChangesByActionNameType(cs []*route53.Change) []*route53.Change {
return cs
}

func filteredChangesBySubdomains(changeSet []*route53.Change, p *AWSProvider) []*route53.Change {
changes := []*route53.Change{}

for _, c := range changeSet {
hostname := aws.StringValue(c.ResourceRecordSet.Name)
if p.subdomainFilter.Match(hostname) {
changes = append(changes, c)
}
}
return changes
}

// changesByZone separates a multi-zone change into a single change per zone.
func changesByZone(zones map[string]*route53.HostedZone, changeSet []*route53.Change) map[string][]*route53.Change {
changes := make(map[string][]*route53.Change)
Expand Down

0 comments on commit b07c7b0

Please sign in to comment.