1// Copyright 2015 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package gce 15 16import ( 17 "fmt" 18 "net/http" 19 "strings" 20 "time" 21 22 "google.golang.org/api/compute/v1" 23 24 "github.com/prometheus/client_golang/prometheus" 25 "github.com/prometheus/common/log" 26 "github.com/prometheus/common/model" 27 "golang.org/x/net/context" 28 "golang.org/x/oauth2" 29 "golang.org/x/oauth2/google" 30 31 "github.com/prometheus/prometheus/config" 32 "github.com/prometheus/prometheus/util/strutil" 33) 34 35const ( 36 gceLabel = model.MetaLabelPrefix + "gce_" 37 gceLabelProject = gceLabel + "project" 38 gceLabelZone = gceLabel + "zone" 39 gceLabelNetwork = gceLabel + "network" 40 gceLabelSubnetwork = gceLabel + "subnetwork" 41 gceLabelPublicIP = gceLabel + "public_ip" 42 gceLabelPrivateIP = gceLabel + "private_ip" 43 gceLabelInstanceName = gceLabel + "instance_name" 44 gceLabelInstanceStatus = gceLabel + "instance_status" 45 gceLabelTags = gceLabel + "tags" 46 gceLabelMetadata = gceLabel + "metadata_" 47) 48 49var ( 50 gceSDRefreshFailuresCount = prometheus.NewCounter( 51 prometheus.CounterOpts{ 52 Name: "prometheus_sd_gce_refresh_failures_total", 53 Help: "The number of GCE-SD refresh failures.", 54 }) 55 gceSDRefreshDuration = prometheus.NewSummary( 56 prometheus.SummaryOpts{ 57 Name: "prometheus_sd_gce_refresh_duration", 58 Help: "The duration of a GCE-SD refresh in seconds.", 59 }) 60) 61 62func init() { 63 prometheus.MustRegister(gceSDRefreshFailuresCount) 64 prometheus.MustRegister(gceSDRefreshDuration) 65} 66 67// Discovery periodically performs GCE-SD requests. It implements 68// the TargetProvider interface. 69type Discovery struct { 70 project string 71 zone string 72 filter string 73 client *http.Client 74 svc *compute.Service 75 isvc *compute.InstancesService 76 interval time.Duration 77 port int 78 tagSeparator string 79 logger log.Logger 80} 81 82// NewDiscovery returns a new Discovery which periodically refreshes its targets. 83func NewDiscovery(conf *config.GCESDConfig, logger log.Logger) (*Discovery, error) { 84 gd := &Discovery{ 85 project: conf.Project, 86 zone: conf.Zone, 87 filter: conf.Filter, 88 interval: time.Duration(conf.RefreshInterval), 89 port: conf.Port, 90 tagSeparator: conf.TagSeparator, 91 logger: logger, 92 } 93 var err error 94 gd.client, err = google.DefaultClient(oauth2.NoContext, compute.ComputeReadonlyScope) 95 if err != nil { 96 return nil, fmt.Errorf("error setting up communication with GCE service: %s", err) 97 } 98 gd.svc, err = compute.New(gd.client) 99 if err != nil { 100 return nil, fmt.Errorf("error setting up communication with GCE service: %s", err) 101 } 102 gd.isvc = compute.NewInstancesService(gd.svc) 103 return gd, nil 104} 105 106// Run implements the TargetProvider interface. 107func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) { 108 // Get an initial set right away. 109 tg, err := d.refresh() 110 if err != nil { 111 d.logger.Error(err) 112 } else { 113 select { 114 case ch <- []*config.TargetGroup{tg}: 115 case <-ctx.Done(): 116 } 117 } 118 119 ticker := time.NewTicker(d.interval) 120 defer ticker.Stop() 121 122 for { 123 select { 124 case <-ticker.C: 125 tg, err := d.refresh() 126 if err != nil { 127 d.logger.Error(err) 128 continue 129 } 130 select { 131 case ch <- []*config.TargetGroup{tg}: 132 case <-ctx.Done(): 133 } 134 case <-ctx.Done(): 135 return 136 } 137 } 138} 139 140func (d *Discovery) refresh() (tg *config.TargetGroup, err error) { 141 t0 := time.Now() 142 defer func() { 143 gceSDRefreshDuration.Observe(time.Since(t0).Seconds()) 144 if err != nil { 145 gceSDRefreshFailuresCount.Inc() 146 } 147 }() 148 149 tg = &config.TargetGroup{ 150 Source: fmt.Sprintf("GCE_%s_%s", d.project, d.zone), 151 } 152 153 ilc := d.isvc.List(d.project, d.zone) 154 if len(d.filter) > 0 { 155 ilc = ilc.Filter(d.filter) 156 } 157 err = ilc.Pages(nil, func(l *compute.InstanceList) error { 158 for _, inst := range l.Items { 159 if len(inst.NetworkInterfaces) == 0 { 160 continue 161 } 162 labels := model.LabelSet{ 163 gceLabelProject: model.LabelValue(d.project), 164 gceLabelZone: model.LabelValue(inst.Zone), 165 gceLabelInstanceName: model.LabelValue(inst.Name), 166 gceLabelInstanceStatus: model.LabelValue(inst.Status), 167 } 168 priIface := inst.NetworkInterfaces[0] 169 labels[gceLabelNetwork] = model.LabelValue(priIface.Network) 170 labels[gceLabelSubnetwork] = model.LabelValue(priIface.Subnetwork) 171 labels[gceLabelPrivateIP] = model.LabelValue(priIface.NetworkIP) 172 addr := fmt.Sprintf("%s:%d", priIface.NetworkIP, d.port) 173 labels[model.AddressLabel] = model.LabelValue(addr) 174 175 // Tags in GCE are usually only used for networking rules. 176 if inst.Tags != nil && len(inst.Tags.Items) > 0 { 177 // We surround the separated list with the separator as well. This way regular expressions 178 // in relabeling rules don't have to consider tag positions. 179 tags := d.tagSeparator + strings.Join(inst.Tags.Items, d.tagSeparator) + d.tagSeparator 180 labels[gceLabelTags] = model.LabelValue(tags) 181 } 182 183 // GCE metadata are key-value pairs for user supplied attributes. 184 if inst.Metadata != nil { 185 for _, i := range inst.Metadata.Items { 186 // Protect against occasional nil pointers. 187 if i.Value == nil { 188 continue 189 } 190 name := strutil.SanitizeLabelName(i.Key) 191 labels[gceLabelMetadata+model.LabelName(name)] = model.LabelValue(*i.Value) 192 } 193 } 194 195 if len(priIface.AccessConfigs) > 0 { 196 ac := priIface.AccessConfigs[0] 197 if ac.Type == "ONE_TO_ONE_NAT" { 198 labels[gceLabelPublicIP] = model.LabelValue(ac.NatIP) 199 } 200 } 201 tg.Targets = append(tg.Targets, labels) 202 } 203 return nil 204 }) 205 if err != nil { 206 return tg, fmt.Errorf("error retrieving refresh targets from gce: %s", err) 207 } 208 return tg, nil 209} 210