simple script to clean volumes from the csi SP which do not exist in k8s

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2021-09-24 09:58:40 -06:00
parent 911541fd06
commit fd85be9742
3 changed files with 1787 additions and 93 deletions

172
bin/k8s-csi-cleaner Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env -S node --nouse-idle-notification --expose-gc
/**
* The purpose of this script is to prune volumes in the storage system which
* do not have correlating PVs in k8s
*
* env vars:
* # prevents manual input on a per-volume basis to confirm delete action
* # default is 0
* AUTO_DELETE=1
*
* # outputs to the console which volumes would be cleaned vs not
* # default is 0
* DRY_RUN=1
*
* # endpoint for the csi grpc connection
* # default is unix:///csi-data/csi.sock
* CSI_ENDPOINT="localhost:50051"
*/
const k8s = require("@kubernetes/client-node");
const prompt = require("prompt");
prompt.start();
const PROTO_PATH = __dirname + "/../csi_proto/csi-v1.5.0.proto";
//var grpc = require("grpc-uds");
var grpc = require("@grpc/grpc-js");
var protoLoader = require("@grpc/proto-loader");
const { rsort } = require("semver");
// Suggested options for similarity to existing grpc.load behavior
var packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
// The protoDescriptor object has the full package hierarchy
var csi = protoDescriptor.csi.v1;
//console.log(csi);
var connectionEndpoint = process.env.CSI_ENDPOINT || "unix:///csi-data/csi.sock";
var clientIdentity = new csi.Identity(
connectionEndpoint,
grpc.credentials.createInsecure()
);
var clientController = new csi.Controller(
connectionEndpoint,
grpc.credentials.createInsecure()
);
var clientNode = new csi.Node(
connectionEndpoint,
grpc.credentials.createInsecure()
);
async function executeRPC(service, methodName, options = {}) {
//console.log(service[methodName]);
return new Promise((resolve, reject) => {
const call = service[methodName](options, (error, data) => {
//console.log("%s - error: %j, data: %j", methodName, error, data);
if (error) {
reject(error);
}
resolve(data);
});
});
}
async function runControllerListVolumes(starting_token = "") {
const req = {
//max_entries: 3,
//starting_token: "77e73621-6fbc-4aec-9d6b-fc9ac2fd1a44:2"
starting_token,
};
return executeRPC(clientController, "ListVolumes", req);
}
async function runControllerDeleteVolume(volume_id) {
const req = {
volume_id: volume_id,
};
return executeRPC(clientController, "DeleteVolume", req);
}
async function main() {
// get k8s volumes
let k8sVolumes = await new Promise((resolve, reject) => {
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
// V1PersistentVolumeList
k8sApi.listPersistentVolume().then((res) => {
//console.log(res);
//console.dir(res.body.items, { depth: null }); // `depth: null` ensures unlimited recursion
resolve(res.body.items);
});
});
console.log(`${k8sVolumes.length} k8s PVs discovered`);
// get csi volumes
let res;
let csiVolumes = [];
do {
res = await runControllerListVolumes();
csiVolumes = csiVolumes.concat(res.entries);
} while (res.next_token);
console.log(`${csiVolumes.length} csi volumes discovered`);
//console.log(k8sVolumes);
//console.log(csiVolumes);
for (let csiVolume of csiVolumes) {
let volume_id = csiVolume.volume.volume_id;
//console.log(`processing csi volume ${volume_id}`);
let k8sVolume = k8sVolumes.find((i_k8sVolume) => {
return i_k8sVolume.metadata.name == volume_id;
});
//let delete = false;
if (!k8sVolume) {
console.log(`volume ${volume_id} is NOT in k8s`);
if (process.env.DRY_RUN == "1") {
continue;
}
let del = false;
if (process.env.AUTO_DELETE == "1") {
del = true;
} else {
res = await prompt.get([
{
name: "delete",
required: true,
type: "boolean",
},
]);
del = res.delete;
}
if (del) {
res = await runControllerDeleteVolume(volume_id);
console.log(`csi volume ${volume_id} deleted`);
} else {
console.log(`skipping delete of csi volume ${volume_id}`);
}
} else {
console.log(`volume ${volume_id} is in k8s`);
}
}
console.log("Fin");
}
if (require.main === module) {
(async function () {
try {
await main();
} catch (e) {
console.log(e);
}
})();
}

1706
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
"dependencies": {
"@grpc/grpc-js": "^1.3.6",
"@grpc/proto-loader": "^0.6.0",
"@kubernetes/client-node": "^0.15.1",
"async-mutex": "^0.3.1",
"bunyan": "^1.8.15",
"grpc-uds": "^0.1.6",
@ -27,6 +28,7 @@
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"lru-cache": "^6.0.0",
"prompt": "^1.2.0",
"request": "^2.88.2",
"semver": "^7.3.4",
"ssh2": "^1.1.0",