Simple Kubernetes operator written in Java to deploy a Quarkus application
- la branche
01-init-project
contient le résultat de cette étape - installer / mettre à jour la derniÚre version du Operator SDK
- créer le répertoire
java-operator-quarkus-deploy
- dans le répertoire
java-operator-quarkus-deploy
, scaffolding du projet avec Quarkus :operator-sdk init --plugins quarkus --domain wilda.fr --project-name java-operator-quarkus-deploy
- l'arborescence générée est la suivante:
.
âââ Makefile
âââ PROJECT
âââ README.md
âââ pom.xml
âââ src
â âââ main
â âââ java
â âââ resources
â âââ application.properties
- vérification que cela compile :
mvn clean compile
- tester le lancement:
mvn quarkus:dev
:
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-06-13 15:33:52,746 INFO [io.qua.ope.run.ConfigurationServiceRecorder] (Quarkus Main Thread) Leader election deactivated for dev profile
2023-06-13 15:33:53,615 INFO [io.qua.ope.run.OperatorProducer] (Quarkus Main Thread) Quarkus Java Operator SDK extension 5.1.0 (commit: 232db56 on branch: 232db566edf4120b3dc7d5ec724104d5336b8e23) built on Thu Feb 23 15:42:39 UTC 2023
2023-06-13 15:33:53,621 WARN [io.qua.ope.run.AppEventListener] (Quarkus Main Thread) No Reconciler implementation was found so the Operator was not started.
2023-06-13 15:33:53,755 INFO [io.quarkus] (Quarkus Main Thread) java-operator-quarkus-deploy 0.0.1-SNAPSHOT on JVM (powered by Quarkus 2.16.3.Final) started in 8.738s. Listening on: http://localhost:8080
2023-06-13 15:33:53,758 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-06-13 15:33:53,759 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, kubernetes, kubernetes-client, micrometer, openshift-client, operator-sdk, smallrye-context-propagation, smallrye-health, vertx]
- la branche
02-crd-generation
contient le résultat de cette étape - création de l'API :
operator-sdk create api --version v1 --kind QuarkusOperator
- cette commande a créé les 4 classes nécessaires pour créer l'opérateur:
src
âââ main
âââ java
â âââ fr
â âââ wilda
â âââ QuarkusOperator.java
â âââ QuarkusOperatorReconciler.java
â âââ QuarkusOperatorSpec.java
â âââ QuarkusOperatorStatus.java
- désactiver, pour l'instant, la création de l'image :
quarkus.container-image.build=false
#quarkus.container-image.group=
quarkus.container-image.name=java-operator-quarkus-deploy-operator
# set to true to automatically apply CRDs to the cluster when they get regenerated
quarkus.operator-sdk.crd.apply=false
- tester que tout compile que la CRD se génÚre bien:
mvn clean package
(ou restez en modemvn quarkus:dev
pour voir la magie opĂ©rer en direct đ) - la CRD doit ĂȘtre gĂ©nĂ©rĂ©e dans le target,
target/kubernetes/quarkusoperators.wilda.fr-v1.yml
: - elle doit aussi ĂȘtre installĂ©e sur le cluster:
$ kubectl get crds quarkusoperators.wilda.fr
NAME CREATED AT
quarkusoperators.wilda.fr 2022-08-26T15:40:19Z
- la branche
03-hello-world
contient le résultat de cette étape - modifier la partie spec de la CRD en modifiant la classe
QuarkusOperatorSpec
:
public class QuarkusOperatorSpec {
private String version;
private int nodePort;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public int getNodePort() {
return nodePort;
}
public void setNodePort(int nodePort) {
this.nodePort = nodePort;
}
}
- modifier le reconciler
QuarkusOperatorReconciler.java
:
public class QuarkusOperatorReconciler
implements Reconciler<QuarkusOperator>, Cleaner<QuarkusOperator> {
private static final Logger log = LoggerFactory.getLogger(QuarkusOperatorReconciler.class);
private final KubernetesClient client;
public QuarkusOperatorReconciler(KubernetesClient client) {
this.client = client;
}
@Override
public UpdateControl<QuarkusOperator> reconcile(QuarkusOperator resource, Context context) {
log.info("âĄïž Event occurs ! Reconcile called.");
String namespace = resource.getMetadata().getNamespace();
// Create Deployment
log.info("đ Deploy the application!");
Deployment deployment = makeDeployment(resource);
client.apps().deployments().inNamespace(namespace).resource(deployment).create();
// Create service
log.info("âš Create the service!");
Service service = makeService(resource);
Service existingService = client.services().inNamespace(resource.getMetadata().getNamespace())
.withName(service.getMetadata().getName()).get();
if (existingService == null) {
client.services().inNamespace(namespace).resource(service).create();
}
return UpdateControl.noUpdate();
}
@Override
public DeleteControl cleanup(QuarkusOperator resource, Context<QuarkusOperator> context) {
log.info("đ Undeploy the application");
return DeleteControl.defaultDelete();
}
/**
* Generate the Kubernetes deployment resource.
*
* @param resource The created custom resource
* @return The created deployment
*/
private Deployment makeDeployment(QuarkusOperator resource) {
Deployment deployment = new DeploymentBuilder()
.withNewMetadata()
.withName("quarkus-deployment")
.addToLabels("app", "quarkus")
.endMetadata()
.withNewSpec()
.withReplicas(1)
.withNewSelector()
.withMatchLabels(Map.of("app", "quarkus"))
.endSelector()
.withNewTemplate()
.withNewMetadata()
.addToLabels("app", "quarkus")
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName("quarkus")
.withImage("wilda/hello-world-from-quarkus:" + resource.getSpec().getVersion())
.addNewPort()
.withContainerPort(80)
.endPort()
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build();
deployment.addOwnerReference(resource);
log.info("Generated deployment {}", Serialization.asYaml(deployment));
return deployment;
}
/**
* Generate the Kubernetes service resource.
*
* @param resource The custom resource
* @return The service.
*/
private Service makeService(QuarkusOperator resource) {
Service service = new ServiceBuilder()
.withNewMetadata()
.withName("quarkus-service")
.addToLabels("app", "quarkus")
.endMetadata()
.withNewSpec()
.withType("NodePort")
.withSelector(Map.of("app", "quarkus"))
.addNewPort()
.withPort(80)
.withTargetPort(new IntOrString(8080))
.withNodePort(resource.getSpec().getNodePort())
.endPort()
.endSpec()
.build();
service.addOwnerReference(resource);
log.info("Generated service {}", Serialization.asYaml(service));
return service;
}
}
- créer la CR de tests
./src/test/resources/cr-test-deploy-quarkus.yml
:
apiVersion: "wilda.fr/v1"
kind: QuarkusOperator
metadata:
name: quarkus-app
spec:
version: "1.0.0"
nodePort: 30080
- créer le namespace
test-java-operator
:kubectl create ns test-java-operator
- appliquer la CR :
kubectl apply -f ./src/test/resources/cr-test-deploy-quarkus.yml -n test-java-operator
- vérifier que l'application a bien été déployée par l'opérateur :
$ kubectl get pod,svc -n test-java-operator
NAME READY STATUS RESTARTS AGE
pod/quarkus-deployment-5f8c85d587-g445p 1/1 Running 0 2m2s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/quarkus-service NodePort XX.XX.XX.XXX <none> 80:30080/TCP 51s
- tester l'application déployée :
$ curl http://<cluster node URL>:30080/hello
đ Hello, World ! đ
- supprimer la CR :
kubectl delete quarkusoperator/quarkus-app -n test-java-operator
- vérifier que tout a été supprimé :
$ kubectl get pod,svc -n test-java-operator
No resources found in test-java-operator namespace.
- la branche
04-package-deploy
contient le rĂ©sultat de cette Ă©tape - arrĂȘter le mode dev de Quarkus
- ajouter un fichier
src/main/kubernetes/kubernetes.yml
contenant la définition des ClusterRole / ClusterRoleBinding spécifiques à l'opérateur:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: service-deployment-cluster-role
namespace: java-operator-quarkus-deploy
rules:
- apiGroups:
- ""
resources:
- secrets
- serviceaccounts
- services
verbs:
- "*"
- apiGroups:
- "apps"
verbs:
- "*"
resources:
- deployments
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: service-deployment-cluster-role-binding
namespace: java-operator-quarkus-deploy
roleRef:
kind: ClusterRole
apiGroup: rbac.authorization.k8s.io
name: service-deployment-cluster-role
subjects:
- kind: ServiceAccount
name: java-operator-quarkus-deploy-operator
namespace: java-operator-quarkus-deploy
---
- modifier le fichier
application.properties
:
quarkus.container-image.build=true
quarkus.container-image.group=wilda
quarkus.container-image.name=java-operator-quarkus-deploy-operator
# set to true to automatically apply CRDs to the cluster when they get regenerated
quarkus.operator-sdk.crd.apply=true
# Kubernetes options
quarkus.kubernetes.namespace=java-operator-quarkus-deploy
- mettre à jour le pom.xml ave cla dépendance
jib
:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</artifactId>
</dependency>
- lancer le packaging :
mvn clean package
- vérifier que l'image a bien été générée :
$ docker images | grep java-operator-quarkus-deploy-operator
wilda/java-operator-quarkus-deploy-operator 0.0.1-SNAPSHOT 988250eed234 12 seconds ago 412MB
- push de l'image :
docker login
&&docker push wilda/java-operator-quarkus-deploy-operator:0.0.1-SNAPSHOT
- créer le namespace
java-operator-quarkus-deploy
:kubectl create ns java-operator-quarkus-deploy
- appliquer le manifest créé :
kubectl apply -f ./target/kubernetes/kubernetes.yml
- vérifier que tout va bien:
$ kubectl get pod -n java-operator-quarkus-deploy
NAME READY STATUS RESTARTS AGE
java-operator-quarkus-deploy-8b9cf6766-q6mns 1/1 Running 0 42s
- appliquer la CR de test :
kubectl apply -f ./src/test/resources/cr-test-deploy-quarkus.yml -n test-java-operator
- vérifier que l'application a bien été déployée par l'opérateur :
$ kubectl get pod,svc -n test-java-operator
NAME READY STATUS RESTARTS AGE
pod/quarkus-deployment-5f8c85d587-g445p 1/1 Running 0 2m2s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/quarkus-service NodePort XX.XX.XX.XXX <none> 80:30080/TCP 51s
- tester l'application déployée :
$ curl http://<cluster node URL>:30080/hello
đ Hello, World ! đ
- supprimer la CR :
kubectl delete quarkusoperator/quarkus-app -n test-java-operator
- vérifier que tout a été supprimé :
$ kubectl get pod,svc -n test-java-operator
No resources found in test-java-operator namespace.
- supprimer l'opérateur :
kubectl delete -f ./target/kubernetes/kubernetes.yml
- supprimer les namespaces:
kubectl delete ns test-java-operator java-operator-quarkus-deploy
- supprimer la crd:
kubectl delete crds/quarkusoperators.wilda.fr