Skip to content

Commit

Permalink
Add cron scheduling of deletes (#14)
Browse files Browse the repository at this point in the history
* Add deletionSchedule and fix bad metric

* Update Dockerfile and go.sum
  • Loading branch information
akursell-wish authored Jan 28, 2021
1 parent 3854b6d commit 23a331b
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 40 deletions.
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
FROM --platform=$BUILDPLATFORM golang:1.12
FROM --platform=$BUILDPLATFORM golang:1.15

ARG BUILDPLATFORM
ARG TARGETARCH
ARG TARGETOS

ENV GO111MODULE=on
WORKDIR /go/src/github.com/wish/nodereaper

# Cache dependencies
Expand All @@ -20,7 +19,7 @@ RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} GOOS=${TARGETOS} go build -o ./nodereaper
# Build daemon
RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} GOOS=${TARGETOS} go build -o ./nodereaperd/nodereaperd -a -installsuffix cgo ./nodereaperd

FROM alpine:3.7
FROM alpine:3.13
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/wish/nodereaper/nodereaper/nodereaper /root/nodereaper
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Setting Name | Type | Default | Description
`deleteOldLaunchConfig` | `bool` | `false` | Whether to delete nodes with a different Launch Configuration than their group. With this set, `nodereaper` can perform the function of `kops rolling-update cluster` automatically after a change to configuration is made.
`deletionAge` | `*time.Duration` | `nil` | If set, the controller will delete any node older than this value.
`deletionAgeJitter` | `*time.Duration` | `nil` | If this is set, along with `deletionAge`, the controller will randomly delete nodes when their age is somewhere between `deletionAge` and `deletionAge + deletionAgeJitter`.
`deletionSchedule` | `*cron.Schedule` | `nil` | A crontab schedule defining when, in UTC (**not local time!**), nodes can be deleted (ex. `weekends from 6 to 8 pm` -> `* 18-20 * * 0,6`)
`startupGracePeriod` | `*time.Duration` | `nil` | Ignore nodes newer than this. Useful to allow time for new nodes to become `Ready`, schedule pods, etc before terminating more.
`ignoreSelector` | `string` | `kubernetes.io/role=master` | Ignore any node that matches this label selector. Ignored nodes still count towards group size, but they will never be deleted.
`ignore` | `bool` | `false` | Ignore every single node in the group (if specified per-group), or ignore every node in the cluster (if specified globally).
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
Expand Down
30 changes: 30 additions & 0 deletions pkg/config/dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/sirupsen/logrus"
"github.com/wish/nodereaper/pkg/cron"
)

var defaults map[string]string = map[string]string{
Expand All @@ -16,6 +17,7 @@ var defaults map[string]string = map[string]string{
"deleteOldLaunchConfig": "false",
"deletionAge": "",
"deletionAgeJitter": "",
"deletionSchedule": "",
"startupGracePeriod": "",
"ignoreSelector": "kubernetes.io/role=master",
"ignore": "false",
Expand Down Expand Up @@ -145,6 +147,23 @@ func (c *DynamicConfig) GetDuration(groupName, key string) *time.Duration {
panic("No default exists for setting " + key)
}

func (c *DynamicConfig) GetSchedule(groupName, key string) *cron.Schedule {
if groupSettings, ok := c.settings[groupName]; ok {
if setting, ok := groupSettings[key]; ok {
return parseSchedule(setting)
}
}
if globalSettings, ok := c.settings[""]; ok {
if setting, ok := globalSettings[key]; ok {
return parseSchedule(setting)
}
}
if defaultSetting, ok := defaults[key]; ok {
return parseSchedule(defaultSetting)
}
panic("No default exists for setting " + key)
}

func parseBool(s string) bool {
if s == "true" {
return true
Expand All @@ -164,3 +183,14 @@ func parseDuration(s string) *time.Duration {
}
return &d
}

func parseSchedule(s string) *cron.Schedule {
if s == "" {
return nil
}
p, err := cron.ParseStandard(s)
if err != nil {
panic(fmt.Sprintf("Schedule %v is not valid: %v", s, err))
}
return p
}
1 change: 1 addition & 0 deletions pkg/cron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adapted from https://github.com/robfig/cron
85 changes: 85 additions & 0 deletions pkg/cron/cron_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cron

import (
"testing"
"time"
)

type test struct {
t time.Time
res bool
}

func TestHourDay(t *testing.T) {
s, err := ParseStandard("* 2-4 5-10 * *")
if err != nil {
t.Error(err)
}

tests := []test{
// Test day of month
{time.Date(2021, time.March, 4, 3, 0, 0, 0, time.UTC), false},
{time.Date(2021, time.March, 5, 3, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 6, 3, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 9, 3, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 10, 3, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 11, 3, 0, 0, 0, time.UTC), false},

// Test hour
{time.Date(2021, time.March, 5, 1, 0, 0, 0, time.UTC), false},
{time.Date(2021, time.March, 5, 2, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 5, 3, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 5, 4, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 5, 5, 0, 0, 0, time.UTC), false},

// Test minute/second
{time.Date(2021, time.March, 5, 1, 59, 59, 0, time.UTC), false},
{time.Date(2021, time.March, 5, 2, 0, 0, 0, time.UTC), true},
}

for _, test := range tests {
if s.Matches(test.t) != test.res {
t.Errorf("Failed testing date %s, got result %v, wanted %v", test.t, !test.res, test.res)
}
}
}

func TestWeekendNights(t *testing.T) {
// Weekends from 6 to 8 pm
s, err := ParseStandard("* 18-20 * * 0,6")
if err != nil {
t.Error(err)
}

tests := []test{
// Test day
{time.Date(2021, time.March, 5, 18, 0, 0, 0, time.UTC), false},
{time.Date(2021, time.March, 6, 18, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 7, 18, 0, 0, 0, time.UTC), true},
{time.Date(2021, time.March, 8, 18, 0, 0, 0, time.UTC), false},
}

for _, test := range tests {
if s.Matches(test.t) != test.res {
t.Errorf("Failed testing date %s, got result %v, wanted %v", test.t, !test.res, test.res)
}
}
}

func TestMinues(t *testing.T) {
// Weekends from 6 to 8 pm
s, err := ParseStandard("25-30 * * * *")
if err != nil {
t.Error(err)
}

tests := []test{
{time.Date(2021, time.January, 27, 20, 26, 19, 0, time.UTC), true},
}

for _, test := range tests {
if s.Matches(test.t) != test.res {
t.Errorf("Failed testing date %s, got result %v, wanted %v", test.t, !test.res, test.res)
}
}
}
Loading

0 comments on commit 23a331b

Please sign in to comment.