2015-01-30 23:31:36 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-01-30 23:31:36 +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 .
* /
2015-02-08 04:07:00 +00:00
// Package app implements a server that runs a set of active
2015-01-30 23:31:36 +00:00
// components. This includes replication controllers, service endpoints and
// nodes.
2015-06-11 13:13:19 +00:00
//
2015-02-08 04:07:00 +00:00
package app
2015-01-30 23:31:36 +00:00
import (
2015-06-23 22:43:59 +00:00
"fmt"
"io/ioutil"
2015-10-06 09:12:00 +00:00
"math/rand"
2015-01-30 23:31:36 +00:00
"net"
"net/http"
2015-03-13 15:44:11 +00:00
"net/http/pprof"
2015-11-04 23:22:11 +00:00
"os"
2017-02-28 18:43:08 +00:00
goruntime "runtime"
2015-01-30 23:31:36 +00:00
"strconv"
"time"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2017-07-24 13:56:31 +00:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2017-01-10 20:11:25 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2017-03-08 07:03:57 +00:00
2017-01-17 10:38:25 +00:00
"k8s.io/apiserver/pkg/server/healthz"
2017-03-08 07:03:57 +00:00
2017-07-15 05:25:54 +00:00
"k8s.io/api/core/v1"
2017-01-25 19:00:30 +00:00
"k8s.io/client-go/discovery"
2017-01-30 18:39:54 +00:00
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
2017-01-19 18:27:59 +00:00
restclient "k8s.io/client-go/rest"
2017-01-20 18:06:17 +00:00
"k8s.io/client-go/tools/clientcmd"
2017-01-30 18:39:54 +00:00
"k8s.io/client-go/tools/record"
2017-01-23 18:37:22 +00:00
certutil "k8s.io/client-go/util/cert"
2017-03-08 07:03:57 +00:00
2017-06-23 20:56:37 +00:00
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
2017-07-07 20:59:32 +00:00
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
2015-12-24 23:59:05 +00:00
"k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
2017-10-16 11:41:50 +00:00
"k8s.io/kubernetes/pkg/api/legacyscheme"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/cloudprovider"
2015-11-11 21:19:39 +00:00
"k8s.io/kubernetes/pkg/controller"
2015-12-24 21:54:40 +00:00
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/serviceaccount"
2016-02-02 02:09:02 +00:00
"k8s.io/kubernetes/pkg/util/configz"
2017-06-01 21:30:34 +00:00
"k8s.io/kubernetes/pkg/version"
2015-04-18 13:31:24 +00:00
2015-01-30 23:31:36 +00:00
"github.com/golang/glog"
2015-04-22 14:46:03 +00:00
"github.com/prometheus/client_golang/prometheus"
2015-10-12 14:33:39 +00:00
"github.com/spf13/cobra"
2015-01-30 23:31:36 +00:00
"github.com/spf13/pflag"
)
2016-03-30 14:07:30 +00:00
const (
// Jitter used when starting controller managers
ControllerStartJitter = 1.0
)
2017-09-12 22:47:03 +00:00
type ControllerLoopMode int
const (
IncludeCloudLoops ControllerLoopMode = iota
ExternalLoops
)
2015-10-12 14:33:39 +00:00
// NewControllerManagerCommand creates a *cobra.Command object with default parameters
func NewControllerManagerCommand ( ) * cobra . Command {
2015-12-24 23:59:05 +00:00
s := options . NewCMServer ( )
2017-01-10 20:11:25 +00:00
s . AddFlags ( pflag . CommandLine , KnownControllers ( ) , ControllersDisabledByDefault . List ( ) )
2015-10-12 14:33:39 +00:00
cmd := & cobra . Command {
Use : "kube-controller-manager" ,
Long : ` The Kubernetes controller manager is a daemon that embeds
the core control loops shipped with Kubernetes . In applications of robotics and
automation , a control loop is a non - terminating loop that regulates the state of
the system . In Kubernetes , a controller is a control loop that watches the shared
state of the cluster through the apiserver and makes changes attempting to move the
current state towards the desired state . Examples of controllers that ship with
Kubernetes today are the replication controller , endpoints controller , namespace
controller , and serviceaccounts controller . ` ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
} ,
}
return cmd
}
2017-02-09 02:02:55 +00:00
// ResyncPeriod returns a function which generates a duration each time it is
// invoked; this is so that multiple controllers don't get into lock-step and all
// hammer the apiserver with list requests simultaneously.
2015-12-24 23:59:05 +00:00
func ResyncPeriod ( s * options . CMServer ) func ( ) time . Duration {
return func ( ) time . Duration {
factor := rand . Float64 ( ) + 1
return time . Duration ( float64 ( s . MinResyncPeriod . Nanoseconds ( ) ) * factor )
}
2015-10-06 09:12:00 +00:00
}
2015-01-30 23:31:36 +00:00
// Run runs the CMServer. This should never exit.
2015-12-24 23:59:05 +00:00
func Run ( s * options . CMServer ) error {
2017-06-01 21:30:34 +00:00
// To help debugging, immediately log version
glog . Infof ( "Version: %+v" , version . Get ( ) )
2017-01-10 20:11:25 +00:00
if err := s . Validate ( KnownControllers ( ) , ControllersDisabledByDefault . List ( ) ) ; err != nil {
return err
}
2016-02-02 02:09:02 +00:00
if c , err := configz . New ( "componentconfig" ) ; err == nil {
c . Set ( s . KubeControllerManagerConfiguration )
} else {
glog . Errorf ( "unable to register configz: %s" , err )
}
2017-08-28 10:15:49 +00:00
kubeClient , leaderElectionClient , kubeconfig , err := createClients ( s )
2015-04-17 07:18:07 +00:00
if err != nil {
return err
}
2017-08-28 10:15:49 +00:00
go startHTTP ( s )
2015-01-30 23:31:36 +00:00
2017-08-28 10:15:49 +00:00
recorder := createRecorder ( kubeClient )
2016-06-21 16:13:23 +00:00
2015-11-04 23:22:11 +00:00
run := func ( stop <- chan struct { } ) {
2016-09-22 19:53:08 +00:00
rootClientBuilder := controller . SimpleControllerClientBuilder {
ClientConfig : kubeconfig ,
}
var clientBuilder controller . ControllerClientBuilder
2017-08-08 04:48:17 +00:00
if s . UseServiceAccountCredentials {
2017-08-21 17:51:47 +00:00
if len ( s . ServiceAccountKeyFile ) == 0 {
2017-08-08 04:48:17 +00:00
// It's possible another controller process is creating the tokens for us.
// If one isn't, we'll timeout and exit when our client builder is unable to create the tokens.
glog . Warningf ( "--use-service-account-credentials was specified without providing a --service-account-private-key-file" )
}
2016-09-22 19:53:08 +00:00
clientBuilder = controller . SAControllerClientBuilder {
2017-02-17 20:48:22 +00:00
ClientConfig : restclient . AnonymousClientConfig ( kubeconfig ) ,
2017-07-25 04:35:12 +00:00
CoreClient : kubeClient . CoreV1 ( ) ,
2017-11-27 14:13:19 +00:00
AuthenticationClient : kubeClient . AuthenticationV1 ( ) ,
2017-02-17 20:48:22 +00:00
Namespace : "kube-system" ,
2016-09-22 19:53:08 +00:00
}
} else {
clientBuilder = rootClientBuilder
}
2017-06-01 16:03:18 +00:00
ctx , err := CreateControllerContext ( s , rootClientBuilder , clientBuilder , stop )
if err != nil {
glog . Fatalf ( "error building controller context: %v" , err )
}
saTokenControllerInitFunc := serviceAccountTokenControllerStarter { rootClientBuilder : rootClientBuilder } . startServiceAccountTokenController
2016-09-22 19:53:08 +00:00
2017-09-12 22:47:03 +00:00
if err := StartControllers ( ctx , saTokenControllerInitFunc , NewControllerInitializers ( ctx . LoopMode ) ) ; err != nil {
2017-06-01 16:03:18 +00:00
glog . Fatalf ( "error starting controllers: %v" , err )
}
ctx . InformerFactory . Start ( ctx . Stop )
2017-08-24 16:39:55 +00:00
close ( ctx . InformersStarted )
2017-06-01 16:03:18 +00:00
select { }
2015-11-04 23:22:11 +00:00
}
if ! s . LeaderElection . LeaderElect {
Fix garbage collector when leader-elect=false
**What this PR does / why we need it**:
In a 1.8.x master with --leader-elect=false, the garbage collector controller
does not work.
When deleting a deployment with v1meta.DeletePropagationForeground, the deployment
had its deletionTimestamp set and a foreground Deletion finalizer was added,
but the deployment, rs and pod were not deleted.
This is an issue with how the garbage collector graph_builder behaves when the
stopCh=nil. This PR creates a dummy stop channel for the garbage collector controller (and other
controllers started by the controller-manager) so that they can work more like they do when
when the controller-manager is configured with --leader-elect=true.
**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #57044
**Special notes for your reviewer**:
**Release note**:
<!-- Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
Garbage collection doesn't work when the controller-manager uses --leader-elect=false
```
2017-12-18 17:39:51 +00:00
stopCh := make ( chan struct { } )
defer close ( stopCh )
run ( stopCh )
2015-11-04 23:22:11 +00:00
panic ( "unreachable" )
}
id , err := os . Hostname ( )
if err != nil {
return err
}
2017-05-12 19:13:37 +00:00
rl , err := resourcelock . New ( s . LeaderElection . ResourceLock ,
"kube-system" ,
"kube-controller-manager" ,
2017-07-25 04:35:12 +00:00
leaderElectionClient . CoreV1 ( ) ,
2017-05-12 19:13:37 +00:00
resourcelock . ResourceLockConfig {
2016-09-15 19:17:18 +00:00
Identity : id ,
EventRecorder : recorder ,
2017-05-12 19:13:37 +00:00
} )
2017-05-05 12:01:59 +00:00
if err != nil {
2017-05-12 19:13:37 +00:00
glog . Fatalf ( "error creating lock: %v" , err )
2016-09-15 19:17:18 +00:00
}
leaderelection . RunOrDie ( leaderelection . LeaderElectionConfig {
2017-05-05 12:01:59 +00:00
Lock : rl ,
2016-09-26 07:46:59 +00:00
LeaseDuration : s . LeaderElection . LeaseDuration . Duration ,
RenewDeadline : s . LeaderElection . RenewDeadline . Duration ,
RetryPeriod : s . LeaderElection . RetryPeriod . Duration ,
2015-11-04 23:22:11 +00:00
Callbacks : leaderelection . LeaderCallbacks {
OnStartedLeading : run ,
OnStoppedLeading : func ( ) {
glog . Fatalf ( "leaderelection lost" )
} ,
} ,
} )
panic ( "unreachable" )
}
2017-08-28 10:15:49 +00:00
func startHTTP ( s * options . CMServer ) {
mux := http . NewServeMux ( )
healthz . InstallHandler ( mux )
if s . EnableProfiling {
mux . HandleFunc ( "/debug/pprof/" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/profile" , pprof . Profile )
mux . HandleFunc ( "/debug/pprof/symbol" , pprof . Symbol )
mux . HandleFunc ( "/debug/pprof/trace" , pprof . Trace )
if s . EnableContentionProfiling {
goruntime . SetBlockProfileRate ( 1 )
}
}
configz . InstallHandler ( mux )
mux . Handle ( "/metrics" , prometheus . Handler ( ) )
server := & http . Server {
Addr : net . JoinHostPort ( s . Address , strconv . Itoa ( int ( s . Port ) ) ) ,
Handler : mux ,
}
glog . Fatal ( server . ListenAndServe ( ) )
}
func createRecorder ( kubeClient * clientset . Clientset ) record . EventRecorder {
eventBroadcaster := record . NewBroadcaster ( )
eventBroadcaster . StartLogging ( glog . Infof )
eventBroadcaster . StartRecordingToSink ( & v1core . EventSinkImpl { Interface : v1core . New ( kubeClient . CoreV1 ( ) . RESTClient ( ) ) . Events ( "" ) } )
2017-10-16 11:41:50 +00:00
return eventBroadcaster . NewRecorder ( legacyscheme . Scheme , v1 . EventSource { Component : "controller-manager" } )
2017-08-28 10:15:49 +00:00
}
func createClients ( s * options . CMServer ) ( * clientset . Clientset , * clientset . Clientset , * restclient . Config , error ) {
kubeconfig , err := clientcmd . BuildConfigFromFlags ( s . Master , s . Kubeconfig )
if err != nil {
return nil , nil , nil , err
}
kubeconfig . ContentConfig . ContentType = s . ContentType
// Override kubeconfig qps/burst settings from flags
kubeconfig . QPS = s . KubeAPIQPS
kubeconfig . Burst = int ( s . KubeAPIBurst )
kubeClient , err := clientset . NewForConfig ( restclient . AddUserAgent ( kubeconfig , "controller-manager" ) )
if err != nil {
glog . Fatalf ( "Invalid API configuration: %v" , err )
}
2017-09-13 03:22:50 +00:00
leaderElectionClient := clientset . NewForConfigOrDie ( restclient . AddUserAgent ( kubeconfig , "leader-election" ) )
2017-08-28 10:15:49 +00:00
return kubeClient , leaderElectionClient , kubeconfig , nil
}
2016-12-02 19:18:16 +00:00
type ControllerContext struct {
// ClientBuilder will provide a client for this controller to use
ClientBuilder controller . ControllerClientBuilder
2017-02-06 18:35:50 +00:00
// InformerFactory gives access to informers for the controller.
2016-12-02 19:18:16 +00:00
InformerFactory informers . SharedInformerFactory
// Options provides access to init options for a given controller
Options options . CMServer
// AvailableResources is a map listing currently available resources
AvailableResources map [ schema . GroupVersionResource ] bool
2017-06-01 16:03:18 +00:00
// Cloud is the cloud provider interface for the controllers to use.
// It must be initialized and ready to use.
Cloud cloudprovider . Interface
2017-09-12 22:47:03 +00:00
// Control for which control loops to be run
// IncludeCloudLoops is for a kube-controller-manager running all loops
// ExternalLoops is for a kube-controller-manager running with a cloud-controller-manager
LoopMode ControllerLoopMode
2016-12-02 19:18:16 +00:00
// Stop is the stop channel
Stop <- chan struct { }
2017-08-24 16:39:55 +00:00
// InformersStarted is closed after all of the controllers have been initialized and are running. After this point it is safe,
// for an individual controller to start the shared informers. Before it is closed, they should not.
InformersStarted chan struct { }
2016-12-02 19:18:16 +00:00
}
2017-01-10 20:11:25 +00:00
func ( c ControllerContext ) IsControllerEnabled ( name string ) bool {
return IsControllerEnabled ( name , ControllersDisabledByDefault , c . Options . Controllers ... )
}
func IsControllerEnabled ( name string , disabledByDefaultControllers sets . String , controllers ... string ) bool {
hasStar := false
2017-03-17 06:25:10 +00:00
for _ , ctrl := range controllers {
if ctrl == name {
2017-01-10 20:11:25 +00:00
return true
}
2017-03-17 06:25:10 +00:00
if ctrl == "-" + name {
2017-01-10 20:11:25 +00:00
return false
}
2017-03-17 06:25:10 +00:00
if ctrl == "*" {
2017-01-10 20:11:25 +00:00
hasStar = true
}
}
// if we get here, there was no explicit choice
if ! hasStar {
// nothing on by default
return false
}
if disabledByDefaultControllers . Has ( name ) {
return false
}
return true
}
2016-12-02 19:18:16 +00:00
// InitFunc is used to launch a particular controller. It may run additional "should I activate checks".
// Any error returned will cause the controller process to `Fatal`
2016-12-05 14:04:32 +00:00
// The bool indicates whether the controller was enabled.
2016-12-02 19:18:16 +00:00
type InitFunc func ( ctx ControllerContext ) ( bool , error )
2017-01-10 20:11:25 +00:00
func KnownControllers ( ) [ ] string {
2017-09-12 22:47:03 +00:00
ret := sets . StringKeySet ( NewControllerInitializers ( IncludeCloudLoops ) )
2017-03-06 20:58:08 +00:00
2017-06-01 16:03:18 +00:00
// add "special" controllers that aren't initialized normally. These controllers cannot be initialized
// using a normal function. The only known special case is the SA token controller which *must* be started
// first to ensure that the SA tokens for future controllers will exist. Think very carefully before adding
// to this list.
2017-03-06 20:58:08 +00:00
ret . Insert (
saTokenControllerName ,
)
return ret . List ( )
2017-01-10 20:11:25 +00:00
}
2016-12-12 20:17:26 +00:00
var ControllersDisabledByDefault = sets . NewString (
"bootstrapsigner" ,
"tokencleaner" ,
)
2017-01-10 20:11:25 +00:00
2017-06-01 16:03:18 +00:00
const (
saTokenControllerName = "serviceaccount-token"
)
2017-05-08 18:16:05 +00:00
// NewControllerInitializers is a public map of named controller groups (you can start more than one in an init func)
// paired to their InitFunc. This allows for structured downstream composition and subdivision.
2017-09-12 22:47:03 +00:00
func NewControllerInitializers ( loopMode ControllerLoopMode ) map [ string ] InitFunc {
2016-12-02 19:18:16 +00:00
controllers := map [ string ] InitFunc { }
controllers [ "endpoint" ] = startEndpointController
controllers [ "replicationcontroller" ] = startReplicationController
2016-12-05 14:04:32 +00:00
controllers [ "podgc" ] = startPodGCController
2016-12-02 19:18:16 +00:00
controllers [ "resourcequota" ] = startResourceQuotaController
controllers [ "namespace" ] = startNamespaceController
2016-12-05 14:04:32 +00:00
controllers [ "serviceaccount" ] = startServiceAccountController
controllers [ "garbagecollector" ] = startGarbageCollectorController
controllers [ "daemonset" ] = startDaemonSetController
controllers [ "job" ] = startJobController
controllers [ "deployment" ] = startDeploymentController
controllers [ "replicaset" ] = startReplicaSetController
controllers [ "horizontalpodautoscaling" ] = startHPAController
controllers [ "disruption" ] = startDisruptionController
2017-05-09 15:31:06 +00:00
controllers [ "statefulset" ] = startStatefulSetController
2016-12-05 14:04:32 +00:00
controllers [ "cronjob" ] = startCronJobController
2017-05-08 21:44:45 +00:00
controllers [ "csrsigning" ] = startCSRSigningController
controllers [ "csrapproving" ] = startCSRApprovingController
2017-08-30 16:58:04 +00:00
controllers [ "csrcleaner" ] = startCSRCleanerController
2017-01-30 10:48:15 +00:00
controllers [ "ttl" ] = startTTLController
2016-12-12 20:17:26 +00:00
controllers [ "bootstrapsigner" ] = startBootstrapSignerController
controllers [ "tokencleaner" ] = startTokenCleanerController
2017-09-12 22:47:03 +00:00
if loopMode == IncludeCloudLoops {
controllers [ "service" ] = startServiceController
controllers [ "route" ] = startRouteController
// TODO: Move node controller and volume controller into the IncludeCloudLoops only set.
}
2017-06-01 16:03:18 +00:00
controllers [ "node" ] = startNodeController
controllers [ "persistentvolume-binder" ] = startPersistentVolumeBinderController
controllers [ "attachdetach" ] = startAttachDetachController
2017-09-04 07:02:34 +00:00
controllers [ "persistentvolume-expander" ] = startVolumeExpandController
2017-10-20 15:01:41 +00:00
controllers [ "clusterrole-aggregation" ] = startClusterRoleAggregrationController
2017-11-15 22:03:43 +00:00
controllers [ "pvc-protection" ] = startPVCProtectionController
2016-12-02 19:18:16 +00:00
return controllers
}
2016-11-21 20:00:20 +00:00
// TODO: In general, any controller checking this needs to be dynamic so
// users don't have to restart their controller manager if they change the apiserver.
2017-05-08 18:16:05 +00:00
// Until we get there, the structure here needs to be exposed for the construction of a proper ControllerContext.
func GetAvailableResources ( clientBuilder controller . ControllerClientBuilder ) ( map [ schema . GroupVersionResource ] bool , error ) {
2016-11-21 20:00:20 +00:00
var discoveryClient discovery . DiscoveryInterface
2017-08-07 16:55:29 +00:00
var healthzContent string
2016-11-21 20:00:20 +00:00
// If apiserver is not running we should wait for some time and fail only then. This is particularly
// important when we start apiserver and controller manager at the same time.
err := wait . PollImmediate ( time . Second , 10 * time . Second , func ( ) ( bool , error ) {
client , err := clientBuilder . Client ( "controller-discovery" )
if err != nil {
glog . Errorf ( "Failed to get api versions from server: %v" , err )
return false , nil
}
2017-03-16 15:05:03 +00:00
healthStatus := 0
2017-08-07 16:55:29 +00:00
resp := client . Discovery ( ) . RESTClient ( ) . Get ( ) . AbsPath ( "/healthz" ) . Do ( ) . StatusCode ( & healthStatus )
2017-03-16 15:05:03 +00:00
if healthStatus != http . StatusOK {
glog . Errorf ( "Server isn't healthy yet. Waiting a little while." )
return false , nil
}
2017-08-07 16:55:29 +00:00
content , _ := resp . Raw ( )
healthzContent = string ( content )
2017-03-16 15:05:03 +00:00
2016-11-21 20:00:20 +00:00
discoveryClient = client . Discovery ( )
return true , nil
} )
if err != nil {
2017-08-07 16:55:29 +00:00
return nil , fmt . Errorf ( "failed to get api versions from server: %v: %v" , healthzContent , err )
2016-11-21 20:00:20 +00:00
}
resourceMap , err := discoveryClient . ServerResources ( )
if err != nil {
2017-07-24 13:56:31 +00:00
utilruntime . HandleError ( fmt . Errorf ( "unable to get all supported resources from server: %v" , err ) )
}
if len ( resourceMap ) == 0 {
return nil , fmt . Errorf ( "unable to get any supported resources from server" )
2016-11-21 20:00:20 +00:00
}
allResources := map [ schema . GroupVersionResource ] bool { }
for _ , apiResourceList := range resourceMap {
version , err := schema . ParseGroupVersion ( apiResourceList . GroupVersion )
if err != nil {
return nil , err
}
for _ , apiResource := range apiResourceList . APIResources {
allResources [ version . WithResource ( apiResource . Name ) ] = true
}
}
return allResources , nil
}
2017-09-13 00:27:36 +00:00
// CreateControllerContext creates a context struct containing references to resources needed by the
// controllers such as the cloud provider and clientBuilder. rootClientBuilder is only used for
// the shared-informers client and token controller.
2017-06-01 16:03:18 +00:00
func CreateControllerContext ( s * options . CMServer , rootClientBuilder , clientBuilder controller . ControllerClientBuilder , stop <- chan struct { } ) ( ControllerContext , error ) {
2017-02-06 18:35:50 +00:00
versionedClient := rootClientBuilder . ClientOrDie ( "shared-informers" )
2017-02-24 14:52:43 +00:00
sharedInformers := informers . NewSharedInformerFactory ( versionedClient , ResyncPeriod ( s ) ( ) )
2016-08-04 07:06:29 +00:00
2017-06-01 16:03:18 +00:00
availableResources , err := GetAvailableResources ( rootClientBuilder )
if err != nil {
return ControllerContext { } , err
2016-09-22 19:53:08 +00:00
}
2017-09-12 22:47:03 +00:00
var cloud cloudprovider . Interface
var loopMode ControllerLoopMode
if cloudprovider . IsExternal ( s . CloudProvider ) {
loopMode = ExternalLoops
if s . ExternalCloudVolumePlugin != "" {
cloud , err = cloudprovider . InitCloudProvider ( s . ExternalCloudVolumePlugin , s . CloudConfigFile )
}
} else {
loopMode = IncludeCloudLoops
cloud , err = cloudprovider . InitCloudProvider ( s . CloudProvider , s . CloudConfigFile )
}
2016-11-21 20:00:20 +00:00
if err != nil {
2017-06-01 16:03:18 +00:00
return ControllerContext { } , fmt . Errorf ( "cloud provider could not be initialized: %v" , err )
}
2017-09-13 00:27:36 +00:00
if cloud != nil && cloud . HasClusterID ( ) == false {
if s . AllowUntaggedCloud == true {
glog . Warning ( "detected a cluster without a ClusterID. A ClusterID will be required in the future. Please tag your cluster to avoid any future issues" )
} else {
return ControllerContext { } , fmt . Errorf ( "no ClusterID Found. A ClusterID is required for the cloud provider to function properly. This check can be bypassed by setting the allow-untagged-cloud option" )
2017-07-17 04:28:57 +00:00
}
2016-11-21 20:00:20 +00:00
}
2017-09-12 03:20:29 +00:00
if informerUserCloud , ok := cloud . ( cloudprovider . InformerUser ) ; ok {
informerUserCloud . SetInformers ( sharedInformers )
}
2016-12-02 19:18:16 +00:00
ctx := ControllerContext {
ClientBuilder : clientBuilder ,
InformerFactory : sharedInformers ,
Options : * s ,
AvailableResources : availableResources ,
2017-06-01 16:03:18 +00:00
Cloud : cloud ,
2017-09-12 22:47:03 +00:00
LoopMode : loopMode ,
2016-12-02 19:18:16 +00:00
Stop : stop ,
2017-08-24 16:39:55 +00:00
InformersStarted : make ( chan struct { } ) ,
2016-12-02 19:18:16 +00:00
}
2017-06-01 16:03:18 +00:00
return ctx , nil
}
func StartControllers ( ctx ControllerContext , startSATokenController InitFunc , controllers map [ string ] InitFunc ) error {
// Always start the SA token controller first using a full-power client, since it needs to mint tokens for the rest
// If this fails, just return here and fail since other controllers won't be able to get credentials.
if _ , err := startSATokenController ( ctx ) ; err != nil {
return err
}
2015-01-30 23:31:36 +00:00
2017-09-13 00:27:36 +00:00
// Initialize the cloud provider with a reference to the clientBuilder only after token controller
// has started in case the cloud provider uses the client builder.
if ctx . Cloud != nil {
ctx . Cloud . Initialize ( ctx . ClientBuilder )
}
2016-12-02 19:18:16 +00:00
for controllerName , initFn := range controllers {
2017-01-10 20:11:25 +00:00
if ! ctx . IsControllerEnabled ( controllerName ) {
glog . Warningf ( "%q is disabled" , controllerName )
continue
}
2017-06-01 16:03:18 +00:00
time . Sleep ( wait . Jitter ( ctx . Options . ControllerStartInterval . Duration , ControllerStartJitter ) )
2015-01-30 23:31:36 +00:00
2016-12-02 19:18:16 +00:00
glog . V ( 1 ) . Infof ( "Starting %q" , controllerName )
started , err := initFn ( ctx )
if err != nil {
glog . Errorf ( "Error starting %q" , controllerName )
return err
}
if ! started {
glog . Warningf ( "Skipping %q" , controllerName )
2017-01-14 00:27:00 +00:00
continue
2016-12-02 19:18:16 +00:00
}
glog . Infof ( "Started %q" , controllerName )
}
2015-09-21 22:51:27 +00:00
2017-06-01 16:03:18 +00:00
return nil
}
2015-02-07 19:53:42 +00:00
2017-06-01 16:03:18 +00:00
// serviceAccountTokenControllerStarter is special because it must run first to set up permissions for other controllers.
// It cannot use the "normal" client builder, so it tracks its own. It must also avoid being included in the "normal"
// init map so that it can always run first.
type serviceAccountTokenControllerStarter struct {
rootClientBuilder controller . ControllerClientBuilder
}
2017-05-17 21:38:25 +00:00
2017-06-01 16:03:18 +00:00
func ( c serviceAccountTokenControllerStarter ) startServiceAccountTokenController ( ctx ControllerContext ) ( bool , error ) {
if ! ctx . IsControllerEnabled ( saTokenControllerName ) {
glog . Warningf ( "%q is disabled" , saTokenControllerName )
return false , nil
2015-03-24 17:32:43 +00:00
}
2017-06-01 16:03:18 +00:00
if len ( ctx . Options . ServiceAccountKeyFile ) == 0 {
glog . Warningf ( "%q is disabled because there is no private key" , saTokenControllerName )
return false , nil
2015-05-15 21:49:26 +00:00
}
2017-07-19 03:06:32 +00:00
privateKey , err := certutil . PrivateKeyFromFile ( ctx . Options . ServiceAccountKeyFile )
2017-06-01 16:03:18 +00:00
if err != nil {
return true , fmt . Errorf ( "error reading key for service account token controller: %v" , err )
2016-09-26 12:15:25 +00:00
}
2015-04-16 17:26:08 +00:00
2017-06-01 16:03:18 +00:00
var rootCA [ ] byte
if ctx . Options . RootCAFile != "" {
rootCA , err = ioutil . ReadFile ( ctx . Options . RootCAFile )
if err != nil {
return true , fmt . Errorf ( "error reading root-ca-file at %s: %v" , ctx . Options . RootCAFile , err )
2017-03-06 20:58:08 +00:00
}
2017-06-01 16:03:18 +00:00
if _ , err := certutil . ParseCertsPEM ( rootCA ) ; err != nil {
return true , fmt . Errorf ( "error parsing root-ca-file at %s: %v" , ctx . Options . RootCAFile , err )
2017-03-13 07:41:59 +00:00
}
2017-03-06 20:58:08 +00:00
} else {
2017-06-01 16:03:18 +00:00
rootCA = c . rootClientBuilder . ConfigOrDie ( "tokens-controller" ) . CAData
2017-01-06 22:24:51 +00:00
}
2017-10-31 14:19:55 +00:00
controller , err := serviceaccountcontroller . NewTokensController (
2017-06-01 16:03:18 +00:00
ctx . InformerFactory . Core ( ) . V1 ( ) . ServiceAccounts ( ) ,
ctx . InformerFactory . Core ( ) . V1 ( ) . Secrets ( ) ,
c . rootClientBuilder . ClientOrDie ( "tokens-controller" ) ,
serviceaccountcontroller . TokensControllerOptions {
TokenGenerator : serviceaccount . JWTTokenGenerator ( privateKey ) ,
RootCA : rootCA ,
} ,
)
2017-10-31 14:19:55 +00:00
if err != nil {
return true , fmt . Errorf ( "error creating Tokens controller: %v" , err )
}
2017-06-01 16:03:18 +00:00
go controller . Run ( int ( ctx . Options . ConcurrentSATokenSyncs ) , ctx . Stop )
2016-04-30 06:36:27 +00:00
2017-06-01 16:03:18 +00:00
// start the first set of informers now so that other controllers can start
ctx . InformerFactory . Start ( ctx . Stop )
2016-04-14 18:00:52 +00:00
2017-06-01 16:03:18 +00:00
return true , nil
2015-01-30 23:31:36 +00:00
}