Skip to content

Commit

Permalink
Merge pull request planetlabs#66 from planetlabs/koobz/no-drain
Browse files Browse the repository at this point in the history
Add skip-drain flag.
  • Loading branch information
jacobstr authored Mar 28, 2020
2 parents a500759 + fcebe27 commit 2c3d2b4
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 5 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ Flags:
--drain-buffer=10m0s Minimum time between starting each drain. Nodes are always cordoned immediately.
--node-label=KEY=VALUE ...
Only nodes with this label will be eligible for cordoning and draining. May be specified multiple times.
--namespace="kube-system" Namespace used to create leader election lock object.
--leader-election-lease-duration=15s
Lease duration for leader election.
--leader-election-renew-deadline=10s
Leader election renew deadline.
--leader-election-retry-period=2s
Leader election retry period.
--skip-drain Whether to skip draining nodes after cordoning.
--evict-daemonset-pods Evict pods that were created by an extant DaemonSet.
--evict-emptydir-pods Evict pods with local storage, i.e. with emptyDir volumes.
--evict-unreplicated-pods Evict pods that were not created by a replication controller.
Expand All @@ -46,7 +54,6 @@ Flags:
Args:
<node-conditions> Nodes for which any of these conditions are true will be cordoned and drained.
```

## Considerations
Expand Down Expand Up @@ -149,4 +156,11 @@ is marked as `Failed`. If you want to reschedule a drain tentative on that node,

```
kubectl annotate node {node-name} draino/drain-retry=true
```
```
## Modes

### Dry Run
Draino can be run in dry run mode using the `--dry-run` flag.

### Cordon Only
Draino can also optionally be run in a mode where the nodes are only cordoned, and not drained. This can be achieved by using the `--skip-drain` flag.
6 changes: 5 additions & 1 deletion cmd/draino/draino.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func main() {
leaderElectionRenewDeadline = app.Flag("leader-election-renew-deadline", "Leader election renew deadline.").Default(DefaultLeaderElectionRenewDeadline.String()).Duration()
leaderElectionRetryPeriod = app.Flag("leader-election-retry-period", "Leader election retry period.").Default(DefaultLeaderElectionRetryPeriod.String()).Duration()

skipDrain = app.Flag("skip-drain", "Whether to skip draining nodes after cordoning.").Default("false").Bool()
evictDaemonSetPods = app.Flag("evict-daemonset-pods", "Evict pods that were created by an extant DaemonSet.").Bool()
evictLocalStoragePods = app.Flag("evict-emptydir-pods", "Evict pods with local storage, i.e. with emptyDir volumes.").Bool()
evictUnreplicatedPods = app.Flag("evict-unreplicated-pods", "Evict pods that were not created by a replication controller.").Bool()
Expand Down Expand Up @@ -142,7 +143,10 @@ func main() {
kubernetes.NewAPICordonDrainer(cs,
kubernetes.MaxGracePeriod(*maxGracePeriod),
kubernetes.EvictionHeadroom(*evictionHeadroom),
kubernetes.WithPodFilter(kubernetes.NewPodFilters(pf...))),
kubernetes.WithSkipDrain(*skipDrain),
kubernetes.WithPodFilter(kubernetes.NewPodFilters(pf...)),
kubernetes.WithAPICordonDrainerLogger(log),
),
kubernetes.NewEventRecorder(cs),
kubernetes.WithLogger(log),
kubernetes.WithDrainBuffer(*drainBuffer))
Expand Down
28 changes: 28 additions & 0 deletions internal/kubernetes/drainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/pkg/errors"
"go.uber.org/zap"
core "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -38,6 +39,7 @@ const (
kindDaemonSet = "DaemonSet"

ConditionDrainedScheduled = "DrainScheduled"
DefaultSkipDrain = false
)

type errTimeout struct{}
Expand Down Expand Up @@ -93,11 +95,13 @@ func (d *NoopCordonDrainer) MarkDrain(n *core.Node, when, finish time.Time, fail
// APICordonDrainer drains Kubernetes nodes via the Kubernetes API.
type APICordonDrainer struct {
c kubernetes.Interface
l *zap.Logger

filter PodFilterFunc

maxGracePeriod time.Duration
evictionHeadroom time.Duration
skipDrain bool
}

// SuppliedCondition defines the condition will be watched.
Expand Down Expand Up @@ -135,14 +139,31 @@ func WithPodFilter(f PodFilterFunc) APICordonDrainerOption {
}
}

// WithDrain determines if we're actually going to drain nodes
func WithSkipDrain(b bool) APICordonDrainerOption {
return func(d *APICordonDrainer) {
d.skipDrain = b
}
}

// WithAPICordonDrainerLogger configures a APICordonDrainer to use the supplied
// logger.
func WithAPICordonDrainerLogger(l *zap.Logger) APICordonDrainerOption {
return func(d *APICordonDrainer) {
d.l = l
}
}

// NewAPICordonDrainer returns a CordonDrainer that cordons and drains nodes via
// the Kubernetes API.
func NewAPICordonDrainer(c kubernetes.Interface, ao ...APICordonDrainerOption) *APICordonDrainer {
d := &APICordonDrainer{
c: c,
l: zap.NewNop(),
filter: NewPodFilters(),
maxGracePeriod: DefaultMaxGracePeriod,
evictionHeadroom: DefaultEvictionOverhead,
skipDrain: DefaultSkipDrain,
}
for _, o := range ao {
o(d)
Expand Down Expand Up @@ -234,6 +255,13 @@ func IsMarkedForDrain(n *core.Node) bool {

// Drain the supplied node. Evicts the node of all but mirror and DaemonSet pods.
func (d *APICordonDrainer) Drain(n *core.Node) error {

// Do nothing if draining is not enabled.
if d.skipDrain {
d.l.Debug("Skipping drain because draining is disabled")
return nil
}

pods, err := d.getPods(n.GetName())
if err != nil {
return errors.Wrapf(err, "cannot get pods for node %s", n.GetName())
Expand Down
4 changes: 2 additions & 2 deletions internal/kubernetes/podfilters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func TestPodFilters(t *testing.T) {
name: "NoPodAnnotations",
pod: core.Pod{
ObjectMeta: meta.ObjectMeta{
Name: podName,
Name: podName,
},
},
filter: UnprotectedPodFilter("Protect"),
Expand All @@ -208,7 +208,7 @@ func TestPodFilters(t *testing.T) {
name: "NoPodAnnotationsWithEmptyUserValue",
pod: core.Pod{
ObjectMeta: meta.ObjectMeta{
Name: podName,
Name: podName,
},
},
filter: UnprotectedPodFilter("Protect="),
Expand Down

0 comments on commit 2c3d2b4

Please sign in to comment.