2016-02-22 00:13:08 +00:00
|
|
|
/*
|
2016-07-12 03:42:47 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2016-02-22 00:13:08 +00:00
|
|
|
|
|
|
|
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 gce
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2017-05-21 00:11:38 +00:00
|
|
|
"net/http"
|
2016-02-22 00:13:08 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
"cloud.google.com/go/compute/metadata"
|
|
|
|
|
2016-11-10 22:57:28 +00:00
|
|
|
"gopkg.in/gcfg.v1"
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
"k8s.io/client-go/util/flowcontrol"
|
2016-02-22 00:13:08 +00:00
|
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
2017-05-21 00:11:38 +00:00
|
|
|
"k8s.io/kubernetes/pkg/controller"
|
2016-02-22 00:13:08 +00:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
"golang.org/x/oauth2/google"
|
2017-05-21 00:11:38 +00:00
|
|
|
computebeta "google.golang.org/api/compute/v0.beta"
|
2016-02-22 00:13:08 +00:00
|
|
|
compute "google.golang.org/api/compute/v1"
|
|
|
|
container "google.golang.org/api/container/v1"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ProviderName = "gce"
|
|
|
|
|
|
|
|
k8sNodeRouteTag = "k8s-node-route"
|
|
|
|
|
|
|
|
// AffinityTypeNone - no session affinity.
|
|
|
|
gceAffinityTypeNone = "NONE"
|
|
|
|
// AffinityTypeClientIP - affinity based on Client IP.
|
|
|
|
gceAffinityTypeClientIP = "CLIENT_IP"
|
|
|
|
// AffinityTypeClientIPProto - affinity based on Client IP and port.
|
|
|
|
gceAffinityTypeClientIPProto = "CLIENT_IP_PROTO"
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
operationPollInterval = 3 * time.Second
|
|
|
|
// Creating Route in very large clusters, may take more than half an hour.
|
|
|
|
operationPollTimeoutDuration = time.Hour
|
2016-03-19 23:00:11 +00:00
|
|
|
|
|
|
|
// Each page can have 500 results, but we cap how many pages
|
|
|
|
// are iterated through to prevent infinite loops if the API
|
|
|
|
// were to continuously return a nextPageToken.
|
|
|
|
maxPages = 25
|
2016-05-10 13:30:56 +00:00
|
|
|
|
2016-07-12 03:42:47 +00:00
|
|
|
maxTargetPoolCreateInstances = 200
|
2016-08-10 18:53:55 +00:00
|
|
|
|
|
|
|
// HTTP Load Balancer parameters
|
|
|
|
// Configure 2 second period for external health checks.
|
|
|
|
gceHcCheckIntervalSeconds = int64(2)
|
|
|
|
gceHcTimeoutSeconds = int64(1)
|
|
|
|
// Start sending requests as soon as a pod is found on the node.
|
|
|
|
gceHcHealthyThreshold = int64(1)
|
|
|
|
// Defaults to 5 * 2 = 10 seconds before the LB will steer traffic away
|
|
|
|
gceHcUnhealthyThreshold = int64(5)
|
2016-02-22 00:13:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// GCECloud is an implementation of Interface, LoadBalancer and Instances for Google Compute Engine.
|
|
|
|
type GCECloud struct {
|
2016-04-17 20:19:22 +00:00
|
|
|
service *compute.Service
|
2017-05-21 00:11:38 +00:00
|
|
|
serviceBeta *computebeta.Service
|
2016-04-17 20:19:22 +00:00
|
|
|
containerService *container.Service
|
|
|
|
projectID string
|
|
|
|
region string
|
|
|
|
localZone string // The zone in which we are running
|
2016-08-10 18:53:55 +00:00
|
|
|
managedZones []string // List of zones we are spanning (for multi-AZ clusters, primarily when running on master)
|
2016-04-17 20:19:22 +00:00
|
|
|
networkURL string
|
2016-05-09 18:53:58 +00:00
|
|
|
nodeTags []string // List of tags to use on firewall rules for load balancers
|
2016-06-21 18:58:43 +00:00
|
|
|
nodeInstancePrefix string // If non-"", an advisory prefix for all nodes in the cluster
|
2016-04-17 20:19:22 +00:00
|
|
|
useMetadataServer bool
|
|
|
|
operationPollRateLimiter flowcontrol.RateLimiter
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
Global struct {
|
2016-06-21 18:58:43 +00:00
|
|
|
TokenURL string `gcfg:"token-url"`
|
|
|
|
TokenBody string `gcfg:"token-body"`
|
|
|
|
ProjectID string `gcfg:"project-id"`
|
|
|
|
NetworkName string `gcfg:"network-name"`
|
|
|
|
NodeTags []string `gcfg:"node-tags"`
|
|
|
|
NodeInstancePrefix string `gcfg:"node-instance-prefix"`
|
|
|
|
Multizone bool `gcfg:"multizone"`
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2017-04-01 14:42:02 +00:00
|
|
|
cloudprovider.RegisterCloudProvider(
|
|
|
|
ProviderName,
|
|
|
|
func(config io.Reader) (cloudprovider.Interface, error) {
|
|
|
|
return newGCECloud(config)
|
|
|
|
})
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Raw access to the underlying GCE service, probably should only be used for e2e tests
|
|
|
|
func (g *GCECloud) GetComputeService() *compute.Service {
|
|
|
|
return g.service
|
|
|
|
}
|
|
|
|
|
|
|
|
// newGCECloud creates a new instance of GCECloud.
|
|
|
|
func newGCECloud(config io.Reader) (*GCECloud, error) {
|
|
|
|
projectID, zone, err := getProjectAndZone()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
region, err := GetGCERegion(zone)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
networkName, err := getNetworkNameViaMetadata()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
networkURL := gceNetworkURL(projectID, networkName)
|
|
|
|
|
|
|
|
// By default, Kubernetes clusters only run against one zone
|
|
|
|
managedZones := []string{zone}
|
|
|
|
|
|
|
|
tokenSource := google.ComputeTokenSource("")
|
2016-05-09 18:53:58 +00:00
|
|
|
var nodeTags []string
|
2016-06-21 18:58:43 +00:00
|
|
|
var nodeInstancePrefix string
|
2016-02-22 00:13:08 +00:00
|
|
|
if config != nil {
|
|
|
|
var cfg Config
|
|
|
|
if err := gcfg.ReadInto(&cfg, config); err != nil {
|
|
|
|
glog.Errorf("Couldn't read config: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-05-09 18:53:58 +00:00
|
|
|
glog.Infof("Using GCE provider config %+v", cfg)
|
2016-02-22 00:13:08 +00:00
|
|
|
if cfg.Global.ProjectID != "" {
|
|
|
|
projectID = cfg.Global.ProjectID
|
|
|
|
}
|
|
|
|
if cfg.Global.NetworkName != "" {
|
|
|
|
if strings.Contains(cfg.Global.NetworkName, "/") {
|
|
|
|
networkURL = cfg.Global.NetworkName
|
|
|
|
} else {
|
|
|
|
networkURL = gceNetworkURL(cfg.Global.ProjectID, cfg.Global.NetworkName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if cfg.Global.TokenURL != "" {
|
2016-06-21 18:58:43 +00:00
|
|
|
tokenSource = NewAltTokenSource(cfg.Global.TokenURL, cfg.Global.TokenBody)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
2016-05-09 18:53:58 +00:00
|
|
|
nodeTags = cfg.Global.NodeTags
|
2016-06-21 18:58:43 +00:00
|
|
|
nodeInstancePrefix = cfg.Global.NodeInstancePrefix
|
2016-02-22 00:13:08 +00:00
|
|
|
if cfg.Global.Multizone {
|
|
|
|
managedZones = nil // Use all zones in region
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
return CreateGCECloud(projectID, region, zone, managedZones, networkURL, nodeTags,
|
|
|
|
nodeInstancePrefix, tokenSource, true /* useMetadataServer */)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a GCECloud object using the specified parameters.
|
|
|
|
// If no networkUrl is specified, loads networkName via rest call.
|
|
|
|
// If no tokenSource is specified, uses oauth2.DefaultTokenSource.
|
|
|
|
// If managedZones is nil / empty all zones in the region will be managed.
|
2017-04-01 14:42:02 +00:00
|
|
|
func CreateGCECloud(projectID, region, zone string, managedZones []string, networkURL string, nodeTags []string,
|
|
|
|
nodeInstancePrefix string, tokenSource oauth2.TokenSource, useMetadataServer bool) (*GCECloud, error) {
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
client, err := newOauthClient(tokenSource)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
service, err := compute.New(client)
|
|
|
|
if err != nil {
|
2016-09-21 23:00:42 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
client, err = newOauthClient(tokenSource)
|
|
|
|
serviceBeta, err := computebeta.New(client)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
containerService, err := container.New(client)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if networkURL == "" {
|
2017-05-21 00:11:38 +00:00
|
|
|
networkName, err := getNetworkNameViaAPICall(service, projectID)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
networkURL = gceNetworkURL(projectID, networkName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(managedZones) == 0 {
|
2017-05-21 00:11:38 +00:00
|
|
|
managedZones, err = getZonesForRegion(service, projectID, region)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(managedZones) != 1 {
|
|
|
|
glog.Infof("managing multiple zones: %v", managedZones)
|
|
|
|
}
|
|
|
|
|
2016-04-17 20:19:22 +00:00
|
|
|
operationPollRateLimiter := flowcontrol.NewTokenBucketRateLimiter(10, 100) // 10 qps, 100 bucket size.
|
|
|
|
|
2016-02-22 00:13:08 +00:00
|
|
|
return &GCECloud{
|
2017-05-21 00:11:38 +00:00
|
|
|
service: service,
|
|
|
|
serviceBeta: serviceBeta,
|
|
|
|
containerService: containerService,
|
2016-04-17 20:19:22 +00:00
|
|
|
projectID: projectID,
|
|
|
|
region: region,
|
|
|
|
localZone: zone,
|
|
|
|
managedZones: managedZones,
|
|
|
|
networkURL: networkURL,
|
2016-05-09 18:53:58 +00:00
|
|
|
nodeTags: nodeTags,
|
2016-06-21 18:58:43 +00:00
|
|
|
nodeInstancePrefix: nodeInstancePrefix,
|
2016-04-17 20:19:22 +00:00
|
|
|
useMetadataServer: useMetadataServer,
|
|
|
|
operationPollRateLimiter: operationPollRateLimiter,
|
2016-02-22 00:13:08 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
|
|
|
|
func (gce *GCECloud) Initialize(clientBuilder controller.ControllerClientBuilder) {}
|
|
|
|
|
2016-02-22 00:13:08 +00:00
|
|
|
// LoadBalancer returns an implementation of LoadBalancer for Google Compute Engine.
|
|
|
|
func (gce *GCECloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
|
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instances returns an implementation of Instances for Google Compute Engine.
|
|
|
|
func (gce *GCECloud) Instances() (cloudprovider.Instances, bool) {
|
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Zones returns an implementation of Zones for Google Compute Engine.
|
|
|
|
func (gce *GCECloud) Zones() (cloudprovider.Zones, bool) {
|
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
func (gce *GCECloud) Clusters() (cloudprovider.Clusters, bool) {
|
2016-02-22 00:13:08 +00:00
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// Routes returns an implementation of Routes for Google Compute Engine.
|
|
|
|
func (gce *GCECloud) Routes() (cloudprovider.Routes, bool) {
|
|
|
|
return gce, true
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// ProviderName returns the cloud provider ID.
|
|
|
|
func (gce *GCECloud) ProviderName() string {
|
|
|
|
return ProviderName
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// Known-useless DNS search path.
|
|
|
|
var uselessDNSSearchRE = regexp.MustCompile(`^[0-9]+.google.internal.$`)
|
2016-02-22 00:13:08 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// ScrubDNS filters DNS settings for pods.
|
|
|
|
func (gce *GCECloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
|
|
|
|
// GCE has too many search paths by default. Filter the ones we know are useless.
|
|
|
|
for _, s := range searches {
|
|
|
|
if !uselessDNSSearchRE.MatchString(s) {
|
|
|
|
srchOut = append(srchOut, s)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-01 14:42:02 +00:00
|
|
|
return nameservers, srchOut
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// GCECloud implements cloudprovider.Interface.
|
|
|
|
var _ cloudprovider.Interface = (*GCECloud)(nil)
|
2016-02-22 00:13:08 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
func gceNetworkURL(project, network string) string {
|
|
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", project, network)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
func getNetworkNameViaMetadata() (string, error) {
|
|
|
|
result, err := metadata.Get("instance/network-interfaces/0/network")
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
2017-04-01 14:42:02 +00:00
|
|
|
return "", err
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
2017-04-01 14:42:02 +00:00
|
|
|
parts := strings.Split(result, "/")
|
|
|
|
if len(parts) != 4 {
|
|
|
|
return "", fmt.Errorf("unexpected response: %s", result)
|
2016-04-17 20:19:22 +00:00
|
|
|
}
|
2017-04-01 14:42:02 +00:00
|
|
|
return parts[3], nil
|
|
|
|
}
|
2016-04-17 20:19:22 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
func getNetworkNameViaAPICall(svc *compute.Service, projectID string) (string, error) {
|
|
|
|
// TODO: use PageToken to list all not just the first 500
|
|
|
|
networkList, err := svc.Networks.List(projectID).Do()
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
2017-04-01 14:42:02 +00:00
|
|
|
return "", err
|
2016-08-10 18:53:55 +00:00
|
|
|
}
|
2016-02-22 00:13:08 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
if networkList == nil || len(networkList.Items) <= 0 {
|
2017-05-21 00:11:38 +00:00
|
|
|
return "", fmt.Errorf("GCE Network List call returned no networks for project %q", projectID)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
return networkList.Items[0].Name, nil
|
|
|
|
}
|
2016-03-19 23:00:11 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
func getZonesForRegion(svc *compute.Service, projectID, region string) ([]string, error) {
|
|
|
|
// TODO: use PageToken to list all not just the first 500
|
|
|
|
listCall := svc.Zones.List(projectID)
|
2016-02-22 00:13:08 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
// Filtering by region doesn't seem to work
|
|
|
|
// (tested in https://cloud.google.com/compute/docs/reference/latest/zones/list)
|
|
|
|
// listCall = listCall.Filter("region eq " + region)
|
2016-02-22 00:13:08 +00:00
|
|
|
|
2017-04-01 14:42:02 +00:00
|
|
|
res, err := listCall.Do()
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
2017-04-01 14:42:02 +00:00
|
|
|
return nil, fmt.Errorf("unexpected response listing zones: %v", err)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
2017-04-01 14:42:02 +00:00
|
|
|
zones := []string{}
|
|
|
|
for _, zone := range res.Items {
|
|
|
|
regionName := lastComponent(zone.Region)
|
|
|
|
if regionName == region {
|
|
|
|
zones = append(zones, zone.Name)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-01 14:42:02 +00:00
|
|
|
return zones, nil
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
func newOauthClient(tokenSource oauth2.TokenSource) (*http.Client, error) {
|
|
|
|
if tokenSource == nil {
|
|
|
|
var err error
|
|
|
|
tokenSource, err = google.DefaultTokenSource(
|
|
|
|
oauth2.NoContext,
|
|
|
|
compute.CloudPlatformScope,
|
|
|
|
compute.ComputeScope)
|
|
|
|
glog.Infof("Using DefaultTokenSource %#v", tokenSource)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
glog.Infof("Using existing Token Source %#v", tokenSource)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := wait.PollImmediate(5*time.Second, 30*time.Second, func() (bool, error) {
|
|
|
|
if _, err := tokenSource.Token(); err != nil {
|
|
|
|
glog.Errorf("error fetching initial token: %v", err)
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-08-10 18:53:55 +00:00
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
return oauth2.NewClient(oauth2.NoContext, tokenSource), nil
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|