A master can be configured to use a leader lease to determine
whether controllers can be started up. An admin would set
controllerLeaseTTL in master-config, and then start multiple
masters (or multiple controller processes). Each process would
try to acquire a leader lease on the key
<storageprefix>/leases/controllers, setting a TTL equal to the
configured value in seconds. Whichever controller acquires the
lock will be able to begin running. When a controller cannot
renew a lease due to errors talking to etcd, the key expires,
the key is deleted, or the value of the key is changed, the controller
will exit and terminate the process (either a full master, or just the
controller process).
Because terminating a master results in API connections being terminated
(including log sessions or exec proxy sessions) it is better to run the
masters as distinct API and controller sub processes.
| ... | ... |
@@ -8,11 +8,14 @@ when that change will happen. |
| 8 | 8 |
## Origin 1.0.x / OSE 3.0.x |
| 9 | 9 |
|
| 10 | 10 |
1. Currently all build pods have a label named `build`. This label is being deprecated |
| 11 |
- in favor of `openshift.io/build.name` in Origin 1.0.x / OSE 3.1.x at which point both |
|
| 12 |
- labels will be supported. All the newly created builds will have just the new label. |
|
| 13 |
- In Origin 1.y / OSE 3.y the support for the old label (`build`) will be removed entirely. |
|
| 11 |
+ in favor of `openshift.io/build.name` in Origin 1.0.x (OSE 3.0.x) - both are supported. |
|
| 12 |
+ In Origin 1.1 we will only set the new label and remove support for the old label. |
|
| 14 | 13 |
See #3502. |
| 15 | 14 |
|
| 16 | 15 |
1. Currently `oc exec` will attempt to `POST` to `pods/podname/exec`, if that fails it will |
| 17 |
-fallback to a `GET` to match older policy roles. In Origin 1.y/ OSE 3.y the support for the |
|
| 18 |
-old `oc exec` endpoint via `GET` will be removed. |
|
| 19 | 16 |
\ No newline at end of file |
| 17 |
+ fallback to a `GET` to match older policy roles. In Origin 1.1 (OSE 3.1) the support for the |
|
| 18 |
+ old `oc exec` endpoint via `GET` will be removed. |
|
| 19 |
+ |
|
| 20 |
+1. The `pauseControllers` field in `master-config.yaml` is deprecated as of Origin 1.0.4 and will |
|
| 21 |
+ no longer be supported in Origin 1.1. After that, a warning will be printed on startup if it |
|
| 22 |
+ is set to true. |
|
| 20 | 23 |
\ No newline at end of file |
| ... | ... |
@@ -116,7 +116,14 @@ type MasterConfig struct {
|
| 116 | 116 |
Controllers string |
| 117 | 117 |
// PauseControllers instructs the master to not automatically start controllers, but instead |
| 118 | 118 |
// to wait until a notification to the server is received before launching them. |
| 119 |
+ // TODO: will be disabled in function for 1.1. |
|
| 119 | 120 |
PauseControllers bool |
| 121 |
+ // ControllerLeaseTTL enables controller election, instructing the master to attempt to acquire |
|
| 122 |
+ // a lease before controllers start and renewing it within a number of seconds defined by this value. |
|
| 123 |
+ // Setting this value non-negative forces pauseControllers=true. This value defaults off (0, or |
|
| 124 |
+ // omitted) and controller election can be disabled with -1. |
|
| 125 |
+ ControllerLeaseTTL int |
|
| 126 |
+ // TODO: the next field added to controllers must be added to a new controllers struct |
|
| 120 | 127 |
|
| 121 | 128 |
// Allow to disable OpenShift components |
| 122 | 129 |
DisabledFeatures FeatureList |
| ... | ... |
@@ -99,6 +99,11 @@ type MasterConfig struct {
|
| 99 | 99 |
// PauseControllers instructs the master to not automatically start controllers, but instead |
| 100 | 100 |
// to wait until a notification to the server is received before launching them. |
| 101 | 101 |
PauseControllers bool `json:"pauseControllers,omitempty"` |
| 102 |
+ // ControllerLeaseTTL enables controller election, instructing the master to attempt to acquire |
|
| 103 |
+ // a lease before controllers start and renewing it within a number of seconds defined by this value. |
|
| 104 |
+ // Setting this value non-negative forces pauseControllers=true. This value defaults off (0, or |
|
| 105 |
+ // omitted) and controller election can be disabled with -1. |
|
| 106 |
+ ControllerLeaseTTL int `json:"controllerLeaseTTL,omitempty"` |
|
| 102 | 107 |
|
| 103 | 108 |
// DisabledFeatures is a list of features that should not be started. We |
| 104 | 109 |
// omitempty here because its very unlikely that anyone will want to |
| ... | ... |
@@ -61,6 +61,13 @@ func ValidateMasterConfig(config *api.MasterConfig) ValidationResults {
|
| 61 | 61 |
validationResults.AddErrors(urlErrs...) |
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 |
+ switch {
|
|
| 65 |
+ case config.ControllerLeaseTTL > 300, |
|
| 66 |
+ config.ControllerLeaseTTL < -1, |
|
| 67 |
+ config.ControllerLeaseTTL > 0 && config.ControllerLeaseTTL < 10: |
|
| 68 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("controllerLeaseTTL", config.ControllerLeaseTTL, "TTL must be -1 (disabled), 0 (default), or between 10 and 300 seconds"))
|
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 64 | 71 |
validationResults.AddErrors(ValidateDisabledFeatures(config.DisabledFeatures, "disabledFeatures")...) |
| 65 | 72 |
|
| 66 | 73 |
if config.AssetConfig != nil {
|
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"errors" |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"net/http" |
| 9 |
+ "path" |
|
| 9 | 10 |
|
| 10 | 11 |
etcdclient "github.com/coreos/go-etcd/etcd" |
| 11 | 12 |
"github.com/golang/glog" |
| ... | ... |
@@ -21,6 +22,7 @@ import ( |
| 21 | 21 |
"k8s.io/kubernetes/pkg/storage" |
| 22 | 22 |
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" |
| 23 | 23 |
kutil "k8s.io/kubernetes/pkg/util" |
| 24 |
+ kutilrand "k8s.io/kubernetes/pkg/util/rand" |
|
| 24 | 25 |
|
| 25 | 26 |
"github.com/openshift/origin/pkg/api/latest" |
| 26 | 27 |
"github.com/openshift/origin/pkg/auth/authenticator" |
| ... | ... |
@@ -58,6 +60,7 @@ import ( |
| 58 | 58 |
groupstorage "github.com/openshift/origin/pkg/user/registry/group/etcd" |
| 59 | 59 |
userregistry "github.com/openshift/origin/pkg/user/registry/user" |
| 60 | 60 |
useretcd "github.com/openshift/origin/pkg/user/registry/user/etcd" |
| 61 |
+ "github.com/openshift/origin/pkg/util/leaderlease" |
|
| 61 | 62 |
) |
| 62 | 63 |
|
| 63 | 64 |
const ( |
| ... | ... |
@@ -83,7 +86,8 @@ type MasterConfig struct {
|
| 83 | 83 |
|
| 84 | 84 |
TLS bool |
| 85 | 85 |
|
| 86 |
- ControllerPlug plug.Plug |
|
| 86 |
+ ControllerPlug plug.Plug |
|
| 87 |
+ ControllerPlugStart func() |
|
| 87 | 88 |
|
| 88 | 89 |
// a function that returns the appropriate image to use for a named component |
| 89 | 90 |
ImageFor func(component string) string |
| ... | ... |
@@ -174,6 +178,8 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
|
| 174 | 174 |
return nil, err |
| 175 | 175 |
} |
| 176 | 176 |
|
| 177 |
+ plug, plugStart := newControllerPlug(options, client) |
|
| 178 |
+ |
|
| 177 | 179 |
config := &MasterConfig{
|
| 178 | 180 |
Options: options, |
| 179 | 181 |
|
| ... | ... |
@@ -191,7 +197,8 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
|
| 191 | 191 |
|
| 192 | 192 |
TLS: configapi.UseTLS(options.ServingInfo.ServingInfo), |
| 193 | 193 |
|
| 194 |
- ControllerPlug: plug.NewPlug(!options.PauseControllers), |
|
| 194 |
+ ControllerPlug: plug, |
|
| 195 |
+ ControllerPlugStart: plugStart, |
|
| 195 | 196 |
|
| 196 | 197 |
ImageFor: imageTemplate.ExpandOrDie, |
| 197 | 198 |
EtcdHelper: etcdHelper, |
| ... | ... |
@@ -213,6 +220,27 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
|
| 213 | 213 |
return config, nil |
| 214 | 214 |
} |
| 215 | 215 |
|
| 216 |
+func newControllerPlug(options configapi.MasterConfig, client *etcdclient.Client) (plug.Plug, func()) {
|
|
| 217 |
+ switch {
|
|
| 218 |
+ case options.ControllerLeaseTTL > 0: |
|
| 219 |
+ // TODO: replace with future API for leasing from Kube |
|
| 220 |
+ id := fmt.Sprintf("master-%s", kutilrand.String(8))
|
|
| 221 |
+ leaser := leaderlease.NewEtcd( |
|
| 222 |
+ client, |
|
| 223 |
+ path.Join(options.EtcdStorageConfig.OpenShiftStoragePrefix, "leases/controllers"), |
|
| 224 |
+ id, |
|
| 225 |
+ uint64(options.ControllerLeaseTTL), |
|
| 226 |
+ ) |
|
| 227 |
+ leased := plug.NewLeased(leaser) |
|
| 228 |
+ return leased, func() {
|
|
| 229 |
+ glog.V(2).Infof("Attempting to acquire controller lease as %s, renewing every %d seconds", id, options.ControllerLeaseTTL)
|
|
| 230 |
+ go leased.Run() |
|
| 231 |
+ } |
|
| 232 |
+ default: |
|
| 233 |
+ return plug.New(!options.PauseControllers), func() {}
|
|
| 234 |
+ } |
|
| 235 |
+} |
|
| 236 |
+ |
|
| 216 | 237 |
func newServiceAccountTokenGetter(options configapi.MasterConfig, client *etcdclient.Client) (serviceaccount.ServiceAccountTokenGetter, error) {
|
| 217 | 238 |
var tokenGetter serviceaccount.ServiceAccountTokenGetter |
| 218 | 239 |
if options.KubernetesMasterConfig == nil {
|
| ... | ... |
@@ -437,12 +437,16 @@ func StartControllers(openshiftConfig *origin.MasterConfig, kubeMasterConfig *ku |
| 437 | 437 |
} |
| 438 | 438 |
|
| 439 | 439 |
go func() {
|
| 440 |
+ openshiftConfig.ControllerPlugStart() |
|
| 441 |
+ // when a manual shutdown (DELETE /controllers) or lease lost occurs, the process should exit |
|
| 442 |
+ // this ensures no code is still running as a controller, and allows a process manager to reset |
|
| 443 |
+ // the controller to come back into a candidate state and compete for the lease |
|
| 440 | 444 |
openshiftConfig.ControllerPlug.WaitForStop() |
| 441 |
- glog.Fatalf("Master shutdown requested")
|
|
| 445 |
+ glog.Fatalf("Controller shutdown requested")
|
|
| 442 | 446 |
}() |
| 443 | 447 |
|
| 444 | 448 |
openshiftConfig.ControllerPlug.WaitForStart() |
| 445 |
- glog.Infof("Master controllers starting (%s)", openshiftConfig.Options.Controllers)
|
|
| 449 |
+ glog.Infof("Controllers starting (%s)", openshiftConfig.Options.Controllers)
|
|
| 446 | 450 |
|
| 447 | 451 |
// Start these first, because they provide credentials for other controllers' clients |
| 448 | 452 |
openshiftConfig.RunServiceAccountsController() |
| ... | ... |
@@ -4,14 +4,26 @@ import ( |
| 4 | 4 |
"sync" |
| 5 | 5 |
) |
| 6 | 6 |
|
| 7 |
+// Plug represents a synchronization primitive that holds and releases |
|
| 8 |
+// execution for other objects. |
|
| 7 | 9 |
type Plug interface {
|
| 10 |
+ // Begins operation of the plug and unblocks WaitForStart(). |
|
| 11 |
+ // May be invoked multiple times but only the first invocation has |
|
| 12 |
+ // an effect. |
|
| 8 | 13 |
Start() |
| 14 |
+ // Ends operation of the plug and unblocks WaitForStop() |
|
| 15 |
+ // May be invoked multiple times but only the first invocation has |
|
| 16 |
+ // an effect. Calling Stop() before Start() is undefined. |
|
| 9 | 17 |
Stop() |
| 18 |
+ // Blocks until Start() is invoked |
|
| 10 | 19 |
WaitForStart() |
| 20 |
+ // Blocks until Stop() is invoked |
|
| 11 | 21 |
WaitForStop() |
| 22 |
+ // Returns true if Start() has been invoked |
|
| 12 | 23 |
IsStarted() bool |
| 13 | 24 |
} |
| 14 | 25 |
|
| 26 |
+// plug is the default implementation of Plug |
|
| 15 | 27 |
type plug struct {
|
| 16 | 28 |
start sync.Once |
| 17 | 29 |
stop sync.Once |
| ... | ... |
@@ -19,7 +31,8 @@ type plug struct {
|
| 19 | 19 |
stopCh chan struct{}
|
| 20 | 20 |
} |
| 21 | 21 |
|
| 22 |
-func NewPlug(started bool) Plug {
|
|
| 22 |
+// New returns a new plug that can begin in the Started state. |
|
| 23 |
+func New(started bool) Plug {
|
|
| 23 | 24 |
p := &plug{
|
| 24 | 25 |
startCh: make(chan struct{}),
|
| 25 | 26 |
stopCh: make(chan struct{}),
|
| ... | ... |
@@ -54,3 +67,52 @@ func (p *plug) WaitForStart() {
|
| 54 | 54 |
func (p *plug) WaitForStop() {
|
| 55 | 55 |
<-p.stopCh |
| 56 | 56 |
} |
| 57 |
+ |
|
| 58 |
+// Leaser controls access to a lease |
|
| 59 |
+type Leaser interface {
|
|
| 60 |
+ // AcquireAndHold tries to acquire the lease and hold it until it expires, the lease is deleted, |
|
| 61 |
+ // or we observe another party take the lease. The notify channel will be sent a value |
|
| 62 |
+ // when the lease is held, and closed when the lease is lost. |
|
| 63 |
+ AcquireAndHold(chan struct{})
|
|
| 64 |
+ Release() |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+// leased uses a Leaser to control Start and Stop on a Plug |
|
| 68 |
+type Leased struct {
|
|
| 69 |
+ Plug |
|
| 70 |
+ |
|
| 71 |
+ leaser Leaser |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+var _ Plug = &Leased{}
|
|
| 75 |
+ |
|
| 76 |
+// NewLeased creates a Plug that starts when a lease is acquired |
|
| 77 |
+// and stops when it is lost. |
|
| 78 |
+func NewLeased(leaser Leaser) *Leased {
|
|
| 79 |
+ return &Leased{
|
|
| 80 |
+ Plug: New(false), |
|
| 81 |
+ leaser: leaser, |
|
| 82 |
+ } |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// Stop releases the acquired lease |
|
| 86 |
+func (l *Leased) Stop() {
|
|
| 87 |
+ l.leaser.Release() |
|
| 88 |
+ l.Plug.Stop() |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+// Run tries to acquire and hold a lease, invoking Start() |
|
| 92 |
+// when the lease is held and invoking Stop() when the lease |
|
| 93 |
+// is lost. |
|
| 94 |
+func (l *Leased) Run() {
|
|
| 95 |
+ ch := make(chan struct{}, 1)
|
|
| 96 |
+ go l.leaser.AcquireAndHold(ch) |
|
| 97 |
+ defer l.Stop() |
|
| 98 |
+ for {
|
|
| 99 |
+ _, ok := <-ch |
|
| 100 |
+ if !ok {
|
|
| 101 |
+ return |
|
| 102 |
+ } |
|
| 103 |
+ l.Start() |
|
| 104 |
+ } |
|
| 105 |
+} |