Browse Source

add finalizers and purge PVC flags

tags/0.0.3
Vladimir Smagin 3 weeks ago
parent
commit
aee47ae9f9

+ 1
- 1
Makefile View File

@@ -13,8 +13,8 @@ build:

push:
docker tag $(OPERATOR_IMAGE_NAME):$(VERSION) iam21h/wordpress-operator:$(VERSION)
docker tag $(OPERATOR_IMAGE_NAME):$(VERSION) iam21h/wordpress-operator:latest
docker push iam21h/wordpress-operator:$(VERSION)
docker tag $(OPERATOR_IMAGE_NAME):$(VERSION) iam21h/wordpress-operator:latest
docker push iam21h/wordpress-operator:latest

clean:

+ 4
- 1
README.md View File

@@ -28,7 +28,7 @@ Use internal or external database. _Internal_ database means a single pod with M

Think scalability before first start, because you can't mount `ReadWriteOnce` persistent volume to multiple pods (`replicas` > 1), so you need storage class with `ReadWriteMany` feature.

<mark>If you remove `Wordpress` resource operator delete all wordpress pods, internal database pod and PVC's.</mark>
<mark>Changed: now PVCs not purging after custom resource deletion. To delete PVCs use special flags.</mark>

This sample resource can help you run your first instance with many pods:

@@ -43,6 +43,9 @@ spec:
wordpressVolumeSize: 2Gi
# set storage class with ReadWriteMany feature
wordpressStorageClass: nfs
# purge PVCs after use
databaseVolumeCleanup: true
wordpressVolumeCleanup: true
```

Or if you want to use external database:

+ 1
- 1
VERSION View File

@@ -1 +1 @@
0.0.2
0.0.3

+ 5
- 2
deploy/test-instance.yaml View File

@@ -7,9 +7,12 @@ spec:
# set few replicas, so you need storage class with ReadWriteMany feature
replicas: 3
databaseVolumeSize: 1Gi
databaseVolumeCleanup: true
wordpressVolumeSize: 2Gi
# set storage class with ReadWriteMany feature
wordpressStorageClass: nfs
wordpressVolumeCleanup: true


---
apiVersion: extensions/v1beta1
@@ -22,10 +25,10 @@ metadata:
spec:
tls:
- hosts:
- "blog3.k8s.blindage.org"
- "blog.k8s.blindage.org"
secretName: my-wordpress-tls
rules:
- host: blog3.k8s.blindage.org
- host: blog.k8s.blindage.org
http:
paths:
- path: /

+ 0
- 1
go.mod View File

@@ -1,7 +1,6 @@
module git.blindage.org/21h/wordpress-operator

require (
github.com/NYTimes/gziphandler v1.0.1 // indirect
github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect

+ 12
- 10
pkg/apis/blindage/v1alpha1/wordpress_types.go View File

@@ -27,13 +27,14 @@ type WordpressSpec struct {
// Define database if you want use external DB
Database *ExternalMysql `json:"database,omitempty"`
// Internal database will be initialized with this parametes
DatabaseImage string `json:"databaseImage,omitempty"` // default mariadb:latest
DatabasePodRequests *v1.ResourceList `json:"databasePodRequests,omitempty"`
DatabasePodLimits *v1.ResourceList `json:"databasePodLimits,omitempty"`
DatabaseAffinity *v1.Affinity `json:"databaseAffinity,omitempty"`
DatabaseVolumeSize *resource.Quantity `json:"databaseVolumeSize,omitempty"` //default 1 Gb
DatabaseNodeSelector *map[string]string `json:"databaseNodeSelector"`
DatabaseTolerations *[]v1.Toleration `json:"databaseTolerations"`
DatabaseImage string `json:"databaseImage,omitempty"` // default mariadb:latest
DatabasePodRequests *v1.ResourceList `json:"databasePodRequests,omitempty"`
DatabasePodLimits *v1.ResourceList `json:"databasePodLimits,omitempty"`
DatabaseAffinity *v1.Affinity `json:"databaseAffinity,omitempty"`
DatabaseVolumeSize *resource.Quantity `json:"databaseVolumeSize,omitempty"` //default 1 Gb
DatabaseVolumeCleanup bool `json:"databaseVolumeCleanup,omitempty"` // true to save PVC after CR delete
DatabaseNodeSelector *map[string]string `json:"databaseNodeSelector"`
DatabaseTolerations *[]v1.Toleration `json:"databaseTolerations"`

// manage resources
WordpressPodRequests *v1.ResourceList `json:"wordpressPodRequests,omitempty"`
@@ -42,9 +43,10 @@ type WordpressSpec struct {
WordpressNodeSelector *map[string]string `json:"wordpressNodeSelector"`
WordpressTolerations *[]v1.Toleration `json:"wordpressTolerations"`

// set volume size for wp-content
WordpressVolumeSize *resource.Quantity `json:"wordpressVolumeSize,omitempty"` // default 1 Gb
WordpressStorageClass string `json:"wordpressStorageClass,omitempty"`
// set volume for wp-content
WordpressVolumeCleanup bool `json:"wordpressVolumeCleanup,omitempty"` // true to save PVC after CR delete
WordpressVolumeSize *resource.Quantity `json:"wordpressVolumeSize,omitempty"` // default 1 Gb
WordpressStorageClass string `json:"wordpressStorageClass,omitempty"`

// set annotations to service and pod, default is empty
WordpressServiceAnnotations *map[string]string `json:"wordpressServiceAnnotations,omitempty"`

+ 83
- 0
pkg/controller/wordpress/finalizers.go View File

@@ -0,0 +1,83 @@
package wordpress

import (
"context"

blindagev1alpha1 "git.blindage.org/21h/wordpress-operator/pkg/apis/blindage/v1alpha1"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func (r *ReconcileWordpress) deleteDependentResoucePVC(reqLogger logr.Logger, cr *blindagev1alpha1.Wordpress) (reconcile.Result, error) {
reqLogger.Info("Deleting PVC")
// Finalizer PVC: remove finalizer from CR
if containsString(cr.ObjectMeta.Finalizers, "PVC") {
// get dependent PVCs
foundDependentPVCs := &corev1.PersistentVolumeClaimList{}

labels := mergeLabels(baseLabels(cr))

err := r.client.List(context.TODO(), client.InNamespace(cr.Namespace).MatchingLabels(labels), foundDependentPVCs)
if err != nil {
reqLogger.Info("Listing dependent PVCs error", "Namespace", cr.Namespace, ".Name", cr.Name)
return reconcile.Result{}, err
}

for _, pvc := range foundDependentPVCs.Items {
// delete dependent PVCs
if pvc.Labels["component"] == "wordpress" && cr.Spec.WordpressVolumeCleanup {
if err := r.client.Delete(context.Background(), &pvc); err != nil {
reqLogger.Info("Deleting PVC failed")
return reconcile.Result{}, err
}
}
if pvc.Labels["component"] == "database" && cr.Spec.DatabaseVolumeCleanup {
if err := r.client.Delete(context.Background(), &pvc); err != nil {
reqLogger.Info("Deleting PVC failed")
return reconcile.Result{}, err
}
}
}
}
return reconcile.Result{}, nil
}

func (r *ReconcileWordpress) reconcileFinalizers(reqLogger logr.Logger, instance *blindagev1alpha1.Wordpress) (reconcile.Result, error) {
reqLogger.Info("Processing finalizers", "Namespace", instance.Namespace, ".Name", instance.Name)
// Define finalizer strings to prevent deletion of CR before dependent resources deletion
finalizersList := []string{"PVC"}

// Check CR is being deleted or not
if instance.ObjectMeta.DeletionTimestamp.IsZero() {
// Its a new CR, add finalizers from list
instance.ObjectMeta.Finalizers = finalizersList
if err := r.client.Update(context.Background(), instance); err != nil {
return reconcile.Result{}, err
}

} else {
// The object is being deleted, removing dependencies and finalizers after it

// Finalizer "PVC": check to remove dependent PVCs
if instance.Spec.WordpressVolumeCleanup || instance.Spec.DatabaseVolumeCleanup {
_, err := r.deleteDependentResoucePVC(reqLogger, instance)
if err != nil {
reqLogger.Info("PVC deletion error", "Namespace", instance.Namespace, ".Name", instance.Name)
return reconcile.Result{}, err
}
}

// Finalizer "PVC": remove "PVC" finalizer from CR
instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, "PVC")
if err := r.client.Update(context.Background(), instance); err != nil {
reqLogger.Info("Removing PVC finalizer failed")
return reconcile.Result{}, err
}

// Our finalizer has finished, so the reconciler can do nothing.
}

return reconcile.Result{}, nil
}

+ 0
- 59
pkg/controller/wordpress/manifests.go View File

@@ -4,7 +4,6 @@ import (
blindagev1alpha1 "git.blindage.org/21h/wordpress-operator/pkg/apis/blindage/v1alpha1"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
@@ -161,64 +160,6 @@ func generateWordpressDeployment(cr *blindagev1alpha1.Wordpress) v1.Deployment {
return deployment
}

func generateWordpressContentVolume(cr *blindagev1alpha1.Wordpress) corev1.PersistentVolumeClaim {
volumeSize, _ := resource.ParseQuantity("1Gi")
if cr.Spec.WordpressVolumeSize != nil {
volumeSize = *cr.Spec.WordpressVolumeSize
}

claim := corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name + "-content",
Namespace: cr.Namespace,
},
Spec: corev1.PersistentVolumeClaimSpec{
// allow read and write for many pods
// but if you using few pods on few nodes, block device can't replicate between nodes
// so you can set your own storage class with this ability, efs, s3 or something like this
// https://medium.com/asl19-developers/create-readwritemany-persistentvolumeclaims-on-your-kubernetes-cluster-3a8db51f98e3
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: volumeSize,
},
},
},
}

// set your own storage class
if cr.Spec.WordpressStorageClass != "" {
claim.Spec.StorageClassName = &cr.Spec.WordpressStorageClass
}

return claim

}

func generateDatabaseVolume(cr *blindagev1alpha1.Wordpress) corev1.PersistentVolumeClaim {
volumeSize, _ := resource.ParseQuantity("1Gi")
if cr.Spec.DatabaseVolumeSize != nil {
volumeSize = *cr.Spec.DatabaseVolumeSize
}

claim := corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name + "-database",
Namespace: cr.Namespace,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: volumeSize,
},
},
},
}

return claim
}

func generateConfigmap(cr *blindagev1alpha1.Wordpress) corev1.ConfigMap {

data := map[string]string{}

+ 68
- 0
pkg/controller/wordpress/pvc.go View File

@@ -0,0 +1,68 @@
package wordpress

import (
blindagev1alpha1 "git.blindage.org/21h/wordpress-operator/pkg/apis/blindage/v1alpha1"
corev1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func generateWordpressContentVolume(cr *blindagev1alpha1.Wordpress) corev1.PersistentVolumeClaim {
volumeSize, _ := resource.ParseQuantity("1Gi")
if cr.Spec.WordpressVolumeSize != nil {
volumeSize = *cr.Spec.WordpressVolumeSize
}

claim := corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name + "-content",
Namespace: cr.Namespace,
Labels: mergeLabels(baseLabels(cr),
map[string]string{"component": "wordpress"},
),
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: volumeSize,
},
},
},
}

// set your own storage class
if cr.Spec.WordpressStorageClass != "" {
claim.Spec.StorageClassName = &cr.Spec.WordpressStorageClass
}

return claim

}

func generateDatabaseVolume(cr *blindagev1alpha1.Wordpress) corev1.PersistentVolumeClaim {
volumeSize, _ := resource.ParseQuantity("1Gi")
if cr.Spec.DatabaseVolumeSize != nil {
volumeSize = *cr.Spec.DatabaseVolumeSize
}

claim := corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name + "-database",
Namespace: cr.Namespace,
Labels: mergeLabels(baseLabels(cr),
map[string]string{"component": "database"},
),
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: volumeSize,
},
},
},
}

return claim
}

+ 19
- 0
pkg/controller/wordpress/utils.go View File

@@ -38,3 +38,22 @@ func mergeENV(envs []v1.EnvVar) []v1.EnvVar {
},
}, envs...)
}

func containsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}

func removeString(slice []string, s string) (result []string) {
for _, item := range slice {
if item == s {
continue
}
result = append(result, item)
}
return
}

+ 6
- 9
pkg/controller/wordpress/wordpress_controller.go View File

@@ -104,14 +104,16 @@ func (r *ReconcileWordpress) Reconcile(request reconcile.Request) (reconcile.Res
return reconcile.Result{}, err
}

// reconcile Wordpress Volume
newWordpressPersistentVolume := generateWordpressContentVolume(instance)

if err := controllerutil.SetControllerReference(instance, &newWordpressPersistentVolume, r.scheme); err != nil {
// set/remove finalizers
_, err = r.reconcileFinalizers(reqLogger, instance)
if err != nil {
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
}

// reconcile Wordpress Volume
newWordpressPersistentVolume := generateWordpressContentVolume(instance)

foundWordpressPersistentVolume := corev1.PersistentVolumeClaim{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: newWordpressPersistentVolume.Name, Namespace: newWordpressPersistentVolume.Namespace}, &foundWordpressPersistentVolume)
if err != nil && errors.IsNotFound(err) {
@@ -174,11 +176,6 @@ func (r *ReconcileWordpress) Reconcile(request reconcile.Request) (reconcile.Res
// reconcile Database Volume
newDatabasePersistentVolume := generateDatabaseVolume(instance)

if err := controllerutil.SetControllerReference(instance, &newDatabasePersistentVolume, r.scheme); err != nil {
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
}

foundDatabasePersistentVolume := corev1.PersistentVolumeClaim{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: newDatabasePersistentVolume.Name, Namespace: newDatabasePersistentVolume.Namespace}, &foundDatabasePersistentVolume)
if err != nil && errors.IsNotFound(err) {

+ 1
- 1
version/version.go View File

@@ -1,5 +1,5 @@
package version

var (
Version = "0.0.2"
Version = "0.0.3"
)

Loading…
Cancel
Save