Skip to content

Commit

Permalink
Merge pull request #85 from MorrisLaw/add-webhook-check-for-timeout
Browse files Browse the repository at this point in the history
Add webhook check for timeouts
  • Loading branch information
MorrisLaw authored Jun 15, 2020
2 parents ee0ddd9 + e6ec7b4 commit 65df37b
Show file tree
Hide file tree
Showing 5 changed files with 390 additions and 9 deletions.
68 changes: 68 additions & 0 deletions checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,74 @@ webhooks:
operator: "DoesNotExist"
```
## Admission Controller Webhook Timeout
- Name: `admission-controller-webhook-timeout`
- Groups: `doks`

Admission control webhook timeouts can block upgrades, when the API call times out, due to an incorrectly configured TimeoutSeconds value. Since webhooks inherently add to API latency, we must stay within the recommended range in order for API requests to be successful. Specifically, this happens when an admission control webhook does not respond within 30 seconds.

### Example

```yaml
# Error: Configure a webhook with a TimeoutSeconds value greater than 30 seconds.
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: sample-webhook.example.com
webhooks:
- name: sample-webhook.example.com
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: "Namespaced"
clientConfig:
service:
namespace: webhook
name: webhook-server
path: /pods
admissionReviewVersions:
- v1beta1
timeoutSeconds: 60
```

### How to Fix

Set the TimeoutSeconds value to anything within the 1 to 30 second range.

```yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: sample-webhook.example.com
webhooks:
- name: sample-webhook.example.com
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: "Namespaced"
clientConfig:
service:
namespace: webhook
name: webhook-server
path: /pods
admissionReviewVersions:
- v1beta1
timeoutSeconds: 10
```

## Pod State

- Name: `pod-state`
Expand Down
12 changes: 6 additions & 6 deletions checks/doks/admission_controller_webhook_replacement.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,29 @@ import (
)

func init() {
checks.Register(&webhookReplaementCheck{})
checks.Register(&webhookReplacementCheck{})
}

type webhookReplaementCheck struct{}
type webhookReplacementCheck struct{}

// Name returns a unique name for this check.
func (w *webhookReplaementCheck) Name() string {
func (w *webhookReplacementCheck) Name() string {
return "admission-controller-webhook-replacement"
}

// Groups returns a list of group names this check should be part of.
func (w *webhookReplaementCheck) Groups() []string {
func (w *webhookReplacementCheck) Groups() []string {
return []string{"doks"}
}

// Description returns a detailed human-readable description of what this check
// does.
func (w *webhookReplaementCheck) Description() string {
func (w *webhookReplacementCheck) Description() string {
return "Check for admission control webhooks that could cause problems during upgrades or node replacement"
}

// Run runs this check on a set of Kubernetes objects.
func (w *webhookReplaementCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
func (w *webhookReplacementCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
const apiserverServiceName = "kubernetes"

var diagnostics []checks.Diagnostic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import (
var webhookURL = "https://example.com/webhook"

func TestWebhookCheckMeta(t *testing.T) {
webhookCheck := webhookReplaementCheck{}
webhookCheck := webhookReplacementCheck{}
assert.Equal(t, "admission-controller-webhook-replacement", webhookCheck.Name())
assert.Equal(t, []string{"doks"}, webhookCheck.Groups())
assert.NotEmpty(t, webhookCheck.Description())
}

func TestWebhookCheckRegistration(t *testing.T) {
webhookCheck := &webhookReplaementCheck{}
webhookCheck := &webhookReplacementCheck{}
check, err := checks.Get("admission-controller-webhook-replacement")
assert.NoError(t, err)
assert.Equal(t, check, webhookCheck)
Expand Down Expand Up @@ -236,7 +236,7 @@ func TestWebhookError(t *testing.T) {
},
}

webhookCheck := webhookReplaementCheck{}
webhookCheck := webhookReplacementCheck{}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
84 changes: 84 additions & 0 deletions checks/doks/admission_controller_webhook_timeout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2020 DigitalOcean
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 doks

import (
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
)

func init() {
checks.Register(&webhookTimeoutCheck{})
}

type webhookTimeoutCheck struct{}

// Name returns a unique name for this check.
func (w *webhookTimeoutCheck) Name() string {
return "admission-controller-webhook-timeout"
}

// Groups returns a list of group names this check should be part of.
func (w *webhookTimeoutCheck) Groups() []string {
return []string{"doks"}
}

// Description returns a detailed human-readable description of what this check
// does.
func (w *webhookTimeoutCheck) Description() string {
return "Check for admission control webhooks that have exceeded a timeout of 30 seconds."
}

// Run runs this check on a set of Kubernetes objects.
func (w *webhookTimeoutCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic

for _, config := range objects.ValidatingWebhookConfigurations.Items {
for _, wh := range config.Webhooks {
if *wh.TimeoutSeconds >= int32(1) && *wh.TimeoutSeconds < int32(30) {
// Webhooks with TimeoutSeconds set: between 1 and 30 is fine.
continue
}
d := checks.Diagnostic{
Severity: checks.Error,
Message: "Validating webhook with a TimeoutSeconds value greater than 30 seconds will block upgrades.",
Kind: checks.ValidatingWebhookConfiguration,
Object: &config.ObjectMeta,
Owners: config.ObjectMeta.GetOwnerReferences(),
}
diagnostics = append(diagnostics, d)
}
}

for _, config := range objects.MutatingWebhookConfigurations.Items {
for _, wh := range config.Webhooks {
if *wh.TimeoutSeconds >= int32(1) && *wh.TimeoutSeconds < int32(30) {
// Webhooks with TimeoutSeconds set: between 1 and 30 is fine.
continue
}
d := checks.Diagnostic{
Severity: checks.Error,
Message: "Mutating webhook with a TimeoutSeconds value greater than 30 seconds will block upgrades.",
Kind: checks.MutatingWebhookConfiguration,
Object: &config.ObjectMeta,
Owners: config.ObjectMeta.GetOwnerReferences(),
}
diagnostics = append(diagnostics, d)
}
}
return diagnostics, nil
}
Loading

0 comments on commit 65df37b

Please sign in to comment.