2015-07-18 21:23:58 +00:00
|
|
|
// Copyright 2015 The Prometheus 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 kubernetes
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2015-10-03 08:21:43 +00:00
|
|
|
"github.com/prometheus/common/log"
|
2015-08-20 15:18:46 +00:00
|
|
|
"github.com/prometheus/common/model"
|
2015-08-22 07:42:45 +00:00
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
"github.com/prometheus/prometheus/config"
|
|
|
|
"github.com/prometheus/prometheus/util/httputil"
|
|
|
|
"github.com/prometheus/prometheus/util/strutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
sourceServicePrefix = "services"
|
|
|
|
|
|
|
|
// kubernetesMetaLabelPrefix is the meta prefix used for all meta labels.
|
|
|
|
// in this discovery.
|
2015-08-20 15:18:46 +00:00
|
|
|
metaLabelPrefix = model.MetaLabelPrefix + "kubernetes_"
|
2015-07-18 21:23:58 +00:00
|
|
|
// serviceNamespaceLabel is the name for the label containing a target's service namespace.
|
|
|
|
serviceNamespaceLabel = metaLabelPrefix + "service_namespace"
|
|
|
|
// serviceNameLabel is the name for the label containing a target's service name.
|
|
|
|
serviceNameLabel = metaLabelPrefix + "service_name"
|
|
|
|
// nodeLabelPrefix is the prefix for the node labels.
|
|
|
|
nodeLabelPrefix = metaLabelPrefix + "node_label_"
|
|
|
|
// serviceLabelPrefix is the prefix for the service labels.
|
|
|
|
serviceLabelPrefix = metaLabelPrefix + "service_label_"
|
|
|
|
// serviceAnnotationPrefix is the prefix for the service annotations.
|
|
|
|
serviceAnnotationPrefix = metaLabelPrefix + "service_annotation_"
|
|
|
|
// nodesTargetGroupName is the name given to the target group for nodes.
|
|
|
|
nodesTargetGroupName = "nodes"
|
2015-09-03 09:47:09 +00:00
|
|
|
// mastersTargetGroupName is the name given to the target group for masters.
|
|
|
|
mastersTargetGroupName = "masters"
|
|
|
|
// roleLabel is the name for the label containing a target's role.
|
|
|
|
roleLabel = metaLabelPrefix + "role"
|
2015-07-18 21:23:58 +00:00
|
|
|
|
|
|
|
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
|
|
|
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
|
|
|
|
|
|
|
apiVersion = "v1"
|
2015-09-03 09:47:09 +00:00
|
|
|
apiPrefix = "/api/" + apiVersion
|
2015-07-18 21:23:58 +00:00
|
|
|
nodesURL = apiPrefix + "/nodes"
|
|
|
|
servicesURL = apiPrefix + "/services"
|
|
|
|
endpointsURL = apiPrefix + "/endpoints"
|
|
|
|
serviceEndpointsURL = apiPrefix + "/namespaces/%s/endpoints/%s"
|
|
|
|
)
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
// Discovery implements a TargetProvider for Kubernetes services.
|
|
|
|
type Discovery struct {
|
2015-07-18 21:23:58 +00:00
|
|
|
client *http.Client
|
|
|
|
Conf *config.KubernetesSDConfig
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
masters []config.URL
|
|
|
|
mastersMu sync.RWMutex
|
2015-07-18 21:23:58 +00:00
|
|
|
nodesResourceVersion string
|
|
|
|
servicesResourceVersion string
|
|
|
|
endpointsResourceVersion string
|
|
|
|
nodes map[string]*Node
|
|
|
|
services map[string]map[string]*Service
|
|
|
|
nodesMu sync.RWMutex
|
|
|
|
servicesMu sync.RWMutex
|
|
|
|
runDone chan struct{}
|
|
|
|
}
|
|
|
|
|
2015-08-18 12:37:28 +00:00
|
|
|
// Initialize sets up the discovery for usage.
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) Initialize() error {
|
2015-07-18 21:23:58 +00:00
|
|
|
client, err := newKubernetesHTTPClient(kd.Conf)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
kd.masters = kd.Conf.Masters
|
2015-07-18 21:23:58 +00:00
|
|
|
kd.client = client
|
|
|
|
kd.nodes = map[string]*Node{}
|
|
|
|
kd.services = map[string]map[string]*Service{}
|
|
|
|
kd.runDone = make(chan struct{})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sources implements the TargetProvider interface.
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) Sources() []string {
|
2015-09-03 09:47:09 +00:00
|
|
|
sourceNames := make([]string, 0, len(kd.masters))
|
|
|
|
for _, master := range kd.masters {
|
|
|
|
sourceNames = append(sourceNames, mastersTargetGroupName+":"+master.Host)
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := kd.queryMasterPath(nodesURL)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
// If we can't list nodes then we can't watch them. Assume this is a misconfiguration
|
|
|
|
// & log & return empty.
|
|
|
|
log.Errorf("Unable to list Kubernetes nodes: %s", err)
|
|
|
|
return []string{}
|
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
log.Errorf("Unable to list Kubernetes nodes. Unexpected response: %d %s", res.StatusCode, res.Status)
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodes NodeList
|
|
|
|
if err := json.NewDecoder(res.Body).Decode(&nodes); err != nil {
|
|
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
|
|
log.Errorf("Unable to list Kubernetes nodes. Unexpected response body: %s", string(body))
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
kd.nodesMu.Lock()
|
|
|
|
defer kd.nodesMu.Unlock()
|
|
|
|
|
|
|
|
kd.nodesResourceVersion = nodes.ResourceVersion
|
|
|
|
for idx, node := range nodes.Items {
|
|
|
|
sourceNames = append(sourceNames, nodesTargetGroupName+":"+node.ObjectMeta.Name)
|
|
|
|
kd.nodes[node.ObjectMeta.Name] = &nodes.Items[idx]
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
res, err = kd.queryMasterPath(servicesURL)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
// If we can't list services then we can't watch them. Assume this is a misconfiguration
|
|
|
|
// & log & return empty.
|
|
|
|
log.Errorf("Unable to list Kubernetes services: %s", err)
|
|
|
|
return []string{}
|
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
log.Errorf("Unable to list Kubernetes services. Unexpected response: %d %s", res.StatusCode, res.Status)
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
var services ServiceList
|
|
|
|
if err := json.NewDecoder(res.Body).Decode(&services); err != nil {
|
|
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
|
|
log.Errorf("Unable to list Kubernetes services. Unexpected response body: %s", string(body))
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
kd.servicesMu.Lock()
|
|
|
|
defer kd.servicesMu.Unlock()
|
|
|
|
|
|
|
|
kd.servicesResourceVersion = services.ResourceVersion
|
|
|
|
for idx, service := range services.Items {
|
|
|
|
sourceNames = append(sourceNames, serviceSource(&service))
|
|
|
|
namespace, ok := kd.services[service.ObjectMeta.Namespace]
|
|
|
|
if !ok {
|
|
|
|
namespace = map[string]*Service{}
|
|
|
|
kd.services[service.ObjectMeta.Namespace] = namespace
|
|
|
|
}
|
|
|
|
namespace[service.ObjectMeta.Name] = &services.Items[idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
return sourceNames
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run implements the TargetProvider interface.
|
2015-10-08 16:06:58 +00:00
|
|
|
func (kd *Discovery) Run(ch chan<- config.TargetGroup, done <-chan struct{}) {
|
2015-07-18 21:23:58 +00:00
|
|
|
defer close(ch)
|
|
|
|
|
2015-10-08 16:06:58 +00:00
|
|
|
if tg := kd.updateMastersTargetGroup(); tg != nil {
|
|
|
|
select {
|
|
|
|
case ch <- *tg:
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
2015-09-03 09:47:09 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 16:06:58 +00:00
|
|
|
if tg := kd.updateNodesTargetGroup(); tg != nil {
|
|
|
|
select {
|
|
|
|
case ch <- *tg:
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
|
|
|
|
for _, ns := range kd.services {
|
|
|
|
for _, service := range ns {
|
2015-10-08 16:06:58 +00:00
|
|
|
tg := kd.addService(service)
|
|
|
|
|
|
|
|
if tg == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
select {
|
2015-10-08 16:06:58 +00:00
|
|
|
case ch <- *tg:
|
2015-08-14 11:04:23 +00:00
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retryInterval := time.Duration(kd.Conf.RetryInterval)
|
|
|
|
|
|
|
|
update := make(chan interface{}, 10)
|
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
go kd.watchNodes(update, done, retryInterval)
|
|
|
|
go kd.watchServices(update, done, retryInterval)
|
|
|
|
go kd.watchServiceEndpoints(update, done, retryInterval)
|
2015-07-18 21:23:58 +00:00
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
var tg *config.TargetGroup
|
2015-07-18 21:23:58 +00:00
|
|
|
for {
|
|
|
|
select {
|
2015-08-14 11:04:23 +00:00
|
|
|
case <-done:
|
2015-07-18 21:23:58 +00:00
|
|
|
return
|
|
|
|
case event := <-update:
|
|
|
|
switch obj := event.(type) {
|
|
|
|
case *nodeEvent:
|
|
|
|
kd.updateNode(obj.Node, obj.EventType)
|
2015-08-14 11:04:23 +00:00
|
|
|
tg = kd.updateNodesTargetGroup()
|
2015-07-18 21:23:58 +00:00
|
|
|
case *serviceEvent:
|
2015-08-14 11:04:23 +00:00
|
|
|
tg = kd.updateService(obj.Service, obj.EventType)
|
2015-07-18 21:23:58 +00:00
|
|
|
case *endpointsEvent:
|
2015-08-14 11:04:23 +00:00
|
|
|
tg = kd.updateServiceEndpoints(obj.Endpoints, obj.EventType)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-10-08 16:06:58 +00:00
|
|
|
if tg == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
select {
|
2015-10-08 16:06:58 +00:00
|
|
|
case ch <- *tg:
|
2015-08-14 11:04:23 +00:00
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
func (kd *Discovery) queryMasterPath(path string) (*http.Response, error) {
|
|
|
|
req, err := http.NewRequest("GET", path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return kd.queryMasterReq(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (kd *Discovery) queryMasterReq(req *http.Request) (*http.Response, error) {
|
|
|
|
// Lock in case we need to rotate masters to request.
|
|
|
|
kd.mastersMu.Lock()
|
|
|
|
defer kd.mastersMu.Unlock()
|
|
|
|
for i := 0; i < len(kd.masters); i++ {
|
|
|
|
cloneReq := *req
|
|
|
|
cloneReq.URL.Host = kd.masters[0].Host
|
|
|
|
cloneReq.URL.Scheme = kd.masters[0].Scheme
|
|
|
|
res, err := kd.client.Do(&cloneReq)
|
|
|
|
if err == nil {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
kd.rotateMasters()
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unable to query any masters")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (kd *Discovery) rotateMasters() {
|
|
|
|
if len(kd.masters) > 1 {
|
|
|
|
kd.masters = append(kd.masters[1:], kd.masters[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (kd *Discovery) updateMastersTargetGroup() *config.TargetGroup {
|
|
|
|
tg := &config.TargetGroup{
|
|
|
|
Source: mastersTargetGroupName,
|
|
|
|
Labels: model.LabelSet{
|
|
|
|
roleLabel: model.LabelValue("master"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, master := range kd.masters {
|
|
|
|
masterAddress := master.Host
|
|
|
|
_, _, err := net.SplitHostPort(masterAddress)
|
|
|
|
// If error then no port is specified - use default for scheme.
|
|
|
|
if err != nil {
|
|
|
|
switch master.Scheme {
|
|
|
|
case "http":
|
|
|
|
masterAddress = net.JoinHostPort(masterAddress, "80")
|
|
|
|
case "https":
|
|
|
|
masterAddress = net.JoinHostPort(masterAddress, "443")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t := model.LabelSet{
|
|
|
|
model.AddressLabel: model.LabelValue(masterAddress),
|
|
|
|
model.SchemeLabel: model.LabelValue(master.Scheme),
|
|
|
|
}
|
|
|
|
tg.Targets = append(tg.Targets, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tg
|
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) updateNodesTargetGroup() *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
kd.nodesMu.Lock()
|
|
|
|
defer kd.nodesMu.Unlock()
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
tg := &config.TargetGroup{
|
|
|
|
Source: nodesTargetGroupName,
|
|
|
|
Labels: model.LabelSet{
|
|
|
|
roleLabel: model.LabelValue("node"),
|
|
|
|
},
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
|
|
|
|
// Now let's loop through the nodes & add them to the target group with appropriate labels.
|
|
|
|
for nodeName, node := range kd.nodes {
|
|
|
|
address := fmt.Sprintf("%s:%d", node.Status.Addresses[0].Address, kd.Conf.KubeletPort)
|
|
|
|
|
2015-08-20 15:18:46 +00:00
|
|
|
t := model.LabelSet{
|
2015-10-12 20:26:09 +00:00
|
|
|
model.AddressLabel: model.LabelValue(address),
|
|
|
|
model.InstanceLabel: model.LabelValue(nodeName),
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
for k, v := range node.ObjectMeta.Labels {
|
|
|
|
labelName := strutil.SanitizeLabelName(nodeLabelPrefix + k)
|
2015-08-20 15:18:46 +00:00
|
|
|
t[model.LabelName(labelName)] = model.LabelValue(v)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
tg.Targets = append(tg.Targets, t)
|
|
|
|
}
|
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
return tg
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) updateNode(node *Node, eventType EventType) {
|
2015-07-18 21:23:58 +00:00
|
|
|
kd.nodesMu.Lock()
|
|
|
|
defer kd.nodesMu.Unlock()
|
|
|
|
updatedNodeName := node.ObjectMeta.Name
|
|
|
|
switch eventType {
|
|
|
|
case deleted:
|
|
|
|
// Deleted - remove from nodes map.
|
|
|
|
delete(kd.nodes, updatedNodeName)
|
|
|
|
case added, modified:
|
|
|
|
// Added/Modified - update the node in the nodes map.
|
|
|
|
kd.nodes[updatedNodeName] = node
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// watchNodes watches nodes as they come & go.
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) watchNodes(events chan interface{}, done <-chan struct{}, retryInterval time.Duration) {
|
2015-07-18 21:23:58 +00:00
|
|
|
until(func() {
|
2015-09-03 09:47:09 +00:00
|
|
|
req, err := http.NewRequest("GET", nodesURL, nil)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch nodes: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
values := req.URL.Query()
|
|
|
|
values.Add("watch", "true")
|
|
|
|
values.Add("resourceVersion", kd.nodesResourceVersion)
|
|
|
|
req.URL.RawQuery = values.Encode()
|
2015-09-03 09:47:09 +00:00
|
|
|
res, err := kd.queryMasterReq(req)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch nodes: %s", err)
|
|
|
|
return
|
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
2015-08-26 00:04:01 +00:00
|
|
|
log.Errorf("Failed to watch nodes: %d", res.StatusCode)
|
2015-07-18 21:23:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
d := json.NewDecoder(res.Body)
|
|
|
|
|
|
|
|
for {
|
|
|
|
var event nodeEvent
|
|
|
|
if err := d.Decode(&event); err != nil {
|
|
|
|
log.Errorf("Failed to watch nodes: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
kd.nodesResourceVersion = event.Node.ObjectMeta.ResourceVersion
|
2015-08-14 11:04:23 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case events <- &event:
|
|
|
|
case <-done:
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
}, retryInterval, done)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// watchServices watches services as they come & go.
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) watchServices(events chan interface{}, done <-chan struct{}, retryInterval time.Duration) {
|
2015-07-18 21:23:58 +00:00
|
|
|
until(func() {
|
2015-09-03 09:47:09 +00:00
|
|
|
req, err := http.NewRequest("GET", servicesURL, nil)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch services: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
values := req.URL.Query()
|
|
|
|
values.Add("watch", "true")
|
|
|
|
values.Add("resourceVersion", kd.servicesResourceVersion)
|
|
|
|
req.URL.RawQuery = values.Encode()
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
res, err := kd.queryMasterReq(req)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch services: %s", err)
|
|
|
|
return
|
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
2015-08-26 00:04:01 +00:00
|
|
|
log.Errorf("Failed to watch services: %d", res.StatusCode)
|
2015-07-18 21:23:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
d := json.NewDecoder(res.Body)
|
|
|
|
|
|
|
|
for {
|
|
|
|
var event serviceEvent
|
|
|
|
if err := d.Decode(&event); err != nil {
|
|
|
|
log.Errorf("Unable to watch services: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
kd.servicesResourceVersion = event.Service.ObjectMeta.ResourceVersion
|
2015-08-14 11:04:23 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case events <- &event:
|
|
|
|
case <-done:
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
}, retryInterval, done)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) updateService(service *Service, eventType EventType) *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
kd.servicesMu.Lock()
|
|
|
|
defer kd.servicesMu.Unlock()
|
2015-08-14 11:04:23 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
name = service.ObjectMeta.Name
|
|
|
|
namespace = service.ObjectMeta.Namespace
|
|
|
|
_, exists = kd.services[namespace][name]
|
|
|
|
)
|
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
switch eventType {
|
|
|
|
case deleted:
|
2015-08-14 11:04:23 +00:00
|
|
|
if exists {
|
|
|
|
return kd.deleteService(service)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
case added, modified:
|
2015-08-14 11:04:23 +00:00
|
|
|
return kd.addService(service)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
return nil
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) deleteService(service *Service) *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
tg := &config.TargetGroup{Source: serviceSource(service)}
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
delete(kd.services[service.ObjectMeta.Namespace], service.ObjectMeta.Name)
|
|
|
|
if len(kd.services[service.ObjectMeta.Namespace]) == 0 {
|
|
|
|
delete(kd.services, service.ObjectMeta.Namespace)
|
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
|
|
|
|
return tg
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) addService(service *Service) *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
namespace, ok := kd.services[service.ObjectMeta.Namespace]
|
|
|
|
if !ok {
|
|
|
|
namespace = map[string]*Service{}
|
|
|
|
kd.services[service.ObjectMeta.Namespace] = namespace
|
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
namespace[service.ObjectMeta.Name] = service
|
|
|
|
endpointURL := fmt.Sprintf(serviceEndpointsURL, service.ObjectMeta.Namespace, service.ObjectMeta.Name)
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
res, err := kd.queryMasterPath(endpointURL)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error getting service endpoints: %s", err)
|
2015-08-14 11:04:23 +00:00
|
|
|
return nil
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
2015-08-26 00:04:01 +00:00
|
|
|
log.Errorf("Failed to get service endpoints: %d", res.StatusCode)
|
2015-08-14 11:04:23 +00:00
|
|
|
return nil
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
var eps Endpoints
|
|
|
|
if err := json.NewDecoder(res.Body).Decode(&eps); err != nil {
|
2015-07-18 21:23:58 +00:00
|
|
|
log.Errorf("Error getting service endpoints: %s", err)
|
2015-08-14 11:04:23 +00:00
|
|
|
return nil
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
return kd.updateServiceTargetGroup(service, &eps)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) updateServiceTargetGroup(service *Service, eps *Endpoints) *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
tg := &config.TargetGroup{
|
|
|
|
Source: serviceSource(service),
|
2015-08-20 15:18:46 +00:00
|
|
|
Labels: model.LabelSet{
|
|
|
|
serviceNamespaceLabel: model.LabelValue(service.ObjectMeta.Namespace),
|
|
|
|
serviceNameLabel: model.LabelValue(service.ObjectMeta.Name),
|
2015-09-03 09:47:09 +00:00
|
|
|
roleLabel: model.LabelValue("service"),
|
2015-07-18 21:23:58 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range service.ObjectMeta.Labels {
|
|
|
|
labelName := strutil.SanitizeLabelName(serviceLabelPrefix + k)
|
2015-08-20 15:18:46 +00:00
|
|
|
tg.Labels[model.LabelName(labelName)] = model.LabelValue(v)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range service.ObjectMeta.Annotations {
|
|
|
|
labelName := strutil.SanitizeLabelName(serviceAnnotationPrefix + k)
|
2015-08-20 15:18:46 +00:00
|
|
|
tg.Labels[model.LabelName(labelName)] = model.LabelValue(v)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now let's loop through the endpoints & add them to the target group with appropriate labels.
|
2015-08-24 13:07:27 +00:00
|
|
|
for _, ss := range eps.Subsets {
|
|
|
|
epPort := ss.Ports[0].Port
|
2015-07-18 21:23:58 +00:00
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
for _, addr := range ss.Addresses {
|
2015-07-18 21:23:58 +00:00
|
|
|
ipAddr := addr.IP
|
|
|
|
if len(ipAddr) == net.IPv6len {
|
|
|
|
ipAddr = "[" + ipAddr + "]"
|
|
|
|
}
|
|
|
|
address := fmt.Sprintf("%s:%d", ipAddr, epPort)
|
|
|
|
|
2015-08-20 15:18:46 +00:00
|
|
|
t := model.LabelSet{model.AddressLabel: model.LabelValue(address)}
|
2015-07-18 21:23:58 +00:00
|
|
|
|
|
|
|
tg.Targets = append(tg.Targets, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-14 11:04:23 +00:00
|
|
|
return tg
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// watchServiceEndpoints watches service endpoints as they come & go.
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) watchServiceEndpoints(events chan interface{}, done <-chan struct{}, retryInterval time.Duration) {
|
2015-07-18 21:23:58 +00:00
|
|
|
until(func() {
|
2015-09-03 09:47:09 +00:00
|
|
|
req, err := http.NewRequest("GET", endpointsURL, nil)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch service endpoints: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
values := req.URL.Query()
|
|
|
|
values.Add("watch", "true")
|
|
|
|
values.Add("resourceVersion", kd.servicesResourceVersion)
|
|
|
|
req.URL.RawQuery = values.Encode()
|
|
|
|
|
2015-09-03 09:47:09 +00:00
|
|
|
res, err := kd.queryMasterReq(req)
|
2015-07-18 21:23:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to watch service endpoints: %s", err)
|
|
|
|
return
|
|
|
|
}
|
2015-09-11 10:44:28 +00:00
|
|
|
defer res.Body.Close()
|
2015-07-18 21:23:58 +00:00
|
|
|
if res.StatusCode != http.StatusOK {
|
2015-08-26 00:04:01 +00:00
|
|
|
log.Errorf("Failed to watch service endpoints: %d", res.StatusCode)
|
2015-07-18 21:23:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
d := json.NewDecoder(res.Body)
|
|
|
|
|
|
|
|
for {
|
|
|
|
var event endpointsEvent
|
|
|
|
if err := d.Decode(&event); err != nil {
|
|
|
|
log.Errorf("Unable to watch service endpoints: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
kd.servicesResourceVersion = event.Endpoints.ObjectMeta.ResourceVersion
|
2015-08-14 11:04:23 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case events <- &event:
|
|
|
|
case <-done:
|
|
|
|
}
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
}, retryInterval, done)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 13:07:27 +00:00
|
|
|
func (kd *Discovery) updateServiceEndpoints(endpoints *Endpoints, eventType EventType) *config.TargetGroup {
|
2015-07-18 21:23:58 +00:00
|
|
|
kd.servicesMu.Lock()
|
|
|
|
defer kd.servicesMu.Unlock()
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
serviceNamespace := endpoints.ObjectMeta.Namespace
|
|
|
|
serviceName := endpoints.ObjectMeta.Name
|
2015-08-14 11:04:23 +00:00
|
|
|
|
2015-07-18 21:23:58 +00:00
|
|
|
if service, ok := kd.services[serviceNamespace][serviceName]; ok {
|
2015-08-14 11:04:23 +00:00
|
|
|
return kd.updateServiceTargetGroup(service, endpoints)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-08-14 11:04:23 +00:00
|
|
|
return nil
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newKubernetesHTTPClient(conf *config.KubernetesSDConfig) (*http.Client, error) {
|
|
|
|
bearerTokenFile := conf.BearerTokenFile
|
2015-09-06 23:07:44 +00:00
|
|
|
caFile := conf.TLSConfig.CAFile
|
2015-07-18 21:23:58 +00:00
|
|
|
if conf.InCluster {
|
|
|
|
if len(bearerTokenFile) == 0 {
|
|
|
|
bearerTokenFile = serviceAccountToken
|
|
|
|
}
|
|
|
|
if len(caFile) == 0 {
|
2015-09-29 08:38:58 +00:00
|
|
|
// With recent versions, the CA certificate is mounted as a secret
|
2015-07-18 21:23:58 +00:00
|
|
|
// but we need to handle older versions too. In this case, don't
|
2015-09-06 23:07:44 +00:00
|
|
|
// set the CAFile & the configuration will have to use InsecureSkipVerify.
|
2015-07-18 21:23:58 +00:00
|
|
|
if _, err := os.Stat(serviceAccountCACert); err == nil {
|
|
|
|
caFile = serviceAccountCACert
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-06 23:07:44 +00:00
|
|
|
tlsOpts := httputil.TLSOptions{
|
|
|
|
InsecureSkipVerify: conf.TLSConfig.InsecureSkipVerify,
|
|
|
|
CAFile: caFile,
|
|
|
|
CertFile: conf.TLSConfig.CertFile,
|
|
|
|
KeyFile: conf.TLSConfig.KeyFile,
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-09-06 23:07:44 +00:00
|
|
|
tlsConfig, err := httputil.NewTLSConfig(tlsOpts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-09-06 23:07:44 +00:00
|
|
|
var rt http.RoundTripper = &http.Transport{
|
2015-07-18 21:23:58 +00:00
|
|
|
Dial: func(netw, addr string) (c net.Conn, err error) {
|
|
|
|
c, err = net.DialTimeout(netw, addr, time.Duration(conf.RequestTimeout))
|
|
|
|
return
|
|
|
|
},
|
2015-09-06 23:07:44 +00:00
|
|
|
TLSClientConfig: tlsConfig,
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
2015-10-23 15:47:10 +00:00
|
|
|
// If a bearer token is provided, create a round tripper that will set the
|
|
|
|
// Authorization header correctly on each request.
|
|
|
|
bearerToken := conf.BearerToken
|
|
|
|
if len(bearerToken) == 0 && len(bearerTokenFile) > 0 {
|
|
|
|
b, err := ioutil.ReadFile(bearerTokenFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read bearer token file %s: %s", bearerTokenFile, err)
|
|
|
|
}
|
|
|
|
bearerToken = string(b)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
if len(bearerToken) > 0 {
|
2015-10-23 15:47:10 +00:00
|
|
|
rt = httputil.NewBearerAuthRoundTripper(bearerToken, rt)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
2015-10-23 15:47:10 +00:00
|
|
|
|
|
|
|
if conf.BasicAuth != nil {
|
|
|
|
rt = httputil.NewBasicAuthRoundTripper(conf.BasicAuth.Username, conf.BasicAuth.Password, rt)
|
2015-07-18 21:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &http.Client{
|
|
|
|
Transport: rt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func serviceSource(service *Service) string {
|
|
|
|
return sourceServicePrefix + ":" + service.ObjectMeta.Namespace + "/" + service.ObjectMeta.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Until loops until stop channel is closed, running f every period.
|
|
|
|
// f may not be invoked if stop channel is already closed.
|
|
|
|
func until(f func(), period time.Duration, stopCh <-chan struct{}) {
|
|
|
|
select {
|
|
|
|
case <-stopCh:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
f()
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-stopCh:
|
|
|
|
return
|
|
|
|
case <-time.After(period):
|
|
|
|
f()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|