Merge pull request #8501 from ArtfulCoder/kubelet_svc_subdomain

Support old and new style dns service names.
This commit is contained in:
Dawn Chen 2015-05-21 09:22:03 -07:00
commit 9a316dd409
6 changed files with 69 additions and 40 deletions

View File

@ -13,6 +13,11 @@ crashes or scheduling changes). This maps well to DNS, which has a long
history of clients that, on purpose or on accident, do not respect DNS TTLs history of clients that, on purpose or on accident, do not respect DNS TTLs
(see previous remark about Pod IPs changing). (see previous remark about Pod IPs changing).
## DNS Name format for Services
Services get a DNS name with the format my-svc.my-namespace.svc.cluster.local
'svc' should not be used a namespace label to avoid conflicts.
The old format of my-svc.my-namespace.cluster.local has been deprecated.
## How do I find the DNS server? ## How do I find the DNS server?
The DNS server itself runs as a Kubernetes Service. This gives it a stable IP The DNS server itself runs as a Kubernetes Service. This gives it a stable IP
address. When you run the SkyDNS service, you want to assign a static IP to use for address. When you run the SkyDNS service, you want to assign a static IP to use for
@ -35,12 +40,12 @@ example, see `cluster/gce/config-default.sh`.
```shell ```shell
ENABLE_CLUSTER_DNS=true ENABLE_CLUSTER_DNS=true
DNS_SERVER_IP="10.0.0.10" DNS_SERVER_IP="10.0.0.10"
DNS_DOMAIN="kubernetes.local" DNS_DOMAIN="cluster.local"
DNS_REPLICAS=1 DNS_REPLICAS=1
``` ```
This enables DNS with a DNS Service IP of `10.0.0.10` and a local domain of This enables DNS with a DNS Service IP of `10.0.0.10` and a local domain of
`kubernetes.local`, served by a single copy of SkyDNS. `cluster.local`, served by a single copy of SkyDNS.
If you are not using a supported cluster setup, you will have to replicate some If you are not using a supported cluster setup, you will have to replicate some
of this yourself. First, each kubelet needs to run with the following flags of this yourself. First, each kubelet needs to run with the following flags

View File

@ -4,7 +4,7 @@
.PHONY: all kube2sky container push clean test .PHONY: all kube2sky container push clean test
TAG = 1.5 TAG = 1.6
PREFIX = gcr.io/google_containers PREFIX = gcr.io/google_containers
all: container all: container

View File

@ -204,10 +204,14 @@ func newKubeClient() (*kclient.Client, error) {
return kclient.New(config) return kclient.New(config)
} }
func buildNameString(service, namespace, domain string) string { func buildOldNameString(service, namespace, domain string) string {
return fmt.Sprintf("%s.%s.%s.", service, namespace, domain) return fmt.Sprintf("%s.%s.%s.", service, namespace, domain)
} }
func buildNewServiceNameString(service, namespace, domain string) string {
return fmt.Sprintf("%s.%s.svc.%s.", service, namespace, domain)
}
// Returns a cache.ListWatch that gets all changes to services. // Returns a cache.ListWatch that gets all changes to services.
func createServiceLW(kubeClient *kclient.Client) *cache.ListWatch { func createServiceLW(kubeClient *kclient.Client) *cache.ListWatch {
return cache.NewListWatchFromClient(kubeClient, "services", kapi.NamespaceAll, kSelector.Everything()) return cache.NewListWatchFromClient(kubeClient, "services", kapi.NamespaceAll, kSelector.Everything())
@ -215,15 +219,20 @@ func createServiceLW(kubeClient *kclient.Client) *cache.ListWatch {
func (ks *kube2sky) newService(obj interface{}) { func (ks *kube2sky) newService(obj interface{}) {
if s, ok := obj.(*kapi.Service); ok { if s, ok := obj.(*kapi.Service); ok {
name := buildNameString(s.Name, s.Namespace, ks.domain) //TODO(artfulcoder) stop adding and deleting old-format string for service
name := buildOldNameString(s.Name, s.Namespace, ks.domain)
ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s) }) ks.mutateEtcdOrDie(func() error { return ks.addDNS(name, s) })
name1 := buildNewServiceNameString(s.Name, s.Namespace, ks.domain)
ks.mutateEtcdOrDie(func() error { return ks.addDNS(name1, s) })
} }
} }
func (ks *kube2sky) removeService(obj interface{}) { func (ks *kube2sky) removeService(obj interface{}) {
if s, ok := obj.(*kapi.Service); ok { if s, ok := obj.(*kapi.Service); ok {
name := buildNameString(s.Name, s.Namespace, ks.domain) name := buildOldNameString(s.Name, s.Namespace, ks.domain)
ks.mutateEtcdOrDie(func() error { return ks.removeDNS(name) }) ks.mutateEtcdOrDie(func() error { return ks.removeDNS(name) })
name1 := buildNewServiceNameString(s.Name, s.Namespace, ks.domain)
ks.mutateEtcdOrDie(func() error { return ks.removeDNS(name1) })
} }
} }

View File

@ -49,8 +49,9 @@ func (ec *fakeEtcdClient) Delete(path string, recursive bool) (*etcd.Response, e
} }
const ( const (
testDomain = "cluster.local" testDomain = "cluster.local"
basePath = "/skydns/local/cluster" basePath = "/skydns/local/cluster"
serviceSubDomain = "svc"
) )
func newKube2Sky(ec etcdClient) *kube2sky { func newKube2Sky(ec etcdClient) *kube2sky {
@ -61,25 +62,12 @@ func newKube2Sky(ec etcdClient) *kube2sky {
} }
} }
func TestAddNoServiceIP(t *testing.T) { func getEtcdOldStylePath(name, namespace string) string {
const ( return path.Join(basePath, namespace, name)
testService = "testService"
testNamespace = "default"
)
ec := &fakeEtcdClient{make(map[string]string)}
k2s := newKube2Sky(ec)
service := kapi.Service{
ObjectMeta: kapi.ObjectMeta{
Name: testNamespace,
Namespace: testNamespace,
},
}
k2s.newService(&service)
assert.Empty(t, ec.writes)
} }
func getEtcdPath(name, namespace string) string { func getEtcdNewStylePath(name, namespace string) string {
return path.Join(basePath, namespace, name) return path.Join(basePath, serviceSubDomain, namespace, name)
} }
type hostPort struct { type hostPort struct {
@ -100,6 +88,39 @@ func getHostPortFromString(data string) (*hostPort, error) {
return &res, err return &res, err
} }
func assertDnsServiceEntryInEtcd(t *testing.T, ec *fakeEtcdClient, serviceName, namespace string, expectedHostPort *hostPort) {
oldStyleKey := getEtcdOldStylePath(serviceName, namespace)
val, exists := ec.writes[oldStyleKey]
require.True(t, exists)
actualHostPort, err := getHostPortFromString(val)
require.NoError(t, err)
assert.Equal(t, actualHostPort, expectedHostPort)
newStyleKey := getEtcdNewStylePath(serviceName, namespace)
val, exists = ec.writes[newStyleKey]
require.True(t, exists)
actualHostPort, err = getHostPortFromString(val)
require.NoError(t, err)
assert.Equal(t, actualHostPort, expectedHostPort)
}
func TestHeadlessService(t *testing.T) {
const (
testService = "testService"
testNamespace = "default"
)
ec := &fakeEtcdClient{make(map[string]string)}
k2s := newKube2Sky(ec)
service := kapi.Service{
ObjectMeta: kapi.ObjectMeta{
Name: testNamespace,
Namespace: testNamespace,
},
}
k2s.newService(&service)
assert.Empty(t, ec.writes)
}
func TestAddSinglePortService(t *testing.T) { func TestAddSinglePortService(t *testing.T) {
const ( const (
testService = "testService" testService = "testService"
@ -122,13 +143,8 @@ func TestAddSinglePortService(t *testing.T) {
}, },
} }
k2s.newService(&service) k2s.newService(&service)
expectedKey := getEtcdPath(testService, testNamespace)
expectedValue := getHostPort(&service) expectedValue := getHostPort(&service)
val, exists := ec.writes[expectedKey] assertDnsServiceEntryInEtcd(t, ec, testService, testNamespace, expectedValue)
require.True(t, exists)
actualValue, err := getHostPortFromString(val)
require.NoError(t, err)
assert.Equal(t, actualValue, expectedValue)
} }
func TestUpdateSinglePortService(t *testing.T) { func TestUpdateSinglePortService(t *testing.T) {
@ -153,16 +169,11 @@ func TestUpdateSinglePortService(t *testing.T) {
}, },
} }
k2s.newService(&service) k2s.newService(&service)
assert.Len(t, ec.writes, 1) assert.Len(t, ec.writes, 2)
service.Spec.PortalIP = "0.0.0.0" service.Spec.PortalIP = "0.0.0.0"
k2s.newService(&service) k2s.newService(&service)
expectedKey := getEtcdPath(testService, testNamespace)
expectedValue := getHostPort(&service) expectedValue := getHostPort(&service)
val, exists := ec.writes[expectedKey] assertDnsServiceEntryInEtcd(t, ec, testService, testNamespace, expectedValue)
require.True(t, exists)
actualValue, err := getHostPortFromString(val)
require.NoError(t, err)
assert.Equal(t, actualValue, expectedValue)
} }
func TestDeleteSinglePortService(t *testing.T) { func TestDeleteSinglePortService(t *testing.T) {
@ -188,7 +199,9 @@ func TestDeleteSinglePortService(t *testing.T) {
} }
// Add the service // Add the service
k2s.newService(&service) k2s.newService(&service)
assert.Len(t, ec.writes, 1) // two entries should get created, one with the svc subdomain (new-style)
// , and one without the svc subdomain (old-style)
assert.Len(t, ec.writes, 2)
// Delete the service // Delete the service
k2s.removeService(&service) k2s.removeService(&service)
assert.Empty(t, ec.writes) assert.Empty(t, ec.writes)

View File

@ -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.5 image: gcr.io/google_containers/kube2sky:1.6
args: args:
# command = "/kube2sky" # command = "/kube2sky"
- -domain={{ pillar['dns_domain'] }} - -domain={{ pillar['dns_domain'] }}

View File

@ -72,6 +72,8 @@ var _ = Describe("DNS", func() {
// TODO: Spin up a separate test service and test that dns works for that service. // TODO: Spin up a separate test service and test that dns works for that service.
namesToResolve := []string{ namesToResolve := []string{
"kubernetes-ro.default", "kubernetes-ro.default",
"kubernetes-ro.default.svc",
"kubernetes-ro.default.svc.cluster.local",
"kubernetes-ro.default.cluster.local", "kubernetes-ro.default.cluster.local",
"google.com", "google.com",
} }