Skip to content

Commit

Permalink
Merge pull request #2833 from flexera-public/POL-1405_fix_kubecost_po…
Browse files Browse the repository at this point in the history
…licy

POL-1405 Replace deprecated Kubecost endpoints for Kubecost Cluster Policy
  • Loading branch information
joseluiszuflores authored Dec 19, 2024
2 parents 1b1ca76 + 26d3275 commit cb322a1
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 73 deletions.
6 changes: 6 additions & 0 deletions cost/kubecost/cluster/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v0.4.0

- Updated API endpoint for cluster sizing.
- Added parameter `Shared Core` to indicate whether shared cores should be considered in the cluster sizing calculation.
- Added parameter `Architecture` to optimize cost calculations and resource allocation based on hardware architecture.

## v0.3.3

- Currency values in incident table are automatically converted to the local currency of the Flexera organization using the most recent exchange rate.
Expand Down
2 changes: 2 additions & 0 deletions cost/kubecost/cluster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This policy template reports a Kubecost cluster rightsizing recommendation gener
- *Lookback Period (Days)* - Number of historical days of usage to analyze when generating recommendations.
- *Target Utilization (%)* - Utilization target to use when generating recommendations.
- *Recommendation Strategy* - Recommendation strategy to use. 'Optimal' will automatically select whichever strategy has the highest potential savings.
- *Allow Shared Core* - Whether to include shared core node types for recommendations.
- *Architecture* - Chipset architecture for recommended nodes (e.g., 'x86' or 'ARM'). Note: 'ARM' is currently supported only on AWS clusters.

## Policy Actions

Expand Down
208 changes: 135 additions & 73 deletions cost/kubecost/cluster/kubecost_cluster_rightsizing_recommendations.pt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ category "Cost"
severity "low"
default_frequency "weekly"
info(
version: "0.3.3",
version: "0.4.0",
provider: "Kubecost",
service: "Kubernetes",
policy_set: "Rightsize Clusters",
Expand Down Expand Up @@ -36,6 +36,24 @@ parameter "param_kubecost_host" do
# No default value, user input required
end

parameter "param_allow_shared_core" do
type "string"
category "Policy Settings"
label "Shared Core"
description "Whether shared core node types can be included in the recommendations."
allowed_values "true", "false"
default "false"
end

parameter "param_architecture" do
type "string"
category "Policy Settings"
label "Architecture"
description "Chipset architecture for the recommended nodes.'ARM' is currently supported only on AWS clusters."
allowed_values "All Chipset Architectures", "x86", "ARM"
default "x86"
end

parameter "param_min_nodes" do
type "number"
category "Policy Settings"
Expand Down Expand Up @@ -108,6 +126,23 @@ datasource "ds_kubecost_currency_code" do
end
end

# Get current architecture
datasource "ds_architecture" do
run_script $js_architecture, $param_architecture
end

script "js_architecture", type: "javascript" do
parameters "param_architecture"
result "result"
code <<-EOS
if (param_architecture == "All Chipset Architectures"){
result = { value: "" }
} else {
result = { values: param_architecture}
}
EOS
end

# Gather local currency info
datasource "ds_currency_target" do
run_script $js_currency_target, $ds_currency_reference, $ds_currency_code
Expand Down Expand Up @@ -228,27 +263,50 @@ script "js_target_util", type:"javascript" do
code "result = { value: (param_target_util / 100).toString() }"
end

datasource "ds_cluster_sizing" do
datasource "ds_clusters" do
request do
host $param_kubecost_host
path "/model/savings/clusterSizing"
path "/model/savings/clusterSizingETL"
query "minNodeCount", to_s($param_min_nodes)
query "window", join([$param_lookback, "d"])
query "targetUtilization", val($ds_target_util, "value")
query "allowSharedCore", $param_allow_shared_core
query "architecture", val($ds_architecture, "value")
# Note: `includeOverhead` is explicitly set to `false` to ensure consistency between the recommendations and the Kube-cost UI.
# Also, the field `includeOverhead` is not exposed as a configurable parameter because it is not part of the Cluster Right-Sizing Recommendation API specification.
query "includeOverhead", "false"
header "User-Agent", "RS Policies"
end
result do
encoding "json"
field "accountID", jmes_path(response, "data.parameters.clusterId")
field "accountName", jmes_path(response, "data.parameters.clusterName")
field "totalNodeCount", jmes_path(response, "data.currentClusterInfo.totalCounts.totalNodeCount")
field "totalRAMGB", jmes_path(response, "data.currentClusterInfo.totalCounts.totalRAMGB")
field "totalVCPUs", jmes_path(response, "data.currentClusterInfo.totalCounts.totalVCPUs")
field "monthlyRate", jmes_path(response, "data.currentClusterInfo.monthlyRate")
field "recommendations", jmes_path(response, "data.recommendations")
field "accountID", jmes_path(response, "data")
end
end

datasource "ds_cluster_sizing" do
run_script $js_cluster_sizing, $ds_clusters
end

script "js_cluster_sizing", type: "javascript" do
parameters "data"
result "result"
code <<-'EOS'
result = []
for (var accountKey in data.accountID) {
var accountData = data.accountID[accountKey];
result.push({
accountID: accountKey,
clusterName: accountKey,
totalNodeCount: accountData.currentClusterInfo.totalCounts.totalNodeCount,
totalRAMGB: accountData.currentClusterInfo.totalCounts.totalRAMGB,
totalVCPUs: accountData.currentClusterInfo.totalCounts.totalVCPUs,
monthlyRate: accountData.currentClusterInfo.monthlyRate,
recommendations: accountData.recommendations
});
}
EOS
end

datasource "ds_recommendations" do
run_script $js_recommendations, $ds_cluster_sizing, $ds_currency, $ds_applied_policy, $param_strategy, $param_min_nodes, $param_lookback, $param_target_util
end
Expand All @@ -257,6 +315,7 @@ script "js_recommendations", type: "javascript" do
parameters "ds_cluster_sizing", "ds_currency", "ds_applied_policy", "param_strategy", "param_min_nodes", "param_lookback", "param_target_util"
result "result"
code <<-'EOS'
result = []
// Function for formatting currency numbers later
function formatNumber(number, separator) {
formatted_number = "0"
Expand All @@ -281,75 +340,78 @@ script "js_recommendations", type: "javascript" do
return formatted_number
}
selected_strategy = param_strategy
exchange_rate = ds_currency['exchange_rate']
if (selected_strategy == "Optimal") {
selected_strategy = "Single"
// Note: Savings is presented as a negative value. Hence why we select the lowest, not the highest.
if (ds_cluster_sizing['recommendations']['multi']['monthlySavings'] < ds_cluster_sizing['recommendations']['single']['monthlySavings']) {
selected_strategy = "Multi"
_.each(ds_cluster_sizing, function(crs){
if (selected_strategy == "Optimal") {
selected_strategy = "Single"
// Note: Savings is presented as a negative value. Hence why we select the lowest, not the highest.
if (crs['recommendations']['multi']['monthlySavings'] < crs['recommendations']['single']['monthlySavings']) {
selected_strategy = "Multi"
}
}
}
recommendation = ds_cluster_sizing['recommendations'][selected_strategy.toLowerCase()]
regions = []
_.each(recommendation['pools'], function(pool) { regions.push(pool['type']['region']) })
region = _.uniq(_.compact(regions)).join(', ')
accountID = ds_cluster_sizing['accountID']
accountName = ds_cluster_sizing['accountName']
if (typeof(accountName) != 'string' || accountName == '') { accountName = accountID }
message_strategy = selected_strategy
if (param_strategy == "Optimal") { message_strategy += " (Optimal)" }
message = [
"Recommendation was produced with the following settings:\n\n",
"- Strategy: ", message_strategy, "\n",
"- Minimum Nodes: ", param_min_nodes, "\n",
"- Lookback Period: ", param_lookback, " Days\n",
"- Target Utilization: ", param_target_util, "%\n\n",
"The above settings can be modified by editing the applied policy and changing the appropriate parameters."
].join('')
recommendationDetails = [
"Modify settings for cluster ", accountID,
" so that node count is set to ", recommendation['nodeCount'],
" and pools are configured to match the Recommended Pools field."
].join('')
result = [{
accountID: accountID,
accountName: accountName,
strategy: selected_strategy,
totalNodeCount: ds_cluster_sizing['totalNodeCount'],
totalRAMGB: ds_cluster_sizing['totalRAMGB'],
totalVCPUs: ds_cluster_sizing['totalVCPUs'],
monthlyRate: Math.round(ds_cluster_sizing['monthlyRate'] * exchange_rate * 1000) / 1000,
totalMonthlyCost: Math.round(recommendation['totalMonthlyCost'] * exchange_rate * 1000) / 1000,
// Note: Savings is presented as a negative value. Hence why we multiply by a negative number to invert it
savings: Math.round(recommendation['monthlySavings'] * exchange_rate * -1000) / 1000,
savingsCurrency: ds_currency['symbol'],
nodeCount: recommendation['nodeCount'],
pools: JSON.stringify(recommendation['pools']),
recommendationDetails: recommendationDetails,
region: region,
service: "Kubernetes",
policy_name: ds_applied_policy['name'],
min_nodes: param_min_nodes,
lookback: param_lookback,
target_util: param_target_util,
message: message,
message_savings: [
ds_currency['symbol'], ' ',
formatNumber(Math.round(recommendation['monthlySavings'] * -1000) / 1000, ds_currency['separator'])
recommendation = crs['recommendations'][selected_strategy.toLowerCase()]
regions = []
_.each(recommendation['pools'], function(pool) { regions.push(pool['type']['region']) })
region = _.uniq(_.compact(regions)).join(', ')
accountID = crs['accountID']
accountName = crs['accountName']
if (typeof(accountName) != 'string' || accountName == '') { accountName = accountID }
message_strategy = selected_strategy
if (param_strategy == "Optimal") { message_strategy += " (Optimal)" }
message = [
"Recommendation was produced with the following settings:\n\n",
"- Strategy: ", message_strategy, "\n",
"- Minimum Nodes: ", param_min_nodes, "\n",
"- Lookback Period: ", param_lookback, " Days\n",
"- Target Utilization: ", param_target_util, "%\n\n",
"The above settings can be modified by editing the applied policy and changing the appropriate parameters."
].join('')
}]
EOS
if (recommendation['monthlySavings'] > 0){
// Note: Savings is presented as a negative value. Hence why we multiply by a negative number to invert it
recommendationSavings = Math.round(recommendation['monthlySavings'] * exchange_rate * -1000) / 1000
totalRAMGB = crs['totalRAMGB']
requiredRAMGB = recommendation['totalRAMGB']
recommendationDetails = [
"Resize Kubernetes node in cluster ", crs['clusterName'], " ",
"in account ", crs['accountID'], " ",
"with ", Math.round(crs['totalVCPUs']), " VCPUs and ", totalRAMGB, " GB RAM, ",
"to a recommended configuration with ", recommendation['totalVCPUs'], " VCPUs and ", requiredRAMGB, " GB RAM, ",
"to save ", recommendationSavings, " monthly."
].join('' )
result.push({
accountID: accountID,
accountName: accountName,
strategy: selected_strategy,
totalNodeCount: crs['totalNodeCount'],
totalRAMGB: crs['totalRAMGB'],
totalVCPUs: crs['totalVCPUs'],
monthlyRate: Math.round(crs['monthlyRate'] * exchange_rate * 1000) / 1000,
totalMonthlyCost: Math.round(recommendation['totalMonthlyCost'] * exchange_rate * 1000) / 1000,
savings: recommendationSavings,
savingsCurrency: ds_currency['symbol'],
nodeCount: recommendation['nodeCount'],
pools: JSON.stringify(recommendation['pools']),
recommendationDetails: recommendationDetails,
region: region,
service: "Kubernetes",
min_nodes: param_min_nodes,
lookback: param_lookback,
target_util: param_target_util,
message: message,
policy_name: ds_applied_policy['name'],
message_savings: [
ds_currency['symbol'], ' ',
formatNumber(Math.round(recommendation['monthlySavings'] * exchange_rate * -1000) / 1000, ds_currency['separator'])
].join('')
})
}
})
EOS
end

###############################################################################
Expand Down

0 comments on commit cb322a1

Please sign in to comment.