Skip to content

Instantly share code, notes, and snippets.

@reoring
Created November 15, 2020 07:24
Show Gist options
  • Save reoring/8fe11a8969300677bd2060ca68cf1eeb to your computer and use it in GitHub Desktop.
Save reoring/8fe11a8969300677bd2060ca68cf1eeb to your computer and use it in GitHub Desktop.
Kubernetes service controller keep deployment example
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