Commit 7d0adbcb authored by Abhishek Shah's avatar Abhishek Shah

SRV record support

parent 03ece333
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.PHONY: all kube2sky container push clean test .PHONY: all kube2sky container push clean test
TAG = 1.8 TAG = 1.9
PREFIX = gcr.io/google_containers PREFIX = gcr.io/google_containers
all: container all: container
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"hash/fnv"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
...@@ -56,7 +57,7 @@ const ( ...@@ -56,7 +57,7 @@ const (
// Maximum number of attempts to connect to etcd server. // Maximum number of attempts to connect to etcd server.
maxConnectAttempts = 12 maxConnectAttempts = 12
// Resync period for the kube controller loop. // Resync period for the kube controller loop.
resyncPeriod = 5 * time.Second resyncPeriod = 30 * time.Minute
// A subdomain added to the user specified domain for all services. // A subdomain added to the user specified domain for all services.
serviceSubdomain = "svc" serviceSubdomain = "svc"
) )
...@@ -90,7 +91,7 @@ type kube2sky struct { ...@@ -90,7 +91,7 @@ type kube2sky struct {
// Removes 'subdomain' from etcd. // Removes 'subdomain' from etcd.
func (ks *kube2sky) removeDNS(subdomain string) error { func (ks *kube2sky) removeDNS(subdomain string) error {
glog.V(2).Infof("Removing %s from DNS", subdomain) glog.V(2).Infof("Removing %s from DNS", subdomain)
resp, err := ks.etcdClient.RawGet(skymsg.Path(subdomain), false, false) resp, err := ks.etcdClient.RawGet(skymsg.Path(subdomain), false, true)
if err != nil { if err != nil {
return err return err
} }
...@@ -109,7 +110,7 @@ func (ks *kube2sky) writeSkyRecord(subdomain string, data string) error { ...@@ -109,7 +110,7 @@ func (ks *kube2sky) writeSkyRecord(subdomain string, data string) error {
} }
// Generates skydns records for a headless service. // Generates skydns records for a headless service.
func (ks *kube2sky) newHeadlessService(subdomain string, service *kapi.Service) error { func (ks *kube2sky) newHeadlessService(subdomain string, service *kapi.Service, isNewStyleFormat bool) error {
// Create an A record for every pod in the service. // Create an A record for every pod in the service.
// This record must be periodically updated. // This record must be periodically updated.
// Format is as follows: // Format is as follows:
...@@ -131,7 +132,7 @@ func (ks *kube2sky) newHeadlessService(subdomain string, service *kapi.Service) ...@@ -131,7 +132,7 @@ func (ks *kube2sky) newHeadlessService(subdomain string, service *kapi.Service)
return nil return nil
} }
if e, ok := e.(*kapi.Endpoints); ok { if e, ok := e.(*kapi.Endpoints); ok {
return ks.generateRecordsForHeadlessService(subdomain, e, service) return ks.generateRecordsForHeadlessService(subdomain, e, service, isNewStyleFormat)
} }
return nil return nil
} }
...@@ -146,18 +147,33 @@ func getSkyMsg(ip string, port int) *skymsg.Service { ...@@ -146,18 +147,33 @@ func getSkyMsg(ip string, port int) *skymsg.Service {
} }
} }
func (ks *kube2sky) generateRecordsForHeadlessService(subdomain string, e *kapi.Endpoints, svc *kapi.Service) error { func (ks *kube2sky) generateRecordsForHeadlessService(subdomain string, e *kapi.Endpoints, svc *kapi.Service, isNewStyleFormat bool) error {
for idx := range e.Subsets { for idx := range e.Subsets {
for subIdx := range e.Subsets[idx].Addresses { for subIdx := range e.Subsets[idx].Addresses {
subdomain := buildDNSNameString(subdomain, fmt.Sprintf("%d%d", idx, subIdx)) b, err := json.Marshal(getSkyMsg(e.Subsets[idx].Addresses[subIdx].IP, 0))
b, err := json.Marshal(getSkyMsg(e.Subsets[idx].Addresses[subIdx].IP, svc.Spec.Ports[0].Port))
if err != nil { if err != nil {
return err return err
} }
glog.V(2).Infof("Setting DNS record: %v -> %q\n", subdomain, string(b)) recordValue := string(b)
if err := ks.writeSkyRecord(subdomain, string(b)); err != nil { recordLabel := getHash(recordValue)
recordKey := buildDNSNameString(subdomain, recordLabel)
glog.V(2).Infof("Setting DNS record: %v -> %q\n", recordKey, recordValue)
if err := ks.writeSkyRecord(recordKey, recordValue); err != nil {
return err return err
} }
if isNewStyleFormat {
for portIdx := range e.Subsets[idx].Ports {
endpointPort := &e.Subsets[idx].Ports[portIdx]
portSegment := buildPortSegmentString(endpointPort.Name, endpointPort.Protocol)
if portSegment != "" {
err := ks.generateSRVRecord(subdomain, portSegment, recordLabel, recordKey, endpointPort.Port)
if err != nil {
return err
}
}
}
}
} }
} }
...@@ -183,7 +199,7 @@ func (ks *kube2sky) getServiceFromEndpoints(e *kapi.Endpoints) (*kapi.Service, e ...@@ -183,7 +199,7 @@ func (ks *kube2sky) getServiceFromEndpoints(e *kapi.Endpoints) (*kapi.Service, e
return nil, fmt.Errorf("got a non service object in services store %v", obj) return nil, fmt.Errorf("got a non service object in services store %v", obj)
} }
func (ks *kube2sky) addDNSUsingEndpoints(subdomain string, e *kapi.Endpoints) error { func (ks *kube2sky) addDNSUsingEndpoints(subdomain string, e *kapi.Endpoints, isNewStyleFormat bool) error {
ks.mlock.Lock() ks.mlock.Lock()
defer ks.mlock.Unlock() defer ks.mlock.Unlock()
svc, err := ks.getServiceFromEndpoints(e) svc, err := ks.getServiceFromEndpoints(e)
...@@ -198,41 +214,90 @@ func (ks *kube2sky) addDNSUsingEndpoints(subdomain string, e *kapi.Endpoints) er ...@@ -198,41 +214,90 @@ func (ks *kube2sky) addDNSUsingEndpoints(subdomain string, e *kapi.Endpoints) er
if err := ks.removeDNS(subdomain); err != nil { if err := ks.removeDNS(subdomain); err != nil {
return err return err
} }
return ks.generateRecordsForHeadlessService(subdomain, e, svc) return ks.generateRecordsForHeadlessService(subdomain, e, svc, isNewStyleFormat)
} }
func (ks *kube2sky) handleEndpointAdd(obj interface{}) { func (ks *kube2sky) handleEndpointAdd(obj interface{}) {
if e, ok := obj.(*kapi.Endpoints); ok { if e, ok := obj.(*kapi.Endpoints); ok {
name := buildDNSNameString(ks.domain, e.Namespace, e.Name) name := buildDNSNameString(ks.domain, e.Namespace, e.Name)
ks.mutateEtcdOrDie(func() error { return ks.addDNSUsingEndpoints(name, e) }) ks.mutateEtcdOrDie(func() error { return ks.addDNSUsingEndpoints(name, e, false) })
name = buildDNSNameString(ks.domain, serviceSubdomain, e.Namespace, e.Name) name = buildDNSNameString(ks.domain, serviceSubdomain, e.Namespace, e.Name)
ks.mutateEtcdOrDie(func() error { return ks.addDNSUsingEndpoints(name, e) }) ks.mutateEtcdOrDie(func() error { return ks.addDNSUsingEndpoints(name, e, true) })
} }
} }
func (ks *kube2sky) generateRecordsForPortalService(subdomain string, service *kapi.Service) error { func (ks *kube2sky) generateRecordsForPortalService(subdomain string, service *kapi.Service, isNewStyleFormat bool) error {
for i := range service.Spec.Ports { b, err := json.Marshal(getSkyMsg(service.Spec.ClusterIP, 0))
b, err := json.Marshal(getSkyMsg(service.Spec.ClusterIP, service.Spec.Ports[i].Port)) if err != nil {
return err
}
recordValue := string(b)
recordKey := subdomain
recordLabel := ""
if isNewStyleFormat {
recordLabel = getHash(recordValue)
if err != nil { if err != nil {
return err return err
} }
glog.V(2).Infof("Setting DNS record: %v -> %q\n", subdomain, string(b)) recordKey = buildDNSNameString(subdomain, recordLabel)
if err := ks.writeSkyRecord(subdomain, string(b)); err != nil { }
return err
glog.V(2).Infof("Setting DNS record: %v -> %q, with recordKey: %v\n", subdomain, recordValue, recordKey)
if err := ks.writeSkyRecord(recordKey, recordValue); err != nil {
return err
}
if !isNewStyleFormat {
return nil
}
// Generate SRV Records
for i := range service.Spec.Ports {
port := &service.Spec.Ports[i]
portSegment := buildPortSegmentString(port.Name, port.Protocol)
if portSegment != "" {
err = ks.generateSRVRecord(subdomain, portSegment, recordLabel, subdomain, port.Port)
if err != nil {
return err
}
} }
} }
return nil return nil
} }
func (ks *kube2sky) addDNS(subdomain string, service *kapi.Service) error { func buildPortSegmentString(portName string, portProtocol kapi.Protocol) string {
if portName == "" {
// we don't create a random name
return ""
}
if portProtocol == "" {
glog.Errorf("Port Protocol not set. port segment string cannot be created.")
return ""
}
return fmt.Sprintf("_%s._%s", portName, strings.ToLower(string(portProtocol)))
}
func (ks *kube2sky) generateSRVRecord(subdomain, portSegment, recordName, cName string, portNumber int) error {
recordKey := buildDNSNameString(subdomain, portSegment, recordName)
srv_rec, err := json.Marshal(getSkyMsg(cName, portNumber))
if err != nil {
return err
}
if err := ks.writeSkyRecord(recordKey, string(srv_rec)); err != nil {
return err
}
return nil
}
func (ks *kube2sky) addDNS(subdomain string, service *kapi.Service, isNewStyleFormat bool) error {
if len(service.Spec.Ports) == 0 { if len(service.Spec.Ports) == 0 {
glog.Fatalf("unexpected service with no ports: %v", service) glog.Fatalf("unexpected service with no ports: %v", service)
} }
// if ClusterIP is not set, a DNS entry should not be created // if ClusterIP is not set, a DNS entry should not be created
if !kapi.IsServiceIPSet(service) { if !kapi.IsServiceIPSet(service) {
return ks.newHeadlessService(subdomain, service) return ks.newHeadlessService(subdomain, service, isNewStyleFormat)
} }
return ks.generateRecordsForPortalService(subdomain, service) return ks.generateRecordsForPortalService(subdomain, service, isNewStyleFormat)
} }
// Implements retry logic for arbitrary mutator. Crashes after retrying for // Implements retry logic for arbitrary mutator. Crashes after retrying for
...@@ -281,9 +346,9 @@ func (ks *kube2sky) newService(obj interface{}) { ...@@ -281,9 +346,9 @@ func (ks *kube2sky) newService(obj interface{}) {
if s, ok := obj.(*kapi.Service); ok { if s, ok := obj.(*kapi.Service); ok {
//TODO(artfulcoder) stop adding and deleting old-format string for service //TODO(artfulcoder) stop adding and deleting old-format string for service
name := buildDNSNameString(ks.domain, s.Namespace, s.Name) name := buildDNSNameString(ks.domain, s.Namespace, s.Name)
ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s) }) ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s, false) })
name = buildDNSNameString(ks.domain, serviceSubdomain, s.Namespace, s.Name) name = buildDNSNameString(ks.domain, serviceSubdomain, s.Namespace, s.Name)
ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s) }) ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s, true) })
} }
} }
...@@ -296,6 +361,12 @@ func (ks *kube2sky) removeService(obj interface{}) { ...@@ -296,6 +361,12 @@ func (ks *kube2sky) removeService(obj interface{}) {
} }
} }
func (ks *kube2sky) updateService(oldObj, newObj interface{}) {
// TODO: Avoid unwanted updates.
ks.removeService(oldObj)
ks.newService(newObj)
}
func newEtcdClient(etcdServer string) (*etcd.Client, error) { func newEtcdClient(etcdServer string) (*etcd.Client, error) {
var ( var (
client *etcd.Client client *etcd.Client
...@@ -390,10 +461,7 @@ func watchForServices(kubeClient *kclient.Client, ks *kube2sky) kcache.Store { ...@@ -390,10 +461,7 @@ func watchForServices(kubeClient *kclient.Client, ks *kube2sky) kcache.Store {
kframework.ResourceEventHandlerFuncs{ kframework.ResourceEventHandlerFuncs{
AddFunc: ks.newService, AddFunc: ks.newService,
DeleteFunc: ks.removeService, DeleteFunc: ks.removeService,
UpdateFunc: func(oldObj, newObj interface{}) { UpdateFunc: ks.updateService,
// TODO: Avoid unwanted updates.
ks.newService(newObj)
},
}, },
) )
go serviceController.Run(util.NeverStop) go serviceController.Run(util.NeverStop)
...@@ -418,6 +486,12 @@ func watchEndpoints(kubeClient *kclient.Client, ks *kube2sky) kcache.Store { ...@@ -418,6 +486,12 @@ func watchEndpoints(kubeClient *kclient.Client, ks *kube2sky) kcache.Store {
return eStore return eStore
} }
func getHash(text string) string {
h := fnv.New32a()
h.Write([]byte(text))
return fmt.Sprintf("%x", h.Sum32())
}
func main() { func main() {
flag.Parse() flag.Parse()
var err error var err error
......
apiVersion: v1beta3 apiVersion: v1beta3
kind: ReplicationController kind: ReplicationController
metadata: metadata:
name: kube-dns-v2 name: kube-dns-v3
namespace: default namespace: default
labels: labels:
k8s-app: kube-dns-v2 k8s-app: kube-dns-v3
kubernetes.io/cluster-service: "true" kubernetes.io/cluster-service: "true"
spec: spec:
replicas: {{ pillar['dns_replicas'] }} replicas: {{ pillar['dns_replicas'] }}
selector: selector:
k8s-app: kube-dns k8s-app: kube-dns
version: v2 version: v3
template: template:
metadata: metadata:
labels: labels:
k8s-app: kube-dns k8s-app: kube-dns
version: v2 version: v3
kubernetes.io/cluster-service: "true" kubernetes.io/cluster-service: "true"
spec: spec:
containers: containers:
...@@ -30,7 +30,7 @@ spec: ...@@ -30,7 +30,7 @@ spec:
- -initial-cluster-token - -initial-cluster-token
- skydns-etcd - skydns-etcd
- name: kube2sky - name: kube2sky
image: gcr.io/google_containers/kube2sky:1.8 image: gcr.io/google_containers/kube2sky:1.9
args: args:
# command = "/kube2sky" # command = "/kube2sky"
- -domain={{ pillar['dns_domain'] }} - -domain={{ pillar['dns_domain'] }}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment