上一篇 介绍了kubedns服务启动过程,现在看服务怎样运行。分为两个主要部分,第一个是怎么提供DNS解析服务,第二是怎样同步更新sevice信息。那下面开始讲解。
服务怎么同步呢?必须是listwatch,继承kubernetes一套API体系,我以后要说的ingress同步,也是这样的。下面看代码:
//service
kcache.ResourceEventHandlerFuncs{
AddFunc: kd.newService,
DeleteFunc: kd.removeService,
UpdateFunc: kd.updateService,
}
//endpoint
kcache.ResourceEventHandlerFuncs{
AddFunc: kd.handleEndpointAdd,
UpdateFunc: kd.handleEndpointUpdate,
DeleteFunc: kd.handleEndpointDelete,
},
读者可能有疑惑,DNS是提供service域名解析,服务的,应该是只要将service的域名解析到clusterIP就可以,为啥还要同步endpoint呢?以为有个特殊的服务headless service(不依赖k8s负载均衡,直接映射到后端容器endpoint),所以这里必须还需要有一个endpoint的同步服务。
那么下面具体看看各种情况下service和endpoint是怎样变化的:
服务创建的的时候
func (kd *KubeDNS) newService(obj interface{}) {
if service, ok := assertIsService(obj); ok {
glog.V(2).Infof("New service: %v", service.Name)
glog.V(4).Infof("Service details: %v", service)
// 外部服务使用CNAME records
if service.Spec.Type == v1.ServiceTypeExternalName {
kd.newExternalNameService(service)
return
}
// 如果没有clusterip创建headless service
if !v1.IsServiceIPSet(service) {
kd.newHeadlessService(service)
return
}
if len(service.Spec.Ports) == 0 {
glog.Warningf("Service with no ports, this should not have happened: %v",
service)
}
kd.newPortalService(service)
}
}
上面代码创建服务,具体看newPortalService这个方法实现
“`
func (kd *KubeDNS) newPortalService(service *v1.Service) {
subCache := treecache.NewTreeCache()
recordValue, recordLabel := util.GetSkyMsg(service.Spec.ClusterIP, 0)
subCache.SetEntry(recordLabel, recordValue, kd.fqdn(service, recordLabel))
// 创建 SRV Records
for i := range service.Spec.Ports {
port := &service.Spec.Ports[i]
if port.Name != "" && port.Protocol != "" {
srvValue := kd.generateSRVRecordValue(service, int(port.Port))
l := []string{"_" + strings.ToLower(string(port.Protocol)), "_" + port.Name}
glog.V(2).Infof("Added SRV record %+v", srvValue)
subCache.SetEntry(recordLabel, srvValue, kd.fqdn(service, append(l, recordLabel)...), l...)
}
}
subCachePath := append(kd.domainPath, serviceSubdomain, service.Namespace)
host := getServiceFQDN(kd.domain, service)
reverseRecord, _ := util.GetSkyMsg(host, 0)
kd.cacheLock.Lock()
defer kd.cacheLock.Unlock()
kd.cache.SetSubCache(service.Name, subCache, subCachePath...)
kd.reverseRecordMap[service.Spec.ClusterIP] = reverseRecord
kd.clusterIPServiceMap[service.Spec.ClusterIP] = service
}
“`
这里面有个很有意思的东西就是treecache,他是替换etcd存储service的数据结构代码在:pkg/dns/treecache/treecache.go里面。它是一个逆向存储service的是一个数据结构,这个设计的原因是由于域名结构就是这样设计的,多级域名,后面是顶级域名,前面可能是2级(如:www.baidu.com)、3级(www.hi.baidu.com)甚至更多级域名。所以反过来存储能够更快的检索到域名。通过fqdn返回完整域名FQDN,所以在末尾会多出一个点(.),并且这是已经通过ReverseArray将域名颠倒,如下:
func (kd *KubeDNS) fqdn(service *v1.Service, subpaths ...string) string {
domainLabels := append(append(kd.domainPath, serviceSubdomain, service.Namespace, service.Name), subpaths...)
return dns.Fqdn(strings.Join(util.ReverseArray(domainLabels), "."))
}
在通过SetEntry创建entity
func (cache *treeCache) SetEntry(key string, val *skymsg.Service, fqdn string, path ...string) {
node := cache.ensureChildNode(path...)
val.Key = skymsg.Path(fqdn)
node.Entries[key] = val
}
将这个sevice的域名保存到treecache里面。eg:nginx.default.svc.cluster.local
删除服务
和创建服务相反的,删除服务代码如下:
func (kd *KubeDNS) removeService(obj interface{}) {
if s, ok := assertIsService(obj); ok {
subCachePath := append(kd.domainPath, serviceSubdomain, s.Namespace, s.Name)
kd.cacheLock.Lock()
defer kd.cacheLock.Unlock()
success := kd.cache.DeletePath(subCachePath...)
glog.V(2).Infof("removeService %v at path %v. Success: %v",
s.Name, subCachePath, success)
// ExternalName services have no IP
if v1.IsServiceIPSet(s) {
delete(kd.reverseRecordMap, s.Spec.ClusterIP)
delete(kd.clusterIPServiceMap, s.Spec.ClusterIP)
}
}
}
通过kd.cache.DeletePath删除路径,并且删除reverseRecordMap和clusterIPServiceMap记录。具体删除方法DeletePath如下:
func (cache *treeCache) DeletePath(path ...string) bool {
if len(path) == 0 {
return false
}
if parentNode := cache.getSubCache(path[:len(path)-1]...); parentNode != nil {
name := path[len(path)-1]
if _, ok := parentNode.ChildNodes[name]; ok {
delete(parentNode.ChildNodes, name)
return true
}
// ExternalName services are stored with their name as the leaf key
if _, ok := parentNode.Entries[name]; ok {
delete(parentNode.Entries, name)
return true
}
}
return false
}
删除节点,先找到父节点,然后删除父节点以下的对应的子节点。
添加endpoint
这时会执行,
func (kd *KubeDNS) addDNSUsingEndpoints(e *v1.Endpoints) error {
svc, err := kd.getServiceFromEndpoints(e)
if err != nil {
return err
}
if svc == nil || v1.IsServiceIPSet(svc) {
// No headless service found corresponding to endpoints object.
return nil
}
return kd.generateRecordsForHeadlessService(e, svc)
}
创建一个headless service。其它的方法也是类似的,再次不再赘述。