Commit b88a17a3 authored by Ioannis Plakas's avatar Ioannis Plakas

images

parent 20478242
# pgouv-operator
This is an operator made for training purpose. It leverages Kubernetes API
by installing CRD(`api/v1alpha1/pkg/pgouv_types.go`)
to the Kubernetes Cluster and reconciling in order to match the desired
state described in `Spec` to the observed state (`Status`).
## Objective
The purpose of this controller is to create the a number of pods that will
run a command.Both the pods and the command will be provided in CRD.
## Prerequesties
* Running Kubernetes 1.15+ cluster (e.g [kind](https://kind.sigs.k8s.io/ ))
* kubebuilder 2.20 ([install from site](https://kind.sigs.k8s.io/))
* go 1.13+
## Set-up
1. Clone the directory
```bash
git clone https://gitlab.ubitech.eu/iplakas/first-controller
cd first-controller
```
2. Kubernetes does not know/have a definition about the created type (`pgouv_types.go`)
```bash
kubectl get crds
```
![Image of crds](https://gitlab.ubitech.eu/iplakas/first-controller/-/blob/master/images/empty-crds.png)
3. Install the CRD to the cluster
```bash
make manifests
make install
```
The created resource is retrieved `kubectl get crd`
![Image of new crds](https://gitlab.ubitech.eu/iplakas/first-controller/-/blob/master/images/crds.png)
4. The generated CRD is fully described in yaml format in `config/crd/bases/ubi.ubitech.eu_pgouvs.yaml` which is generated by the corresponding go type
```go
// PgouvSpec defines the desired state of Pgouv
type PgouvSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Pgouv. Edit Pgouv_types.go to remove/update
Command string `json:"command"`
Replicas int32 `json:"replicas"`
}
// PgouvStatus defines the observed state of Pgouv
type PgouvStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
AliveRelplicas int32 `json:"alivereplicas,omitempty"`
}
```
5. Now that the Kubernetes Cluster knows about the Custom Resource
its time to initiate one, we do that be updating the `config/samples/ubi_v1alpha1_pgouv` file (the name of file follows the GVK convention)
```go
apiVersion: ubi.ubitech.eu/v1alpha1
kind: Pgouv
metadata:
name: pgouv-sample
spec:
# Add fields here
command: "echo ORIGINAL21"
replicas: 3
```
In order to apply the instance of the resource `kubectl applay -f config/samples/ubi_v1alpha1_pgouv`
![Created resource ](https://gitlab.ubitech.eu/iplakas/first-controller/-/blob/master/images/created.png)
6. Now that the cluster has a Custom Resource the determined logic
served with it is written in `controllers\pgouv_controller.go`
```go
// PgouvReconciler reconciles a Pgouv object
type PgouvReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ubi.ubitech.eu,resources=pgouvs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ubi.ubitech.eu,resources=pgouvs/status,verbs=get;update;patch
func (r *PgouvReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
context := context.Background()
//fitst step create the logger
logger := r.Log.WithValues("pgouv", req.NamespacedName)
logger.Info("just starded")
```
The reconcile logic will be called once new Events regrading the handled resource
(and the sub-Resources that owns) are detected . That's why `role-based-access-control` needs to be provided to the Controller(eg. `kubebuilder:rbac:`).
7. The difference that Reconcile Function uses in order to get to the desired state
is described in the following snippet
```go
//just to get the items right
podlist := &corev1.PodList{}
err = r.List(context, podlist, client.InNamespace(req.Namespace), client.MatchingLabels{"app": instance.Name})
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
instance.Status.AliveRelplicas = int32(len(podlist.Items))
diff := instance.Spec.Replicas - instance.Status.AliveRelplicas
```
Firstly the Status of the CR is determined by retrieving the number of pods (owned by the Resource). Then the difference between `Spec` and `Status` will
determine if more Pods running the specified `Command` are needed or
Pods need to get deleted.
8. The example logic in controller :
```go
//need to create
if diff > 0 {
logger.Info("Need to reconcile invrementaly")
//Define new pod object
pod := spawn.NewPodForCR(instance)
//Set owner for the pod before initiate it
if err = controllerutil.SetControllerReference(instance, pod, r.Scheme); err != nil {
return reconcile.Result{}, err
}
err = r.Create(context, pod)
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
instance.Status.AliveRelplicas++
err = r.Status().Update(context, instance)
if err != nil {
return ctrl.Result{}, err
}
logger.Info("Pod created")
return reconcile.Result{}, err
```
Or when we need to delete Pods
```go
else if diff < 0 {
logger.Info("need to delete a pod")
podlist := &corev1.PodList{}
//lbs := map[string]string{
// "app": instance.Name,
// }
//labelSelector := labels.SelectorFromSet(lbs)
// lsopt := &client.ListOption{LabelSelector: labelSelector, Namespace: instance.name}
err := r.List(context, podlist, client.InNamespace(req.Namespace), client.MatchingLabels{"app": instance.Name})
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
logger.Info("Deleted Pod")
instance.Status.AliveRelplicas--
logger.Info("Update deteted status")
err = r.Status().Update(context, instance)
if err != nil {
return ctrl.Result{}, err
}
err = r.Delete(context, &podlist.Items[0])
if err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
```
9. The Reconcile function needs to have a Manger (possibly managing other controllers/reconcilers)
which is resposible for the shared depedancies (e.g Client , Cache)
The manager handles a reconcile function that is triggered by events(Add,Delete
,Update) on the CR and the Pod resources created from it.
```go
func (r *PgouvReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&ubiv1alpha1.Pgouv{}).
Owns(&corev1.Pod{}).
Complete(r)
}
```
10. Run the Controller :
```bash
make run
```
`kubectl logs ` will retrieve the output of the `Command` for the specified Pod
![Created resource ](https://gitlab.ubitech.eu/iplakas/first-controller/-/blob/master/images/output.png)
11. The CR can be updated by updating the `Replicas` field on the `config/samples/ubi_v1alpha1_pgouv` and applying it to the cluster via
`kubectl apply -f `
[Created resource2 ](https://gitlab.ubitech.eu/iplakas/first-controller/-/blob/master/images/output2.png)
......@@ -5,4 +5,4 @@ metadata:
spec:
# Add fields here
command: "echo ORIGINAL21"
replicas: 3
replicas: 5
......@@ -104,7 +104,7 @@ func (r *PgouvReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
} else if diff < 0 {
logger.Info("need to delete a pod")
podlist := &corev1.PodList{}
err := r.List(context, podlist, client.InNamespace(req.Namespace), client.MatchingLabels{"app": instance.Name})
err := r.List(context, podlist, client.InNamespace(req.Namespace), client.MatchingLabels{"app": instance.Name})
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment