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:
		
							parent
							
								
									c1cb216262
								
							
						
					
					
						commit
						eee47333cf
					
				
							
								
								
									
										39
									
								
								README.md
								
								
								
								
							
							
						
						
									
										39
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
# kustomize-controller
 | 
					# kustomize-controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://github.com/fluxcd/kustomize-controller/actions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The kustomize-controller is a Kubernetes operator that applies kustomizations in-cluster.
 | 
					The kustomize-controller is a Kubernetes operator that applies kustomizations in-cluster.
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +21,7 @@ A kustomization object defines the source of Kubernetes manifests by referencing
 | 
				
			||||||
the path to the kustomization file, 
 | 
					the path to the kustomization file, 
 | 
				
			||||||
and a label selector used for garbage collection of resources removed from the Git source.
 | 
					and a label selector used for garbage collection of resources removed from the Git source.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Specification:
 | 
					### Specification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```go
 | 
					```go
 | 
				
			||||||
// KustomizationSpec defines the desired state of a kustomization.
 | 
					// KustomizationSpec defines the desired state of a kustomization.
 | 
				
			||||||
| 
						 | 
					@ -29,7 +31,7 @@ type KustomizationSpec struct {
 | 
				
			||||||
	// +required
 | 
						// +required
 | 
				
			||||||
	Path string `json:"path"`
 | 
						Path string `json:"path"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Label selector used for prune operations, e.g. env=staging.
 | 
						// Label selector used for garbage collection.
 | 
				
			||||||
	// +kubebuilder:validation:Pattern="^.*=.*$"
 | 
						// +kubebuilder:validation:Pattern="^.*=.*$"
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	Prune string `json:"prune,omitempty"`
 | 
						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)
 | 
					* [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
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Build prerequisites:
 | 
					Build prerequisites:
 | 
				
			||||||
| 
						 | 
					@ -74,6 +94,8 @@ make docker-build docker-push dev-deploy IMG=your-docker-hub-username/kustomize-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Define a Git repository source
 | 
					### Define a Git repository source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Create a source object that points to a Git repository containing Kubernetes and Kustomize manifests:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```yaml
 | 
					```yaml
 | 
				
			||||||
apiVersion: source.fluxcd.io/v1alpha1
 | 
					apiVersion: source.fluxcd.io/v1alpha1
 | 
				
			||||||
kind: GitRepository
 | 
					kind: GitRepository
 | 
				
			||||||
| 
						 | 
					@ -87,6 +109,9 @@ spec:
 | 
				
			||||||
    branch: master
 | 
					    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.
 | 
					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:
 | 
					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
 | 
					    name: podinfo
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
With `spec.path` we tell the controller where to look for the kustomization file and with `spec.prune` we 
 | 
					With `spec.path` we tell the controller where to look for the `kustomization.yaml` file.
 | 
				
			||||||
configure garbage collection. With `spec.interval` we tell the controller how often it should reconcile 
 | 
					With `spec.prune` we configure garbage collection.
 | 
				
			||||||
the cluster state.
 | 
					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.
 | 
					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:
 | 
					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
 | 
					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.
 | 
					set in the Git repository manifest.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## GitOps pipeline
 | 
					## GitOps workflow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Example:
 | 
					Example:
 | 
				
			||||||
* create a `GitRepository` per app (example repo [podinfo-deploy](https://github.com/stefanprodan/podinfo-deploy))
 | 
					* create a `GitRepository` per app (example repo [podinfo-deploy](https://github.com/stefanprodan/podinfo-deploy))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ type KustomizationSpec struct {
 | 
				
			||||||
	// +required
 | 
						// +required
 | 
				
			||||||
	Path string `json:"path"`
 | 
						Path string `json:"path"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Label selector used for prune operations, e.g. env=staging.
 | 
						// Label selector used for garbage collection.
 | 
				
			||||||
	// +kubebuilder:validation:Pattern="^.*=.*$"
 | 
						// +kubebuilder:validation:Pattern="^.*=.*$"
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	Prune string `json:"prune,omitempty"`
 | 
						Prune string `json:"prune,omitempty"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ spec:
 | 
				
			||||||
              pattern: ^\./
 | 
					              pattern: ^\./
 | 
				
			||||||
              type: string
 | 
					              type: string
 | 
				
			||||||
            prune:
 | 
					            prune:
 | 
				
			||||||
              description: Label selector used for prune operations, e.g. env=staging.
 | 
					              description: Label selector used for garbage collection.
 | 
				
			||||||
              pattern: ^.*=.*$
 | 
					              pattern: ^.*=.*$
 | 
				
			||||||
              type: string
 | 
					              type: string
 | 
				
			||||||
            sourceRef:
 | 
					            sourceRef:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,7 +83,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
 | 
				
			||||||
	// try git sync
 | 
						// try git sync
 | 
				
			||||||
	syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy(), source)
 | 
						syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy(), source)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error(err, "Kustomization sync failed")
 | 
							log.Error(err, "Kustomization apply failed")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// update status
 | 
						// update status
 | 
				
			||||||
| 
						 | 
					@ -101,6 +101,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
 | 
				
			||||||
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
 | 
					func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
 | 
				
			||||||
	return ctrl.NewControllerManagedBy(mgr).
 | 
						return ctrl.NewControllerManagedBy(mgr).
 | 
				
			||||||
		For(&kustomizev1.Kustomization{}).
 | 
							For(&kustomizev1.Kustomization{}).
 | 
				
			||||||
 | 
							WithEventFilter(KustomizationGarbageCollectPredicate{Log: r.Log}).
 | 
				
			||||||
		WithEventFilter(KustomizationSyncAtPredicate{}).
 | 
							WithEventFilter(KustomizationSyncAtPredicate{}).
 | 
				
			||||||
		Complete(r)
 | 
							Complete(r)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,10 @@ limitations under the License.
 | 
				
			||||||
package controllers
 | 
					package controllers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-logr/logr"
 | 
				
			||||||
	"sigs.k8s.io/controller-runtime/pkg/event"
 | 
						"sigs.k8s.io/controller-runtime/pkg/event"
 | 
				
			||||||
	"sigs.k8s.io/controller-runtime/pkg/predicate"
 | 
						"sigs.k8s.io/controller-runtime/pkg/predicate"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,3 +54,30 @@ func (KustomizationSyncAtPredicate) Update(e event.UpdateEvent) bool {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return false
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue