Skip to content

Commit 9941bb2

Browse files
committed
fix: high memory usage in kubelink informers
1 parent c49b4aa commit 9941bb2

10 files changed

Lines changed: 481 additions & 205 deletions

File tree

kubelink/internals/middleware/instrument.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ import (
2727

2828
// metrics name constant
2929
const (
30-
KUBELINK_HTTP_DURATION_SECONDS = "kubelink_http_duration_seconds"
31-
KUBELINK_HTTP_REQUESTS_TOTAL = "kubelink_http_requests_total"
32-
KUBELINK_HTTP_REQUESTS_CURRENT = "kubelink_http_requests_current"
30+
KUBELINK_HTTP_DURATION_SECONDS = "kubelink_http_duration_seconds"
31+
KUBELINK_HTTP_REQUESTS_TOTAL = "kubelink_http_requests_total"
32+
KUBELINK_HTTP_REQUESTS_CURRENT = "kubelink_http_requests_current"
33+
KUBELINK_INFORMER_DATA_TRANSFORM_DURATION_SECONDS = "kubelink_informer_data_transform_duration_seconds"
3334
)
3435

3536
// metrics labels constants
@@ -57,6 +58,17 @@ var currentRequestGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
5758
Help: "no of request being served currently",
5859
}, []string{PATH, METHOD})
5960

61+
var InformerDataTransformDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
62+
Name: KUBELINK_INFORMER_DATA_TRANSFORM_DURATION_SECONDS,
63+
Help: "Duration of informer data transform request",
64+
}, []string{CLUSTER_NAME, NAMESPACE, RELEASE_NAME})
65+
66+
const (
67+
CLUSTER_NAME = "clusterName"
68+
NAMESPACE = "namespace"
69+
RELEASE_NAME = "releaseName"
70+
)
71+
6072
// prometheusMiddleware implements mux.MiddlewareFunc.
6173
func PrometheusMiddleware(next http.Handler) http.Handler {
6274
// prometheus.MustRegister(requestCounter)

kubelink/pkg/k8sInformer/K8sInformer.go

Lines changed: 133 additions & 110 deletions
Large diffs are not rendered by default.

kubelink/pkg/k8sInformer/bean.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package k8sInformer
18+
19+
import (
20+
"fmt"
21+
client "github.com/devtron-labs/kubelink/grpc"
22+
)
23+
24+
const (
25+
secretKeyAppDetailKey = "appDetail"
26+
)
27+
28+
type DeployedAppDetailDto struct {
29+
*client.DeployedAppDetail
30+
}
31+
32+
func NewDeployedAppDetailDto(appDetail *client.DeployedAppDetail) *DeployedAppDetailDto {
33+
return &DeployedAppDetailDto{DeployedAppDetail: appDetail}
34+
}
35+
36+
func (r *DeployedAppDetailDto) getUniqueReleaseIdentifier() string {
37+
if r == nil || r.EnvironmentDetail == nil {
38+
return ""
39+
}
40+
// adding cluster id with release name and namespace because there can be case when two cluster or two namespaces have release with same name
41+
return fmt.Sprintf("%s_%s_%d", r.EnvironmentDetail.Namespace, r.AppName, r.EnvironmentDetail.ClusterId)
42+
}
Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,11 @@ package k8sInformer
1818

1919
import (
2020
"errors"
21-
"helm.sh/helm/v3/pkg/release"
2221
)
2322

24-
type ReleaseDto struct {
25-
*release.Release
26-
}
27-
28-
func (r *ReleaseDto) getUniqueReleaseIdentifier() string {
29-
return r.Namespace + "_" + r.Name
30-
}
31-
3223
var (
24+
// ErrorCacheMissReleaseNotFound is returned when a release is not found in the cache
3325
ErrorCacheMissReleaseNotFound = errors.New("release not found in cache")
26+
// InformerAlreadyExistError is returned when an informer already exists
27+
InformerAlreadyExistError = errors.New(INFORMER_ALREADY_EXIST_MESSAGE)
3428
)

kubelink/pkg/k8sInformer/util.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package k8sInformer
18+
19+
import (
20+
"bytes"
21+
"compress/gzip"
22+
"encoding/base64"
23+
"encoding/json"
24+
"errors"
25+
client "github.com/devtron-labs/kubelink/grpc"
26+
"github.com/devtron-labs/kubelink/pkg/service/helmApplicationService/release"
27+
"io"
28+
)
29+
30+
func parseSecretDataForDeployedAppDetail(appDetail *client.DeployedAppDetail) (map[string][]byte, error) {
31+
appDetailBytes, err := json.Marshal(appDetail)
32+
if err != nil {
33+
return nil, err
34+
}
35+
data := make(map[string][]byte)
36+
data[secretKeyAppDetailKey] = appDetailBytes
37+
return data, nil
38+
}
39+
40+
func getDeployedAppDetailFromSecretData(data map[string][]byte) (*client.DeployedAppDetail, error) {
41+
if appDetailBytes, ok := data[secretKeyAppDetailKey]; ok {
42+
var appDetail client.DeployedAppDetail
43+
err := json.Unmarshal(appDetailBytes, &appDetail)
44+
if err != nil {
45+
return nil, err
46+
}
47+
return &appDetail, nil
48+
}
49+
return nil, errors.New("app detail not found in secret data")
50+
}
51+
52+
func decodeHelmReleaseData(data string) (*release.Release, error) {
53+
// base64 decode string
54+
b64 := base64.StdEncoding
55+
b, err := b64.DecodeString(data)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
var magicGzip = []byte{0x1f, 0x8b, 0x08}
61+
62+
// For backwards compatibility with releases that were stored before
63+
// compression was introduced we skip decompression if the
64+
// gzip magic header is not found
65+
if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) {
66+
r, err := gzip.NewReader(bytes.NewReader(b))
67+
if err != nil {
68+
return nil, err
69+
}
70+
defer r.Close()
71+
b2, err := io.ReadAll(r)
72+
if err != nil {
73+
return nil, err
74+
}
75+
b = b2
76+
}
77+
78+
var rls release.Release
79+
// unmarshal release object bytes
80+
if err := json.Unmarshal(b, &rls); err != nil {
81+
return nil, err
82+
}
83+
return &rls, nil
84+
}

kubelink/pkg/service/helmApplicationService/adapter/adapter.go

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package adapter
22

33
import (
44
"github.com/devtron-labs/common-lib/helmLib/registry"
5-
"github.com/devtron-labs/common-lib/utils/remoteConnection/bean"
5+
remoteConnection "github.com/devtron-labs/common-lib/utils/remoteConnection/bean"
66
client "github.com/devtron-labs/kubelink/grpc"
7+
"github.com/devtron-labs/kubelink/pkg/service/helmApplicationService/release"
78
"github.com/devtron-labs/kubelink/pkg/util"
89
"google.golang.org/protobuf/types/known/timestamppb"
9-
"helm.sh/helm/v3/pkg/release"
10+
helmChart "helm.sh/helm/v3/pkg/chart"
11+
helmRelease "helm.sh/helm/v3/pkg/release"
1012
)
1113

1214
func NewRegistryConfig(credential *client.RegistryCredential) (*registry.Configuration, error) {
@@ -36,36 +38,36 @@ func NewRegistryConfig(credential *client.RegistryCredential) (*registry.Configu
3638

3739
connectionConfig := credential.RemoteConnectionConfig
3840
if connectionConfig != nil {
39-
registryConfig.RemoteConnectionConfig = &bean.RemoteConnectionConfigBean{}
41+
registryConfig.RemoteConnectionConfig = &remoteConnection.RemoteConnectionConfigBean{}
4042
switch connectionConfig.RemoteConnectionMethod {
4143
case client.RemoteConnectionMethod_DIRECT:
42-
registryConfig.RemoteConnectionConfig.ConnectionMethod = bean.RemoteConnectionMethodDirect
44+
registryConfig.RemoteConnectionConfig.ConnectionMethod = remoteConnection.RemoteConnectionMethodDirect
4345
case client.RemoteConnectionMethod_PROXY:
44-
registryConfig.RemoteConnectionConfig.ConnectionMethod = bean.RemoteConnectionMethodProxy
46+
registryConfig.RemoteConnectionConfig.ConnectionMethod = remoteConnection.RemoteConnectionMethodProxy
4547
registryConfig.RemoteConnectionConfig.ProxyConfig = ConvertConfigToProxyConfig(connectionConfig)
4648
case client.RemoteConnectionMethod_SSH:
47-
registryConfig.RemoteConnectionConfig.ConnectionMethod = bean.RemoteConnectionMethodSSH
49+
registryConfig.RemoteConnectionConfig.ConnectionMethod = remoteConnection.RemoteConnectionMethodSSH
4850
registryConfig.RemoteConnectionConfig.SSHTunnelConfig = ConvertConfigToSSHTunnelConfig(connectionConfig)
4951
}
5052
}
5153
}
5254
return registryConfig, nil
5355
}
5456

55-
func ConvertConfigToProxyConfig(config *client.RemoteConnectionConfig) *bean.ProxyConfig {
56-
var proxyConfig *bean.ProxyConfig
57+
func ConvertConfigToProxyConfig(config *client.RemoteConnectionConfig) *remoteConnection.ProxyConfig {
58+
var proxyConfig *remoteConnection.ProxyConfig
5759
if config.ProxyConfig != nil {
58-
proxyConfig = &bean.ProxyConfig{
60+
proxyConfig = &remoteConnection.ProxyConfig{
5961
ProxyUrl: config.ProxyConfig.ProxyUrl,
6062
}
6163
}
6264
return proxyConfig
6365
}
6466

65-
func ConvertConfigToSSHTunnelConfig(config *client.RemoteConnectionConfig) *bean.SSHTunnelConfig {
66-
var sshConfig *bean.SSHTunnelConfig
67+
func ConvertConfigToSSHTunnelConfig(config *client.RemoteConnectionConfig) *remoteConnection.SSHTunnelConfig {
68+
var sshConfig *remoteConnection.SSHTunnelConfig
6769
if config.SSHTunnelConfig != nil {
68-
sshConfig = &bean.SSHTunnelConfig{
70+
sshConfig = &remoteConnection.SSHTunnelConfig{
6971
SSHUsername: config.SSHTunnelConfig.SSHUsername,
7072
SSHPassword: config.SSHTunnelConfig.SSHPassword,
7173
SSHAuthKey: config.SSHTunnelConfig.SSHAuthKey,
@@ -119,3 +121,46 @@ func GetAppDetailRequestFromGetResourceTreeRequest(req *client.GetResourceTreeRe
119121
CacheConfig: req.CacheConfig,
120122
}
121123
}
124+
125+
func NewRelease(helmRelease *helmRelease.Release) *release.Release {
126+
if helmRelease == nil {
127+
return nil
128+
}
129+
return &release.Release{
130+
Name: helmRelease.Name,
131+
Namespace: helmRelease.Namespace,
132+
Info: NewReleaseInfo(helmRelease.Info),
133+
Chart: NewChart(helmRelease.Chart),
134+
}
135+
}
136+
137+
func NewReleaseInfo(helmReleaseInfo *helmRelease.Info) *release.Info {
138+
if helmReleaseInfo == nil {
139+
return nil
140+
}
141+
return &release.Info{
142+
LastDeployed: helmReleaseInfo.LastDeployed,
143+
Status: helmReleaseInfo.Status,
144+
}
145+
}
146+
147+
func NewChart(helmChart *helmChart.Chart) *release.Chart {
148+
if helmChart == nil {
149+
return nil
150+
}
151+
return &release.Chart{
152+
Metadata: NewChartMetadata(helmChart.Metadata),
153+
}
154+
}
155+
156+
func NewChartMetadata(helmChartMetadata *helmChart.Metadata) *release.Metadata {
157+
if helmChartMetadata == nil {
158+
return nil
159+
}
160+
return &release.Metadata{
161+
Name: helmChartMetadata.Name,
162+
Version: helmChartMetadata.Version,
163+
Icon: helmChartMetadata.Icon,
164+
Home: helmChartMetadata.Home,
165+
}
166+
}

0 commit comments

Comments
 (0)