Created
November 15, 2020 07:24
-
-
Save reoring/8fe11a8969300677bd2060ca68cf1eeb to your computer and use it in GitHub Desktop.
Kubernetes service controller keep deployment example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers | |
import ( | |
"context" | |
"encoding/base64" | |
appsv1 "k8s.io/api/apps/v1" | |
v1 "k8s.io/api/core/v1" | |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
"k8s.io/client-go/kubernetes" | |
"k8s.io/client-go/tools/record" | |
"os" | |
"github.com/go-logr/logr" | |
"k8s.io/apimachinery/pkg/runtime" | |
ctrl "sigs.k8s.io/controller-runtime" | |
"sigs.k8s.io/controller-runtime/pkg/client" | |
awsv1beta1 "github.com/inflion/aws-controller/api/v1beta1" | |
) | |
// ServiceReconciler reconciles a Service object | |
type ServiceReconciler struct { | |
client.Client | |
Clientset *kubernetes.Clientset | |
Log logr.Logger | |
Scheme *runtime.Scheme | |
Recorder record.EventRecorder | |
} | |
var ( | |
deploymentOwnerKey = ".metadata.controller" | |
apiGVStr = awsv1beta1.GroupVersion.String() | |
) | |
func decodeString(encoded string) (string, error) { | |
decoded, err := base64.StdEncoding.DecodeString(encoded) | |
if err != nil { | |
return "", err | |
} | |
return string(decoded), nil | |
} | |
func base64Decode(message []byte) (b []byte, err error) { | |
var l int | |
b = make([]byte, base64.StdEncoding.DecodedLen(len(message))) | |
l, err = base64.StdEncoding.Decode(b, message) | |
if err != nil { | |
return | |
} | |
return b[:l], nil | |
} | |
// +kubebuilder:rbac:groups=aws.aws-controller,resources=services,verbs=get;list;watch;create;update;patch;delete | |
// +kubebuilder:rbac:groups=aws.aws-controller,resources=services/status,verbs=get;update;patch | |
// +kubebuilder:rbac:groups=aws.aws-controller,resources=deployments,verbs=get;list;watch;create;update;delete | |
// +kubebuilder:rbac:groups=aws.aws-controller,resources=events,verbs=create;patch | |
// +kubebuilder:rbac:groups=aws.aws-controller,resources=secrets,verbs=get | |
func (r *ServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { | |
ctx := context.Background() | |
log := r.Log.WithValues("service", req.NamespacedName) | |
var service awsv1beta1.Service | |
log.Info("namesacedName is " + req.NamespacedName.String()) | |
log.Info(apiGVStr) | |
log.Info("req is " + req.String()) | |
// 1: Load the Service by name | |
log.Info("fetching service resources") | |
if err := r.Get(ctx, req.NamespacedName, &service); err != nil { | |
log.Error(err, "unable to fetch Service") | |
return ctrl.Result{}, client.IgnoreNotFound(err) | |
} | |
// 2: Cleanup old deployment | |
if err := r.cleanupOwnedResources(ctx, log, &service); err != nil { | |
log.Error(err, "failed to clean up old Deployment resources for this Service") | |
return ctrl.Result{}, err | |
} | |
// 3: Create or Update deployment object | |
deploymentName := service.Spec.DeploymentName | |
deploy := &appsv1.Deployment{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: deploymentName, | |
Namespace: req.Namespace, | |
}, | |
} | |
if _, err := ctrl.CreateOrUpdate(ctx, r.Client, deploy, func() error { | |
// This function is controllerutil.MutateFn | |
replicas := int32(1) | |
if service.Spec.Replicas != nil { | |
replicas = *service.Spec.Replicas | |
} | |
deploy.Spec.Replicas = &replicas | |
labels := map[string]string{ | |
"app": "nginx", | |
"controller": req.Name, | |
} | |
if deploy.Spec.Selector == nil { | |
deploy.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels} | |
} | |
if deploy.Spec.Template.ObjectMeta.Labels == nil { | |
deploy.Spec.Template.ObjectMeta.Labels = labels | |
} | |
containers := []v1.Container{ | |
{ | |
Name: "nginx", | |
Image: "nginx:latest", | |
}, | |
} | |
if deploy.Spec.Template.Spec.Containers == nil { | |
deploy.Spec.Template.Spec.Containers = containers | |
} | |
if err := ctrl.SetControllerReference(&service, deploy, r.Scheme); err != nil { | |
log.Error(err, "unable to set ownerReference from Service to Deployment") | |
return err | |
} | |
return nil | |
}); err != nil { | |
log.Error(err, "unable to ensure deployment is correct") | |
return ctrl.Result{}, err | |
} | |
// 4: Update status of the service | |
var deployment appsv1.Deployment | |
var deploymentNamespacedName = client.ObjectKey{Namespace: req.Namespace, Name: service.Spec.DeploymentName} | |
if err := r.Get(ctx, deploymentNamespacedName, &deployment); err != nil { | |
log.Error(err, "unable to fetch Deployment") | |
return ctrl.Result{}, client.IgnoreNotFound(err) | |
} | |
availableReplicas := deployment.Status.AvailableReplicas | |
if availableReplicas == service.Status.AvailableReplicas { | |
return ctrl.Result{}, nil | |
} | |
service.Status.AvailableReplicas = availableReplicas | |
if err := r.Status().Update(ctx, &service); err != nil { | |
log.Error(err, "unable to update Service status") | |
return ctrl.Result{}, err | |
} | |
r.Recorder.Eventf(&service, v1.EventTypeNormal, "Updated", "Update service.status.AvailableReplicas %d", service.Status.AvailableReplicas) | |
return ctrl.Result{}, nil | |
} | |
func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { | |
if err := mgr.GetFieldIndexer().IndexField(&appsv1.Deployment{}, deploymentOwnerKey, func(rawObj runtime.Object) []string { | |
deployment := rawObj.(*appsv1.Deployment) | |
owner := metav1.GetControllerOf(deployment) | |
if owner == nil { | |
return nil | |
} | |
if owner.APIVersion != apiGVStr || owner.Kind != "Service" { | |
return nil | |
} | |
return []string{owner.Name} | |
}); err != nil { | |
return err | |
} | |
return ctrl.NewControllerManagedBy(mgr). | |
For(&awsv1beta1.Service{}). | |
Owns(&appsv1.Deployment{}). | |
Complete(r) | |
} | |
func (r *ServiceReconciler) cleanupOwnedResources(ctx context.Context, log logr.Logger, service *awsv1beta1.Service) error { | |
log.Info("finding existing Deployments for Foo resource") | |
var deployments appsv1.DeploymentList | |
if err := r.List(ctx, | |
&deployments, | |
client.InNamespace(service.Namespace), | |
client.MatchingFields(map[string]string{deploymentOwnerKey: service.Name}), | |
); err != nil { | |
return err | |
} | |
for _, deployment := range deployments.Items { | |
if deployment.Name == service.Spec.DeploymentName { | |
continue | |
} | |
if err := r.Delete(ctx, &deployment); err != nil { | |
log.Error(err, "failed to delete Deployment resource") | |
return err | |
} | |
log.Info("delete deployment resource: " + deployment.Name) | |
r.Recorder.Eventf(service, v1.EventTypeNormal, "Deleted", "Deleted deployment %q", deployment.Name) | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment