Skip to content
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

Opencost integration #995

Merged
merged 8 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/lambda v1.23.4
github.com/aws/aws-sdk-go-v2/service/pricing v1.20.0
github.com/aws/aws-sdk-go-v2/service/rds v1.30.1
github.com/aws/aws-sdk-go-v2/service/redshift v1.29.5
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.1
github.com/aws/aws-sdk-go-v2/service/sns v1.18.3
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.12
Expand Down Expand Up @@ -81,7 +82,6 @@ require (
require (
cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/apache/arrow/go/v11 v11.0.0 // indirect
github.com/aws/aws-sdk-go-v2/service/redshift v1.29.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
)
Expand Down
42 changes: 20 additions & 22 deletions go.sum

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,18 @@ func Load(configPath string, telemetry bool, analytics utils.Analytics, db *bun.
log.Fatal(err)
}

client, err := kubernetes.NewForConfig(kubeConfig)
k8sClient, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
log.Fatal(err)
}

client := providers.K8sClient{
Client: k8sClient,
OpencostBaseUrl: account.OpencostBaseUrl,
}

clients = append(clients, providers.ProviderClient{
K8sClient: client,
K8sClient: &client,
Name: account.Name,
})
}
Expand Down
7 changes: 4 additions & 3 deletions models/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ type ScalewayConfig struct {
}

type KubernetesConfig struct {
Name string `toml:"name"`
Path string `toml:"path"`
Contexts []string `toml:"contexts"`
Name string `toml:"name"`
Path string `toml:"path"`
Contexts []string `toml:"contexts"`
OpencostBaseUrl string `toml:"opencostBaseUrl"`
}

type LinodeConfig struct {
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func Deployments(ctx context.Context, client providers.ProviderClient) ([]Resour
var config metav1.ListOptions

for {
res, err := client.K8sClient.AppsV1().Deployments("").List(ctx, config)
res, err := client.K8sClient.Client.AppsV1().Deployments("").List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func Ingress(ctx context.Context, client providers.ProviderClient) ([]Resource,
var config metav1.ListOptions

for {
res, err := client.K8sClient.NetworkingV1().Ingresses("").List(ctx, config)
res, err := client.K8sClient.Client.NetworkingV1().Ingresses("").List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
30 changes: 22 additions & 8 deletions providers/k8s/core/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,54 @@ import (

log "github.com/sirupsen/logrus"

. "github.com/tailwarden/komiser/models"
"github.com/tailwarden/komiser/models"
"github.com/tailwarden/komiser/providers"
oc "github.com/tailwarden/komiser/providers/k8s/opencost"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Pods(ctx context.Context, client providers.ProviderClient) ([]Resource, error) {
resources := make([]Resource, 0)
func Pods(ctx context.Context, client providers.ProviderClient) ([]models.Resource, error) {
resources := make([]models.Resource, 0)

var config metav1.ListOptions

opencostEnabled := true
podsCost, err := oc.GetOpencostInfo(client.K8sClient.OpencostBaseUrl, "pod")
if err != nil {
log.Errorf("ERROR: Couldn't get pods info from OpenCost: %v", err)
log.Warn("Opencost disabled")
opencostEnabled = false
}

for {
res, err := client.K8sClient.CoreV1().Pods("").List(ctx, config)
res, err := client.K8sClient.Client.CoreV1().Pods("").List(ctx, config)
if err != nil {
return nil, err
}

for _, pod := range res.Items {
tags := make([]Tag, 0)
tags := make([]models.Tag, 0)

for key, value := range pod.Labels {
tags = append(tags, Tag{
tags = append(tags, models.Tag{
Key: key,
Value: value,
})
}

resources = append(resources, Resource{
cost := 0.0
if opencostEnabled {
cost = podsCost[pod.Name].TotalCost
}

resources = append(resources, models.Resource{
Provider: "Kubernetes",
Account: client.Name,
Service: "Pod",
ResourceId: string(pod.UID),
Name: pod.Name,
Region: pod.Namespace,
Cost: 0,
Cost: cost,
CreatedAt: pod.CreationTimestamp.Time,
FetchedAt: time.Now(),
Tags: tags,
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/pv.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func PersistentVolumes(ctx context.Context, client providers.ProviderClient) ([]
var config metav1.ListOptions

for {
res, err := client.K8sClient.CoreV1().PersistentVolumes().List(ctx, config)
res, err := client.K8sClient.Client.CoreV1().PersistentVolumes().List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/pvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func PersistentVolumeClaims(ctx context.Context, client providers.ProviderClient
var config metav1.ListOptions

for {
res, err := client.K8sClient.CoreV1().PersistentVolumeClaims("").List(ctx, config)
res, err := client.K8sClient.Client.CoreV1().PersistentVolumeClaims("").List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/sa.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func ServiceAccounts(ctx context.Context, client providers.ProviderClient) ([]Re
var config metav1.ListOptions

for {
res, err := client.K8sClient.CoreV1().ServiceAccounts("").List(ctx, config)
res, err := client.K8sClient.Client.CoreV1().ServiceAccounts("").List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion providers/k8s/core/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func Services(ctx context.Context, client providers.ProviderClient) ([]Resource,
var config metav1.ListOptions

for {
res, err := client.K8sClient.CoreV1().Services("").List(ctx, config)
res, err := client.K8sClient.Client.CoreV1().Services("").List(ctx, config)
if err != nil {
return nil, err
}
Expand Down
111 changes: 111 additions & 0 deletions providers/k8s/opencost/opencost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package k8s

import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)

type OpenCostResponse struct {
Code int `json:"code"`
Status string `json:"status"`
Data []map[string]costData `json:"data"`
}

type costData struct {
Properties struct {
Cluster string `json:"cluster"`
Node string `json:"node"`
} `json:"properties"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Minutes int `json:"minutes"`
CPUCores float64 `json:"cpuCores"`
CPUCoreRequestAvg float64 `json:"cpuCoreRequestAverage"`
CPUCoreUsageAvg float64 `json:"cpuCoreUsageAverage"`
CPUCoreHours float64 `json:"cpuCoreHours"`
CPUCost float64 `json:"cpuCost"`
CPUCostAdjustment float64 `json:"cpuCostAdjustment"`
CPUEfficiency float64 `json:"cpuEfficiency"`
GPUCount int `json:"gpuCount"`
GPUHours int `json:"gpuHours"`
GPUCost float64 `json:"gpuCost"`
GPUCostAdjustment float64 `json:"gpuCostAdjustment"`
NetworkTransfer int64 `json:"networkTransferBytes"`
NetworkReceive int64 `json:"networkReceiveBytes"`
NetworkCost float64 `json:"networkCost"`
NetworkCrossZone float64 `json:"networkCrossZoneCost"`
NetworkCrossRegion float64 `json:"networkCrossRegionCost"`
NetworkInternet float64 `json:"networkInternetCost"`
NetworkAdjustment float64 `json:"networkCostAdjustment"`
LoadBalancerCost float64 `json:"loadBalancerCost"`
LBCostAdjustment float64 `json:"loadBalancerCostAdjustment"`
PVBytes int64 `json:"pvBytes"`
PVByteHours float64 `json:"pvByteHours"`
PVCost float64 `json:"pvCost"`
PVs map[string]pvInfo `json:"pvs"`
PVCostAdjustment float64 `json:"pvCostAdjustment"`
RAMBytes int64 `json:"ramBytes"`
RAMByteRequestAvg int64 `json:"ramByteRequestAverage"`
RAMByteUsageAvg int64 `json:"ramByteUsageAverage"`
RAMByteHours float64 `json:"ramByteHours"`
RAMCost float64 `json:"ramCost"`
RAMCostAdjustment float64 `json:"ramCostAdjustment"`
RAMEfficiency float64 `json:"ramEfficiency"`
ExternalCost float64 `json:"externalCost"`
SharedCost float64 `json:"sharedCost"`
TotalCost float64 `json:"totalCost"`
TotalEfficiency float64 `json:"totalEfficiency"`
}

type pvInfo struct {
ByteHours float64 `json:"byteHours"`
Cost float64 `json:"cost"`
}

func GetOpencostInfo(ocBaseUrl string, aggregate string) (map[string]costData, error) {
apiURL := fmt.Sprintf("http://%s/allocation/compute", ocBaseUrl)

httpClient := &http.Client{}
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return nil, fmt.Errorf("ERROR: Couldn't build request: %v", err)
}

query := req.URL.Query()
query.Add("window", "month")
query.Add("aggregate", aggregate)
req.URL.RawQuery = query.Encode()

resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("ERROR: Couldn't send request: %v", err)
}
defer resp.Body.Close()

var ocResp OpenCostResponse
if resp.StatusCode == http.StatusOK {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("ERROR: Couldn't read response body: %v", err)
}
err = json.Unmarshal(bodyBytes, &ocResp)
if err != nil {
return nil, fmt.Errorf("ERROR: Couldn't unmarshal json: %v", err)
}
}

return FlattenMapSlice(ocResp.Data), nil
}

func FlattenMapSlice(mapSlice []map[string]costData) map[string]costData {
res := make(map[string]costData)
for _, data := range mapSlice {
for key, value := range data {
res[key] = value
}
}
return res
}
7 changes: 6 additions & 1 deletion providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ProviderClient struct {
DigitalOceanClient *godo.Client
OciClient common.ConfigurationProvider
CivoClient *civogo.Client
K8sClient *kubernetes.Clientset
K8sClient *K8sClient
LinodeClient *linodego.Client
TencentClient *tccvm.Client
AzureClient *AzureClient
Expand All @@ -42,3 +42,8 @@ type AzureClient struct {
type GCPClient struct {
Credentials *google.Credentials
}

type K8sClient struct {
Client *kubernetes.Clientset
OpencostBaseUrl string
}