Skip to content

Commit

Permalink
Merge pull request #399 from linki/namespace-scope
Browse files Browse the repository at this point in the history
Add flag that limits Kubernetes API calls to a certain namespace
  • Loading branch information
linki committed Jan 4, 2023
2 parents bc3c6ba + 678be42 commit d9db104
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 93 deletions.
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,27 +200,28 @@ INFO[0000] setting timezone location=Europe/Berlin name=CET offset=1
Use `UTC`, `Local` or pick a timezone name from the [(IANA) tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). If you're testing `chaoskube` from your local machine then `Local` makes the most sense. Once you deploy `chaoskube` to your cluster you should deploy it with a specific timezone, e.g. where most of your team members are living, so that both your team and `chaoskube` have a common understanding when a particular weekday begins and ends, for instance. If your team is spread across multiple time zones it's probably best to pick `UTC` which is also the default. Picking the wrong timezone shifts the meaning of a particular weekday by a couple of hours between you and the server.

## Flags
| Option | Environment | Description | Default |
| ------------------------- | --------------------------------- | -------------------------------------------------------------------- | -------------------------- |
| `--interval` | `CHAOSKUBE_INTERVAL` | interval between pod terminations | 10m |
| `--labels` | `CHAOSKUBE_LABELS` | label selector to filter pods by | (matches everything) |
| `--annotations` | `CHAOSKUBE_ANNOTATIONS` | annotation selector to filter pods by | (matches everything) |
| `--kinds` | `CHAOSKUBE_KINDS` | owner's kind selector to filter pods by | (all kinds) |
| `--namespaces` | `CHAOSKUBE_NAMESPACES` | namespace selector to filter pods by | (all namespaces) |
| `--namespace-labels` | `CHAOSKUBE_NAMESPACE_LABELS` | label selector to filter namespaces and its pods by | (all namespaces) |
| `--included-pod-names` | `CHAOSKUBE_INCLUDED_POD_NAMES` | regular expression pattern for pod names to include | (all included) |
| `--excluded-pod-names` | `CHAOSKUBE_EXCLUDED_POD_NAMES` | regular expression pattern for pod names to exclude | (none excluded) |
| `--excluded-weekdays` | `CHAOSKUBE_EXCLUDED_WEEKDAYS` | weekdays when chaos is to be suspended, e.g. "Sat,Sun" | (no weekday excluded) |
| `--excluded-times-of-day` | `CHAOSKUBE_EXCLUDED_TIMES_OF_DAY` | times of day when chaos is to be suspended, e.g. "22:00-08:00" | (no times of day excluded) |
| `--excluded-days-of-year` | `CHAOSKUBE_EXCLUDED_DAYS_OF_YEAR` | days of a year when chaos is to be suspended, e.g. "Apr1,Dec24" | (no days of year excluded) |
| `--timezone` | `CHAOSKUBE_TIMEZONE` | timezone from tz database, e.g. "America/New_York", "UTC" or "Local" | (UTC) |
| `--max-runtime` | `CHAOSKUBE_MAX_RUNTIME` | Maximum runtime before chaoskube exits | -1s (infinite time) |
| `--max-kill` | `CHAOSKUBE_MAX_KILL` | Specifies the maximum number of pods to be terminated per interval | 1 |
| `--minimum-age` | `CHAOSKUBE_MINIMUM_AGE` | Minimum age to filter pods by | 0s (matches every pod) |
| `--dry-run` | `CHAOSKUBE_DRY_RUN` | don't kill pods, only log what would have been done | true |
| `--log-format` | `CHAOSKUBE_LOG_FORMAT` | specify the format of the log messages. Options are text and json | text |
| `--log-caller` | `CHAOSKUBE_LOG_CALLER` | include the calling function name and location in the log messages | false |
| `--slack-webhook` | `CHAOSKUBE_SLACK_WEBHOOK` | The address of the slack webhook for notifications | disabled |
| Option | Environment | Description | Default |
| -------------------------- | ---------------------------------- | -------------------------------------------------------------------- | -------------------------- |
| `--interval` | `CHAOSKUBE_INTERVAL` | interval between pod terminations | 10m |
| `--labels` | `CHAOSKUBE_LABELS` | label selector to filter pods by | (matches everything) |
| `--annotations` | `CHAOSKUBE_ANNOTATIONS` | annotation selector to filter pods by | (matches everything) |
| `--kinds` | `CHAOSKUBE_KINDS` | owner's kind selector to filter pods by | (all kinds) |
| `--namespaces` | `CHAOSKUBE_NAMESPACES` | namespace selector to filter pods by | (all namespaces) |
| `--namespace-labels` | `CHAOSKUBE_NAMESPACE_LABELS` | label selector to filter namespaces and its pods by | (all namespaces) |
| `--included-pod-names` | `CHAOSKUBE_INCLUDED_POD_NAMES` | regular expression pattern for pod names to include | (all included) |
| `--excluded-pod-names` | `CHAOSKUBE_EXCLUDED_POD_NAMES` | regular expression pattern for pod names to exclude | (none excluded) |
| `--excluded-weekdays` | `CHAOSKUBE_EXCLUDED_WEEKDAYS` | weekdays when chaos is to be suspended, e.g. "Sat,Sun" | (no weekday excluded) |
| `--excluded-times-of-day` | `CHAOSKUBE_EXCLUDED_TIMES_OF_DAY` | times of day when chaos is to be suspended, e.g. "22:00-08:00" | (no times of day excluded) |
| `--excluded-days-of-year` | `CHAOSKUBE_EXCLUDED_DAYS_OF_YEAR` | days of a year when chaos is to be suspended, e.g. "Apr1,Dec24" | (no days of year excluded) |
| `--timezone` | `CHAOSKUBE_TIMEZONE` | timezone from tz database, e.g. "America/New_York", "UTC" or "Local" | (UTC) |
| `--max-runtime` | `CHAOSKUBE_MAX_RUNTIME` | Maximum runtime before chaoskube exits | -1s (infinite time) |
| `--max-kill` | `CHAOSKUBE_MAX_KILL` | Specifies the maximum number of pods to be terminated per interval | 1 |
| `--minimum-age` | `CHAOSKUBE_MINIMUM_AGE` | Minimum age to filter pods by | 0s (matches every pod) |
| `--dry-run` | `CHAOSKUBE_DRY_RUN` | don't kill pods, only log what would have been done | true |
| `--log-format` | `CHAOSKUBE_LOG_FORMAT` | specify the format of the log messages. Options are text and json | text |
| `--log-caller` | `CHAOSKUBE_LOG_CALLER` | include the calling function name and location in the log messages | false |
| `--slack-webhook` | `CHAOSKUBE_SLACK_WEBHOOK` | The address of the slack webhook for notifications | disabled |
| `--client-namespace-scope` | `CHAOSKUBE_CLIENT_NAMESPACE_SCOPE` | Scope Kubernetes API calls to the given namespace | (all namespaces) |

## Related work

Expand Down
49 changes: 26 additions & 23 deletions chaoskube/chaoskube.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ type Chaoskube struct {
MaxKill int
// chaos events notifier
Notifier notifier.Notifier
// namespace scope for the Kubernetes client
ClientNamespaceScope string
}

var (
Expand All @@ -95,32 +97,33 @@ var (
// * a logger implementing logrus.FieldLogger to send log output to
// * what specific terminator to use to imbue chaos on victim pods
// * whether to enable/disable dry-run mode
func New(client kubernetes.Interface, labels, annotations, kinds, namespaces, namespaceLabels labels.Selector, includedPodNames, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool, terminator terminator.Terminator, maxKill int, notifier notifier.Notifier) *Chaoskube {
func New(client kubernetes.Interface, labels, annotations, kinds, namespaces, namespaceLabels labels.Selector, includedPodNames, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool, terminator terminator.Terminator, maxKill int, notifier notifier.Notifier, clientNamespaceScope string) *Chaoskube {
broadcaster := record.NewBroadcaster()
broadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)})
broadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: client.CoreV1().Events(clientNamespaceScope)})
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "chaoskube"})

return &Chaoskube{
Client: client,
Labels: labels,
Annotations: annotations,
Kinds: kinds,
Namespaces: namespaces,
NamespaceLabels: namespaceLabels,
IncludedPodNames: includedPodNames,
ExcludedPodNames: excludedPodNames,
ExcludedWeekdays: excludedWeekdays,
ExcludedTimesOfDay: excludedTimesOfDay,
ExcludedDaysOfYear: excludedDaysOfYear,
Timezone: timezone,
MinimumAge: minimumAge,
Logger: logger,
DryRun: dryRun,
Terminator: terminator,
EventRecorder: recorder,
Now: time.Now,
MaxKill: maxKill,
Notifier: notifier,
Client: client,
Labels: labels,
Annotations: annotations,
Kinds: kinds,
Namespaces: namespaces,
NamespaceLabels: namespaceLabels,
IncludedPodNames: includedPodNames,
ExcludedPodNames: excludedPodNames,
ExcludedWeekdays: excludedWeekdays,
ExcludedTimesOfDay: excludedTimesOfDay,
ExcludedDaysOfYear: excludedDaysOfYear,
Timezone: timezone,
MinimumAge: minimumAge,
Logger: logger,
DryRun: dryRun,
Terminator: terminator,
EventRecorder: recorder,
Now: time.Now,
MaxKill: maxKill,
Notifier: notifier,
ClientNamespaceScope: clientNamespaceScope,
}
}

Expand Down Expand Up @@ -211,7 +214,7 @@ func (c *Chaoskube) Victims(ctx context.Context) ([]v1.Pod, error) {
func (c *Chaoskube) Candidates(ctx context.Context) ([]v1.Pod, error) {
listOptions := metav1.ListOptions{LabelSelector: c.Labels.String()}

podList, err := c.Client.CoreV1().Pods(v1.NamespaceAll).List(ctx, listOptions)
podList, err := c.Client.CoreV1().Pods(c.ClientNamespaceScope).List(ctx, listOptions)
if err != nil {
return nil, err
}
Expand Down
54 changes: 52 additions & 2 deletions chaoskube/chaoskube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (suite *Suite) TestNew() {
terminator,
maxKill,
notifier,
v1.NamespaceAll,
)
suite.Require().NotNil(chaoskube)

Expand Down Expand Up @@ -124,6 +125,7 @@ func (suite *Suite) TestRunContextCanceled() {
false,
10,
1,
v1.NamespaceAll,
)

ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -179,6 +181,7 @@ func (suite *Suite) TestCandidates() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)

suite.assertCandidates(chaoskube, tt.pods)
Expand Down Expand Up @@ -224,6 +227,41 @@ func (suite *Suite) TestCandidatesNamespaceLabels() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)

suite.assertCandidates(chaoskube, tt.pods)
}
}

func (suite *Suite) TestCandidatesClientNamespaceScope() {
foo := map[string]string{"namespace": "default", "name": "foo"}
bar := map[string]string{"namespace": "testing", "name": "bar"}

for _, tt := range []struct {
clientNamespaceScope string
pods []map[string]string
}{
{v1.NamespaceAll, []map[string]string{foo, bar}},
{"default", []map[string]string{foo}},
{"testing", []map[string]string{bar}},
} {
chaoskube := suite.setupWithPods(
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
labels.Everything(),
nil,
nil,
[]time.Weekday{},
[]util.TimePeriod{},
[]time.Time{},
time.UTC,
time.Duration(0),
false,
10,
tt.clientNamespaceScope,
)

suite.assertCandidates(chaoskube, tt.pods)
Expand Down Expand Up @@ -267,6 +305,7 @@ func (suite *Suite) TestCandidatesPodNameRegexp() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)

suite.assertCandidates(chaoskube, tt.pods)
Expand Down Expand Up @@ -307,6 +346,7 @@ func (suite *Suite) TestVictim() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)

suite.assertVictim(chaoskube, tt.victim)
Expand Down Expand Up @@ -361,6 +401,7 @@ func (suite *Suite) TestVictims() {
false,
10,
tt.maxKill,
v1.NamespaceAll,
)
suite.createPods(chaoskube.Client, podsInfo)

Expand All @@ -386,6 +427,7 @@ func (suite *Suite) TestNoVictimReturnsError() {
false,
10,
1,
v1.NamespaceAll,
)

_, err := chaoskube.Victims(context.Background())
Expand Down Expand Up @@ -420,6 +462,7 @@ func (suite *Suite) TestDeletePod() {
time.Duration(0),
tt.dryRun,
10,
v1.NamespaceAll,
)

victim := util.NewPod("default", "foo", v1.PodRunning)
Expand Down Expand Up @@ -450,6 +493,7 @@ func (suite *Suite) TestDeletePodNotFound() {
false,
10,
1,
v1.NamespaceAll,
)

victim := util.NewPod("default", "foo", v1.PodRunning)
Expand Down Expand Up @@ -681,6 +725,7 @@ func (suite *Suite) TestTerminateVictim() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)
chaoskube.Now = tt.now

Expand Down Expand Up @@ -712,6 +757,7 @@ func (suite *Suite) TestTerminateNoVictimLogsInfo() {
false,
10,
1,
v1.NamespaceAll,
)

err := chaoskube.TerminateVictims(context.Background())
Expand Down Expand Up @@ -746,7 +792,7 @@ func (suite *Suite) assertNotified(notifier *notifier.Noop) {
suite.Assert().Greater(notifier.Calls, 0)
}

func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, kinds labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration) *Chaoskube {
func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations labels.Selector, kinds labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration, clientNamespaceScope string) *Chaoskube {
chaoskube := suite.setup(
labelSelector,
annotations,
Expand All @@ -763,6 +809,7 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
dryRun,
gracePeriod,
1,
clientNamespaceScope,
)

for _, namespace := range []v1.Namespace{
Expand Down Expand Up @@ -798,7 +845,7 @@ func (suite *Suite) createPods(client kubernetes.Interface, podsInfo []podInfo)
}
}

func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, kinds labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration, maxKill int) *Chaoskube {
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, kinds labels.Selector, namespaces labels.Selector, namespaceLabels labels.Selector, includedPodNames *regexp.Regexp, excludedPodNames *regexp.Regexp, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, gracePeriod time.Duration, maxKill int, clientNamespaceScope string) *Chaoskube {
logOutput.Reset()

client := fake.NewSimpleClientset()
Expand All @@ -823,6 +870,7 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
terminator.NewDeletePodTerminator(client, nullLogger, gracePeriod),
maxKill,
testNotifier,
clientNamespaceScope,
)
}

Expand Down Expand Up @@ -928,6 +976,7 @@ func (suite *Suite) TestMinimumAge() {
false,
10,
1,
v1.NamespaceAll,
)
chaoskube.Now = tt.now

Expand Down Expand Up @@ -1110,6 +1159,7 @@ func (suite *Suite) TestNotifierCall() {
time.Duration(0),
false,
10,
v1.NamespaceAll,
)

victim := util.NewPod("default", "foo", v1.PodRunning)
Expand Down
Loading

0 comments on commit d9db104

Please sign in to comment.