-
Notifications
You must be signed in to change notification settings - Fork 5
Feat/cordon, drain #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pranludi
wants to merge
34
commits into
ten1010-io:staging/project
Choose a base branch
from
pranludi:feat/cordon_wip
base: staging/project
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
f215167
chore
c1279f7
wip
2647a44
wip
7fd5b5c
kubectl 으로 특정 노드를 cordon 하면 해당 node 의 pod 을 모두 삭제
5208694
wip
3b36bfd
wip
6a8ab34
rollback - files
13582c7
wip - daemonset 에 대한 대응
c5b8f2b
wip - uncordon 지금은 watch 가 작동될때 체크해서 진행하도록 되어 있음
09cbae8
maintenance yaml 으로 cordon, drain
c66399a
fix
d9cada0
fix
5a8579d
fix
a769e90
fix
bd27fe8
fix
7d7df90
fix
91c9aa4
fix
647d2fc
fix
b073a46
fix
a5bf096
fix
1f449cb
fix
a8d4a88
fix
ebeef3d
fix
c62c8d8
fix
d31e900
fix
66a0c35
fix
72678db
fix
4b3cfa4
fix
d5cac77
fix
81cbe39
fix
92786c7
fix
cd1e75c
fix
8b04ef7
fix
2f5bd9d
admission controller
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| apiVersion: project.aipub.ten1010.io/v1alpha1 | ||
| kind: NodeMaintenance | ||
| metadata: | ||
| name: node1-maintenance | ||
| spec: | ||
| targetNodes: | ||
| - minikube-m02 | ||
| action: | ||
| type: cordon | ||
| ignoreDaemonSets: true | ||
| force: false |
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| apiVersion: v1 | ||
| kind: Pod | ||
| metadata: | ||
| name: nginx-pod | ||
| spec: | ||
| containers: | ||
| - name: my-nginx | ||
| image: nginx |
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| apiVersion: v1 | ||
| kind: Pod | ||
| metadata: | ||
| name: nginx-pod2 | ||
| spec: | ||
| containers: | ||
| - name: my-nginx | ||
| image: nginx |
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| apiVersion: apps/v1 | ||
| kind: DaemonSet | ||
| metadata: | ||
| name: nginx-pod-daemonset | ||
| namespace: project-controller | ||
| spec: | ||
| selector: | ||
| matchLabels: | ||
| app: nginx-pod-daemonset | ||
| template: | ||
| metadata: | ||
| labels: | ||
| app: nginx-pod-daemonset | ||
| spec: | ||
| containers: | ||
| - name: my-nginx | ||
| image: nginx |
This file contains hidden or 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
48 changes: 48 additions & 0 deletions
48
...va/io/ten1010/aipub/projectcontroller/controller/cr/NodeMaintenanceControllerFactory.java
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package io.ten1010.aipub.projectcontroller.controller.cr; | ||
|
|
||
| import io.kubernetes.client.extended.controller.Controller; | ||
| import io.kubernetes.client.extended.controller.ControllerWatch; | ||
| import io.kubernetes.client.extended.controller.builder.ControllerBuilder; | ||
| import io.kubernetes.client.extended.controller.reconciler.Request; | ||
| import io.kubernetes.client.extended.workqueue.WorkQueue; | ||
| import io.kubernetes.client.informer.SharedInformerFactory; | ||
| import io.ten1010.aipub.projectcontroller.controller.ControllerFactory; | ||
| import io.ten1010.aipub.projectcontroller.controller.watch.DefaultControllerWatch; | ||
| import io.ten1010.aipub.projectcontroller.controller.watch.OnUpdateFilterFactory; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.K8sApiProvider; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.ReconciliationService; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1NodeMaintenance; | ||
|
|
||
| public class NodeMaintenanceControllerFactory implements ControllerFactory { | ||
|
|
||
| private final SharedInformerFactory sharedInformerFactory; | ||
| private final K8sApiProvider k8sApiProvider; | ||
| private final OnUpdateFilterFactory onUpdateFilterFactory; | ||
|
|
||
| public NodeMaintenanceControllerFactory( | ||
| SharedInformerFactory sharedInformerFactory, | ||
| K8sApiProvider k8sApiProvider, | ||
| ReconciliationService reconciliationService) { | ||
| this.sharedInformerFactory = sharedInformerFactory; | ||
| this.k8sApiProvider = k8sApiProvider; | ||
| this.onUpdateFilterFactory = new OnUpdateFilterFactory(); | ||
| } | ||
|
|
||
| @Override | ||
| public Controller createController() { | ||
| return ControllerBuilder.defaultBuilder(this.sharedInformerFactory) | ||
| .withName("node-maintenance-controller") | ||
| .withWorkerCount(1) | ||
| .withReadyFunc(this.sharedInformerFactory.getExistingSharedIndexInformer(V1alpha1NodeMaintenance.class)::hasSynced) | ||
| .watch(this::createNodeMaintenanceWatch) | ||
| .withReconciler(new NodeMaintenanceReconciler(this.sharedInformerFactory, this.k8sApiProvider)) | ||
| .build(); | ||
| } | ||
|
|
||
| private ControllerWatch<V1alpha1NodeMaintenance> createNodeMaintenanceWatch(WorkQueue<Request> workQueue) { | ||
| DefaultControllerWatch<V1alpha1NodeMaintenance> watch = new DefaultControllerWatch<>(workQueue, V1alpha1NodeMaintenance.class); | ||
| watch.setOnUpdateFilter(this.onUpdateFilterFactory.nodeMaintenanceSpecFieldFilter()); | ||
| return watch; | ||
| } | ||
|
|
||
| } |
180 changes: 180 additions & 0 deletions
180
...main/java/io/ten1010/aipub/projectcontroller/controller/cr/NodeMaintenanceReconciler.java
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| package io.ten1010.aipub.projectcontroller.controller.cr; | ||
|
|
||
| import io.kubernetes.client.extended.controller.reconciler.Request; | ||
| import io.kubernetes.client.extended.controller.reconciler.Result; | ||
| import io.kubernetes.client.informer.SharedInformerFactory; | ||
| import io.kubernetes.client.informer.cache.Indexer; | ||
| import io.kubernetes.client.openapi.ApiException; | ||
| import io.kubernetes.client.openapi.ApiResponse; | ||
| import io.kubernetes.client.openapi.apis.CoreV1Api; | ||
| import io.kubernetes.client.openapi.models.*; | ||
| import io.ten1010.aipub.projectcontroller.controller.AbstractReconciler; | ||
| import io.ten1010.aipub.projectcontroller.controller.RequestHelper; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.K8sApiProvider; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.K8sObjectTypeConstants; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.KeyResolver; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.ProjectApiConstants; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1NodeMaintenance; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1NodeMaintenanceAction; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.dto.V1alpha1NodeMaintenanceStatus; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.util.K8sObjectUtils; | ||
| import io.ten1010.aipub.projectcontroller.domain.k8s.util.StatusPatchHelper; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
| @Slf4j | ||
| public class NodeMaintenanceReconciler extends AbstractReconciler { | ||
|
|
||
| private final KeyResolver keyResolver; | ||
| private final Indexer<V1alpha1NodeMaintenance> projectIndexer; | ||
| private final CoreV1Api coreV1Api; | ||
| private final StatusPatchHelper<V1alpha1NodeMaintenance> statusPatchHelper; | ||
| private final String namespace = "project-controller"; | ||
pranludi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public NodeMaintenanceReconciler( | ||
| SharedInformerFactory sharedInformerFactory, | ||
| K8sApiProvider k8sApiProvider) { | ||
| this.keyResolver = new KeyResolver(); | ||
| this.projectIndexer = sharedInformerFactory | ||
| .getExistingSharedIndexInformer(V1alpha1NodeMaintenance.class) | ||
| .getIndexer(); | ||
| this.coreV1Api = new CoreV1Api(k8sApiProvider.getApiClient()); | ||
| this.statusPatchHelper = new StatusPatchHelper<>( | ||
| k8sApiProvider.getApiClient(), | ||
| K8sObjectTypeConstants.NODE_MAINTENANCE_V1ALPHA1, | ||
| ProjectApiConstants.NODE_MAINTENANCE_RESOURCE_PLURAL); | ||
| } | ||
|
|
||
| @Override | ||
| protected Result reconcileInternal(Request request) throws ApiException { | ||
| String projectKey = new RequestHelper(this.keyResolver).resolveKey(request); | ||
| Optional<V1alpha1NodeMaintenance> nodeMaintenanceOpt = Optional.ofNullable(this.projectIndexer.getByKey(projectKey)); | ||
| if (nodeMaintenanceOpt.isEmpty()) { | ||
| return new Result(false); | ||
| } | ||
|
|
||
| var nodeMaintenance = nodeMaintenanceOpt.get(); | ||
| var spec = Objects.requireNonNull(nodeMaintenance.getSpec()); | ||
| var status = nodeMaintenance.getStatus(); | ||
| var action = Objects.requireNonNull(spec.getAction()); | ||
| var actionType = Objects.requireNonNull(action.getType()); | ||
| String nodeMaintenanceName = K8sObjectUtils.getName(nodeMaintenance); | ||
| List<String> targetNodeNames = Objects.requireNonNull(spec.getTargetNodes()); | ||
|
|
||
| if (status != null && ( | ||
| targetNodeNames.equals(status.getAllEffectedNodes()) && action.equals(status.getAction()) | ||
| )) { | ||
| return new Result(false); | ||
| } | ||
|
|
||
| List<String> effectedNodes = new ArrayList<>(); | ||
| for (String targetNodeName : targetNodeNames) { | ||
| var targetNodeRequest = coreV1Api.readNode(targetNodeName); | ||
pranludi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var targetNodeResponse = targetNodeRequest.executeWithHttpInfo(); | ||
| if (targetNodeResponse.getStatusCode() != 200) { | ||
| return new Result(false); | ||
| } | ||
| V1Node targetNode = targetNodeResponse.getData(); | ||
| Objects.requireNonNull(targetNode.getSpec()); | ||
|
|
||
| // | ||
| switch (actionType) { | ||
| case "cordon": | ||
| // | ||
| executeSchedulable(targetNode, true, nodeMaintenanceName); | ||
| effectedNodes.add(targetNodeName); | ||
| break; | ||
| case "uncordon": | ||
| // | ||
| executeSchedulable(targetNode, false, nodeMaintenanceName); | ||
| effectedNodes.add(targetNodeName); | ||
| break; | ||
| case "drain": | ||
| // | ||
| if (targetNode.getSpec().getUnschedulable() == null || Boolean.FALSE.equals(targetNode.getSpec().getUnschedulable())) { | ||
| log.error("Node {} isn't cordon state", nodeMaintenanceName); | ||
| return new Result(false); | ||
| } | ||
|
|
||
| var podRequest = coreV1Api.listNamespacedPod(namespace); | ||
pranludi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var podResponse = podRequest.executeWithHttpInfo(); | ||
| if (podResponse.getStatusCode() != 200) { | ||
| return new Result(false); | ||
| } | ||
| executePodDelete(podResponse, targetNodeName, action, nodeMaintenanceName); | ||
| effectedNodes.add(targetNodeName); | ||
| break; | ||
| default: | ||
| } | ||
| } | ||
|
|
||
| // | ||
| V1alpha1NodeMaintenanceStatus edited = new V1alpha1NodeMaintenanceStatus(); | ||
| edited.setAllEffectedNodes(targetNodeNames); | ||
| edited.setAction(action); | ||
| edited.setStatus("COMPLETED"); | ||
| nodeMaintenance.setStatus(edited); | ||
|
|
||
| this.updateNodeMaintenanceStatus(nodeMaintenance); | ||
|
|
||
| return new Result(false); | ||
| } | ||
|
|
||
| private void executePodDelete(ApiResponse<V1PodList> podResponse, String targetNodeName, V1alpha1NodeMaintenanceAction action, String maintenanceName) throws ApiException { | ||
| boolean isIgnoreDaemonSets = Boolean.TRUE.equals(action.getIgnoreDaemonSets()); | ||
| boolean isForce = Boolean.TRUE.equals(action.getForce()); | ||
| for (V1Pod _item : podResponse.getData().getItems()) { | ||
| V1PodSpec _spec = Objects.requireNonNull(_item.getSpec()); | ||
| if (targetNodeName.equals(_spec.getNodeName())) { | ||
| V1ObjectMeta _metadata = Objects.requireNonNull(_item.getMetadata()); | ||
| String podName = _metadata.getName(); | ||
| String podNamespace = _metadata.getNamespace(); | ||
| int gracePeriodSeconds = isForce ? 0 : Objects.requireNonNull(_spec.getTerminationGracePeriodSeconds()).intValue(); | ||
|
|
||
| if (isIgnoreDaemonSets) { | ||
| var ownerReferences = Objects.requireNonNull(_metadata.getOwnerReferences()); | ||
| boolean isDaemonset = false; | ||
| for (V1OwnerReference ownerReference : ownerReferences) { | ||
| if (ownerReference.getKind().equalsIgnoreCase("DaemonSet")) { | ||
| isDaemonset = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!isDaemonset) { | ||
| log.info("drain daemonSet ignore : pod name - {} / namespace - {}", podName, podNamespace); | ||
| coreV1Api.deleteNamespacedPod(podName, podNamespace) | ||
| .gracePeriodSeconds(gracePeriodSeconds) | ||
| .execute(); | ||
| } | ||
| } else { | ||
| log.info("drain all pods : pod name - {} / namespace - {}", podName, podNamespace); | ||
| coreV1Api.deleteNamespacedPod(podName, podNamespace) | ||
| .gracePeriodSeconds(gracePeriodSeconds) | ||
| .execute(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void executeSchedulable(V1Node targetNode, boolean isUnschedulable, String maintenanceName) throws ApiException { | ||
| String nodeName = targetNode.getMetadata().getName(); | ||
| targetNode.getSpec().setUnschedulable(isUnschedulable); | ||
|
|
||
| if (isUnschedulable) { | ||
| log.info("cordon : node name - {} / yaml - {}", nodeName, maintenanceName); | ||
| } else { | ||
| log.info("uncordon : node name - {} / yaml - {}", nodeName, maintenanceName); | ||
| } | ||
| coreV1Api.replaceNode(nodeName, targetNode).execute(); | ||
| } | ||
|
|
||
| private void updateNodeMaintenanceStatus(V1alpha1NodeMaintenance nodeMaintenance) throws ApiException { | ||
| Objects.requireNonNull(nodeMaintenance.getStatus()); | ||
| this.statusPatchHelper.patchStatus(null, K8sObjectUtils.getName(nodeMaintenance), nodeMaintenance.getStatus()); | ||
| } | ||
|
|
||
| } | ||
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.