diff --git a/CHANGELOG.md b/CHANGELOG.md index 657431e04..b17eda5e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A) +- Added annotation to rotate ArangoDeployment in secure way ## [1.0.0](https://github.com/arangodb/kube-arangodb/tree/1.0.0) (2020-03-03) - Removal of v1alpha support for ArangoDeployment, ArangoDeploymentReplication, ArangoBackup diff --git a/docs/design/README.md b/docs/design/README.md index 61fb869bb..ee091d638 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -8,4 +8,5 @@ - [Scaling](./scaling.md) - [Status](./status.md) - [Upgrading](./upgrading.md) +- [Rotating Pods](./rotating.md) - [Maintenance](./maintenance.md) \ No newline at end of file diff --git a/docs/design/rotating.md b/docs/design/rotating.md new file mode 100644 index 000000000..2cd7a1591 --- /dev/null +++ b/docs/design/rotating.md @@ -0,0 +1,13 @@ +# Rotation + +## ArangoDeployment + +Rotation of ArangoDeployment Pods can be triggered by Pod deletion or by annotation (safe way). + +Using annotation Pods gonna be rotated one-by-one which will keep cluster alive. + +Key: `deployment.arangodb.com/rotation` +Value: `true` + +To rotate ArangoDeployment Pod kubectl command can be used: +`kubectl annotate pod arango-pod deployment.arangodb.com/rotation=true` \ No newline at end of file diff --git a/pkg/apis/deployment/annotations.go b/pkg/apis/deployment/annotations.go new file mode 100644 index 000000000..f47e26353 --- /dev/null +++ b/pkg/apis/deployment/annotations.go @@ -0,0 +1,29 @@ +// +// DISCLAIMER +// +// Copyright 2020 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package deployment + +const ( + ArangoDeploymentAnnotationPrefix = "deployment.arangodb.com" + ArangoDeploymentPodMaintenanceAnnotation = ArangoDeploymentAnnotationPrefix + "/maintenance" + ArangoDeploymentPodRotateAnnotation = ArangoDeploymentAnnotationPrefix + "/rotate" +) diff --git a/pkg/deployment/deployment_inspector.go b/pkg/deployment/deployment_inspector.go index aab2cc399..98686ba15 100644 --- a/pkg/deployment/deployment_inspector.go +++ b/pkg/deployment/deployment_inspector.go @@ -26,6 +26,8 @@ import ( "context" "time" + "github.com/arangodb/kube-arangodb/pkg/apis/deployment" + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/metrics" "github.com/arangodb/kube-arangodb/pkg/util" @@ -37,10 +39,6 @@ var ( inspectDeploymentDurationGauges = metrics.MustRegisterGaugeVec(metricsComponent, "inspect_deployment_duration", "Amount of time taken by a single inspection of a deployment (in sec)", metrics.DeploymentName) ) -const ( - arangoDeploymentMaintenanceAnnotation = "deployment.arangodb.com/maintenance" -) - // inspectDeployment inspects the entire deployment, creates // a plan to update if needed and inspects underlying resources. // This function should be called when: @@ -74,7 +72,7 @@ func (d *Deployment) inspectDeployment(lastInterval util.Interval) util.Interval } else { // Check if maintenance annotation is set if updated != nil && updated.Annotations != nil { - if v, ok := updated.Annotations[arangoDeploymentMaintenanceAnnotation]; ok && v == "true" { + if v, ok := updated.Annotations[deployment.ArangoDeploymentPodMaintenanceAnnotation]; ok && v == "true" { // Disable checks if we will enter maintenance mode log.Info().Str("deployment", deploymentName).Msg("Deployment in maintenance mode") return nextInterval diff --git a/pkg/deployment/reconcile/plan_builder_rotate_upgrade.go b/pkg/deployment/reconcile/plan_builder_rotate_upgrade.go index f9254f344..42b555ffa 100644 --- a/pkg/deployment/reconcile/plan_builder_rotate_upgrade.go +++ b/pkg/deployment/reconcile/plan_builder_rotate_upgrade.go @@ -26,6 +26,9 @@ import ( "reflect" "strings" + "github.com/arangodb/kube-arangodb/pkg/apis/deployment" + "github.com/arangodb/kube-arangodb/pkg/deployment/pod" + "github.com/arangodb/kube-arangodb/pkg/deployment/pod" "github.com/arangodb/go-driver" @@ -87,6 +90,17 @@ func createRotateOrUpgradePlan(log zerolog.Logger, apiObject k8sutil.APIObject, newPlan = createRotateMemberPlan(log, m, group, reason) } } + + if !newPlan.IsEmpty() { + // Only rotate/upgrade 1 pod at a time + continue + } + + if pod.Annotations != nil { + if _, ok := pod.Annotations[deployment.ArangoDeploymentPodRotateAnnotation]; ok { + newPlan = createRotateMemberPlan(log, m, group, "Rotation flag present") + } + } } return nil }) diff --git a/pkg/util/k8sutil/map.go b/pkg/util/k8sutil/map.go index fd1c6ebd7..010a672ed 100644 --- a/pkg/util/k8sutil/map.go +++ b/pkg/util/k8sutil/map.go @@ -22,11 +22,15 @@ package k8sutil -import "regexp" +import ( + "regexp" + + "github.com/arangodb/kube-arangodb/pkg/apis/deployment" +) const ( kubernetesAnnotationMatch = ".*kubernetes\\.io/.*" - arangoAnnotationMatch = ".*arangodb\\.com/" + arangoAnnotationMatch = ".*arangodb\\.com/.*" ) var ( @@ -50,6 +54,29 @@ func init() { arangoAnnotationRegex = r } +func isFilteredBlockedAnnotation(key string) bool { + switch key { + case deployment.ArangoDeploymentPodRotateAnnotation: + return true + default: + return false + } +} + +func filterBlockedAnnotations(m map[string]string) map[string]string { + n := map[string]string{} + + for key, value := range m { + if isFilteredBlockedAnnotation(key) { + continue + } + + n[key] = value + } + + return n +} + // MergeAnnotations into one annotations map func MergeAnnotations(annotations ...map[string]string) map[string]string { ret := map[string]string{} @@ -114,7 +141,12 @@ func filterActualAnnotations(actual, expected map[string]string) map[string]stri // CompareAnnotations will compare annotations, but will ignore secured annotations which are present in // actual but not specified in expected map +// It will also filter out blocked annotations func CompareAnnotations(actual, expected map[string]string) bool { + return compareAnnotations(filterBlockedAnnotations(actual), filterBlockedAnnotations(expected)) +} + +func compareAnnotations(actual, expected map[string]string) bool { actualFiltered := filterActualAnnotations(actual, expected) if actualFiltered == nil && expected == nil {