Rework protokube dns so it shares code with dns-controller

This commit is contained in:
Justin Santa Barbara 2016-10-19 00:03:47 -04:00
parent fbbfa98872
commit 22a963d5af
8 changed files with 134 additions and 282 deletions

View File

@ -32,6 +32,8 @@ import (
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
) )
const DefaultTTL = time.Minute
// DNSController applies the desired DNS state to the DNS backend // DNSController applies the desired DNS state to the DNS backend
type DNSController struct { type DNSController struct {
zoneRules *ZoneRules zoneRules *ZoneRules
@ -252,10 +254,10 @@ func (c *DNSController) runOnce() error {
continue continue
} }
ttl := 60 ttl := DefaultTTL
glog.Infof("Using default TTL of %d seconds", ttl) glog.Infof("Using default TTL of %v", ttl)
err := op.updateRecords(k, newValues, int64(ttl)) err := op.updateRecords(k, newValues, int64(ttl.Seconds()))
if err != nil { if err != nil {
glog.Infof("error updating records for %s: %v", k, err) glog.Infof("error updating records for %s: %v", k, err)
errors = append(errors, err) errors = append(errors, err)
@ -362,10 +364,12 @@ func (o *dnsOp) findZone(fqdn string) dnsprovider.Zone {
func (o *dnsOp) deleteRecords(k recordKey) error { func (o *dnsOp) deleteRecords(k recordKey) error {
glog.V(2).Infof("Deleting all records for %s", k) glog.V(2).Infof("Deleting all records for %s", k)
zone := o.findZone(k.FQDN) fqdn := EnsureDotSuffix(k.FQDN)
zone := o.findZone(fqdn)
if zone == nil { if zone == nil {
// TODO: Post event into service / pod // TODO: Post event into service / pod
return fmt.Errorf("no suitable zone found for %q", k.FQDN) return fmt.Errorf("no suitable zone found for %q", fqdn)
} }
rrsProvider, ok := zone.ResourceRecordSets() rrsProvider, ok := zone.ResourceRecordSets()
@ -383,8 +387,8 @@ func (o *dnsOp) deleteRecords(k recordKey) error {
empty := true empty := true
for _, rr := range rrs { for _, rr := range rrs {
rrName := EnsureDotSuffix(rr.Name()) rrName := EnsureDotSuffix(rr.Name())
if rrName != k.FQDN { if rrName != fqdn {
glog.V(8).Infof("Skipping delete of record %q (name != %s)", rrName, k.FQDN) glog.V(8).Infof("Skipping delete of record %q (name != %s)", rrName, fqdn)
continue continue
} }
if string(rr.Type()) != string(k.RecordType) { if string(rr.Type()) != string(k.RecordType) {
@ -411,10 +415,12 @@ func (o *dnsOp) deleteRecords(k recordKey) error {
func (o *dnsOp) updateRecords(k recordKey, newRecords []string, ttl int64) error { func (o *dnsOp) updateRecords(k recordKey, newRecords []string, ttl int64) error {
glog.V(2).Infof("Updating records for %s: %v", k, newRecords) glog.V(2).Infof("Updating records for %s: %v", k, newRecords)
zone := o.findZone(k.FQDN) fqdn := EnsureDotSuffix(k.FQDN)
zone := o.findZone(fqdn)
if zone == nil { if zone == nil {
// TODO: Post event into service / pod // TODO: Post event into service / pod
return fmt.Errorf("no suitable zone found for %q", k.FQDN) return fmt.Errorf("no suitable zone found for %q", fqdn)
} }
rrsProvider, ok := zone.ResourceRecordSets() rrsProvider, ok := zone.ResourceRecordSets()
@ -429,12 +435,13 @@ func (o *dnsOp) updateRecords(k recordKey, newRecords []string, ttl int64) error
var existing dnsprovider.ResourceRecordSet var existing dnsprovider.ResourceRecordSet
for _, rr := range rrs { for _, rr := range rrs {
if rr.Name() != k.FQDN { rrName := EnsureDotSuffix(rr.Name())
glog.V(8).Infof("Skipping record %q (name != %s)", rr.Name(), k.FQDN) if rrName != fqdn {
glog.V(8).Infof("Skipping record %q (name != %s)", rrName, fqdn)
continue continue
} }
if string(rr.Type()) != string(k.RecordType) { if string(rr.Type()) != string(k.RecordType) {
glog.V(8).Infof("Skipping record %q (type %s != %s)", rr.Name(), rr.Type(), k.RecordType) glog.V(8).Infof("Skipping record %q (type %s != %s)", rrName, rr.Type(), k.RecordType)
continue continue
} }
@ -451,11 +458,11 @@ func (o *dnsOp) updateRecords(k recordKey, newRecords []string, ttl int64) error
} }
glog.V(2).Infof("Updating resource record %s %s", k, newRecords) glog.V(2).Infof("Updating resource record %s %s", k, newRecords)
rr := rrsProvider.New(k.FQDN, newRecords, ttl, rrstype.RrsType(k.RecordType)) rr := rrsProvider.New(fqdn, newRecords, ttl, rrstype.RrsType(k.RecordType))
cs.Add(rr) cs.Add(rr)
if err := cs.Apply(); err != nil { if err := cs.Apply(); err != nil {
return fmt.Errorf("error updating resource record %s %s: %v", k.FQDN, rr.Type(), err) return fmt.Errorf("error updating resource record %s %s: %v", fqdn, rr.Type(), err)
} }
return nil return nil

View File

@ -20,22 +20,50 @@ import (
"flag" "flag"
"fmt" "fmt"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/kops/dns-controller/pkg/dns"
"k8s.io/kops/protokube/pkg/protokube" "k8s.io/kops/protokube/pkg/protokube"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"net" "net"
"os" "os"
"strings" "strings"
// Load DNS plugins
_ "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53"
_ "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns"
)
var (
flags = pflag.NewFlagSet("", pflag.ExitOnError)
// value overwritten during build. This can be used to resolve issues.
BuildVersion = "0.1"
) )
func main() { func main() {
fmt.Printf("protokube version %s\n", BuildVersion)
err := run()
if err != nil {
glog.Errorf("Error: %v", err)
os.Exit(1)
}
os.Exit(0)
}
func run() error {
dnsProviderId := "aws-route53"
flags.StringVar(&dnsProviderId, "dns", dnsProviderId, "DNS provider we should use (aws-route53, google-clouddns)")
var zones []string
flags.StringSliceVarP(&zones, "zone", "z", []string{}, "Configure permitted zones and their mappings")
master := false master := false
flag.BoolVar(&master, "master", master, "Act as master") flag.BoolVar(&master, "master", master, "Act as master")
containerized := false containerized := false
flag.BoolVar(&containerized, "containerized", containerized, "Set if we are running containerized.") flag.BoolVar(&containerized, "containerized", containerized, "Set if we are running containerized.")
dnsZoneName := ""
flag.StringVar(&dnsZoneName, "dns-zone-name", dnsZoneName, "Name of zone to use for DNS")
dnsInternalSuffix := "" dnsInternalSuffix := ""
flag.StringVar(&dnsInternalSuffix, "dns-internal-suffix", dnsInternalSuffix, "DNS suffix for internal domain names") flag.StringVar(&dnsInternalSuffix, "dns-internal-suffix", dnsInternalSuffix, "DNS suffix for internal domain names")
@ -45,20 +73,24 @@ func main() {
flagChannels := "" flagChannels := ""
flag.StringVar(&flagChannels, "channels", flagChannels, "channels to install") flag.StringVar(&flagChannels, "channels", flagChannels, "channels to install")
// Trick to avoid 'logging before flag.Parse' warning
flag.CommandLine.Parse([]string{})
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")
flag.Parse()
flags.AddGoFlagSet(flag.CommandLine)
flags.Parse(os.Args)
volumes, err := protokube.NewAWSVolumes() volumes, err := protokube.NewAWSVolumes()
if err != nil { if err != nil {
glog.Errorf("Error initializing AWS: %q", err) return fmt.Errorf("Error initializing AWS: %q", err)
os.Exit(1)
} }
if clusterID == "" { if clusterID == "" {
clusterID = volumes.ClusterID() clusterID = volumes.ClusterID()
if clusterID == "" { if clusterID == "" {
glog.Errorf("cluster-id is required (cannot be determined from cloud)") return fmt.Errorf("cluster-id is required (cannot be determined from cloud)")
os.Exit(1)
} else { } else {
glog.Infof("Setting cluster-id from cloud: %s", clusterID) glog.Infof("Setting cluster-id from cloud: %s", clusterID)
} }
@ -75,11 +107,6 @@ func main() {
dnsInternalSuffix = "." + dnsInternalSuffix dnsInternalSuffix = "." + dnsInternalSuffix
} }
if dnsZoneName == "" {
tokens := strings.Split(dnsInternalSuffix, ".")
dnsZoneName = strings.Join(tokens[len(tokens)-2:], ".")
}
// Get internal IP from cloud, to avoid problems if we're in a container // Get internal IP from cloud, to avoid problems if we're in a container
// TODO: Just run with --net=host ?? // TODO: Just run with --net=host ??
//internalIP, err := findInternalIP() //internalIP, err := findInternalIP()
@ -89,10 +116,34 @@ func main() {
//} //}
internalIP := volumes.InternalIP() internalIP := volumes.InternalIP()
dns, err := protokube.NewRoute53DNSProvider(dnsZoneName) var dnsScope dns.Scope
if err != nil { var dnsController *dns.DNSController
glog.Errorf("Error initializing DNS: %q", err) {
os.Exit(1) dnsProvider, err := dnsprovider.GetDnsProvider(dnsProviderId, nil)
if err != nil {
return fmt.Errorf("Error initializing DNS provider %q: %v", dnsProviderId, err)
}
if dnsProvider == nil {
return fmt.Errorf("DNS provider %q could not be initialized", dnsProviderId)
}
zoneRules, err := dns.ParseZoneRules(zones)
if err != nil {
return fmt.Errorf("unexpected zone flags: %q", err)
}
dnsController, err = dns.NewDNSController(dnsProvider, zoneRules)
if err != nil {
return err
}
dnsScope, err = dnsController.CreateScope("protokube")
if err != nil {
return err
}
// We don't really use readiness - our records are simple
dnsScope.MarkReady()
} }
rootfs := "/" rootfs := "/"
@ -117,7 +168,7 @@ func main() {
//EtcdClusters : fromVolume //EtcdClusters : fromVolume
ModelDir: modelDir, ModelDir: modelDir,
DNS: dns, DNSScope: dnsScope,
Channels: channels, Channels: channels,
@ -125,10 +176,11 @@ func main() {
} }
k.Init(volumes) k.Init(volumes)
go dnsController.Run()
k.RunSyncLoop() k.RunSyncLoop()
glog.Infof("Unexpected exit") return fmt.Errorf("Unexpected exit")
os.Exit(1)
} }
// TODO: run with --net=host ?? // TODO: run with --net=host ??

View File

@ -1,230 +0,0 @@
/*
Copyright 2016 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 protokube
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/golang/glog"
"reflect"
"strings"
"time"
)
type Route53DNSProvider struct {
client *route53.Route53
zoneName string
zone *route53.HostedZone
}
func NewRoute53DNSProvider(zoneName string) (*Route53DNSProvider, error) {
if zoneName == "" {
return nil, fmt.Errorf("zone name is required")
}
p := &Route53DNSProvider{
zoneName: zoneName,
}
s := session.New()
s.Handlers.Send.PushFront(func(r *request.Request) {
// Log requests
glog.V(4).Infof("AWS API Request: %s/%s", r.ClientInfo.ServiceName, r.Operation.Name)
})
config := aws.NewConfig()
config = config.WithCredentialsChainVerboseErrors(true)
p.client = route53.New(s, config)
return p, nil
}
func (p *Route53DNSProvider) getZone() (*route53.HostedZone, error) {
if p.zone != nil {
return p.zone, nil
}
if !strings.Contains(p.zoneName, ".") {
// Looks like a zone ID
zoneID := p.zoneName
glog.Infof("Querying for hosted zone by id: %q", zoneID)
request := &route53.GetHostedZoneInput{
Id: aws.String(zoneID),
}
response, err := p.client.GetHostedZone(request)
if err != nil {
if AWSErrorCode(err) == "NoSuchHostedZone" {
glog.Infof("Zone not found with id %q; will reattempt by name", zoneID)
} else {
return nil, fmt.Errorf("error querying for DNS HostedZones %q: %v", zoneID, err)
}
} else {
p.zone = response.HostedZone
return p.zone, nil
}
}
glog.Infof("Querying for hosted zone by name: %q", p.zoneName)
findZone := p.zoneName
if !strings.HasSuffix(findZone, ".") {
findZone += "."
}
request := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(findZone),
}
response, err := p.client.ListHostedZonesByName(request)
if err != nil {
return nil, fmt.Errorf("error querying for DNS HostedZones %q: %v", findZone, err)
}
var zones []*route53.HostedZone
for _, zone := range response.HostedZones {
if aws.StringValue(zone.Name) == findZone {
zones = append(zones, zone)
}
}
if len(zones) == 0 {
return nil, nil
}
if len(zones) != 1 {
return nil, fmt.Errorf("found multiple hosted zones matched name %q", findZone)
}
p.zone = zones[0]
return p.zone, nil
}
func (p *Route53DNSProvider) findResourceRecord(hostedZoneID string, name string, resourceType string) (*route53.ResourceRecordSet, error) {
name = strings.TrimSuffix(name, ".")
request := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String(hostedZoneID),
// TODO: Start at correct name?
}
var found *route53.ResourceRecordSet
err := p.client.ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
for _, rr := range p.ResourceRecordSets {
if aws.StringValue(rr.Type) != resourceType {
continue
}
rrName := aws.StringValue(rr.Name)
rrName = strings.TrimSuffix(rrName, ".")
if name == rrName {
found = rr
break
}
}
// TODO: Also exit if we are on the 'next' name?
return found == nil
})
if err != nil {
return nil, fmt.Errorf("error listing DNS ResourceRecords: %v", err)
}
if found == nil {
return nil, nil
}
return found, nil
}
func (p *Route53DNSProvider) Set(fqdn string, recordType string, value string, ttl time.Duration) error {
zone, err := p.getZone()
if err != nil {
return err
}
// More correct, and makes the simple comparisons later on work correctly
if !strings.HasSuffix(fqdn, ".") {
fqdn += "."
}
existing, err := p.findResourceRecord(aws.StringValue(zone.Id), fqdn, recordType)
if err != nil {
return err
}
rrs := &route53.ResourceRecordSet{
Name: aws.String(fqdn),
Type: aws.String(recordType),
TTL: aws.Int64(int64(ttl.Seconds())),
ResourceRecords: []*route53.ResourceRecord{
{Value: aws.String(value)},
},
}
if existing != nil {
if reflect.DeepEqual(rrs, existing) {
glog.V(2).Infof("DNS %q %s record already set to %q", fqdn, recordType, value)
return nil
} else {
glog.Infof("ResourceRecordSet change:")
glog.Infof("Existing: %v", DebugString(existing))
glog.Infof("Desired: %v", DebugString(rrs))
}
}
change := &route53.Change{
Action: aws.String("UPSERT"),
ResourceRecordSet: rrs,
}
changeBatch := &route53.ChangeBatch{}
changeBatch.Changes = []*route53.Change{change}
request := &route53.ChangeResourceRecordSetsInput{}
request.HostedZoneId = zone.Id
request.ChangeBatch = changeBatch
glog.V(2).Infof("Updating DNS record %q", fqdn)
glog.V(4).Infof("route53 request: %s", DebugString(request))
response, err := p.client.ChangeResourceRecordSets(request)
if err != nil {
return fmt.Errorf("error creating ResourceRecordSets: %v", err)
}
glog.V(2).Infof("Change id is %q", aws.StringValue(response.ChangeInfo.Id))
return nil
}
// AWSErrorCode returns the aws error code, if it is an awserr.Error, otherwise ""
func AWSErrorCode(err error) string {
if awsError, ok := err.(awserr.Error); ok {
return awsError.Code()
}
return ""
}

View File

@ -18,11 +18,11 @@ package protokube
import ( import (
"fmt" "fmt"
"github.com/golang/glog"
"k8s.io/kops/dns-controller/pkg/dns"
"net" "net"
"os/exec" "os/exec"
"time" "time"
"github.com/golang/glog"
) )
type KubeBoot struct { type KubeBoot struct {
@ -35,7 +35,7 @@ type KubeBoot struct {
volumeMounter *VolumeMountController volumeMounter *VolumeMountController
etcdControllers map[string]*EtcdController etcdControllers map[string]*EtcdController
DNS DNSProvider DNSScope dns.Scope
ModelDir string ModelDir string

View File

@ -17,22 +17,28 @@ limitations under the License.
package protokube package protokube
import ( import (
"fmt" "github.com/golang/glog"
"k8s.io/kops/dns-controller/pkg/dns"
"time" "time"
) )
const defaultTTL = time.Minute const defaultTTL = time.Minute
type DNSProvider interface {
Set(fqdn string, recordType string, value string, ttl time.Duration) error
}
// CreateInternalDNSNameRecord maps a FQDN to the internal IP address of the current machine // CreateInternalDNSNameRecord maps a FQDN to the internal IP address of the current machine
func (k *KubeBoot) CreateInternalDNSNameRecord(fqdn string) error { func (k *KubeBoot) CreateInternalDNSNameRecord(fqdn string) error {
err := k.DNS.Set(fqdn, "A", k.InternalIP.String(), defaultTTL) ttl := defaultTTL
if err != nil { if ttl != dns.DefaultTTL {
return fmt.Errorf("error configuring DNS name %q: %v", fqdn, err) glog.Infof("Ignoring ttl %v for %q", ttl, fqdn)
} }
var records []dns.Record
records = append(records, dns.Record{
RecordType: dns.RecordTypeA,
FQDN: fqdn,
Value: k.InternalIP.String(),
})
k.DNSScope.Replace(fqdn, records)
return nil return nil
} }

View File

@ -211,7 +211,9 @@ func (tf *TemplateFunctions) DnsControllerArgv() ([]string, error) {
} }
// permit wildcard updates // permit wildcard updates
argv = append(argv, "--zone=*/*") argv = append(argv, "--zone=*/*")
argv = append(argv, "-v=8")
// Verbose, but not crazy logging
argv = append(argv, "-v=2")
return argv, nil return argv, nil
} }

View File

@ -17,10 +17,13 @@ limitations under the License.
package nodeup package nodeup
type ProtokubeFlags struct { type ProtokubeFlags struct {
DNSZoneName *string `json:"dnsZoneName,omitempty" flag:"dns-zone-name"` Master *bool `json:"master,omitempty" flag:"master"`
Master *bool `json:"master,omitempty" flag:"master"` Containerized *bool `json:"containerized,omitempty" flag:"containerized"`
Containerized *bool `json:"containerized,omitempty" flag:"containerized"` LogLevel *int `json:"logLevel,omitempty" flag:"v"`
LogLevel *int `json:"logLevel,omitempty" flag:"v"`
DNSProvider *string `json:"dnsProvider,omitempty" flag:"dns"`
Zone []string `json:"zone,omitempty" flag:"zone"`
Channels []string `json:"channels,omitempty" flag:"channels"` Channels []string `json:"channels,omitempty" flag:"channels"`
} }

View File

@ -279,8 +279,20 @@ func (t *templateFunctions) ProtokubeFlags() *ProtokubeFlags {
f.LogLevel = fi.Int(8) f.LogLevel = fi.Int(8)
f.Containerized = fi.Bool(true) f.Containerized = fi.Bool(true)
if t.cluster.Spec.DNSZone != "" {
f.DNSZoneName = fi.String(t.cluster.Spec.DNSZone) zone := t.cluster.Spec.DNSZone
if zone != "" {
if strings.Contains(zone, ".") {
// match by name
f.Zone = append(f.Zone, zone)
} else {
// match by id
f.Zone = append(f.Zone, "*/"+zone)
}
} else {
glog.Warningf("DNSZone not specified; protokube won't be able to update DNS")
// TODO: Should we permit wildcard updates if zone is not specified?
//argv = append(argv, "--zone=*/*")
} }
return f return f