Implement garbage collection

- add GarbageCollect predicate
- remove the Kubernetes objects previously applied on the cluster, based on the prune label select
- add garbage collection spec to docs
This commit is contained in:
stefanprodan 2020-04-17 12:39:24 +03:00
parent c1cb216262
commit eee47333cf
5 changed files with 67 additions and 10 deletions

View File

@ -1,5 +1,7 @@
# kustomize-controller
[![e2e](https://github.com/fluxcd/kustomize-controller/workflows/e2e/badge.svg)](https://github.com/fluxcd/kustomize-controller/actions)
The kustomize-controller is a Kubernetes operator that applies kustomizations in-cluster.
![overview](docs/diagrams/fluxcd-kustomize-source-controllers.png)
@ -19,7 +21,7 @@ A kustomization object defines the source of Kubernetes manifests by referencing
the path to the kustomization file,
and a label selector used for garbage collection of resources removed from the Git source.
Specification:
### Specification
```go
// KustomizationSpec defines the desired state of a kustomization.
@ -29,7 +31,7 @@ type KustomizationSpec struct {
// +required
Path string `json:"path"`
// Label selector used for prune operations, e.g. env=staging.
// Label selector used for garbage collection.
// +kubebuilder:validation:Pattern="^.*=.*$"
// +optional
Prune string `json:"prune,omitempty"`
@ -44,9 +46,27 @@ type KustomizationSpec struct {
}
```
Supported source kinds:
### Supported source kinds
* [GitRepository](https://github.com/fluxcd/source-controller/blob/master/docs/spec/v1alpha1/gitrepositories.md)
### Garbage collection
Garbage collection means that the Kubernetes objects that were previously applied on the cluster
but are missing from the current apply, will be removed. Garbage collection is also performed when a Kustomization
object is deleted, triggering a removal of all Kubernetes objects previously applied on the cluster.
When garbage collection is enabled, all Kubernetes objects must have a common label that matches the `prune`
[label selector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/).
For example, `prune: env=dev` requires a `kustomization.yaml` with `commonLabels`:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels:
env: dev
```
## Usage
Build prerequisites:
@ -74,6 +94,8 @@ make docker-build docker-push dev-deploy IMG=your-docker-hub-username/kustomize-
### Define a Git repository source
Create a source object that points to a Git repository containing Kubernetes and Kustomize manifests:
```yaml
apiVersion: source.fluxcd.io/v1alpha1
kind: GitRepository
@ -87,6 +109,9 @@ spec:
branch: master
```
For private repositories, SSH or token based authentication can be
[configured with Kubernetes secrets](https://github.com/fluxcd/source-controller/blob/master/docs/spec/v1alpha1/gitrepositories.md).
Save the above file and apply it on the cluster.
You can wait for the source controller to assemble an artifact from the head of the repo master branch with:
@ -118,9 +143,9 @@ spec:
name: podinfo
```
With `spec.path` we tell the controller where to look for the kustomization file and with `spec.prune` we
configure garbage collection. With `spec.interval` we tell the controller how often it should reconcile
the cluster state.
With `spec.path` we tell the controller where to look for the `kustomization.yaml` file.
With `spec.prune` we configure garbage collection.
With `spec.interval` we tell the controller how often it should reconcile the cluster state.
Save the above file and apply it on the cluster.
You can wait for the kustomize controller to apply the manifest corresponding to the dev overlay with:
@ -201,7 +226,7 @@ spec:
Based on the above definition, the kustomize controller will build and apply a kustomization that matches the semver range
set in the Git repository manifest.
## GitOps pipeline
## GitOps workflow
Example:
* create a `GitRepository` per app (example repo [podinfo-deploy](https://github.com/stefanprodan/podinfo-deploy))

View File

@ -28,7 +28,7 @@ type KustomizationSpec struct {
// +required
Path string `json:"path"`
// Label selector used for prune operations, e.g. env=staging.
// Label selector used for garbage collection.
// +kubebuilder:validation:Pattern="^.*=.*$"
// +optional
Prune string `json:"prune,omitempty"`

View File

@ -54,7 +54,7 @@ spec:
pattern: ^\./
type: string
prune:
description: Label selector used for prune operations, e.g. env=staging.
description: Label selector used for garbage collection.
pattern: ^.*=.*$
type: string
sourceRef:

View File

@ -83,7 +83,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
// try git sync
syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy(), source)
if err != nil {
log.Error(err, "Kustomization sync failed")
log.Error(err, "Kustomization apply failed")
}
// update status
@ -101,6 +101,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}).
WithEventFilter(KustomizationGarbageCollectPredicate{Log: r.Log}).
WithEventFilter(KustomizationSyncAtPredicate{}).
Complete(r)
}

View File

@ -17,6 +17,10 @@ limitations under the License.
package controllers
import (
"fmt"
"os/exec"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
@ -50,3 +54,30 @@ func (KustomizationSyncAtPredicate) Update(e event.UpdateEvent) bool {
return false
}
type KustomizationGarbageCollectPredicate struct {
predicate.Funcs
Log logr.Logger
}
// Delete removes all Kubernetes objects based on the prune label selector.
func (gc KustomizationGarbageCollectPredicate) Delete(e event.DeleteEvent) bool {
if k, ok := e.Object.(*kustomizev1.Kustomization); ok {
if k.Spec.Prune != "" {
cmd := fmt.Sprintf("kubectl delete all --all-namespaces --timeout=%s -l %s",
k.Spec.Interval.Duration.String(), k.Spec.Prune)
command := exec.Command("/bin/sh", "-c", cmd)
if output, err := command.CombinedOutput(); err != nil {
gc.Log.Error(err, "Garbage collection failed",
"output", string(output),
"Kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName()))
} else {
gc.Log.Info("Garbage collection completed",
"output", string(output),
"Kustomization", fmt.Sprintf("%s/%s", k.GetNamespace(), k.GetName()))
}
}
}
return true
}